diff --git a/core/src/main/java/de/neitzel/core/util/StopWatch.java b/core/src/main/java/de/neitzel/core/util/StopWatch.java new file mode 100644 index 0000000..406ffc4 --- /dev/null +++ b/core/src/main/java/de/neitzel/core/util/StopWatch.java @@ -0,0 +1,128 @@ +package de.neitzel.core.util; + +import java.time.Duration; +import java.time.Instant; +import java.util.Locale; + +/** + * A utility class for measuring elapsed time. The StopWatch can be started, stopped, and queried for the elapsed time in various formats. + */ +public class StopWatch { + + /** + * Represents the starting time of the stopwatch. This variable is set when the stopwatch is started and is used to calculate the elapsed + * duration between the start and the stop or the current time. + */ + private Instant startTime; + /** + * Represents the stopping time of the stopwatch. This variable is set when the stopwatch is stopped and is used to calculate the elapsed + * duration between the start and stop times. + */ + private Instant stopTime; + + /** + * Starts the stopwatch by recording the current time as the starting time. If the stopwatch was previously stopped or not yet started, + * this method resets the starting time to the current time and clears the stopping time. This method is typically called before measuring + * elapsed time. + */ + public void start() { + this.startTime = Instant.now(); + this.stopTime = null; + } + + /** + * Stops the stopwatch by recording the current time as the stopping time. + *

+ * This method should only be called after the stopwatch has been started using the {@code start()} method. If the stopwatch has not been + * started, an {@code IllegalStateException} will be thrown. Once this method is called, the elapsed time can be calculated as the + * duration between the starting and stopping times. + * + * @throws IllegalStateException if the stopwatch has not been started prior to calling this method. + */ + public void stop() { + if (startTime == null) { + throw new IllegalStateException("StopWatch must be started before stopping."); + } + this.stopTime = Instant.now(); + } + + /** + * Calculates the amount of time elapsed between the stopwatch's starting time and either its stopping time or the current time if the + * stopwatch is still running. + * + * @return the duration representing the elapsed time between the start and end points of the stopwatch. + * @throws IllegalStateException if the stopwatch has not been started before calling this method. + */ + public Duration getUsedTime() { + if (startTime == null) { + throw new IllegalStateException("StopWatch has not been started."); + } + Instant end = (stopTime != null) ? stopTime : Instant.now(); + return Duration.between(startTime, end); + } + + /** + * Formats the duration of elapsed time measured by the stopwatch into a human-readable string. The formatted output may include days, + * hours, minutes, seconds, and milliseconds, depending on the parameters. The method retrieves the elapsed time via the + * {@code getUsedTime} method and formats it based on the specified flags. + * + * @param includeMillis whether to include milliseconds in the formatted output + * @param includeSeconds whether to include seconds in the formatted output + * @param includeMinutes whether to include minutes in the formatted output + * @param includeHours whether to include hours in the formatted output + * @param includeDays whether to include days in the formatted output + * @return a formatted string representing the elapsed time, including the components specified by the parameters + */ + public String getUsedTimeFormatted(boolean includeMillis, boolean includeSeconds, boolean includeMinutes, boolean includeHours, + boolean includeDays) { + Duration duration = getUsedTime(); + + long millis = duration.toMillis(); + long days = millis / (24 * 60 * 60 * 1000); + millis %= (24 * 60 * 60 * 1000); + + long hours = millis / (60 * 60 * 1000); + millis %= (60 * 60 * 1000); + long minutes = millis / (60 * 1000); + millis %= (60 * 1000); + long seconds = millis / 1000; + millis %= 1000; + + StringBuilder sb = new StringBuilder(); + + if (includeDays && days > 0) { + sb.append(days).append("d "); + } + if (includeHours && (hours > 0 || sb.length() > 0)) { + sb.append(hours).append("h "); + } + if (includeMinutes && (minutes > 0 || sb.length() > 0)) { + sb.append(minutes).append("m "); + } + if (includeSeconds && (seconds > 0 || sb.length() > 0)) { + sb.append(seconds); + } + if (includeMillis) { + if (includeSeconds) { + sb.append(String.format(Locale.US, ".%03d", millis)); + } else { + sb.append(millis).append("ms"); + } + } + + String result = sb.toString().trim(); + return result.isEmpty() ? "0ms" : result; + } + + /** + * Provides a compact and human-readable formatted string representing the elapsed time measured by the stopwatch. The method formats the + * duration using a combination of days, hours, minutes, seconds, and milliseconds. This compact format removes additional verbosity in + * favor of a shorter, more concise representation. + * + * @return a compact formatted string representing the elapsed time, including days, hours, minutes, seconds, and milliseconds as + * applicable + */ + public String getUsedTimeFormattedCompact() { + return getUsedTimeFormatted(true, true, true, true, true); + } +} diff --git a/gson/src/main/java/de/neitzel/gson/adapter/ExcludeFieldsAdapter.java b/gson/src/main/java/de/neitzel/gson/adapter/ExcludeFieldsAdapter.java new file mode 100644 index 0000000..373ca35 --- /dev/null +++ b/gson/src/main/java/de/neitzel/gson/adapter/ExcludeFieldsAdapter.java @@ -0,0 +1,136 @@ +package de.neitzel.gson.adapter; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.google.gson.TypeAdapter; +import com.google.gson.internal.Streams; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +/** + * A custom {@link TypeAdapter} implementation for selectively excluding specified fields during JSON serialization. + * + * This adapter facilitates both serialization and deserialization of objects while providing functionality to remove specified + * fields from the serialized output. The set of excluded field names is provided during the adapter's initialization. + * + * @param the type of objects that this adapter can serialize and deserialize + */ +public class ExcludeFieldsAdapter extends TypeAdapter { + + /** + * The type of objects that this adapter handles for serialization and deserialization. + * + * This is a generic class type parameter representing the class of objects that the + * {@link ExcludeFieldsAdapter} instance is designed to process. It ensures type + * safety and guarantees the compatibility of the adapter with a specific object type. + */ + private final Class type; + /** + * A set containing the names of fields to be excluded during JSON serialization. + * + * This collection is used in the {@link ExcludeFieldsAdapter} to determine which fields should be removed from the + * serialized JSON representation of an object. The fields to be excluded are specified during the initialization + * of the adapter. + */ + private final Set excludedFields; + + /** + * An instance of the Gson library used for JSON serialization and deserialization. + * + * This variable holds the Gson object that is utilized to handle the conversion processes within the + * custom serialization and deserialization logic. If not explicitly provided during initialization, + * it can be lazily initialized using a GsonBuilder. + */ + private Gson gson; + + /** + * A {@link GsonBuilder} instance used for configuring and creating {@link Gson} objects within the adapter. + * + * This variable serves as a reference to a GsonBuilder that allows customization of the Gson instance, + * including registering custom type adapters, serializers, and deserializers, as well as adjusting serialization policies. + * + * The {@link #gsonBuilder} is primarily utilized when an existing {@link Gson} instance is not directly provided. + * It ensures that the adapter can defer the creation of a Gson object until it is explicitly required. + */ + private GsonBuilder gsonBuilder; + + /** + * Creates a new instance of {@code ExcludeFieldsAdapter} for managing the serialization and deserialization + * of objects of the specified {@code type}, while excluding certain fields during serialization. + * + * @param type the class type of the objects to be serialized and deserialized + * @param excludedFields the set of field names to be excluded during serialization + * @param gson the Gson instance to be used for serialization and deserialization + */ + public ExcludeFieldsAdapter(Class type, Set excludedFields, Gson gson) { + this.type = type; + this.excludedFields = new HashSet<>(excludedFields); + this.gsonBuilder = null; + this.gson = gson; + } + + /** + * Constructs an instance of {@code ExcludeFieldsAdapter} that selectively excludes specified fields from + * the JSON output based on the configuration provided during initialization. + * + * @param type the class of the type to be serialized and deserialized + * @param excludedFields a set of field names to be excluded from the serialized JSON output + * @param gsonBuilder an instance of {@code GsonBuilder} used to create a custom {@code Gson} instance if needed + */ + public ExcludeFieldsAdapter(Class type, Set excludedFields, GsonBuilder gsonBuilder) { + this.type = type; + this.excludedFields = new HashSet<>(excludedFields); + this.gsonBuilder = gsonBuilder; + this.gson = null; + } + + /** + * Lazily initializes and retrieves the {@link Gson} instance. If the instance is null, it creates a new {@link Gson} + * object using the {@link GsonBuilder} provided during the adapter's initialization. + * + * @return the {@link Gson} instance used for serialization and deserialization + */ + private Gson getGson() { + if (gson == null) { + gson = gsonBuilder.create(); + } + return gson; + } + + /** + * Serializes an object of type {@code T} into JSON format, excluding certain fields specified during the initialization of this adapter. + * The object is first converted into a {@link JsonObject}, then the fields listed in the excluded fields set are removed from the JSON + * representation before writing it to the provided {@link JsonWriter}. + * + * @param out the {@link JsonWriter} to write the serialized JSON data to + * @param value the object of type {@code T} to serialize into JSON + * @throws IOException if an I/O error occurs during writing + */ + @Override + public void write(JsonWriter out, T value) throws IOException { + JsonObject obj = getGson().toJsonTree(value).getAsJsonObject(); + + for (String field : excludedFields) { + obj.remove(field); + } + + Streams.write(obj, out); + } + + /** + * Reads a JSON input stream and deserializes it into an object of the specified type. + * + * @param in the {@link JsonReader} to read the JSON input from + * @return an instance of type {@code T} deserialized from the JSON input + * @throws IOException if an error occurs during reading or deserialization + */ + @Override + public T read(JsonReader in) throws IOException { + return getGson().fromJson(in, type); + } +}