feature/github-copilot-consolidation #1

Merged
konrad merged 10 commits from feature/github-copilot-consolidation into main 2025-12-14 13:29:43 +01:00
2 changed files with 398 additions and 0 deletions
Showing only changes of commit 6c1338d171 - Show all commits

View File

@ -0,0 +1,270 @@
package de.neitzel.gson.config;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import lombok.Builder;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
/**
* Represents a configuration utility class that manages application settings and allows
* loading and saving the configuration in JSON format. This class is built using Gson for
* JSON serialization and deserialization.
* <p>
* The class maintains application settings in memory and provides methods to store
* the configuration on the filesystem and retrieve it as needed.
*/
@Builder(builderClassName = "ConfigurationBuilder")
public class JsonConfiguration {
/**
* The name of the application.
* This variable is used to identify the application and is incorporated into various configurations,
* such as the naming of directories or files associated with the application's settings.
* It is finalized and set during the construction of the {@code JsonConfiguration} instance and cannot be changed afterward.
*/
private final String appName;
/**
* The filesystem path representing the home directory utilized by the application.
* This directory serves as the location where application-specific configuration files
* and data are stored. The path is initialized when the {@code JsonConfiguration} instance
* is constructed, based on the settings provided through the {@code JsonConfigurationBuilder}.
* It can be customized to a user-specified value or defaults to the user's home directory.
* <p>
* The {@code homeDir} variable is integral in determining the location of the main configuration
* file and other related resources used by the application.
*/
private final String homeDir;
/**
* A map that stores configuration settings as key-value pairs,
* where both the keys and values are represented as strings.
* <p>
* This map is used to manage dynamic or runtime settings for the configuration,
* allowing for flexible assignment and retrieval of values associated with specific
* configuration keys. It is initialized as a final instance, ensuring it cannot be
* reassigned after creation.
*/
private final Map<String, String> settings;
/**
* A Gson instance used for handling JSON serialization and deserialization within
* the JsonConfiguration class. This instance is immutable and customized with
* registered adapters during the initialization of the JsonConfiguration.
* <p>
* The Gson object provides functionality for converting Java objects to JSON
* and vice versa. It supports complex serialization and deserialization
* workflows by leveraging adapters specified during the configuration phase.
* <p>
* Adapters can be registered to customize the behavior of (de)serialization
* for specific types.
*/
private final Gson gson;
/**
* Stores a key-value pair into the configuration settings.
* The value is serialized into a JSON string before being stored.
*
* @param key the key under which the value will be stored
* @param value the object to be associated with the specified key
*/
public void set(String key, Object value) {
settings.put(key, gson.toJson(value));
}
/**
* Retrieves a value associated with the specified key from the configuration,
* deserializing it into the specified class type using Gson.
* If the key does not exist in the settings, the method returns {@code null}.
*
* @param <T> the type of the object to be deserialized
* @param key the key corresponding to the value in the configuration
* @param clazz the {@code Class} object representing the expected type of the value
* @return the deserialized object of type {@code T}, or {@code null}
* if the key does not exist
*/
public <T> T get(String key, Class<T> clazz) {
String json = settings.get(key);
return json != null ? gson.fromJson(json, clazz) : null;
}
/**
* Retrieves a value associated with the given key from the configuration as an object of the specified type.
* If the key does not exist in the configuration, the provided default value is returned.
*
* @param <T> the type of the object to be returned
* @param key the key whose associated value is to be retrieved
* @param clazz the class of the object to deserialize the value into
* @param defaultValue the default value to return if the key does not exist or the value is null
* @return the value associated with the specified key, deserialized into the specified type,
* or the default value if the key does not exist or the value is null
*/
public <T> T get(String key, Class<T> clazz, T defaultValue) {
String json = settings.get(key);
return json != null ? gson.fromJson(json, clazz) : defaultValue;
}
/**
* Loads the configuration data from the JSON configuration file located at the path
* determined by the {@code getConfigFilePath()} method.
* If the configuration file exists, its content is read and deserialized into a map
* of key-value pairs using Gson. The method clears the current settings and populates
* them with the loaded values.
*
* @throws IOException if an I/O error occurs while reading the configuration file
*/
public void load() throws IOException {
Path configPath = getConfigFilePath();
if (Files.exists(configPath)) {
try (var reader = Files.newBufferedReader(configPath)) {
Map<String, String> loaded = gson.fromJson(reader, new TypeToken<Map<String, String>>() {
}.getType());
settings.clear();
settings.putAll(loaded);
}
}
}
/**
* Constructs the path to the configuration file for the application.
* The configuration file is located in the user's home directory, within a hidden
* folder named after the application, and is named "config.json".
*
* @return the {@code Path} to the application's configuration file
*/
private Path getConfigFilePath() {
return Path.of(homeDir, "." + appName, "config.json");
}
/**
* Saves the current configuration to a JSON file.
* <p>
* This method serializes the current settings into a JSON format and writes
* them to a file located at the configuration path. If the parent directory
* of the configuration file does not exist, it is created automatically.
* <p>
* The configuration file path is determined based on the application name
* and home directory. Any existing content in the file will be overwritten
* during this operation.
*
* @throws IOException if an I/O error occurs while creating the directory,
* opening the file, or writing to the file
*/
public void save() throws IOException {
Path configPath = getConfigFilePath();
Files.createDirectories(configPath.getParent());
try (var writer = Files.newBufferedWriter(configPath)) {
gson.toJson(settings, writer);
}
}
/**
* Builder class for creating instances of {@code JsonConfiguration}.
* The {@code JsonConfigurationBuilder} allows the configuration of application
* name, home directory, and custom Gson adapters before building a {@code JsonConfiguration} object.
*/
public static class JsonConfigurationBuilder {
/**
* A map that holds custom Gson adapters to register with a GsonBuilder.
* The keys represent the classes for which the adapters are applicable,
* and the values are the adapter instances associated with those classes.
* <p>
* This variable is used to store user-defined type adapters, allowing
* for customized serialization and deserialization behavior for specific
* classes when constructing a {@code JsonConfiguration}.
* <p>
* It is populated using the {@code addGsonAdapter} method in the
* {@code JsonConfigurationBuilder} class and is later passed to a
* {@code GsonBuilder} for registration.
*/
private final Map<Class<?>, Object> gsonAdapters = new HashMap<>();
/**
* The name of the application being configured.
* This variable holds a string representation of the application's name and is used
* to identify the application within the context of the {@code JsonConfiguration}.
* It is set during the construction of a {@code JsonConfigurationBuilder} instance.
*/
private String appName;
/**
* Represents the home directory path of the current user.
* By default, this variable is initialized to the value of the "user.home" system property,
* which typically points to the user's home directory on the filesystem.
* <p>
* This field can be customized to point to a different directory via the builder class
* methods when building an instance of {@code JsonConfiguration}.
* <p>
* Example system values for "user.home" may include paths such as "~/Users/username" for macOS,
* "C:/Users/username" for Windows, or "/home/username" for Unix/Linux systems.
*/
private String homeDir = System.getProperty("user.home");
/**
* Sets the application name to be used in the configuration.
*
* @param appName the name of the application to be set
* @return the current instance of {@code JsonConfigurationBuilder} for chaining
*/
public JsonConfigurationBuilder appName(String appName) {
this.appName = appName;
return this;
}
/**
* Sets the home directory for the configuration being built.
* This method specifies the directory path where the application's configuration
* files or settings should be stored.
*
* @param homeDir the path to the desired home directory
* @return the current instance of {@code JsonConfigurationBuilder} to enable method chaining
*/
public JsonConfigurationBuilder homeDir(String homeDir) {
this.homeDir = homeDir;
return this;
}
/**
* Adds a custom Gson adapter to the builder for the specified type.
* This method allows registration of a type-to-adapter mapping that will be applied
* when building the Gson instance within the {@code JsonConfiguration}.
*
* @param type the {@code Class} of the type for which the adapter is being registered
* @param adapter the adapter object to be used for the specified type
* @return the current instance of {@code JsonConfigurationBuilder}, allowing method chaining
*/
public JsonConfigurationBuilder addGsonAdapter(Class<?> type, Object adapter) {
gsonAdapters.put(type, adapter);
return this;
}
/**
* Builds and returns an instance of {@code JsonConfiguration} with the specified
* settings, including the application name, home directory, and any registered
* custom Gson adapters.
*
* @return a {@code JsonConfiguration} instance configured with the builder's parameters.
*/
public JsonConfiguration build() {
GsonBuilder gsonBuilder = new GsonBuilder();
for (var entry : gsonAdapters.entrySet()) {
gsonBuilder.registerTypeAdapter(entry.getKey(), entry.getValue());
}
Gson gson = gsonBuilder.create();
return new JsonConfiguration(
appName,
homeDir,
new HashMap<>(),
gson
);
}
}
}

