Skip to content
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@

# OS
.DS_Store

# Skills Portfolio
/Skills.md
/skills/

41 changes: 41 additions & 0 deletions benchmark-throwaway/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>de.x132</groupId>
<artifactId>objectmerger-parent</artifactId>
<version>0.2.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>de.x132.benchmark</groupId>
<artifactId>benchmark-throwaway</artifactId>
<version>0.2.0</version>

<dependencies>
<dependency>
<groupId>de.x132</groupId>
<artifactId>objectmerger-graaljs</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>de.x132</groupId>
<artifactId>objectmerger-mvel</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>de.x132</groupId>
<artifactId>objectmerger-nashorn</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package de.x132.benchmark;

import de.x132.objectmerger.LabeledSource;
import de.x132.objectmerger.strategy.graaljs.GraalJsFieldDefinition;
import de.x132.objectmerger.strategy.graaljs.GraalJsMergeStrategy;
import de.x132.objectmerger.strategy.mvel.MvelFieldDefinition;
import de.x132.objectmerger.strategy.mvel.MvelMergeStrategy;
import de.x132.objectmerger.strategy.nashorn.NashornFieldDefinition;
import de.x132.objectmerger.strategy.nashorn.NashornMergeStrategy;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;

public class BenchmarkPlatformTest {

@Test
public void benchmarkGraalJsVsMvelVsNashorn() {
System.setProperty("polyglot.engine.WarnInterpreterOnly", "false");

GraalJsMergeStrategy graalStrategy = new GraalJsMergeStrategy();
MvelMergeStrategy mvelStrategy = new MvelMergeStrategy();
NashornMergeStrategy nashornStrategy = new NashornMergeStrategy();

LabeledSource<Object> source = new LabeledSource<>("test", Map.of("val", "value"));
List<LabeledSource<?>> sources = Collections.singletonList(source);

// GraalJS Setup
GraalJsFieldDefinition graalConfig =
GraalJsFieldDefinition.builder().expression("sources.test.val").build();

// MVEL Setup
MvelFieldDefinition mvelConfig =
MvelFieldDefinition.builder().expression("sources['test']['val']").build();

// Nashorn Setup
NashornFieldDefinition nashornConfig =
NashornFieldDefinition.builder().expression("sources.test.val").build();

// Warmup (to trigger JIT before real measurements)
System.out.println("Warming up engines...");
for (int i = 0; i < 20000; i++) {
graalStrategy.merge(sources, graalConfig, "testField");
mvelStrategy.merge(sources, mvelConfig, "testField");
nashornStrategy.merge(sources, nashornConfig, "testField");
}

int[] sizes = {
10_000, 50_000, 100_000
}; // Reduced size so it doesn't take forever during testing. 1 mil took too long for
// GraalJS/Nashorn

System.out.println("\n=== Benchmark Data (Total Time in ms) ===");
System.out.println("Size,MVEL,GraalJS,Nashorn");

for (int size : sizes) {
// Measure MVEL
long startMvel = System.nanoTime();
for (int i = 0; i < size; i++) {
mvelStrategy.merge(sources, mvelConfig, "testField");
}
long durationMvel = System.nanoTime() - startMvel;
double msMvel = durationMvel / 1_000_000.0;

// Measure GraalJS
long startGraal = System.nanoTime();
for (int i = 0; i < size; i++) {
graalStrategy.merge(sources, graalConfig, "testField");
}
long durationGraal = System.nanoTime() - startGraal;
double msGraal = durationGraal / 1_000_000.0;

// Measure Nashorn
long startNashorn = System.nanoTime();
for (int i = 0; i < size; i++) {
nashornStrategy.merge(sources, nashornConfig, "testField");
}
long durationNashorn = System.nanoTime() - startNashorn;
double msNashorn = durationNashorn / 1_000_000.0;

System.out.printf("%d,%.2f,%.2f,%.2f%n", size, msMvel, msGraal, msNashorn);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package de.x132.objectmerger.strategy.graaljs;

import de.x132.objectmerger.LabeledSource;
import de.x132.objectmerger.strategy.standard.StandardMergeStrategy;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;

public class GraalJsBenchmarkTest {

@Test
public void benchmarkGraalJsVsStandard() {
GraalJsMergeStrategy graalStrategy = new GraalJsMergeStrategy();
StandardMergeStrategy standardStrategy = new StandardMergeStrategy();

LabeledSource<Object> source = new LabeledSource<>("test", Map.of("field", "value"));
List<LabeledSource<?>> sources = Collections.singletonList(source);

// GraalJS Setup
GraalJsFieldDefinition graalConfig =
GraalJsFieldDefinition.builder().expression("sources.test.field").build();

// Standard Setup (Identity)
// StandardMergeStrategy configuration is generic, usually relies on field
// presence.
// But let's assume standard behavior is just getting the value.
// Actually StandardMergeStrategy logic depends on configuration, let's just
// test GraalJS overhead.

// Warmup GraalJS
System.out.println("Warming up GraalJS...");
for (int i = 0; i < 100; i++) {
graalStrategy.merge(sources, graalConfig, "field");
}

// Measure GraalJS
System.out.println("Measuring GraalJS (1000 iterations)...");
long start = System.nanoTime();
int iterations = 1000;
for (int i = 0; i < iterations; i++) {
graalStrategy.merge(sources, graalConfig, "field");
}
long duration = System.nanoTime() - start;
double avgMs = (double) duration / iterations / 1_000_000.0;

System.out.printf("GraalJS Average Merge Time: %.4f ms per call%n", avgMs);
System.out.printf("Total Time for %d calls: %.2f ms%n", iterations, duration / 1_000_000.0);

if (avgMs > 1.0) {
System.out.println(
"WARNING: Performance is potentially slow (> 1ms per call) due to Context creation overhead.");
}
}
}
57 changes: 57 additions & 0 deletions objectmerger-nashorn/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>de.x132</groupId>
<artifactId>objectmerger-parent</artifactId>
<version>0.2.0</version>
</parent>

<artifactId>objectmerger-nashorn</artifactId>
<name>ObjectMerger Nashorn Extension</name>
<description>Nashorn integration for ObjectMerger (alternative to MVEL)</description>

<dependencies>
<!-- Compile dependencies -->
<dependency>
<groupId>de.x132</groupId>
<artifactId>objectmerger</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.nashorn</groupId>
<artifactId>nashorn-core</artifactId>
<version>15.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>

<!-- Provided dependencies -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package de.x132.objectmerger.strategy.config;

public interface NashornConfig {
String getExpression();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package de.x132.objectmerger.strategy.nashorn;

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.conditional.ConditionCase;
import de.x132.objectmerger.strategy.conditional.ConditionalFieldDefinition;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import lombok.extern.slf4j.Slf4j;

/**
* Conditional MergeStrategy using Nashorn expressions.
*
* @param <T> the type of the result
*/
@Slf4j
public class NashornConditionalStrategy<T>
implements MergeStrategy<T, ConditionalFieldDefinition<T>> {

public static final String NAME = "conditional";

@Override
public String getName() {
return NAME;
}

@SuppressWarnings("unchecked")
@Override
public Class<ConditionalFieldDefinition<T>> getConfigurationClass() {
return (Class) ConditionalFieldDefinition.class;
}

@Override
@SuppressWarnings("unchecked")
public T merge(
List<LabeledSource<?>> sources,
ConditionalFieldDefinition<T> fieldDef,
String fieldName) {
return (T) mergeInternal(sources, fieldDef, fieldName);
}

private Object mergeInternal(
List<LabeledSource<?>> sources,
ConditionalFieldDefinition<T> fieldDef,
String fieldName) {

Map<String, Object> values = new HashMap<>();
for (LabeledSource<?> s : sources) {
values.put(s.getLabel(), ObjectMerger.getFieldValue(s.getSource(), fieldName));
}

try {
ScriptEngine engine = NashornHelper.createSecureEngine();

Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
bindings.put("values", values);
bindings.put("sources", sources);

if (fieldDef.getCases() != null) {
for (ConditionCase<?> c : fieldDef.getCases()) {
try {
Object result = engine.eval(c.getCondition());

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 Nashorn condition '{}' for field '{}': {}",
c.getCondition(),
fieldName,
e.getMessage());
}
}
}

} catch (Exception e) {
log.error(
"Error setting up Nashorn context for conditional strategy: {}",
e.getMessage());
}

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<LabeledSource<?>> 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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package de.x132.objectmerger.strategy.nashorn;

import de.x132.objectmerger.strategy.FieldDefinition;
import de.x132.objectmerger.strategy.config.NashornConfig;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;

@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class NashornFieldDefinition extends FieldDefinition<Object> implements NashornConfig {
private String expression;
}
Loading
Loading