Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: introduce commons module #251

Merged
merged 10 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions buildSrc/src/main/kotlin/com.hedera.block.common.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

plugins {
id("java-library")
id("com.hedera.block.conventions")
id("me.champeau.jmh")
}

val maven = publishing.publications.create<MavenPublication>("maven") { from(components["java"]) }

signing.sign(maven)
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,18 @@ plugins { id("com.diffplug.spotless") }
spotless {
java {
targetExclude("build/generated/**/*.java", "build/generated/**/*.proto")
// enable toggle comment support
// Enables the spotless:on and spotless:off comments
toggleOffOn()
// don't need to set target, it is inferred from java
// apply a specific flavor of google-java-format
googleJavaFormat("1.17.0").aosp().reflowLongStrings()
// also reflow long strings, and do not format javadoc
// because the default setup is _very_ bad for javadoc
// We need to figure out a "correct" _separate_ setup for that.
googleJavaFormat("1.17.0").aosp().reflowLongStrings().formatJavadoc(false)
// Fix some left-out items from the google plugin
indentWithSpaces(4)
trimTrailingWhitespace()
endWithNewline()
// make sure every file has the following copyright header.
// optionally, Spotless can set copyright years by digging
// through git history (see "license" section below).
Expand Down
24 changes: 24 additions & 0 deletions common/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

plugins {
id("java-library")
ata-nas marked this conversation as resolved.
Show resolved Hide resolved
id("com.hedera.block.common")
}

description = "Commons module with logic that could be abstracted and reused."

testModuleInfo { requiresStatic("com.github.spotbugs.annotations") }
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.hedera.block.common.constants;

/** A class that hold common String literals used across projects. */
public final class StringsConstants {
/**
* File name for application properties
*/
public static final String APPLICATION_PROPERTIES = "app.properties";

/**
* File name for logging properties
*/
public static final String LOGGING_PROPERTIES = "logging.properties";

private StringsConstants() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
/*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.hedera.block.common.utils;

import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.IOException;
import java.lang.System.Logger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Objects;
import java.util.Set;
import java.util.zip.GZIPInputStream;

/** A utility class that deals with logic related to dealing with files. */
public final class FileUtilities {
private static final Logger LOGGER = System.getLogger(FileUtilities.class.getName());

/**
* The default file permissions for new files.
* <p>
* Default permissions are set to: rw-r--r--
*/
private static final FileAttribute<Set<PosixFilePermission>> DEFAULT_FILE_PERMISSIONS =
PosixFilePermissions.asFileAttribute(
Set.of(
PosixFilePermission.OWNER_READ,
PosixFilePermission.OWNER_WRITE,
PosixFilePermission.GROUP_READ,
PosixFilePermission.OTHERS_READ));

/**
* Default folder permissions for new folders.
* <p>
* Default permissions are set to: rwxr-xr-x
*/
private static final FileAttribute<Set<PosixFilePermission>> DEFAULT_FOLDER_PERMISSIONS =
PosixFilePermissions.asFileAttribute(
Set.of(
PosixFilePermission.OWNER_READ,
PosixFilePermission.OWNER_WRITE,
PosixFilePermission.OWNER_EXECUTE,
PosixFilePermission.GROUP_READ,
PosixFilePermission.GROUP_EXECUTE,
PosixFilePermission.OTHERS_READ,
PosixFilePermission.OTHERS_EXECUTE));

/**
* Log message template used when a path is not created because a file
* or folder already exists at the requested path.
*/
private static final String PRE_EXISTING_FOLDER_MESSAGE =
"Requested %s [%s] not created because %s already exists at %s";

/**
* Create a new path (folder or file) if it does not exist.
* Any folders or files created will use default permissions.
*
* @param toCreate valid, non-null instance of {@link Path} to be created
* @param logLevel valid, non-null instance of {@link System.Logger.Level} to use
* @param semanticPathName valid, non-blank {@link String} used for logging that represents the
* desired path semantically
* @param createDir {@link Boolean} value if we should create a directory or a file
* @throws IOException if the path cannot be created
*/
public static void createPathIfNotExists(
@NonNull final Path toCreate,
@NonNull final System.Logger.Level logLevel,
@NonNull final String semanticPathName,
final boolean createDir)
throws IOException {
createPathIfNotExists(
toCreate,
logLevel,
DEFAULT_FILE_PERMISSIONS,
DEFAULT_FOLDER_PERMISSIONS,
semanticPathName,
createDir);
}

/**
* Create a new path (folder or file) if it does not exist.
*
* @param toCreate The path to be created.
* @param logLevel The logging level to use when logging this event.
* @param filePermissions Permissions to use when creating a new file.
* @param folderPermissions Permissions to use when creating a new folder.
* @param semanticPathName A name to represent the path in a logging
* statement.
* @param createDir A flag indicating we should create a directory
* (true) or a file (false)
* @throws IOException if the path cannot be created due to a filesystem
* error.
*/
public static void createPathIfNotExists(
@NonNull final Path toCreate,
@NonNull final System.Logger.Level logLevel,
@NonNull final FileAttribute<Set<PosixFilePermission>> filePermissions,
@NonNull final FileAttribute<Set<PosixFilePermission>> folderPermissions,
@NonNull final String semanticPathName,
final boolean createDir)
throws IOException {
Objects.requireNonNull(toCreate);
Objects.requireNonNull(logLevel);
Objects.requireNonNull(filePermissions);
Objects.requireNonNull(folderPermissions);
StringUtilities.requireNotBlank(semanticPathName);
final String requestedType = createDir ? "directory" : "file";
if (Files.notExists(toCreate)) {
if (createDir) {
Files.createDirectories(toCreate, folderPermissions);
} else {
Files.createFile(toCreate, filePermissions);
}
final String logMessage =
"Created %s [%s] at %s".formatted(requestedType, semanticPathName, toCreate);
LOGGER.log(logLevel, logMessage);
} else {
final String actualType = Files.isDirectory(toCreate) ? "directory" : "file";
final String logMessage =
PRE_EXISTING_FOLDER_MESSAGE.formatted(
requestedType, semanticPathName, actualType, toCreate);
LOGGER.log(logLevel, logMessage);
}
}

/**
* Read a GZIP file and return the content as a byte array.
* <p>
* This method is _unsafe_ because it reads the entire file content into
* a single byte array, which can cause memory issues, and may fail if the
* file contains a large amount of data.
*
* @param filePath Path to the GZIP file.
* @return byte array containing the _uncompressed_ content of the GZIP file.
* @throws IOException if unable to read the file.
* @throws OutOfMemoryError if a byte array large enough to contain the
* file contents cannot be allocated (either because it exceeds MAX_INT
* bytes or exceeds available heap memory).
*/
public static byte[] readGzipFileUnsafe(@NonNull final Path filePath) throws IOException {
Objects.requireNonNull(filePath);
try (final var gzipInputStream = new GZIPInputStream(Files.newInputStream(filePath))) {
return gzipInputStream.readAllBytes();
}
}

/**
* Read a file and return the content as a byte array.
* <p>
* This method uses default extensions for gzip and block files.
* <p>
* This method is _unsafe_ because it reads the entire file content into
* a single byte array, which can cause memory issues, and may fail if the
* file contains a large amount of data.
*
* @param filePath Path to the file
* @return byte array of the content of the file or null if the file extension is not
* supported
* @throws IOException if unable to read the file.
* @throws OutOfMemoryError if a byte array large enough to contain the
* file contents cannot be allocated (either because it exceeds MAX_INT
* bytes or exceeds available heap memory).
*/
public static byte[] readFileBytesUnsafe(@NonNull final Path filePath) throws IOException {
return readFileBytesUnsafe(filePath, ".blk", ".gz");
}

/**
* Read a file and return the content as a byte array.
* <p>
* This method is _unsafe_ because it reads the entire file content into
* a single byte array, which can cause memory issues, and may fail if the
* file contains a large amount of data.
*
* @param filePath Path to the file to read.
* @param blockFileExtension A file extension for block files.
* @param gzipFileExtension A file extension for gzip files.
* @return A byte array with the full contents of the file, or null if the
* file extension requested does not match at least one of the
* extensions provided (GZip or Block).
* @throws IOException if unable to read the file.
* @throws OutOfMemoryError if a byte array large enough to contain the
* file contents cannot be allocated (either because it exceeds MAX_INT
* bytes or exceeds available heap memory).
*/
public static byte[] readFileBytesUnsafe(
@NonNull final Path filePath,
@NonNull final String blockFileExtension,
@NonNull final String gzipFileExtension)
throws IOException {
final String filePathAsString = Objects.requireNonNull(filePath).toString();
Objects.requireNonNull(blockFileExtension);
Objects.requireNonNull(gzipFileExtension);
if (filePathAsString.endsWith(gzipFileExtension)) {
return readGzipFileUnsafe(filePath);
} else if (filePathAsString.endsWith(blockFileExtension)) {
return Files.readAllBytes(filePath);
} else {
return null;
}
}

private FileUtilities() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.hedera.block.common.utils;

import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Objects;

/** A utility class that deals with logic related to Strings. */
public final class StringUtilities {
/**
* This method checks if a given {@link String} is blank, meaning if it is {@code null} or
* contains only whitespaces as defined by {@link String#isBlank()}. If the given {@link String}
* is not blank, then we return it, else we throw {@link IllegalArgumentException}.
*
* @param toCheck a {@link String} to be checked if is blank as defined above
* @return the {@link String} to be checked if it is not blank as defined above
* @throws IllegalArgumentException if the input {@link String} to be checked is blank
*/
public static String requireNotBlank(@NonNull final String toCheck) {
if (Objects.requireNonNull(toCheck).isBlank()) {
throw new IllegalArgumentException("A String required to be non-blank is blank.");
} else {
return toCheck;
}
}

private StringUtilities() {}
}
6 changes: 6 additions & 0 deletions common/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module com.hedera.block.common {
exports com.hedera.block.common.constants;
exports com.hedera.block.common.utils;

requires static com.github.spotbugs.annotations;
}
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ plugins {
}

// Include the subprojects
include(":common")
include(":suites")
include(":stream")
include(":server")
Expand Down
Loading