View File

@ -0,0 +1,128 @@
package de.neitzel.gson.config;
import com.google.gson.Gson;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
class JsonConfigurationTest {
/**
* Test class for JsonConfiguration containing unit tests for the `get` method.
* The `get` method fetches the value associated with a given key and converts
* it from JSON representation to the specified class type.
*/
@Test
void testGetExistingKeyWithValidValue() {
// Arrange
Gson gson = new Gson();
HashMap<String, String> settings = new HashMap<>();
settings.put("testKey", gson.toJson(42));
JsonConfiguration jsonConfiguration = JsonConfiguration.builder()
.settings(settings)
.gson(gson)
.build();
// Act
Integer value = jsonConfiguration.get("testKey", Integer.class);
// Assert
assertEquals(42, value);
}
@Test
void testGetNonExistingKey() {
// Arrange
Gson gson = new Gson();
HashMap<String, String> settings = new HashMap<>();
JsonConfiguration jsonConfiguration = JsonConfiguration.builder()
.settings(settings)
.gson(gson)
.build();
// Act
String value = jsonConfiguration.get("nonExistentKey", String.class);
// Assert
assertNull(value);
}
@Test
void testGetWithDefaultValueWhenKeyExists() {
// Arrange
Gson gson = new Gson();
HashMap<String, String> settings = new HashMap<>();
settings.put("testKey", gson.toJson("Hello World"));
JsonConfiguration jsonConfiguration = JsonConfiguration.builder()
.settings(settings)
.gson(gson)
.build();
// Act
String value = jsonConfiguration.get("testKey", String.class, "Default Value");
// Assert
assertEquals("Hello World", value);
}
@Test
void testGetWithDefaultValueWhenKeyDoesNotExist() {
// Arrange
Gson gson = new Gson();
HashMap<String, String> settings = new HashMap<>();
JsonConfiguration jsonConfiguration = JsonConfiguration.builder()
.settings(settings)
.gson(gson)
.build();
// Act
String value = jsonConfiguration.get("nonExistentKey", String.class, "Default Value");
// Assert
assertEquals("Default Value", value);
}
@Test
void testGetWithComplexObject() {
// Arrange
Gson gson = new Gson();
HashMap<String, String> settings = new HashMap<>();
TestObject testObject = new TestObject("John Doe", 30);
settings.put("complexKey", gson.toJson(testObject));
JsonConfiguration jsonConfiguration = JsonConfiguration.builder()
.settings(settings)
.gson(gson)
.build();
// Act
TestObject result = jsonConfiguration.get("complexKey", TestObject.class);
// Assert
assertEquals("John Doe", result.name());
assertEquals(30, result.age());
}
// Helper class for complex object tests
static class TestObject {
private final String name;
private final int age;
TestObject(String name, int age) {
this.name = name;
this.age = age;
}
public String name() {
return name;
}
public int age() {
return age;
}
}
}