diff --git a/rune-lang/src/main/java/com/regnosys/rosetta/experimental/ExperimentalFeature.java b/rune-lang/src/main/java/com/regnosys/rosetta/experimental/ExperimentalFeature.java index d9e7aa6cfd..15da03b65a 100644 --- a/rune-lang/src/main/java/com/regnosys/rosetta/experimental/ExperimentalFeature.java +++ b/rune-lang/src/main/java/com/regnosys/rosetta/experimental/ExperimentalFeature.java @@ -7,7 +7,7 @@ public enum ExperimentalFeature { private final String featureName; - private ExperimentalFeature(String featureName) { + ExperimentalFeature(String featureName) { this.featureName = featureName; } diff --git a/rune-runtime/pom.xml b/rune-runtime/pom.xml index 448f3f9cce..89fa276e8d 100644 --- a/rune-runtime/pom.xml +++ b/rune-runtime/pom.xml @@ -113,6 +113,33 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + + + default-test + + test + + + performance + + + + + performance-test + + test + + + performance + -Xint + + + + \ No newline at end of file diff --git a/rune-runtime/src/main/java/com/rosetta/model/lib/context/AbstractFunctionScope.java b/rune-runtime/src/main/java/com/rosetta/model/lib/context/AbstractFunctionScope.java new file mode 100644 index 0000000000..b306bda62f --- /dev/null +++ b/rune-runtime/src/main/java/com/rosetta/model/lib/context/AbstractFunctionScope.java @@ -0,0 +1,64 @@ +package com.rosetta.model.lib.context; + +import java.util.HashMap; +import java.util.Map; + +/** + * Abstract base class for implementing {@link FunctionScope}. + *

+ * Subclasses should override {@link #configure()} and call {@link #addOverride(Class, Class)} + * to define the class overrides for this scope. + *

+ * Example: + *

+ * public class MyScope extends AbstractFunctionScope {
+ *     protected void configure() {
+ *         addOverride(BaseClass.class, OverrideClass.class);
+ *     }
+ * }
+ * 
+ */ +public abstract class AbstractFunctionScope implements FunctionScope { + private final Map, Class> overrides = new HashMap<>(); + + public AbstractFunctionScope() { + configure(); + } + + /** + * Configures this scope by adding class overrides. + *

+ * Subclasses must implement this method to define their overrides using {@link #addOverride(Class, Class)}. + */ + protected abstract void configure(); + + /** + * Adds a class override to this scope. + * + * @param clazz the original class + * @param override the override class (must be a subclass of the original class) + * @param the type of the class + * @throws IllegalArgumentException if the override is not a subclass of the original class, + * or if the class already has an override defined + */ + protected void addOverride(Class clazz, Class override) { + if (!clazz.isAssignableFrom(override)) { + throw new IllegalArgumentException("Override class " + override + " must be a subclass of the original class " + clazz); + } + if (overrides.containsKey(clazz)) { + throw new IllegalArgumentException("Class " + clazz + " is already overridden by " + overrides.get(clazz)); + } + overrides.put(clazz, override); + } + + @Override + @SuppressWarnings("unchecked") + public Class getOverride(Class clazz) { + return (Class) overrides.getOrDefault(clazz, clazz); + } + + @Override + public Map, Class> getAllOverrides() { + return overrides; + } +} diff --git a/rune-runtime/src/main/java/com/rosetta/model/lib/context/ContextAwareProvider.java b/rune-runtime/src/main/java/com/rosetta/model/lib/context/ContextAwareProvider.java new file mode 100644 index 0000000000..eccdc117c0 --- /dev/null +++ b/rune-runtime/src/main/java/com/rosetta/model/lib/context/ContextAwareProvider.java @@ -0,0 +1,47 @@ +package com.rosetta.model.lib.context; + +import com.google.inject.TypeLiteral; + +import javax.inject.Inject; +import javax.inject.Provider; + +/** + * A provider that resolves instances based on the current function context and its scopes. + *

+ * This provider can be injected into classes to enable scope-aware dependency resolution. + * Instead of receiving a fixed instance at injection time, the instance is resolved dynamically + * when {@link #get()} is called, taking into account any active scopes on the current thread. + *

+ * Example usage: + *

+ * public class MyClass {
+ *     {@literal @}Inject
+ *     private ContextAwareProvider<MyDependency> dependencyProvider;
+ *
+ *     public void doWork() {
+ *         MyDependency dep = dependencyProvider.get(); // Resolved based on current scope
+ *         // ... use dep
+ *     }
+ * }
+ * 
+ * + * @param the type to provide + */ +public class ContextAwareProvider implements Provider { + private final TypeLiteral type; + private final FunctionContext context; + + @Inject + public ContextAwareProvider(TypeLiteral type, + FunctionContext context) { + this.type = type; + this.context = context; + } + + @Override + @SuppressWarnings("unchecked") + public T get() { + Class raw = (Class) type.getRawType(); + return context.getInstance(raw); + } +} diff --git a/rune-runtime/src/main/java/com/rosetta/model/lib/context/FunctionContext.java b/rune-runtime/src/main/java/com/rosetta/model/lib/context/FunctionContext.java new file mode 100644 index 0000000000..eba20e0bf5 --- /dev/null +++ b/rune-runtime/src/main/java/com/rosetta/model/lib/context/FunctionContext.java @@ -0,0 +1,58 @@ +package com.rosetta.model.lib.context; + +import com.google.inject.ImplementedBy; + +import java.util.function.Supplier; + +/** + * Provides a context for function execution with scoped overrides. + */ +@ImplementedBy(FunctionContextImpl.class) +public interface FunctionContext { + /** + * Executes code within a {@link FunctionScope}. + * + * @param scopeClass the scope to use + * @param runnable the code to execute + */ + void runInScope(Class scopeClass, Runnable runnable); + + /** + * Executes code within a {@link FunctionScope} and returns the result. + * + * @param scopeClass the scope to use + * @param supplier the code to execute + * @param the return type + * @return the result + */ + T evaluateInScope(Class scopeClass, Supplier supplier); + + /** + * Gets an instance of the specified class, applying any active scope overrides. + * + * @param clazz the class to instantiate + * @param the type + * @return an instance of the class (or its override) + */ + T getInstance(Class clazz); + + /** + * Creates a copy of the current thread's scope state for propagation to other threads. + *

+ * Use this method to capture the current scope stack before spawning async tasks, + * then call {@link #setStateOfCurrentThread(FunctionContextState)} in the new thread. + * + * @return a copy of the current thread's scope state + */ + FunctionContextState copyStateOfCurrentThread(); + + /** + * Sets the current thread's scope state, typically after receiving it from another thread. + *

+ * Use this method in a new thread to restore scope state that was captured + * via {@link #copyStateOfCurrentThread()} in a parent thread. + * + * @param state the scope state to set for the current thread + */ + void setStateOfCurrentThread(FunctionContextState state); +} diff --git a/rune-runtime/src/main/java/com/rosetta/model/lib/context/FunctionContextImpl.java b/rune-runtime/src/main/java/com/rosetta/model/lib/context/FunctionContextImpl.java new file mode 100644 index 0000000000..5f736d5aba --- /dev/null +++ b/rune-runtime/src/main/java/com/rosetta/model/lib/context/FunctionContextImpl.java @@ -0,0 +1,76 @@ +package com.rosetta.model.lib.context; + +import com.google.inject.Injector; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.function.Supplier; + +/** + * Implementation of {@link FunctionContext} that maintains a stack of scopes with cached resolved overrides. + *

+ * This implementation optimizes {@link #getInstance(Class)} to O(1) time complexity (with respect to the depth of the scope stack) + * by maintaining a cache of resolved class overrides at each scope level. When a scope is entered, the cache is computed + * by applying that scope's overrides to the parent scope's cache. When a scope is exited, the parent's + * cache is automatically restored by popping the stack. + */ +@Singleton +public class FunctionContextImpl implements FunctionContext { + private final ThreadLocal statePerThread = ThreadLocal.withInitial(FunctionContextState::empty); + private final Injector injector; + + @Inject + public FunctionContextImpl(Injector injector) { + this.injector = injector; + } + + @Override + public void runInScope(Class scopeClass, Runnable runnable) { + if (!pushScope(scopeClass)) { + runnable.run(); + return; + } + try { + runnable.run(); + } finally { + popScope(); + } + } + + @Override + public T evaluateInScope(Class scopeClass, Supplier supplier) { + if (!pushScope(scopeClass)) { + return supplier.get(); + }; + try { + return supplier.get(); + } finally { + popScope(); + } + } + + @Override + public T getInstance(Class clazz) { + Class resolvedClass = statePerThread.get().getOverride(clazz); + return injector.getInstance(resolvedClass); + } + + @Override + public FunctionContextState copyStateOfCurrentThread() { + return statePerThread.get().copy(); + } + + @Override + public void setStateOfCurrentThread(FunctionContextState state) { + statePerThread.set(state); + } + + private boolean pushScope(Class scopeClass) { + FunctionScope scope = injector.getInstance(scopeClass); + return statePerThread.get().pushScope(scope); + } + + private void popScope() { + statePerThread.get().popScope(); + } +} diff --git a/rune-runtime/src/main/java/com/rosetta/model/lib/context/FunctionContextState.java b/rune-runtime/src/main/java/com/rosetta/model/lib/context/FunctionContextState.java new file mode 100644 index 0000000000..bc2efa6151 --- /dev/null +++ b/rune-runtime/src/main/java/com/rosetta/model/lib/context/FunctionContextState.java @@ -0,0 +1,103 @@ +package com.rosetta.model.lib.context; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.Map; + +/** + * Encapsulates the per-thread state of a {@link FunctionContext}. + *

+ * This class is separated from {@link FunctionContext} to enable explicit propagation of context + * state across thread boundaries. Instances can be copied via {@link #copy()} and transferred + * to other threads, allowing async tasks to inherit the parent thread's context. + *

+ * Maintains a scope stack with cached resolved overrides for O(1) lookup performance. + */ +public class FunctionContextState { + private static class ScopeFrame { + final FunctionScope scope; + final Map, Class> resolvedOverrides; + + ScopeFrame(FunctionScope scope, Map, Class> resolvedOverrides) { + this.scope = scope; + this.resolvedOverrides = resolvedOverrides; + } + } + private final Deque scopeStack; + + public static FunctionContextState empty() { + return new FunctionContextState(); + } + + private FunctionContextState(FunctionContextState otherState) { + this.scopeStack = new ArrayDeque<>(otherState.scopeStack); + } + private FunctionContextState() { + this.scopeStack = new ArrayDeque<>(); + } + + /** + * Creates a shallow copy of this state for propagation to another thread. + *

+ * The scope stack is copied, so modifications in one thread won't affect the other. + * + * @return a copy of this state + */ + public FunctionContextState copy() { + return new FunctionContextState(this); + } + + @SuppressWarnings("unchecked") + public Class getOverride(Class clazz) { + // O(1) cache lookup from the top of the stack + if (scopeStack.isEmpty()) { + return clazz; + } + Map, Class> resolvedOverrides = scopeStack.peekLast().resolvedOverrides; + return (Class) resolvedOverrides.getOrDefault(clazz, clazz); + } + + /** + * Pushes a new scope onto the stack, if the scope is different from the last. + * @param scope the scope to push + * @return true if the scope was pushed, false if the scope equals the top of the stack + */ + public boolean pushScope(FunctionScope scope) { + if (!scopeStack.isEmpty() && scopeStack.peekLast().scope.equals(scope)) { + return false; + } + + // Get the current resolved overrides (from parent scope or empty) + Map, Class> parentResolvedOverrides = scopeStack.isEmpty() + ? new HashMap<>() + : scopeStack.peekLast().resolvedOverrides; + + // Apply new scope's overrides to all classes in the parent overrides. + // This ensures that if class A was already overridden to B, + // and the new scope overrides B to C, then A will resolve to C. + Map, Class> newResolvedOverrides = new HashMap<>(); + for (Map.Entry, Class> entry : parentResolvedOverrides.entrySet()) { + Class baseClass = entry.getKey(); + Class currentOverride = entry.getValue(); + Class newOverride = scope.getOverride(currentOverride); + newResolvedOverrides.put(baseClass, newOverride); + } + + // Also add any new overrides from this scope that aren't in the cache yet. + Map, Class> scopeOverrides = scope.getAllOverrides(); + for (Map.Entry, Class> entry : scopeOverrides.entrySet()) { + Class baseClass = entry.getKey(); + if (!newResolvedOverrides.containsKey(baseClass)) { + newResolvedOverrides.put(baseClass, entry.getValue()); + } + } + + scopeStack.addLast(new ScopeFrame(scope, newResolvedOverrides)); + return true; + } + + public void popScope() { + scopeStack.removeLast(); + } +} diff --git a/rune-runtime/src/main/java/com/rosetta/model/lib/context/FunctionScope.java b/rune-runtime/src/main/java/com/rosetta/model/lib/context/FunctionScope.java new file mode 100644 index 0000000000..290f982e1b --- /dev/null +++ b/rune-runtime/src/main/java/com/rosetta/model/lib/context/FunctionScope.java @@ -0,0 +1,27 @@ +package com.rosetta.model.lib.context; + +import java.util.Map; + +/** + * Defines a scope with class binding overrides. + *

+ * Implementations of this interface specify which classes should be replaced + * with alternative implementations within a particular execution context. + */ +public interface FunctionScope { + /** + * Returns the override class for the given class, or the class itself if no override exists. + * + * @param clazz the class to check for an override + * @param the type of the class + * @return the override class if one exists, otherwise the original class. Never null. + */ + Class getOverride(Class clazz); + + /** + * Returns all class overrides defined in this scope. + * + * @return a map from original classes to their override classes + */ + Map, Class> getAllOverrides(); +} diff --git a/rune-runtime/src/test/java/com/rosetta/model/lib/context/FunctionContextMultithreadingTest.java b/rune-runtime/src/test/java/com/rosetta/model/lib/context/FunctionContextMultithreadingTest.java new file mode 100644 index 0000000000..ca9cd3edfe --- /dev/null +++ b/rune-runtime/src/test/java/com/rosetta/model/lib/context/FunctionContextMultithreadingTest.java @@ -0,0 +1,59 @@ +package com.rosetta.model.lib.context; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.rosetta.model.lib.context.example.One; +import com.rosetta.model.lib.context.example.ScopeA; +import com.rosetta.model.lib.context.example.ScopeB; +import com.rosetta.model.lib.context.example.Two; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javax.inject.Inject; +import java.util.concurrent.CompletableFuture; + +public class FunctionContextMultithreadingTest { + @Inject + private FunctionContext context; + @Inject + private ContextAwareProvider oneProvider; + @Inject + private ContextAwareProvider twoProvider; + + @BeforeEach + void setup() { + Injector injector = Guice.createInjector(); + injector.injectMembers(this); + } + + @Test + void testStateIsCopiedCorrectlyToThreads() { + context.runInScope(ScopeA.class, () -> { + FunctionContextState state = context.copyStateOfCurrentThread(); + CompletableFuture f1 = CompletableFuture.supplyAsync(() -> { + context.setStateOfCurrentThread(state); + return context.evaluateInScope(ScopeB.class, () -> getImplementationName(oneProvider) + getImplementationName(twoProvider)); + }); + CompletableFuture f2 = CompletableFuture.supplyAsync(() -> { + context.setStateOfCurrentThread(state); + return getImplementationName(oneProvider) + getImplementationName(twoProvider); + }); + CompletableFuture.allOf(f1, f2).join(); + + String res1 = f1.join(); + String res2 = f2.join(); + String res3 = getImplementationName(oneProvider) + getImplementationName(twoProvider); + + Assertions.assertAll( + () -> Assertions.assertEquals("OneBTwoA", res1), + () -> Assertions.assertEquals("OneTwoA", res2), + () -> Assertions.assertEquals("OneTwoA", res3) + ); + }); + } + + private String getImplementationName(ContextAwareProvider provider) { + return provider.get().getClass().getSimpleName(); + } +} diff --git a/rune-runtime/src/test/java/com/rosetta/model/lib/context/FunctionContextStatePerformanceTest.java b/rune-runtime/src/test/java/com/rosetta/model/lib/context/FunctionContextStatePerformanceTest.java new file mode 100644 index 0000000000..3863f5d1ac --- /dev/null +++ b/rune-runtime/src/test/java/com/rosetta/model/lib/context/FunctionContextStatePerformanceTest.java @@ -0,0 +1,88 @@ +package com.rosetta.model.lib.context; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.rosetta.model.lib.context.example.One; +import com.rosetta.model.lib.context.example.ScopeA; +import com.rosetta.model.lib.context.example.ScopeB; +import com.rosetta.model.lib.context.example.Two; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import javax.inject.Inject; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Performance tests to verify the O(1) optimization of getOverride. + *

+ * These tests automatically run with JIT disabled (-Xint) via separate Surefire execution + * to measure realistic first-run performance. They run automatically during {@code mvn test} + * or {@code mvn clean install}. + */ +@Tag("performance") +public class FunctionContextStatePerformanceTest { + @Inject + private ScopeA scopeA; + @Inject + private ScopeB scopeB; + + @BeforeEach + void setup() { + Injector injector = Guice.createInjector(); + injector.injectMembers(this); + } + + @Test + void testPerformanceForADeepScopeStack() { + // Test that deep scopes don't degrade performance significantly + // This proves O(1) vs O(n) by comparing shallow vs deep stack performance + // Tests first-run performance (realistic usage without JIT warmup) + + final int ITERATIONS = 10000; + final int SHALLOW_DEPTH = 10; + final int DEEP_DEPTH = 100; + + // Shallow stack + FunctionContextState shallowContextState = createStateWithDepth(SHALLOW_DEPTH); + + // Deep stack + FunctionContextState deepContextState = createStateWithDepth(DEEP_DEPTH); + + // Measure first-run performance (no JIT warmup - simulates real usage) + long shallowMs = runBenchmark(shallowContextState, ITERATIONS); + long deepMs = runBenchmark(deepContextState, ITERATIONS); + + System.out.println("Performance test (first-run, no JIT warmup):"); + System.out.println(" Shallow (depth " + SHALLOW_DEPTH + "): " + shallowMs + "ms for " + ITERATIONS + " lookups"); + System.out.println(" Deep (depth " + DEEP_DEPTH + "): " + deepMs + "ms for " + ITERATIONS + " lookups"); + double ratio = (double)deepMs / shallowMs; + System.out.println(" Ratio: " + String.format("%.2f", ratio) + "x"); + + // With O(1), ratio should be close to 1.0 even without JIT + // With O(n), ratio would be ~10x (DEEP_DEPTH / SHALLOW_DEPTH) + // Allow up to 3x difference for noise, cache effects, and first-run variance + assert ratio < 3.0 : "Deep context is " + String.format("%.2f", ratio) + + "x slower than shallow - suggests O(n) behavior"; + } + + private FunctionContextState createStateWithDepth(int depth) { + FunctionContextState state = FunctionContextState.empty(); + for (int i = 0; i < depth; i++) { + state.pushScope(i % 2 == 0 ? scopeA : scopeB); + } + return state; + } + + private long runBenchmark(FunctionContextState state, int iterations) { + long start = System.nanoTime(); + for (int i = 0; i < iterations; i++) { + Class one = state.getOverride(One.class); + Class two = state.getOverride(Two.class); + assertNotNull(one); + assertNotNull(two); + } + return (System.nanoTime() - start) / 1_000_000; + } +} diff --git a/rune-runtime/src/test/java/com/rosetta/model/lib/context/FunctionContextStateTest.java b/rune-runtime/src/test/java/com/rosetta/model/lib/context/FunctionContextStateTest.java new file mode 100644 index 0000000000..959f24cd28 --- /dev/null +++ b/rune-runtime/src/test/java/com/rosetta/model/lib/context/FunctionContextStateTest.java @@ -0,0 +1,51 @@ +package com.rosetta.model.lib.context; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.rosetta.model.lib.context.example.One; +import com.rosetta.model.lib.context.example.ScopeA; +import com.rosetta.model.lib.context.example.ScopeB; +import com.rosetta.model.lib.context.example.Two; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javax.inject.Inject; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class FunctionContextStateTest { + @Inject + private ScopeA scopeA; + @Inject + private ScopeB scopeB; + + private FunctionContextState state; + + @BeforeEach + void setup() { + Injector injector = Guice.createInjector(); + injector.injectMembers(this); + state = FunctionContextState.empty(); + } + + @Test + void testScopeStackRestoration() { + // Verify that scope stack is properly unwound + assertEquals("One", getImplementationName(One.class)); + assertEquals("Two", getImplementationName(Two.class)); + state.pushScope(scopeA); + state.pushScope(scopeB); + assertEquals("OneB", getImplementationName(One.class)); + assertEquals("TwoA", getImplementationName(Two.class)); + state.popScope(); // pop scopeB + assertEquals("One", getImplementationName(One.class)); + assertEquals("TwoA", getImplementationName(Two.class)); + state.popScope(); // pop scopeA + assertEquals("One", getImplementationName(One.class)); + assertEquals("Two", getImplementationName(Two.class)); + } + + private String getImplementationName(Class clazz) { + return state.getOverride(clazz).getSimpleName(); + } +} diff --git a/rune-runtime/src/test/java/com/rosetta/model/lib/context/FunctionContextTest.java b/rune-runtime/src/test/java/com/rosetta/model/lib/context/FunctionContextTest.java new file mode 100644 index 0000000000..b3d099fbdf --- /dev/null +++ b/rune-runtime/src/test/java/com/rosetta/model/lib/context/FunctionContextTest.java @@ -0,0 +1,43 @@ +package com.rosetta.model.lib.context; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.rosetta.model.lib.context.example.Three; +import com.rosetta.model.lib.context.example.ThreeInScopeA; +import com.rosetta.model.lib.context.example.ThreeInScopeB; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import javax.inject.Inject; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class FunctionContextTest { + @Inject + private Three three; + @Inject + private ThreeInScopeA threeInScopeA; + @Inject + private ThreeInScopeB threeInScopeB; + + @BeforeEach + void setup() { + Injector injector = Guice.createInjector(); + injector.injectMembers(this); + } + + @Test + void testThreeInDefaultScope() { + assertEquals(3, three.evaluate()); + } + + @Test + void testThreeInScopeA() { + assertEquals(5, threeInScopeA.evaluate()); + } + + @Test + void testThreeInScopeB() { + assertEquals(15, threeInScopeB.evaluate()); + } +} diff --git a/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/One.java b/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/One.java new file mode 100644 index 0000000000..10aa8fc8f9 --- /dev/null +++ b/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/One.java @@ -0,0 +1,7 @@ +package com.rosetta.model.lib.context.example; + +public class One { + public int evaluate() { + return 1; + } +} diff --git a/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/OneB.java b/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/OneB.java new file mode 100644 index 0000000000..17d9ad5cc4 --- /dev/null +++ b/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/OneB.java @@ -0,0 +1,16 @@ +package com.rosetta.model.lib.context.example; + +import com.rosetta.model.lib.context.FunctionContext; + +import javax.inject.Inject; + +public class OneB extends One { + @Inject + private FunctionContext context; + @Inject + private One superFunction; + + public int evaluate() { + return context.evaluateInScope(ScopeB.class, () -> 3 * superFunction.evaluate()); + } +} diff --git a/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/ScopeA.java b/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/ScopeA.java new file mode 100644 index 0000000000..240486a7f8 --- /dev/null +++ b/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/ScopeA.java @@ -0,0 +1,13 @@ +package com.rosetta.model.lib.context.example; + +import com.rosetta.model.lib.context.AbstractFunctionScope; + +import javax.inject.Singleton; + +@Singleton +public class ScopeA extends AbstractFunctionScope { + @Override + protected void configure() { + addOverride(Two.class, TwoA.class); + } +} diff --git a/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/ScopeB.java b/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/ScopeB.java new file mode 100644 index 0000000000..8827d56777 --- /dev/null +++ b/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/ScopeB.java @@ -0,0 +1,13 @@ +package com.rosetta.model.lib.context.example; + +import com.rosetta.model.lib.context.AbstractFunctionScope; + +import javax.inject.Singleton; + +@Singleton +public class ScopeB extends AbstractFunctionScope { + @Override + protected void configure() { + addOverride(One.class, OneB.class); + } +} diff --git a/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/Three.java b/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/Three.java new file mode 100644 index 0000000000..6c9d046337 --- /dev/null +++ b/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/Three.java @@ -0,0 +1,18 @@ +package com.rosetta.model.lib.context.example; + +import com.rosetta.model.lib.context.ContextAwareProvider; + +import javax.inject.Inject; + +public class Three { + @Inject + private ContextAwareProvider oneProvider; + @Inject + private ContextAwareProvider twoProvider; + + public int evaluate() { + One one = oneProvider.get(); + Two two = twoProvider.get(); + return one.evaluate() + two.evaluate(); + } +} diff --git a/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/ThreeInScopeA.java b/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/ThreeInScopeA.java new file mode 100644 index 0000000000..e213f8a632 --- /dev/null +++ b/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/ThreeInScopeA.java @@ -0,0 +1,20 @@ +package com.rosetta.model.lib.context.example; + +import com.rosetta.model.lib.context.ContextAwareProvider; +import com.rosetta.model.lib.context.FunctionContext; + +import javax.inject.Inject; + +public class ThreeInScopeA { + @Inject + private FunctionContext context; + @Inject + private ContextAwareProvider threeProvider; + + public int evaluate() { + return context.evaluateInScope(ScopeA.class, () -> { + Three three = threeProvider.get(); + return three.evaluate(); + }); + } +} diff --git a/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/ThreeInScopeB.java b/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/ThreeInScopeB.java new file mode 100644 index 0000000000..a451f40379 --- /dev/null +++ b/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/ThreeInScopeB.java @@ -0,0 +1,20 @@ +package com.rosetta.model.lib.context.example; + +import com.rosetta.model.lib.context.ContextAwareProvider; +import com.rosetta.model.lib.context.FunctionContext; + +import javax.inject.Inject; + +public class ThreeInScopeB { + @Inject + private FunctionContext context; + @Inject + private ContextAwareProvider threeInScopeAProvider; + + public int evaluate() { + return context.evaluateInScope(ScopeB.class, () -> { + ThreeInScopeA threeInScopeA = threeInScopeAProvider.get(); + return threeInScopeA.evaluate(); + }); + } +} diff --git a/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/Two.java b/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/Two.java new file mode 100644 index 0000000000..3bf8e49102 --- /dev/null +++ b/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/Two.java @@ -0,0 +1,15 @@ +package com.rosetta.model.lib.context.example; + +import com.rosetta.model.lib.context.ContextAwareProvider; + +import javax.inject.Inject; + +public class Two { + @Inject + private ContextAwareProvider oneProvider; + + public int evaluate() { + One one = oneProvider.get(); + return 2 * one.evaluate(); + } +} diff --git a/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/TwoA.java b/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/TwoA.java new file mode 100644 index 0000000000..8af061cdd3 --- /dev/null +++ b/rune-runtime/src/test/java/com/rosetta/model/lib/context/example/TwoA.java @@ -0,0 +1,16 @@ +package com.rosetta.model.lib.context.example; + +import com.rosetta.model.lib.context.FunctionContext; + +import javax.inject.Inject; + +public class TwoA extends Two { + @Inject + private FunctionContext context; + @Inject + private Two superFunction; + + public int evaluate() { + return context.evaluateInScope(ScopeA.class, () -> 2 * superFunction.evaluate()); + } +}