diff --git a/objectmerger-cli/pom.xml b/objectmerger-cli/pom.xml
index 0de2639..83bef74 100644
--- a/objectmerger-cli/pom.xml
+++ b/objectmerger-cli/pom.xml
@@ -16,6 +16,7 @@
jar
+
de.x132
objectmerger
@@ -28,11 +29,15 @@
com.google.code.gson
gson
+
+
org.projectlombok
lombok
provided
+
+
org.junit.jupiter
junit-jupiter-api
diff --git a/objectmerger-cli/src/main/java/de/x132/cli/FieldDefinitionDeserializer.java b/objectmerger-cli/src/main/java/de/x132/cli/FieldDefinitionDeserializer.java
index b836eb6..682c9dd 100644
--- a/objectmerger-cli/src/main/java/de/x132/cli/FieldDefinitionDeserializer.java
+++ b/objectmerger-cli/src/main/java/de/x132/cli/FieldDefinitionDeserializer.java
@@ -13,25 +13,25 @@
public class FieldDefinitionDeserializer implements JsonDeserializer> {
- @Override
- public FieldDefinition> deserialize(
- JsonElement json, Type typeOfT, JsonDeserializationContext context)
- throws JsonParseException {
+ @Override
+ public FieldDefinition> deserialize(
+ JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
- JsonObject jsonObject = json.getAsJsonObject();
- String strategyName =
- jsonObject.has("strategy") ? jsonObject.get("strategy").getAsString() : "standard";
+ JsonObject jsonObject = json.getAsJsonObject();
+ String strategyName =
+ jsonObject.has("strategy") ? jsonObject.get("strategy").getAsString() : "standard";
- MergeStrategy, ?> strategy = StrategyRegistry.getInstance().getStrategy(strategyName);
+ MergeStrategy, ?> strategy = StrategyRegistry.getInstance().getStrategy(strategyName);
- // The configuration class for the strategy
- Class extends FieldDefinition> configClass = strategy.getConfigurationClass();
+ // The configuration class for the strategy
+ Class extends FieldDefinition> configClass = strategy.getConfigurationClass();
- // Prevent infinite recursion if the strategy returns the abstract base class
- if (configClass == FieldDefinition.class) {
- configClass = StandardFieldDefinition.class;
- }
+ // Prevent infinite recursion if the strategy returns the abstract base class
+ if (configClass == FieldDefinition.class) {
+ configClass = StandardFieldDefinition.class;
+ }
- return context.deserialize(json, configClass);
- }
+ return context.deserialize(json, configClass);
+ }
}
diff --git a/objectmerger-cli/src/main/java/de/x132/cli/GenerateCommand.java b/objectmerger-cli/src/main/java/de/x132/cli/GenerateCommand.java
index 2475aff..9ae5eb4 100644
--- a/objectmerger-cli/src/main/java/de/x132/cli/GenerateCommand.java
+++ b/objectmerger-cli/src/main/java/de/x132/cli/GenerateCommand.java
@@ -17,41 +17,41 @@
@Command(name = "generate", description = "Generate a merge definition from a sample JSON file")
public class GenerateCommand implements Callable {
- @Parameters(index = "0", description = "Path to sample JSON file")
- private Path samplePath;
-
- @Option(
- names = {"-o", "--output"},
- description = "Output JSON file; defaults to stdout")
- private Path outputPath;
-
- private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
-
- @Override
- public Integer call() throws Exception {
- try {
- Map sampleData;
- try (FileReader reader = new FileReader(samplePath.toFile())) {
- sampleData = gson.fromJson(reader, Map.class);
- }
-
- MergeDefinition definition = MergeDefinitionGenerator.generate(sampleData);
-
- String jsonOut = gson.toJson(definition);
- if (outputPath != null) {
- Files.createDirectories(outputPath.toAbsolutePath().getParent());
- try (FileWriter writer = new FileWriter(outputPath.toFile())) {
- writer.write(jsonOut);
+ @Parameters(index = "0", description = "Path to sample JSON file")
+ private Path samplePath;
+
+ @Option(
+ names = {"-o", "--output"},
+ description = "Output JSON file; defaults to stdout")
+ private Path outputPath;
+
+ private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
+
+ @Override
+ public Integer call() throws Exception {
+ try {
+ Map sampleData;
+ try (FileReader reader = new FileReader(samplePath.toFile())) {
+ sampleData = gson.fromJson(reader, Map.class);
+ }
+
+ MergeDefinition definition = MergeDefinitionGenerator.generate(sampleData);
+
+ String jsonOut = gson.toJson(definition);
+ if (outputPath != null) {
+ Files.createDirectories(outputPath.toAbsolutePath().getParent());
+ try (FileWriter writer = new FileWriter(outputPath.toFile())) {
+ writer.write(jsonOut);
+ }
+ } else {
+ System.out.println(jsonOut);
+ }
+ return 0;
+
+ } catch (Exception e) {
+ System.err.println("Generation failed: " + e.getMessage());
+ e.printStackTrace();
+ return 1;
}
- } else {
- System.out.println(jsonOut);
- }
- return 0;
-
- } catch (Exception e) {
- System.err.println("Generation failed: " + e.getMessage());
- e.printStackTrace();
- return 1;
}
- }
}
diff --git a/objectmerger-cli/src/main/java/de/x132/cli/MergeCommand.java b/objectmerger-cli/src/main/java/de/x132/cli/MergeCommand.java
index 3ef63d9..23c952f 100644
--- a/objectmerger-cli/src/main/java/de/x132/cli/MergeCommand.java
+++ b/objectmerger-cli/src/main/java/de/x132/cli/MergeCommand.java
@@ -22,74 +22,75 @@
@Command(name = "merge", description = "Merge multiple JSON sources")
public class MergeCommand implements Callable {
- @Option(
- names = {"-d", "--definition"},
- required = true,
- description = "Path to merge definition JSON file")
- private Path definitionPath;
+ @Option(
+ names = {"-d", "--definition"},
+ required = true,
+ description = "Path to merge definition JSON file")
+ private Path definitionPath;
- @Option(
- names = {"-s", "--source"},
- required = true,
- arity = "1..*",
- description = "Source in format label=path/to.json (repeat for multiple sources)")
- private List sources;
+ @Option(
+ names = {"-s", "--source"},
+ required = true,
+ arity = "1..*",
+ description = "Source in format label=path/to.json (repeat for multiple sources)")
+ private List sources;
- @Option(
- names = {"-o", "--output"},
- description = "Output JSON file; defaults to stdout")
- private Path outputPath;
+ @Option(
+ names = {"-o", "--output"},
+ description = "Output JSON file; defaults to stdout")
+ private Path outputPath;
- private final Gson gson =
- new GsonBuilder()
- .setPrettyPrinting()
- .registerTypeAdapter(FieldDefinition.class, new FieldDefinitionDeserializer())
- .create();
+ private final Gson gson =
+ new GsonBuilder()
+ .setPrettyPrinting()
+ .registerTypeAdapter(FieldDefinition.class, new FieldDefinitionDeserializer())
+ .create();
- @Override
- public Integer call() throws Exception {
- try {
- MergeDefinition mergeDefinition;
- try (FileReader reader = new FileReader(definitionPath.toFile())) {
- mergeDefinition = gson.fromJson(reader, MergeDefinition.class);
- }
+ @Override
+ public Integer call() throws Exception {
+ try {
+ MergeDefinition mergeDefinition;
+ try (FileReader reader = new FileReader(definitionPath.toFile())) {
+ mergeDefinition = gson.fromJson(reader, MergeDefinition.class);
+ }
- List>> labeledSources = new ArrayList<>();
- for (String src : sources) {
- int eq = src.indexOf('=');
- if (eq <= 0 || eq == src.length() - 1) {
- throw new IllegalArgumentException("Invalid --source format: " + src);
- }
- String label = src.substring(0, eq);
- Path path = Path.of(src.substring(eq + 1));
- try (FileReader reader = new FileReader(path.toFile())) {
- Map obj = gson.fromJson(reader, Map.class);
- labeledSources.add(new LabeledSource<>(label, obj));
- }
- }
+ List>> labeledSources = new ArrayList<>();
+ for (String src : sources) {
+ int eq = src.indexOf('=');
+ if (eq <= 0 || eq == src.length() - 1) {
+ throw new IllegalArgumentException("Invalid --source format: " + src);
+ }
+ String label = src.substring(0, eq);
+ Path path = Path.of(src.substring(eq + 1));
+ try (FileReader reader = new FileReader(path.toFile())) {
+ Map obj = gson.fromJson(reader, Map.class);
+ labeledSources.add(new LabeledSource<>(label, obj));
+ }
+ }
- @SuppressWarnings("unchecked")
- Map merged =
- ObjectMerger.merge(mergeDefinition, labeledSources.toArray(LabeledSource[]::new));
+ @SuppressWarnings("unchecked")
+ Map merged =
+ ObjectMerger.merge(
+ mergeDefinition, labeledSources.toArray(LabeledSource[]::new));
- String jsonOut = gson.toJson(merged);
- if (outputPath != null) {
- Files.createDirectories(outputPath.toAbsolutePath().getParent());
- try (FileWriter writer = new FileWriter(outputPath.toFile())) {
- writer.write(jsonOut);
- }
- } else {
- System.out.println(jsonOut);
- }
- return 0;
+ String jsonOut = gson.toJson(merged);
+ if (outputPath != null) {
+ Files.createDirectories(outputPath.toAbsolutePath().getParent());
+ try (FileWriter writer = new FileWriter(outputPath.toFile())) {
+ writer.write(jsonOut);
+ }
+ } else {
+ System.out.println(jsonOut);
+ }
+ return 0;
- } catch (JsonSyntaxException | JsonIOException e) {
- System.err.println("Failed to parse JSON: " + e.getMessage());
- return 3;
- } catch (Exception e) {
- System.err.println("Merge failed: " + e.getMessage());
- e.printStackTrace();
- return 1;
+ } catch (JsonSyntaxException | JsonIOException e) {
+ System.err.println("Failed to parse JSON: " + e.getMessage());
+ return 3;
+ } catch (Exception e) {
+ System.err.println("Merge failed: " + e.getMessage());
+ e.printStackTrace();
+ return 1;
+ }
}
- }
}
diff --git a/objectmerger-cli/src/main/java/de/x132/cli/ObjectMergerCli.java b/objectmerger-cli/src/main/java/de/x132/cli/ObjectMergerCli.java
index d7aa31f..b31f759 100644
--- a/objectmerger-cli/src/main/java/de/x132/cli/ObjectMergerCli.java
+++ b/objectmerger-cli/src/main/java/de/x132/cli/ObjectMergerCli.java
@@ -4,15 +4,15 @@
import picocli.CommandLine.Command;
@Command(
- name = "objectmerger",
- mixinStandardHelpOptions = true,
- version = "0.1.0",
- description = "ObjectMerger CLI tool",
- subcommands = {MergeCommand.class, GenerateCommand.class})
+ name = "objectmerger",
+ mixinStandardHelpOptions = true,
+ version = "0.1.0",
+ description = "ObjectMerger CLI tool",
+ subcommands = {MergeCommand.class, GenerateCommand.class})
public class ObjectMergerCli {
- public static void main(String[] args) {
- int exit = new CommandLine(new ObjectMergerCli()).execute(args);
- System.exit(exit);
- }
+ public static void main(String[] args) {
+ int exit = new CommandLine(new ObjectMergerCli()).execute(args);
+ System.exit(exit);
+ }
}
diff --git a/objectmerger-cli/src/test/java/de/x132/cli/GenerateCommandTest.java b/objectmerger-cli/src/test/java/de/x132/cli/GenerateCommandTest.java
index bacc5e9..3549b71 100644
--- a/objectmerger-cli/src/test/java/de/x132/cli/GenerateCommandTest.java
+++ b/objectmerger-cli/src/test/java/de/x132/cli/GenerateCommandTest.java
@@ -17,44 +17,47 @@
class GenerateCommandTest {
- private Path tempDir;
- private Path sampleFile;
- private Path outputFile;
-
- @BeforeEach
- void setUp() throws IOException {
- tempDir = Files.createTempDirectory("objectmerger-cli-test");
- sampleFile = tempDir.resolve("sample.json");
- outputFile = tempDir.resolve("output.json");
-
- try (FileWriter writer = new FileWriter(sampleFile.toFile())) {
- writer.write("{\"name\": \"test\", \"value\": 123}");
+ private Path tempDir;
+ private Path sampleFile;
+ private Path outputFile;
+
+ @BeforeEach
+ void setUp() throws IOException {
+ tempDir = Files.createTempDirectory("objectmerger-cli-test");
+ sampleFile = tempDir.resolve("sample.json");
+ outputFile = tempDir.resolve("output.json");
+
+ try (FileWriter writer = new FileWriter(sampleFile.toFile())) {
+ writer.write("{\"name\": \"test\", \"value\": 123}");
+ }
}
- }
-
- @AfterEach
- void tearDown() throws IOException {
- Files.walk(tempDir).sorted((a, b) -> b.compareTo(a)).map(Path::toFile).forEach(File::delete);
- }
-
- @Test
- void testGenerate() {
- GenerateCommand cmd = new GenerateCommand();
- CommandLine commandLine = new CommandLine(cmd);
-
- int exitCode = commandLine.execute(sampleFile.toString(), "-o", outputFile.toString());
-
- assertEquals(0, exitCode);
- assertTrue(Files.exists(outputFile));
-
- try {
- String content = Files.readString(outputFile);
- Gson gson = new Gson();
- Map map = gson.fromJson(content, Map.class);
- assertTrue(((Map) map.get("definitions")).containsKey("name"));
- assertTrue(((Map) map.get("definitions")).containsKey("value"));
- } catch (IOException e) {
- throw new RuntimeException(e);
+
+ @AfterEach
+ void tearDown() throws IOException {
+ Files.walk(tempDir)
+ .sorted((a, b) -> b.compareTo(a))
+ .map(Path::toFile)
+ .forEach(File::delete);
+ }
+
+ @Test
+ void testGenerate() {
+ GenerateCommand cmd = new GenerateCommand();
+ CommandLine commandLine = new CommandLine(cmd);
+
+ int exitCode = commandLine.execute(sampleFile.toString(), "-o", outputFile.toString());
+
+ assertEquals(0, exitCode);
+ assertTrue(Files.exists(outputFile));
+
+ try {
+ String content = Files.readString(outputFile);
+ Gson gson = new Gson();
+ Map map = gson.fromJson(content, Map.class);
+ assertTrue(((Map) map.get("definitions")).containsKey("name"));
+ assertTrue(((Map) map.get("definitions")).containsKey("value"));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
}
- }
}
diff --git a/objectmerger-mvel/pom.xml b/objectmerger-mvel/pom.xml
new file mode 100644
index 0000000..eb7318a
--- /dev/null
+++ b/objectmerger-mvel/pom.xml
@@ -0,0 +1,57 @@
+
+
+ 4.0.0
+
+ de.x132
+ objectmerger-parent
+ 0.2.0
+
+
+ objectmerger-mvel
+ ObjectMerger MVEL Extension
+ MVEL integration for ObjectMerger
+
+
+
+
+ de.x132
+ objectmerger
+ ${project.version}
+
+
+ org.mvel
+ mvel2
+ 2.5.0.Final
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+
+
+ com.google.code.gson
+ gson
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+
diff --git a/objectmerger-mvel/src/main/java/de/x132/objectmerger/strategy/conditional/ConditionalMergeStrategy.java b/objectmerger-mvel/src/main/java/de/x132/objectmerger/strategy/conditional/ConditionalMergeStrategy.java
new file mode 100644
index 0000000..9ba062c
--- /dev/null
+++ b/objectmerger-mvel/src/main/java/de/x132/objectmerger/strategy/conditional/ConditionalMergeStrategy.java
@@ -0,0 +1,100 @@
+package de.x132.objectmerger.strategy.conditional;
+
+import de.x132.objectmerger.LabeledSource;
+import de.x132.objectmerger.ObjectMerger;
+import de.x132.objectmerger.exception.ConfigurationException;
+import de.x132.objectmerger.registry.StrategyRegistry;
+import de.x132.objectmerger.strategy.FieldDefinition;
+import de.x132.objectmerger.strategy.MergeStrategy;
+import de.x132.objectmerger.strategy.config.ConditionalConfig;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class ConditionalMergeStrategy
+ implements MergeStrategy> {
+
+ public static final String NAME = "conditional";
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Class> getConfigurationClass() {
+ return (Class) ConditionalFieldDefinition.class;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T merge(
+ List> sources,
+ ConditionalFieldDefinition fieldDef,
+ String fieldName) {
+ return (T) mergeInternal(sources, fieldDef, fieldName);
+ }
+
+ private Object mergeInternal(
+ List> sources, ConditionalConfig fieldDef, String fieldName) {
+
+ Map context = new HashMap<>();
+ context.put("sources", sources);
+ Map values = new HashMap<>();
+ for (LabeledSource> s : sources) {
+ values.put(s.getLabel(), ObjectMerger.getFieldValue(s.getSource(), fieldName));
+ }
+ if (fieldDef.getCases() != null) {
+ for (ConditionCase> c : fieldDef.getCases()) {
+ try {
+ // Add values to context for easier access (e.g. values['source1'])
+ // MVEL expression example: "values['source1'] == 'admin'"
+ context.put("values", values);
+
+ Object result =
+ de.x132.objectmerger.strategy.mvel.MvelSandbox.evaluate(
+ c.getCondition(), context);
+
+ if (result instanceof Boolean && (Boolean) result) {
+ log.debug(
+ "Condition '{}' matched for field '{}'",
+ c.getCondition(),
+ fieldName);
+ return executeSubStrategy(c.getUseStrategy(), sources, fieldName);
+ }
+ } catch (Exception e) {
+ log.warn(
+ "Failed to evaluate condition '{}' for field '{}': {}",
+ c.getCondition(),
+ fieldName,
+ e.getMessage());
+ // Continue to next case or default
+ }
+ }
+ }
+
+ if (fieldDef.getDefaultStrategy() != null) {
+ log.debug("No condition matched for field '{}', using default strategy", fieldName);
+ return executeSubStrategy(fieldDef.getDefaultStrategy(), sources, fieldName);
+ }
+
+ return null;
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private Object executeSubStrategy(
+ FieldDefinition def, List> sources, String fieldName) {
+ String strategyName = def.getStrategy() != null ? def.getStrategy() : "standard";
+ MergeStrategy strategy = StrategyRegistry.getInstance().getStrategy(strategyName);
+
+ if (strategy == null) {
+ throw new ConfigurationException(
+ "Unknown strategy in conditional case: " + strategyName);
+ }
+
+ return strategy.merge(sources, def, fieldName);
+ }
+}
diff --git a/objectmerger/src/main/java/de/x132/objectmerger/strategy/mvel/MvelFieldDefinition.java b/objectmerger-mvel/src/main/java/de/x132/objectmerger/strategy/mvel/MvelFieldDefinition.java
similarity index 94%
rename from objectmerger/src/main/java/de/x132/objectmerger/strategy/mvel/MvelFieldDefinition.java
rename to objectmerger-mvel/src/main/java/de/x132/objectmerger/strategy/mvel/MvelFieldDefinition.java
index 49eae43..bdd4be8 100644
--- a/objectmerger/src/main/java/de/x132/objectmerger/strategy/mvel/MvelFieldDefinition.java
+++ b/objectmerger-mvel/src/main/java/de/x132/objectmerger/strategy/mvel/MvelFieldDefinition.java
@@ -14,5 +14,5 @@
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class MvelFieldDefinition extends FieldDefinition