diff --git a/projo-runtime-code-generation/pom.xml b/projo-runtime-code-generation/pom.xml
index e34eb15..098564d 100644
--- a/projo-runtime-code-generation/pom.xml
+++ b/projo-runtime-code-generation/pom.xml
@@ -53,10 +53,15 @@
1
test
+
+ jakarta.inject
+ jakarta.inject-api
+ 2.0.1
+
com.google.inject
guice
- 4.2.2
+ 6.0.0
test
diff --git a/projo-runtime-code-generation/src/main/java/pro/projo/internal/rcg/RuntimeCodeGenerationHandler.java b/projo-runtime-code-generation/src/main/java/pro/projo/internal/rcg/RuntimeCodeGenerationHandler.java
index 8ccf82e..8d2973f 100644
--- a/projo-runtime-code-generation/src/main/java/pro/projo/internal/rcg/RuntimeCodeGenerationHandler.java
+++ b/projo-runtime-code-generation/src/main/java/pro/projo/internal/rcg/RuntimeCodeGenerationHandler.java
@@ -1,5 +1,5 @@
// //
-// Copyright 2017 - 2023 Mirko Raner //
+// Copyright 2017 - 2024 Mirko Raner //
// //
// Licensed under the Apache License, Version 2.0 (the "License"); //
// you may not use this file except in compliance with the License. //
@@ -126,8 +126,6 @@ public class RuntimeCodeGenerationHandler<_Artifact_> extends ProjoHandler<_Arti
private Map, Class extends _Artifact_>> implementationClassCache =
new ConcurrentHashMap<>();
- private final String injected = "javax.inject.Inject";
-
private final static String SUFFIX = "$Projo";
private static Map, Class>> baseClasses = new HashMap<>();
@@ -320,7 +318,7 @@ private Builder<_Artifact_> add(Builder<_Artifact_> builder, Method method, List
String methodName = annotations.get(Overrides.class).map(Overrides::value).orElse(method.getName());
String propertyName = matcher.propertyName(methodName);
UnaryOperator> addFieldForGetter;
- Optional inject = annotations.get(injected);
+ Optional inject = annotations.getInject();
Class> returnType = method.getReturnType();
TypeDescription.Generic type = isGetter? getFieldType(annotations, returnType, classLoader):VOID.asGenericType();
addFieldForGetter = isGetter? localBuilder -> annotate(inject, localBuilder.defineField(propertyName, type, PRIVATE)):identity();
@@ -369,9 +367,10 @@ private Builder<_Artifact_> add(Builder<_Artifact_> builder, Method method, List
Implementation getAccessor(Method method, AnnotationList annotations, Type returnType, String property, List additionalImplements, ClassLoader classLoader)
{
- if (annotations.contains(injected))
+ Optional inject;
+ if ((inject = annotations.getInject()).isPresent())
{
- return get(property, returnType, classLoader);
+ return get(property, returnType, inject.get(), classLoader);
}
if (annotations.contains(Cached.class))
{
@@ -463,9 +462,9 @@ private Implementation cached(Cached cached, String field, Type type)
}
}
- private Implementation get(String field, Type type, ClassLoader classLoader)
+ private Implementation get(String field, Type type, Annotation inject, ClassLoader classLoader)
{
- Class> provider = Projo.forName("javax.inject.Provider", classLoader);
+ Class> provider = Projo.forName(provider(inject), classLoader);
Generic genericProvider = Generic.Builder.parameterizedType(provider, type).build();
MethodDescription get = latent(genericProvider.asErasure(), OBJECT.asGenericType(), "get");
return MethodCall.invoke(get).onField(field).withAssigner(DEFAULT, DYNAMIC);
@@ -501,13 +500,14 @@ private ByteBuddy codeGenerator()
**/
Generic getFieldType(AnnotationList annotations, Class> originalReturnType, ClassLoader classLoader)
{
- if (!annotations.contains(injected) && !annotations.contains(Cached.class))
+ if (!annotations.containsInject() && !annotations.contains(Cached.class))
{
return Generic.Builder.rawType(originalReturnType).build();
}
else
{
- Class> container = annotations.contains(injected)? Projo.forName("javax.inject.Provider", classLoader):Cache.class;
+ Optional> optionalContainer = annotations.getInject().map(inject -> Projo.forName(provider(inject), classLoader));
+ Class> container = optionalContainer.orElse(Cache.class);
Type wrappedType = MethodType.methodType(originalReturnType).wrap().returnType();
Optional returns = annotations.get(Returns.class);
if (returns.isPresent())
@@ -535,6 +535,17 @@ Generic getFieldType(AnnotationList annotations, Class> originalReturnType, Cl
}
}
+ /**
+ * Returns the corresponding provider annotation name for an inject annotation.
+ *
+ * @param inject the {@code @Inject} annotation
+ * @return the fully qualified name of the corresponding {@code @Provider}
+ **/
+ private String provider(Annotation inject)
+ {
+ return inject.annotationType().getPackage().getName() + ".Provider";
+ }
+
private <_ValuableBuilder_ extends Valuable<_Artifact_> & Builder<_Artifact_>>
Builder<_Artifact_> annotate(Optional annotation, _ValuableBuilder_ builder)
{
diff --git a/projo-runtime-code-generation/src/test/java/pro/projo/ProjoBooleanInjectionTest.java b/projo-runtime-code-generation/src/test/java/pro/projo/ProjoBooleanInjectionTest.java
index 33feb4b..9da92bd 100644
--- a/projo-runtime-code-generation/src/test/java/pro/projo/ProjoBooleanInjectionTest.java
+++ b/projo-runtime-code-generation/src/test/java/pro/projo/ProjoBooleanInjectionTest.java
@@ -1,5 +1,5 @@
// //
-// Copyright 2019 Mirko Raner //
+// Copyright 2019 - 2024 Mirko Raner //
// //
// Licensed under the Apache License, Version 2.0 (the "License"); //
// you may not use this file except in compliance with the License. //
@@ -49,21 +49,25 @@ static interface Boolean
static interface True extends Boolean, Booleans
{
+ @Override
default Boolean or(Boolean other)
{
return TRUE();
}
+ @Override
default Boolean xor(Boolean other)
{
return other.not();
}
+ @Override
default Boolean and(Boolean other)
{
return other;
}
+ @Override
default Boolean not()
{
return FALSE();
@@ -72,21 +76,25 @@ default Boolean not()
static interface False extends Boolean, Booleans
{
+ @Override
default Boolean or(Boolean other)
{
return other;
}
+ @Override
default Boolean xor(Boolean other)
{
return other;
}
+ @Override
default Boolean and(Boolean other)
{
return FALSE();
}
+ @Override
default Boolean not()
{
return TRUE();
@@ -118,6 +126,7 @@ static class TestUnary extends TestData
this.expected = expected;
}
+ @Override
void test(Function converter)
{
Boolean object = converter.apply(left);
@@ -140,6 +149,7 @@ static class TestBinary extends TestData
this.expected = expected;
}
+ @Override
void test(Function converter)
{
Boolean object = converter.apply(left);
@@ -180,6 +190,7 @@ public void test() throws Exception
{
Module module = new AbstractModule()
{
+ @Override
protected void configure()
{
bind(True.class).to(Projo.getImplementationClass(True.class)).asEagerSingleton();
diff --git a/projo-runtime-code-generation/src/test/java/pro/projo/ProjoInjectionSupportTest.java b/projo-runtime-code-generation/src/test/java/pro/projo/ProjoInjectionSupportTest.java
index 827b081..c99180a 100644
--- a/projo-runtime-code-generation/src/test/java/pro/projo/ProjoInjectionSupportTest.java
+++ b/projo-runtime-code-generation/src/test/java/pro/projo/ProjoInjectionSupportTest.java
@@ -1,5 +1,5 @@
// //
-// Copyright 2019 - 2022 Mirko Raner //
+// Copyright 2019 - 2024 Mirko Raner //
// //
// Licensed under the Apache License, Version 2.0 (the "License"); //
// you may not use this file except in compliance with the License. //
@@ -108,6 +108,7 @@ public void testThatPropertyAnnotationWorksInConjunctionWithInject() throws Exce
{
Module module = new AbstractModule()
{
+ @Override
protected void configure()
{
bind(True.class).to(Projo.getImplementationClass(True.class)).asEagerSingleton();
@@ -133,6 +134,7 @@ public void testThatReturnsAnnotationWorksInConjunctionWithInject() throws Excep
Module module = new AbstractModule()
{
+ @Override
@SuppressWarnings("unchecked")
protected void configure()
{
diff --git a/projo-runtime-code-generation/src/test/java/pro/projo/ProjoNaturalTest.java b/projo-runtime-code-generation/src/test/java/pro/projo/ProjoNaturalTest.java
index 492e83d..b9abf32 100644
--- a/projo-runtime-code-generation/src/test/java/pro/projo/ProjoNaturalTest.java
+++ b/projo-runtime-code-generation/src/test/java/pro/projo/ProjoNaturalTest.java
@@ -1,5 +1,5 @@
// //
-// Copyright 2022 Mirko Raner //
+// Copyright 2022 - 2024 Mirko Raner //
// //
// Licensed under the Apache License, Version 2.0 (the "License"); //
// you may not use this file except in compliance with the License. //
@@ -112,6 +112,7 @@ private Module module(Literals literals)
{
return new AbstractModule()
{
+ @Override
@SuppressWarnings("unchecked")
protected void configure()
{
diff --git a/projo-runtime-code-generation/src/test/java/pro/projo/internal/rcg/RuntimeCodeGenerationHandlerTest.java b/projo-runtime-code-generation/src/test/java/pro/projo/internal/rcg/RuntimeCodeGenerationHandlerTest.java
index 231e9d7..429a602 100644
--- a/projo-runtime-code-generation/src/test/java/pro/projo/internal/rcg/RuntimeCodeGenerationHandlerTest.java
+++ b/projo-runtime-code-generation/src/test/java/pro/projo/internal/rcg/RuntimeCodeGenerationHandlerTest.java
@@ -1,5 +1,5 @@
// //
-// Copyright 2017 - 2023 Mirko Raner //
+// Copyright 2017 - 2024 Mirko Raner //
// //
// Licensed under the Apache License, Version 2.0 (the "License"); //
// you may not use this file except in compliance with the License. //
@@ -15,6 +15,7 @@
// //
package pro.projo.internal.rcg;
+import java.lang.reflect.Method;
import javax.inject.Inject;
import org.junit.Test;
import net.bytebuddy.description.type.TypeDescription.Generic;
@@ -78,6 +79,17 @@ public void testFieldTypeForInjectedMethod() throws Exception
assertEquals("javax.inject.Provider", fieldType.toString());
}
+ @Test
+ @jakarta.inject.Inject
+ public void testFieldTypeForInjectedMethodJakarta() throws Exception
+ {
+ RuntimeCodeGenerationHandler> handler = new RuntimeCodeGenerationHandler<>();
+ Method declaredMethod = getClass().getDeclaredMethod("testFieldTypeForInjectedMethodJakarta");
+ jakarta.inject.Inject inject = declaredMethod.getAnnotation(jakarta.inject.Inject.class);
+ Generic fieldType = handler.getFieldType(new AnnotationList(inject), String.class, classLoader);
+ assertEquals("jakarta.inject.Provider", fieldType.toString());
+ }
+
@Test
@Cached
public void testFieldTypeForCachedMethod() throws Exception
diff --git a/projo-runtime-code-generation/src/test/java/pro/projo/jakarta/ProjoBooleanInjectionTest.java b/projo-runtime-code-generation/src/test/java/pro/projo/jakarta/ProjoBooleanInjectionTest.java
new file mode 100644
index 0000000..a99d062
--- /dev/null
+++ b/projo-runtime-code-generation/src/test/java/pro/projo/jakarta/ProjoBooleanInjectionTest.java
@@ -0,0 +1,206 @@
+// //
+// Copyright 2024 Mirko Raner //
+// //
+// Licensed under the Apache License, Version 2.0 (the "License"); //
+// you may not use this file except in compliance with the License. //
+// You may obtain a copy of the License at //
+// //
+// http://www.apache.org/licenses/LICENSE-2.0 //
+// //
+// Unless required by applicable law or agreed to in writing, software //
+// distributed under the License is distributed on an "AS IS" BASIS, //
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
+// See the License for the specific language governing permissions and //
+// limitations under the License. //
+// //
+package pro.projo.jakarta;
+
+import java.util.function.BinaryOperator;
+import java.util.function.Function;
+import java.util.function.UnaryOperator;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import jakarta.inject.Inject;
+import pro.projo.Projo;
+import static org.junit.Assert.assertEquals;
+
+/**
+* {@link ProjoBooleanInjectionTest} tests Projo's code generation for injected fields for a
+* bootstrapped Boolean algebra that requires circular dependencies.
+*
+* @author Mirko Raner
+**/
+@RunWith(Parameterized.class)
+public class ProjoBooleanInjectionTest
+{
+ static interface Boolean
+ {
+ Boolean or(Boolean other);
+ Boolean xor(Boolean other);
+ Boolean and(Boolean other);
+ Boolean not();
+ }
+
+ static interface True extends Boolean, Booleans
+ {
+ @Override
+ default Boolean or(Boolean other)
+ {
+ return TRUE();
+ }
+
+ @Override
+ default Boolean xor(Boolean other)
+ {
+ return other.not();
+ }
+
+ @Override
+ default Boolean and(Boolean other)
+ {
+ return other;
+ }
+
+ @Override
+ default Boolean not()
+ {
+ return FALSE();
+ }
+ }
+
+ static interface False extends Boolean, Booleans
+ {
+ @Override
+ default Boolean or(Boolean other)
+ {
+ return other;
+ }
+
+ @Override
+ default Boolean xor(Boolean other)
+ {
+ return other;
+ }
+
+ @Override
+ default Boolean and(Boolean other)
+ {
+ return FALSE();
+ }
+
+ @Override
+ default Boolean not()
+ {
+ return TRUE();
+ }
+ }
+
+ static interface Booleans
+ {
+ @Inject True TRUE();
+ @Inject False FALSE();
+ }
+
+ static abstract class TestData
+ {
+ boolean left;
+ boolean expected;
+
+ abstract void test(Function converter);
+ }
+
+ static class TestUnary extends TestData
+ {
+ UnaryOperator operator;
+
+ TestUnary(UnaryOperator operator, boolean left, boolean expected)
+ {
+ this.operator = operator;
+ this.left = left;
+ this.expected = expected;
+ }
+
+ @Override
+ void test(Function converter)
+ {
+ Boolean object = converter.apply(left);
+ Boolean result = operator.apply(object);
+ Boolean expected = converter.apply(this.expected);
+ assertEquals(expected, result);
+ }
+ }
+
+ static class TestBinary extends TestData
+ {
+ BinaryOperator operator;
+ boolean right;
+
+ TestBinary(BinaryOperator operator, boolean left, boolean right, boolean expected)
+ {
+ this.operator = operator;
+ this.left = left;
+ this.right = right;
+ this.expected = expected;
+ }
+
+ @Override
+ void test(Function converter)
+ {
+ Boolean object = converter.apply(left);
+ Boolean other = converter.apply(right);
+ Boolean result = operator.apply(object, other);
+ Boolean expected = converter.apply(this.expected);
+ assertEquals(expected, result);
+ }
+ }
+
+ @Parameters
+ public static TestData[] getTestData()
+ {
+ return new TestData[]
+ {
+ new TestUnary(Boolean::not, false, true),
+ new TestUnary(Boolean::not, true, false),
+ new TestBinary(Boolean::and, false, false, false),
+ new TestBinary(Boolean::and, true, false, false),
+ new TestBinary(Boolean::and, false, true, false),
+ new TestBinary(Boolean::and, true, true, true),
+ new TestBinary(Boolean::xor, false, false, false),
+ new TestBinary(Boolean::xor, true, false, true),
+ new TestBinary(Boolean::xor, false, true, true),
+ new TestBinary(Boolean::xor, true, true, false),
+ new TestBinary(Boolean::or, false, false, false),
+ new TestBinary(Boolean::or, true, false, true),
+ new TestBinary(Boolean::or, false, true, true),
+ new TestBinary(Boolean::or, true, true, true)
+ };
+ }
+
+ @Parameter
+ public TestData test;
+
+ @Test
+ public void test() throws Exception
+ {
+ Module module = new AbstractModule()
+ {
+ @Override
+ protected void configure()
+ {
+ bind(True.class).to(Projo.getImplementationClass(True.class)).asEagerSingleton();
+ bind(False.class).to(Projo.getImplementationClass(False.class)).asEagerSingleton();
+ bind(Booleans.class).to(Projo.getImplementationClass(Booleans.class)).asEagerSingleton();
+ }
+ };
+ Injector injector = Guice.createInjector(module);
+ Booleans booleans = injector.getInstance(Booleans.class);
+ test.test(value -> value? booleans.TRUE():booleans.FALSE());
+ }
+}
diff --git a/projo-runtime-code-generation/src/test/java/pro/projo/jakarta/ProjoInjectionSupportTest.java b/projo-runtime-code-generation/src/test/java/pro/projo/jakarta/ProjoInjectionSupportTest.java
new file mode 100644
index 0000000..12838de
--- /dev/null
+++ b/projo-runtime-code-generation/src/test/java/pro/projo/jakarta/ProjoInjectionSupportTest.java
@@ -0,0 +1,151 @@
+// //
+// Copyright 2024 Mirko Raner //
+// //
+// Licensed under the Apache License, Version 2.0 (the "License"); //
+// you may not use this file except in compliance with the License. //
+// You may obtain a copy of the License at //
+// //
+// http://www.apache.org/licenses/LICENSE-2.0 //
+// //
+// Unless required by applicable law or agreed to in writing, software //
+// distributed under the License is distributed on an "AS IS" BASIS, //
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //
+// See the License for the specific language governing permissions and //
+// limitations under the License. //
+// //
+package pro.projo.jakarta;
+
+import java.awt.Point;
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+import jakarta.inject.Inject;
+import jakarta.inject.Provider;
+import org.junit.Test;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.TypeLiteral;
+import pro.projo.Projo;
+import pro.projo.annotations.Property;
+import pro.projo.test.implementations.IntegerProcessor;
+import pro.projo.test.implementations.jakarta.PointProcessor;
+import pro.projo.test.interfaces.Processor;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+public class ProjoInjectionSupportTest
+{
+ static interface True
+ {
+ //
+ }
+
+ static interface False
+ {
+ //
+ }
+
+ static interface Booleans
+ {
+ @Inject True _true();
+ @Inject False _false();
+ }
+
+ static interface Pass
+ {
+ @Inject
+ @Property
+ default Booleans booleans()
+ {
+ throw new NoSuchMethodError();
+ }
+
+ default True evaluate()
+ {
+ return booleans()._true();
+ }
+ }
+
+ @Test
+ public void testInjectAnnotationCarriesOverToGeneratedFields() throws Exception
+ {
+ Class extends Booleans> booleans = Projo.getImplementationClass(Booleans.class);
+ Field trueField = booleans.getDeclaredField("_true");
+ Field falseField = booleans.getDeclaredField("_false");
+ Inject[] trueInject = trueField.getAnnotationsByType(Inject.class);
+ Inject[] falseInject = falseField.getAnnotationsByType(Inject.class);
+ int[] expected = {1, 1};
+ int[] actual = {trueInject.length, falseInject.length};
+ assertArrayEquals(expected, actual);
+ }
+
+ @Test
+ public void testThatInjectedFieldsAreAlwaysUsingAProvider() throws Exception
+ {
+ Class extends Booleans> booleans = Projo.getImplementationClass(Booleans.class);
+ Field trueField = booleans.getDeclaredField("_true");
+ Field falseField = booleans.getDeclaredField("_false");
+ Type trueType = trueField.getGenericType();
+ Type falseType = falseField.getGenericType();
+ class Expected
+ {
+ @SuppressWarnings("unused") Provider trueExpected;
+ @SuppressWarnings("unused") Provider falseExpected;
+ }
+ Type[] expected =
+ {
+ Expected.class.getDeclaredField("trueExpected").getGenericType(),
+ Expected.class.getDeclaredField("falseExpected").getGenericType()
+ };
+ Type[] actual = {trueType, falseType};
+ assertArrayEquals(expected, actual);
+ }
+
+ @Test
+ public void testThatPropertyAnnotationWorksInConjunctionWithInject() throws Exception
+ {
+ Module module = new AbstractModule()
+ {
+ @Override
+ protected void configure()
+ {
+ bind(True.class).to(Projo.getImplementationClass(True.class)).asEagerSingleton();
+ bind(False.class).to(Projo.getImplementationClass(False.class)).asEagerSingleton();
+ bind(Booleans.class).to(Projo.getImplementationClass(Booleans.class)).asEagerSingleton();
+ bind(Pass.class).to(Projo.getImplementationClass(Pass.class)).asEagerSingleton();
+ }
+ };
+ Injector injector = Guice.createInjector(module);
+ Booleans booleans = injector.getInstance(Booleans.class);
+ Pass pass = injector.getInstance(Pass.class);
+ assertSame(booleans._true(), pass.evaluate());
+ }
+
+ @Test
+ public void testThatReturnsAnnotationWorksInConjunctionWithInject() throws Exception
+ {
+ @SuppressWarnings("rawtypes")
+ TypeLiteral pointProcessor = new TypeLiteral>() {};
+
+ @SuppressWarnings("rawtypes")
+ TypeLiteral integerProcessor = new TypeLiteral>() {};
+
+ Module module = new AbstractModule()
+ {
+ @Override
+ @SuppressWarnings("unchecked")
+ protected void configure()
+ {
+ bind((TypeLiteral