diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/AnnotationConfig.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/AnnotationConfig.java new file mode 100644 index 000000000..97201dddf --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/AnnotationConfig.java @@ -0,0 +1,23 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.lang.model.AnnotationAttribute; +import jakarta.enterprise.lang.model.AnnotationInfo; +import jakarta.enterprise.lang.model.declarations.ClassInfo; +import java.lang.annotation.Annotation; +import java.util.function.Predicate; + +// TODO better name? +// TODO devise a builder-style API instead (see also Annotations) +public interface AnnotationConfig { + void addAnnotation(Class annotationType, AnnotationAttribute... attributes); + + void addAnnotation(ClassInfo annotationType, AnnotationAttribute... attributes); + + void addAnnotation(AnnotationInfo annotation); + + void addAnnotation(Annotation annotation); + + void removeAnnotation(Predicate predicate); + + void removeAllAnnotations(); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Annotations.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Annotations.java new file mode 100644 index 000000000..0d5af2f0a --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Annotations.java @@ -0,0 +1,84 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.lang.model.AnnotationAttribute; +import jakarta.enterprise.lang.model.AnnotationAttributeValue; +import jakarta.enterprise.lang.model.AnnotationInfo; +import jakarta.enterprise.lang.model.declarations.ClassInfo; +import java.lang.annotation.Annotation; +import java.util.List; + +// TODO devise a builder-style API instead (see also AnnotationConfig) +public interface Annotations { + AnnotationAttributeValue value(boolean value); + + AnnotationAttributeValue value(byte value); + + AnnotationAttributeValue value(short value); + + AnnotationAttributeValue value(int value); + + AnnotationAttributeValue value(long value); + + AnnotationAttributeValue value(float value); + + AnnotationAttributeValue value(double value); + + AnnotationAttributeValue value(char value); + + AnnotationAttributeValue value(String value); + + AnnotationAttributeValue value(Enum enumValue); + + AnnotationAttributeValue value(Class> enumType, String enumValue); + + AnnotationAttributeValue value(ClassInfo enumType, String enumValue); + + AnnotationAttributeValue value(Class value); + + AnnotationAttributeValue annotationValue(Class annotationType, AnnotationAttribute... attributes); + + AnnotationAttributeValue annotationValue(ClassInfo annotationType, AnnotationAttribute... attributes); + + AnnotationAttributeValue annotationValue(AnnotationInfo annotation); + + AnnotationAttributeValue annotationValue(Annotation annotation); + + AnnotationAttribute attribute(String name, boolean value); + + AnnotationAttribute attribute(String name, byte value); + + AnnotationAttribute attribute(String name, short value); + + AnnotationAttribute attribute(String name, int value); + + AnnotationAttribute attribute(String name, long value); + + AnnotationAttribute attribute(String name, float value); + + AnnotationAttribute attribute(String name, double value); + + AnnotationAttribute attribute(String name, char value); + + AnnotationAttribute attribute(String name, String value); + + AnnotationAttribute attribute(String name, Enum enumValue); + + AnnotationAttribute attribute(String name, Class> enumType, String enumValue); + + AnnotationAttribute attribute(String name, ClassInfo enumType, String enumValue); + + AnnotationAttribute attribute(String name, Class value); + + AnnotationAttribute arrayAttribute(String name, AnnotationAttributeValue... values); + + AnnotationAttribute arrayAttribute(String name, List values); + + AnnotationAttribute annotationAttribute(String name, Class annotationType, + AnnotationAttribute... attributes); + + AnnotationAttribute annotationAttribute(String name, ClassInfo annotationType, AnnotationAttribute... attributes); + + AnnotationAttribute annotationAttribute(String name, AnnotationInfo annotation); + + AnnotationAttribute annotationAttribute(String name, Annotation annotation); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/AppArchive.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/AppArchive.java new file mode 100644 index 000000000..67db8acb9 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/AppArchive.java @@ -0,0 +1,162 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.lang.model.declarations.ClassInfo; +import jakarta.enterprise.lang.model.declarations.FieldInfo; +import jakarta.enterprise.lang.model.declarations.MethodInfo; +import jakarta.enterprise.lang.model.types.Type; +import java.lang.annotation.Annotation; +import java.util.function.Consumer; + +// TODO remove entirely? +public interface AppArchive { + ClassQuery classes(); + + MethodQuery constructors(); // no static initializers + + MethodQuery methods(); // no constructors nor static initializers + + FieldQuery fields(); + + /** + * The {@code exactly} and {@code subtypeOf} methods are additive. + * When called multiple times, they form a union of requested classes (not an intersection). + * For example, + *
{@code
+     * appArchive.classes()
+     *     .exactly(Foo.class)
+     *     .subtypeOf(Bar.class)
+     *     .forEach(...)
+     * }
+ * runs given code for the {@code Foo} class and all subtypes of the {@code Bar} class. + *

+ * The {@code annotatedWith} methods are additive. + * When called multiple times, they form a union of requested annotations (not an intersection). + * For example, + *

{@code
+     * appArchive.classes()
+     *     .annotatedWith(Foo.class)
+     *     .annotatedWith(Bar.class)
+     *     .forEach(...)
+     * }
+ * runs given code for all classes annotated either with {@code @Foo} or with {@code @Bar} (or both). + */ + interface ClassQuery { + ClassQuery exactly(Class clazz); + + ClassQuery subtypeOf(Class clazz); + + ClassQuery annotatedWith(Class annotationType); + + void forEach(Consumer> consumer); + } + + /** + * The {@code declaredOn} method is additive. + * When called multiple times, it forms a union of requested classes (not an intersection). + * For example, + *
{@code
+     * appArchive.methods()
+     *     .declaredOn(appArchive.classes().exactly(Foo.class))
+     *     .declaredOn(appArchive.classes().subtypeOf(Bar.class))
+     *     .forEach(...)
+     * }
+ * runs given code for all methods declared on the {@code Foo} class and on all subtypes of the {@code Bar} class. + * Note that this example can be rewritten as + *
{@code
+     * appArchive.methods()
+     *     .declaredOn(appArchive.classes().exactly(Foo.class).subtypeOf(Bar.class))
+     *     .forEach(...)
+     * }
+ * which is probably easier to understand. + *

+ * The {@code withReturnType} methods are additive. + * When called multiple times, they form a union of requested return types (not an intersection). + * For example, + *

{@code
+     * appArchive.methods()
+     *     .withReturnType(Foo.class)
+     *     .withReturnType(Bar.class)
+     *     .forEach(...)
+     * }
+ * runs given code for all methods that return either {@code Foo} or {@code Bar}. + *

+ * The {@code annotatedWith} methods are additive. + * When called multiple times, they form a union of requested annotations (not an intersection). + * For example, + *

{@code
+     * appArchive.methods()
+     *     .annotatedWith(Foo.class)
+     *     .annotatedWith(Bar.class)
+     *     .forEach(...)
+     * }
+ * runs given code for all methods annotated either with {@code @Foo} or with {@code @Bar} (or both). + */ + interface MethodQuery { + MethodQuery declaredOn(ClassQuery classes); + + // equivalent to `withReturnType(Types.of(type))` + MethodQuery withReturnType(Class type); + + MethodQuery withReturnType(Type type); + + // TODO parameters? + + MethodQuery annotatedWith(Class annotationType); + + void forEach(Consumer> consumer); + } + + /** + * The {@code declaredOn} method is additive. + * When called multiple times, it forms a union of requested classes (not an intersection). + * For example, + *
{@code
+     * appArchive.fields()
+     *     .declaredOn(appArchive.classes().exactly(Foo.class))
+     *     .declaredOn(appArchive.classes().subtypeOf(Bar.class))
+     *     .forEach(...)
+     * }
+ * runs given code for all fields declared on the {@code Foo} class and on all subtypes of the {@code Bar} class. + * Note that this example can be rewritten as + *
{@code
+     * appArchive.fields()
+     *     .declaredOn(appArchive.classes().exactly(Foo.class).subtypeOf(Bar.class))
+     *     .forEach()
+     * }
+ * which is probably easier to understand. + *

+ * The {@code ofType} methods are additive. + * When called multiple times, they form a union of requested field types (not an intersection). + * For example, + *

{@code
+     * appArchive.fields()
+     *     .ofType(Foo.class)
+     *     .ofType(Bar.class)
+     *     .forEach()
+     * }
+ * runs given code for all fields that are of type either {@code Foo} or {@code Bar}. + *

+ * The {@code annotatedWith} methods are additive. + * When called multiple times, they form a union of requested annotations (not an intersection). + * For example, + *

{@code
+     * appArchive.fields()
+     *     .annotatedWith(Foo.class)
+     *     .annotatedWith(Bar.class)
+     *     .forEach(...)
+     * }
+ * runs given code for all fields annotated either with {@code @Foo} or with {@code @Bar} (or both). + */ + interface FieldQuery { + FieldQuery declaredOn(ClassQuery classes); + + // equivalent to `withReturnType(Types.of(type))` + FieldQuery ofType(Class type); + + FieldQuery ofType(Type type); + + FieldQuery annotatedWith(Class annotationType); + + void forEach(Consumer> consumer); + } +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/AppArchiveBuilder.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/AppArchiveBuilder.java new file mode 100644 index 000000000..b05e74370 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/AppArchiveBuilder.java @@ -0,0 +1,10 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +// TODO better name +public interface AppArchiveBuilder { + void add(String fullyQualifiedClassName); + + // TODO adds the type itself or not? (in theory yes, as subtyping is reflexive) + // TODO looks like it can't be implemented on top of Portable Extensions, so maybe remove? + void addSubtypesOf(String fullyQualifiedClassName); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/AppArchiveConfig.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/AppArchiveConfig.java new file mode 100644 index 000000000..aa02e4c0f --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/AppArchiveConfig.java @@ -0,0 +1,66 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.lang.model.types.Type; +import java.lang.annotation.Annotation; +import java.util.function.Consumer; + +// TODO remove entirely, if we remove AppArchive/AppDeployment +// TODO maybe AppArchiveConfig shouldn't extend AppArchive, and *ConfigQuery shouldn't extend *Query +public interface AppArchiveConfig extends AppArchive { + @Override + ClassConfigQuery classes(); + + @Override + MethodConfigQuery constructors(); // no static initializers + + @Override + MethodConfigQuery methods(); // no constructors nor static initializers + + @Override + FieldConfigQuery fields(); + + interface ClassConfigQuery extends ClassQuery { + @Override + ClassConfigQuery exactly(Class clazz); + + @Override + ClassConfigQuery subtypeOf(Class clazz); + + @Override + ClassConfigQuery annotatedWith(Class annotationType); + + void configure(Consumer> consumer); + } + + interface MethodConfigQuery extends MethodQuery { + @Override + MethodConfigQuery declaredOn(ClassQuery classes); + + @Override + MethodConfigQuery withReturnType(Class type); + + @Override + MethodConfigQuery withReturnType(Type type); + + @Override + MethodConfigQuery annotatedWith(Class annotationType); + + void configure(Consumer> consumer); + } + + interface FieldConfigQuery extends FieldQuery { + @Override + FieldConfigQuery declaredOn(ClassQuery classes); + + @Override + FieldConfigQuery ofType(Class type); + + @Override + FieldConfigQuery ofType(Type type); + + @Override + FieldConfigQuery annotatedWith(Class annotationType); + + void configure(Consumer> consumer); + } +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/AppDeployment.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/AppDeployment.java new file mode 100644 index 000000000..dcfd4a596 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/AppDeployment.java @@ -0,0 +1,138 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.lang.model.declarations.ClassInfo; +import jakarta.enterprise.lang.model.types.Type; +import java.lang.annotation.Annotation; +import java.util.function.Consumer; + +// TODO remove entirely? +public interface AppDeployment { + BeanQuery beans(); + + ObserverQuery observers(); + + /** + * The {@code scope} methods are additive. + * When called multiple times, they form a union of requested scope types (not an intersection). + * For example, + *
{@code
+     * appDeployment.beans()
+     *     .scope(Foo.class)
+     *     .scope(Bar.class)
+     *     .forEach(...)
+     * }
+ * runs given code for all beans with the {@code @Foo} scope or the {@code @Bar} scope. + *

+ * The {@code type} methods are additive. + * When called multiple times, they form a union of requested bean types (not an intersection). + * For example, + *

{@code
+     * appDeployment.beans()
+     *     .type(Foo.class)
+     *     .type(Bar.class)
+     *     .forEach(...)
+     * }
+ * runs given code for all beans with the {@code Foo} type or the {@code Bar} type (or both). + * Note that bean type is not just the class which declares the bean (or return type of a producer method, + * or type of producer field). All superclasses and superinterfaces are also included in the set of bean types. + *

+ * The {@code qualifier} methods are additive. + * When called multiple times, they form a union of requested qualifiers (not an intersection). + * For example, + *

{@code
+     * appDeployment.beans()
+     *     .qualifier(Foo.class)
+     *     .qualifier(Bar.class)
+     *     .forEach(...)
+     * }
+ * runs given code for all beans with the {@code @Foo} qualifier or the {@code @Bar} qualifier (or both). + *

+ * The {@code declaringClass} methods are additive. + * When called multiple times, they form a union of requested declaration classes (not an intersection). + * For example, + *

{@code
+     * appDeployment.beans()
+     *     .declaringClass(Foo.class)
+     *     .declaringClass(Bar.class)
+     *     .forEach(...)
+     * }
+ * runs given code for all beans declared on the {@code Foo} class or the {@code Bar} class. + */ + interface BeanQuery { + BeanQuery scope(Class scopeAnnotation); + + BeanQuery scope(ClassInfo scopeAnnotation); + + BeanQuery type(Class beanType); + + BeanQuery type(ClassInfo beanType); + + BeanQuery type(Type beanType); + + BeanQuery qualifier(Class qualifierAnnotation); + + BeanQuery qualifier(ClassInfo qualifierAnnotation); + + BeanQuery declaringClass(Class declaringClass); + + BeanQuery declaringClass(ClassInfo declaringClass); + + void forEach(Consumer> consumer); + + void ifNone(Runnable runnable); + } + + /** + * The {@code observedType} methods are additive. + * When called multiple times, they form a union of requested observer types (not an intersection). + * For example, + *
{@code
+     * appDeployment.observers()
+     *     .observedType(Foo.class)
+     *     .observedType(Bar.class)
+     *     .forEach(...)
+     * }
+ * runs given code for all observers that observe the {@code Foo} event type or the {@code Bar} event type. + *

+ * The {@code qualifier} methods are additive. + * When called multiple times, they form a union of requested qualifiers (not an intersection). + * For example, + *

{@code
+     * appDeployment.observers()
+     *     .qualifier(Foo.class)
+     *     .qualifier(Bar.class)
+     *     .forEach(...)
+     * }
+ * runs given code for all observers with the {@code @Foo} qualifier or the {@code @Bar} qualifier (or both). + *

+ * The {@code declaringClass} methods are additive. + * When called multiple times, they form a union of requested declaration classes (not an intersection). + * For example, + *

{@code
+     * appDeployment.observers()
+     *     .declaringClass(Foo.class)
+     *     .declaringClass(Bar.class)
+     *     .forEach(...)
+     * }
+ * runs given code for all observers declared on the {@code Foo} class or the {@code Bar} class. + */ + interface ObserverQuery { + ObserverQuery observedType(Class observedType); + + ObserverQuery observedType(ClassInfo observedType); + + ObserverQuery observedType(Type observedType); + + ObserverQuery qualifier(Class qualifierAnnotation); + + ObserverQuery qualifier(ClassInfo qualifierAnnotation); + + ObserverQuery declaringClass(Class declaringClass); + + ObserverQuery declaringClass(ClassInfo declaringClass); + + void forEach(Consumer> consumer); + + void ifNone(Runnable runnable); + } +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/BeanInfo.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/BeanInfo.java new file mode 100644 index 000000000..f04128390 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/BeanInfo.java @@ -0,0 +1,59 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.lang.model.AnnotationInfo; +import jakarta.enterprise.lang.model.declarations.ClassInfo; +import jakarta.enterprise.lang.model.declarations.FieldInfo; +import jakarta.enterprise.lang.model.declarations.MethodInfo; +import jakarta.enterprise.lang.model.types.Type; +import java.util.Collection; + +/** + * @param type of the inspected bean (that is, not the declaring class, but one of the types the bean has) + */ +public interface BeanInfo { + // TODO remove the type parameter? + + ScopeInfo scope(); + + Collection types(); + + // TODO method(s) for getting AnnotationInfo for given qualifier class? + Collection qualifiers(); + + /** + * Returns the class that declares the bean. + * In case of a bean defined by a class, that is the bean class directly. + * In case of a producer method or field, that is the class that declares the producer method or field. + * TODO null for synthetic beans, or return Optional? + * + * @return {@link ClassInfo} for the class that declares the bean + */ + ClassInfo declaringClass(); + + boolean isClassBean(); + + boolean isProducerMethod(); + + boolean isProducerField(); + + boolean isSynthetic(); + + MethodInfo producerMethod(); // TODO null if not producer method, or return Optional? + + FieldInfo producerField(); // TODO null if not producer field, or return Optional? + + boolean isAlternative(); + + int priority(); + + // EL name (from @Named), IIUC + String getName(); + + DisposerInfo disposer(); // TODO null if not producer method/field, or return Optional? + + Collection stereotypes(); + + // TODO interceptors? + + Collection injectionPoints(); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/BuildCompatibleExtension.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/BuildCompatibleExtension.java new file mode 100644 index 000000000..914d4e96b --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/BuildCompatibleExtension.java @@ -0,0 +1,37 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +/** + * Build compatible extensions are service providers for this interface, as defined in {@link java.util.ServiceLoader}. + * This means: they are classes that implement this interface, provide a {@code META-INF/services} file, + * and satisfy all other service provider constraints. Additionally, extensions should not be CDI beans + * and should not be used at application runtime. + *

+ * Extensions can define arbitrary {@code public}, non-{@code static}, {@code void}-returning methods + * without type parameters, annotated with one of the extension annotations. + *

+ * Extension processing occurs in 4 phases, corresponding to 4 extension annotations: + *

    + *
  • {@link Discovery @Discovery}
  • + *
  • {@link Enhancement @Enhancement}
  • + *
  • {@link Synthesis @Synthesis}
  • + *
  • {@link Validation @Validation}
  • + *
+ *

+ * These methods can declare arbitrary number of parameters. Which parameters can be declared depends + * on the particular processing phase and is documented in the corresponding extension annotation. + * All the parameters will be provided by the container when the extension is invoked. + *

+ * Extension methods can be assigned a priority using {@link jakarta.enterprise.inject.build.compatible.spi.ExtensionPriority @ExtensionPriority}. + * Note that priority only affects order of extensions in a single phase. + *

+ * If the extension declares multiple methods, they are all invoked on the same instance of the class. + *

+ * Extension classes can be annotated {@link SkipIfPortableExtensionPresent @SkipIfPortablExtensionPresent} + * when they are supposed to be ignored in presence of a given portable extension. + */ +public interface BuildCompatibleExtension { + // TODO rename? "build compatible" is too long; ideally, we'd have a single word that describes + // the true nature of the "new" extension API (which IMHO is: there's a barrier between extension execution + // and application execution, there's only a very narrow way how to pass information from extension + // to application, and there's _no way whatsoever_ to pass anything in the other direction) +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/ClassConfig.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/ClassConfig.java new file mode 100644 index 000000000..8c35dfaa8 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/ClassConfig.java @@ -0,0 +1,27 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.lang.model.declarations.ClassInfo; + +import java.util.Collection; + +/** + * @param the configured class + */ +public interface ClassConfig extends ClassInfo, AnnotationConfig { + // TODO remove the type parameter? + // TODO even if ClassInfo has equals/hashCode, ClassConfig probably shouldn't + + // only constructors declared by this class, not inherited ones + // no [static] initializers + @Override + Collection> constructors(); + + // only methods declared by this class, not inherited ones + // no constructors nor [static] initializers + @Override + Collection> methods(); + + // only fields declared by this class, not inherited ones + @Override + Collection> fields(); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/ContextBuilder.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/ContextBuilder.java new file mode 100644 index 000000000..dcaba9b1b --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/ContextBuilder.java @@ -0,0 +1,43 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import java.lang.annotation.Annotation; +import jakarta.enterprise.context.spi.AlterableContext; + +public interface ContextBuilder { + /** + * If implementation class is defined using {@link #implementation(Class)}, + * this method doesn't have to be called. If it is, it overrides the scope annotation + * as defined by the implementation class. + * + * @param scopeAnnotation scope annotation of the context + * @return this {@code ContextBuilder} + */ + // if called multiple times, last call wins + ContextBuilder scope(Class scopeAnnotation); + + /** + * If scope annotation is defined using {@link #scope(Class)}, + * this method doesn't have to be called. If it is, it overrides the normal/pseudo state + * as defined by the scope annotation. + * + * @param isNormal whether the scope is normal + * @return this {@code ContextBuilder} + */ + // if called multiple times, last call wins + ContextBuilder normal(boolean isNormal); + + /** + * Defines the context implementation class. + * The implementation class typically provides the scope annotations, and therefore + * also whether the scope is a normal scope or pseudoscope, but this can be overridden + * using {@link #scope(Class)} and {@link #normal(boolean)}. + *

+ * Implementations can impose additional restrictions on what valid parameters are; + * they don't have to accept all implementations of {@link AlterableContext}. + * + * @param implementationClass context object implementation class + * @return this {@code ContextBuilder} + */ + // if called multiple times, last call wins + ContextBuilder implementation(Class implementationClass); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Discovery.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Discovery.java new file mode 100644 index 000000000..cbfad85eb --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Discovery.java @@ -0,0 +1,24 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 1st phase of CDI Lite extension processing. + * Allow registering additional classes to become part of the application. + * Also allows registering custom CDI meta-annotations. + *

+ * Methods annotated {@code @Discovery} can define parameters of these types: + *

    + *
  • {@link AppArchiveBuilder}: to add classes to application
  • + *
  • {@link MetaAnnotations}: to register custom meta-annotations: + * qualifiers, interceptor bindings, stereotypes and scopes
  • + *
  • {@link Messages}: to produce log messages and validation errors
  • + *
+ */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Discovery { +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/DisposerInfo.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/DisposerInfo.java new file mode 100644 index 000000000..a4aeaaede --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/DisposerInfo.java @@ -0,0 +1,10 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.lang.model.declarations.MethodInfo; +import jakarta.enterprise.lang.model.declarations.ParameterInfo; + +public interface DisposerInfo { + MethodInfo disposerMethod(); + + ParameterInfo disposedParameter(); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Enhancement.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Enhancement.java new file mode 100644 index 000000000..8d494bee2 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Enhancement.java @@ -0,0 +1,35 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 2nd phase of CDI Lite extension processing. + * Allows transforming annotations. + *

+ * Methods annotated {@code @Enhancement} must define exactly one parameter of one of these types: + *

    + *
  • {@link ClassConfig ClassConfig}
  • + *
  • {@link MethodConfig MethodConfig}
  • + *
  • {@link FieldConfig FieldConfig}
  • + *
  • {@link AppArchiveConfig AppArchiveConfig}
  • + *
+ * If the parameter is {@code ClassConfig}, {@code MethodConfig} or {@code FieldConfig}, the method + * must have at least one annotation {@link ExactType @ExactType} or {@link SubtypesOf @SubtypesOf}. + *

+ * You can also declare a parameter of type {@link Messages Messages} to produce log messages and validation errors. + *

+ * If you need to create instances of {@link jakarta.enterprise.lang.model.types.Type Type}, you can also declare + * a parameter of type {@link Types Types}. It provides factory methods for the void type, primitive types, + * class types, array types, parameterized types and wildcard types. + *

+ * If you need to create instances of {@link jakarta.enterprise.lang.model.AnnotationAttribute AnnotationAttribute} or + * {@link jakarta.enterprise.lang.model.AnnotationAttributeValue AnnotationAttributeValue}, you can also declare + * a parameter of type {@link Annotations Annotations}. It provides factory methods for all kinds of annotation attributes. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Enhancement { +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/ExactType.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/ExactType.java new file mode 100644 index 000000000..141ccb4f7 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/ExactType.java @@ -0,0 +1,41 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Constraints the {@link Enhancement @Enhancement} or {@link Processing @Processing} method to given types. + *

+ * If the {@code Enhancement} method has a parameter of type {@code ClassConfig}, + * the method is called once for each given type. + * If the {@code @Enhancement} method has a parameter of type {@code MethodConfig} or {@code FieldConfig}, + * the method is called once for each method or field of each given type. + *

+ * If the {@code @Processing} method has a parameter of type {@code BeanInfo}, + * the method is called once for each bean whose set of bean types contains at least one given type. + * If the {@code @Processing} method has a parameter of type {@code ObserverInfo}, + * the method is called once for each observer method whose observed type is among the given types. + *

+ * If the {@code annotatedWith} attribute is set, only types that use given annotations are considered. + * The annotations can appear on the type, or on any member of the type, or any parameter of any member of the type. + * This is ignored for {@code @Processing}. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(ExactType.List.class) +public @interface ExactType { + Class type(); + + // default = any annotation, does that make sense? + Class[] annotatedWith() default Annotation.class; + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @interface List { + ExactType[] value(); + } +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/ExtensionPriority.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/ExtensionPriority.java new file mode 100644 index 000000000..6ec2ca722 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/ExtensionPriority.java @@ -0,0 +1,26 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Allows specifying priority of extension methods. + *

+ * Extension methods with smaller priority values are invoked first. + * Extension methods without specified priority are treated as methods with priority {@code 10_000}; + * If two extension methods have equal priority, the ordering among them is undefined. + * + * @see BuildCompatibleExtension + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ExtensionPriority { + // TODO propose allowing @Priority to be present on methods + + /** + * @return the priority value + */ + int value(); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/FieldConfig.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/FieldConfig.java new file mode 100644 index 000000000..ff53e1786 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/FieldConfig.java @@ -0,0 +1,11 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.lang.model.declarations.FieldInfo; + +/** + * @param type of whomever declares the configured field + */ +public interface FieldConfig extends FieldInfo, AnnotationConfig { + // TODO remove the type parameter? + // TODO even if FieldInfo has equals/hashCode, FieldConfig probably shouldn't +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/InjectionPointInfo.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/InjectionPointInfo.java new file mode 100644 index 000000000..af4e9d0fb --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/InjectionPointInfo.java @@ -0,0 +1,27 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.lang.model.AnnotationInfo; +import jakarta.enterprise.lang.model.declarations.DeclarationInfo; +import jakarta.enterprise.lang.model.types.Type; +import java.util.Collection; + +public interface InjectionPointInfo { + Type type(); + + // TODO method(s) for getting AnnotationInfo for given qualifier class? + Collection qualifiers(); + + /** + * Returns a {@code FieldInfo} for field injection, or {@code ParameterInfo} for: + *

    + *
  • constructor injection,
  • + *
  • initializer method,
  • + *
  • disposer method,
  • + *
  • producer method,
  • + *
  • observer method.
  • + *
+ * + * @return declaration of this injection point + */ + DeclarationInfo declaration(); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Messages.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Messages.java new file mode 100644 index 000000000..86bb9767c --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Messages.java @@ -0,0 +1,106 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.lang.model.AnnotationTarget; + +public interface Messages { + /** + * Add a generic information message that is not related to any particular element, or that information is not known. + * + * @param message information message + */ + void info(String message); + + /** + * Add an information message which is related to given {@link AnnotationTarget} (which is most likely + * a {@link jakarta.enterprise.lang.model.declarations.DeclarationInfo DeclarationInfo}). + * + * @param message information message + * @param relatedTo annotation target to which the message is related + */ + void info(String message, AnnotationTarget relatedTo); + + /** + * Add an information message which is related to given {@link BeanInfo}. + * + * @param message information message + * @param relatedTo bean to which the message is related + */ + void info(String message, BeanInfo relatedTo); + + /** + * Add an information message which is related to given {@link ObserverInfo}. + * + * @param message information message + * @param relatedTo observer to which the message is related + */ + void info(String message, ObserverInfo relatedTo); + + /** + * Add a generic warning that is not related to any particular element, or that information is not known. + * + * @param message warning message + */ + void warn(String message); + + /** + * Add a warning which is related to given {@link AnnotationTarget} (which is most likely + * a {@link jakarta.enterprise.lang.model.declarations.DeclarationInfo DeclarationInfo}). + * + * @param message warning message + * @param relatedTo annotation target to which the message is related + */ + void warn(String message, AnnotationTarget relatedTo); + + /** + * Add a warning which is related to given {@link BeanInfo}. + * + * @param message warning message + * @param relatedTo bean to which the message is related + */ + void warn(String message, BeanInfo relatedTo); + + /** + * Add a warning which is related to given {@link ObserverInfo}. + * + * @param message warning message + * @param relatedTo observer to which the message is related + */ + void warn(String message, ObserverInfo relatedTo); + + /** + * Add a generic error that is not related to any particular element, or that information is not known. + * + * @param message error message + */ + void error(String message); + + /** + * Add an error which is related to given {@link AnnotationTarget} (which is most likely + * a {@link jakarta.enterprise.lang.model.declarations.DeclarationInfo DeclarationInfo}). + * + * @param message error message + * @param relatedTo annotation target to which the message is related + */ + void error(String message, AnnotationTarget relatedTo); + + /** + * Add an error which is related to given {@link BeanInfo}. + * @param message error message + * @param relatedTo bean to which the message is related + */ + void error(String message, BeanInfo relatedTo); + + /** + * Add an error which is related to given {@link ObserverInfo}. + * @param message error message + * @param relatedTo observer to which the message is related + */ + void error(String message, ObserverInfo relatedTo); + + /** + * Add a generic error that is represented by an exception. + * + * @param exception error, represented by an exception + */ + void error(Exception exception); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/MetaAnnotations.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/MetaAnnotations.java new file mode 100644 index 000000000..b3ee5c33a --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/MetaAnnotations.java @@ -0,0 +1,18 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import java.lang.annotation.Annotation; +import java.util.function.Consumer; + +public interface MetaAnnotations { + // TODO this API style is not very common, and makes addContext too different + // I only added it to make Quarkus implementation easier, but should definitely be reconsidered + + void addQualifier(Class annotation, Consumer> config); + + void addInterceptorBinding(Class annotation, Consumer> config); + + void addStereotype(Class annotation, Consumer> config); + + // includes defining the scope annotation + ContextBuilder addContext(); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/MethodConfig.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/MethodConfig.java new file mode 100644 index 000000000..7c5ee0f27 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/MethodConfig.java @@ -0,0 +1,12 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.lang.model.declarations.MethodInfo; + +/** + * @param type of whomever declares the configured method or constructor + */ +public interface MethodConfig extends MethodInfo, AnnotationConfig { + // TODO remove the type parameter? + // TODO split MethodConfig into MethodConfig/ConstructorConfig? + // TODO even if MethodInfo has equals/hashCode, MethodConfig probably shouldn't +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/ObserverInfo.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/ObserverInfo.java new file mode 100644 index 000000000..114fdb109 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/ObserverInfo.java @@ -0,0 +1,44 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.lang.model.AnnotationInfo; +import jakarta.enterprise.lang.model.declarations.ClassInfo; +import jakarta.enterprise.lang.model.declarations.MethodInfo; +import jakarta.enterprise.lang.model.declarations.ParameterInfo; +import jakarta.enterprise.lang.model.types.Type; +import java.util.Collection; +import jakarta.enterprise.event.Reception; +import jakarta.enterprise.event.TransactionPhase; + +/** + * @param type observed by the inspected observer + */ +public interface ObserverInfo { + // TODO remove the type parameter? + + String id(); // TODO remove entirely? + + Type observedType(); + + // TODO method(s) for getting AnnotationInfo for given qualifier class? + Collection qualifiers(); + + ClassInfo declaringClass(); // never null, even if synthetic + + MethodInfo observerMethod(); // TODO null for synthetic observers, or return Optional? see also isSynthetic below + + ParameterInfo eventParameter(); // TODO null for synthetic observers, or return Optional? see also isSynthetic below + + BeanInfo bean(); // TODO null for synthetic observers, or return Optional? see also isSynthetic below + + default boolean isSynthetic() { + return bean() == null; + } + + int priority(); + + boolean isAsync(); + + Reception reception(); + + TransactionPhase transactionPhase(); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Processing.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Processing.java new file mode 100644 index 000000000..d7c289b88 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Processing.java @@ -0,0 +1,33 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 3rd phase of CDI Lite extension processing. + * Allows processing registered beans and observers. + * Note that synthetic beans and observers, registered in {@link Synthesis}, will not be processed. + *

+ * Methods annotated {@code @Processing} must define exactly one parameter of one of these types: + *

    + *
  • {@link BeanInfo BeanInfo}
  • + *
  • {@link ObserverInfo ObserverInfo}
  • + *
+ * The method must also have at least one annotation {@link ExactType @ExactType} or {@link SubtypesOf @SubtypesOf}. + *

+ * You can also declare a parameter of type {@link Messages Messages} to produce log messages and validation errors. + *

+ * If you need to create instances of {@link jakarta.enterprise.lang.model.types.Type Type}, you can also declare + * a parameter of type {@link Types}. It provides factory methods for the void type, primitive types, class types, + * array types, parameterized types and wildcard types. + */ +// TODO allow @Processing methods to be executed even for synthetic components? that would break the nice +// sequential model, but being able to observe synthetic components is important +// TODO add a way to _modify_ beans? at least veto-ing should be possible +// (add BeanConfig extending BeanInfo? or some other way?) +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Processing { +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/ScopeInfo.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/ScopeInfo.java new file mode 100644 index 000000000..45c073174 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/ScopeInfo.java @@ -0,0 +1,27 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.lang.model.declarations.ClassInfo; + +public interface ScopeInfo { + /** + * @return declaration of the scope annotation + */ + ClassInfo annotation(); + + /** + * Equivalent to {@code annotation().name()}. + * + * @return fully qualified name of the annotation + */ + default String name() { + return annotation().name(); + } + + /** + * Returns whether the scope is normal. In other words, returns whether + * the scope annotation type is meta-annotated with {@code @NormalScope}. + * + * @return whether the scope is normal + */ + boolean isNormal(); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SkipIfPortableExtensionPresent.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SkipIfPortableExtensionPresent.java new file mode 100644 index 000000000..5f1c52516 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SkipIfPortableExtensionPresent.java @@ -0,0 +1,36 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * If a {@link BuildCompatibleExtension} is annotated {@code @SkipIfPortablExtensionPresent}, + * it is skipped when the CDI implementation is able to execute portable extensions and + * the specified class is present on the classpath as visible by the current thread's + * context classloader. It is expected that the specified class will be a class of a portable + * extension that mirrors the functionality of the annotated build compatible extension. + *

+ * This allows for gradual migration from portable extensions to build compatible extensions. + * It is expected that a build compatible extension will be packaged together with the corresponding + * portable extension, so checking whether the class is present should always yield the correct + * result. + * + * @see #value() + */ +// TODO should clarify the "current thread" wording above and below +// TODO maybe runtime implementations should instead check if the BeanManager has an instance of the extension class? +// in other words, maybe we should give implementations more freedom in how they discover whether the class exists? +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface SkipIfPortableExtensionPresent { + /** + * Fully qualified class name that will be searched for in the current thread's context + * classloader. It is expected that the value will be a class of a portable extension + * that mirrors the functionality of the annotated build compatible extension. + * + * @return fully qualified class name + */ + String value(); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/StereotypeInfo.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/StereotypeInfo.java new file mode 100644 index 000000000..0e02320fa --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/StereotypeInfo.java @@ -0,0 +1,18 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.lang.model.AnnotationInfo; +import java.util.Collection; + +public interface StereotypeInfo { + // TODO null if not present, or return Optional? + ScopeInfo defaultScope(); + + Collection interceptorBindings(); + + boolean isAlternative(); + + // TODO https://github.com/eclipse-ee4j/cdi/issues/495 + //int priority(); + + boolean isNamed(); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SubtypesOf.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SubtypesOf.java new file mode 100644 index 000000000..cde9c95d1 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SubtypesOf.java @@ -0,0 +1,33 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Constraints the {@link Enhancement @Enhancement} method to subtypes of given types. + * If the {@code Enhancement} method has a parameter of type {@code ClassConfig}, + * the method is called once for each subtype of each given type. + * If the {@code @Enhancement} method has a parameter of type {@code MethodConfig} or {@code FieldConfig}, + * the method is called once for each method or field of each subtype of each given type. + *

+ * If the {@code annotatedWith} attribute is set, only types that use given annotations are considered. + * The annotations can appear on the type, or on any member of the type, or any parameter of any member of the type. + */ +// TODO should the given type itself also match? (in theory yes, as subtyping is reflexive) +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface SubtypesOf { + Class type(); + + // default = any annotation, does that make sense? + Class[] annotatedWith() default Annotation.class; + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @interface List { + SubtypesOf[] value(); + } +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Synthesis.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Synthesis.java new file mode 100644 index 000000000..7840b1f75 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Synthesis.java @@ -0,0 +1,26 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 4th phase of CDI Lite extension processing. + * Allows registering synthetic beans and observers. + *

+ * Methods annotated {@code @Synthesis} can define parameters of these types: + *

    + *
  • {@link AppArchive}: to find declarations in the application
  • + *
  • {@link AppDeployment}: to find beans and observers in the application
  • + *
  • {@link SyntheticComponents}: to add synthetic beans and observers to the application
  • + *
  • {@link Messages}: to produce log messages and validation errors
  • + *
+ * If you need to create instances of {@link jakarta.enterprise.lang.model.types.Type Type}, you can also declare + * a parameter of type {@link Types}. It provides factory methods for the void type, primitive types, class types, + * array types, parameterized types and wildcard types. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Synthesis { +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticBeanBuilder.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticBeanBuilder.java new file mode 100644 index 000000000..537aaaaba --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticBeanBuilder.java @@ -0,0 +1,85 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.lang.model.AnnotationAttribute; +import jakarta.enterprise.lang.model.AnnotationInfo; +import jakarta.enterprise.lang.model.declarations.ClassInfo; +import jakarta.enterprise.lang.model.types.Type; +import java.lang.annotation.Annotation; + +/** + * Instances are not reusable. For each synthetic bean, new instance + * must be created by {@link SyntheticComponents#addBean(Class)}. + * + * @param type of bean instances + */ +public interface SyntheticBeanBuilder { + // TODO should have the type parameter? (see also SyntheticObserverBuilder and SyntheticComponents) + + // can be called multiple times and is additive + // TODO methods to add multiple types at once? + SyntheticBeanBuilder type(Class type); + + SyntheticBeanBuilder type(ClassInfo type); + + SyntheticBeanBuilder type(Type type); + + // can be called multiple times and is additive + // TODO methods to add multiple qualifiers at once? + SyntheticBeanBuilder qualifier(Class qualifierAnnotation, AnnotationAttribute... attributes); + + SyntheticBeanBuilder qualifier(ClassInfo qualifierAnnotation, AnnotationAttribute... attributes); + + SyntheticBeanBuilder qualifier(AnnotationInfo qualifierAnnotation); + + SyntheticBeanBuilder qualifier(Annotation qualifierAnnotation); + + // if called multiple times, last call wins + // if not called, defaults to @Dependent + SyntheticBeanBuilder scope(Class scopeAnnotation); + + // if called with `true`, priority is automatically 0, unless `priority` is also called + // if called multiple times, last call wins + SyntheticBeanBuilder alternative(boolean isAlternative); + + // if called, alternative is automatically true + // if called multiple times, last call wins + SyntheticBeanBuilder priority(int priority); + + // EL name (equivalent to @Named), IIUC + // if called multiple times, last call wins + SyntheticBeanBuilder name(String name); + + // can be called multiple times and is additive + SyntheticBeanBuilder stereotype(Class stereotypeAnnotation); + + SyntheticBeanBuilder stereotype(ClassInfo stereotypeAnnotation); + + // params for creation and destruction functions + SyntheticBeanBuilder withParam(String key, boolean value); + + SyntheticBeanBuilder withParam(String key, boolean[] value); + + SyntheticBeanBuilder withParam(String key, int value); + + SyntheticBeanBuilder withParam(String key, int[] value); + + SyntheticBeanBuilder withParam(String key, long value); + + SyntheticBeanBuilder withParam(String key, long[] value); + + SyntheticBeanBuilder withParam(String key, double value); + + SyntheticBeanBuilder withParam(String key, double[] value); + + SyntheticBeanBuilder withParam(String key, String value); + + SyntheticBeanBuilder withParam(String key, String[] value); + + SyntheticBeanBuilder withParam(String key, Class value); + + SyntheticBeanBuilder withParam(String key, Class[] value); + + SyntheticBeanBuilder createWith(Class> creatorClass); + + SyntheticBeanBuilder disposeWith(Class> disposerClass); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticBeanCreator.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticBeanCreator.java new file mode 100644 index 000000000..be34545ba --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticBeanCreator.java @@ -0,0 +1,18 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import java.util.Map; +import jakarta.enterprise.context.spi.CreationalContext; +import jakarta.enterprise.inject.spi.InjectionPoint; + +/** + * Implementations must be {@code public} classes with a {@code public} zero-parameter constructor. + * + * @param type of created instances + */ +public interface SyntheticBeanCreator { + // TODO maybe a more high-level API that takes an Instance? + // compare with BeanConfigurator.createWith and BeanConfigurator.produceWith + + // injectionPoint is only set when the synthetic bean is @Dependent + T create(CreationalContext creationalContext, InjectionPoint injectionPoint, Map params); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticBeanDisposer.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticBeanDisposer.java new file mode 100644 index 000000000..f9f463323 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticBeanDisposer.java @@ -0,0 +1,16 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import java.util.Map; +import jakarta.enterprise.context.spi.CreationalContext; + +/** + * Implementations must be {@code public} classes with a {@code public} zero-parameter constructor. + * + * @param type of disposed instances + */ +public interface SyntheticBeanDisposer { + // TODO maybe a more high-level API that takes an Instance? + // compare with BeanConfigurator.destroyWith and BeanConfigurator.disposeWith + + void dispose(T instance, CreationalContext creationalContext, Map params); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticComponents.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticComponents.java new file mode 100644 index 000000000..7f79eae55 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticComponents.java @@ -0,0 +1,9 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +public interface SyntheticComponents { + // TODO type parameters? (see also SyntheticBeanBuilder and SyntheticObserverBuilder) + + SyntheticBeanBuilder addBean(Class implementationClass); + + SyntheticObserverBuilder addObserver(); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticObserver.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticObserver.java new file mode 100644 index 000000000..812a3d74d --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticObserver.java @@ -0,0 +1,13 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.inject.spi.EventContext; + +/** + * Implementations must be {@code public} classes with a {@code public} zero-parameter constructor. + * + * @param type of observed event instances + */ +public interface SyntheticObserver { + // TODO throws Exception? compare with ObserverMethodConfigurator.EventConsumer.accept + void observe(EventContext event); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticObserverBuilder.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticObserverBuilder.java new file mode 100644 index 000000000..14141fc98 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/SyntheticObserverBuilder.java @@ -0,0 +1,67 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.lang.model.AnnotationAttribute; +import jakarta.enterprise.lang.model.AnnotationInfo; +import jakarta.enterprise.lang.model.declarations.ClassInfo; +import jakarta.enterprise.lang.model.types.Type; +import java.lang.annotation.Annotation; +import jakarta.enterprise.event.Reception; +import jakarta.enterprise.event.TransactionPhase; + +/** + * Instances are not reusable. For each synthetic observer, new instance + * must be created by {@link SyntheticComponents#addObserver()}. + */ +public interface SyntheticObserverBuilder { + // TODO should add a type parameter? (see also SyntheticBeanBuilder and SyntheticComponents) + + /** + * Class that, hypothetically, "declares" the synthetic observer. + * If not called, the class declaring the extension which creates this synthetic observer is used. + *

+ * Used to identify an observer when multiple synthetic observers are otherwise identical. + * TODO can this have implementation consequences? e.g., must the class be added to the bean archive? + * + * @param declaringClass class that, hypothetically, "declares" the synthetic observer + * @return this {@code SyntheticObserverBuilder} + */ + // if called multiple times, last call wins + SyntheticObserverBuilder declaringClass(Class declaringClass); + + SyntheticObserverBuilder declaringClass(ClassInfo declaringClass); + + // if called multiple times, last call wins + // TODO methods to add multiple types at once? + SyntheticObserverBuilder type(Class type); + + SyntheticObserverBuilder type(ClassInfo type); + + SyntheticObserverBuilder type(Type type); + + // can be called multiple times and is additive + // TODO methods to add multiple qualifiers at once? + SyntheticObserverBuilder qualifier(Class qualifierAnnotation, AnnotationAttribute... attributes); + + SyntheticObserverBuilder qualifier(ClassInfo qualifierAnnotation, AnnotationAttribute... attributes); + + SyntheticObserverBuilder qualifier(AnnotationInfo qualifierAnnotation); + + SyntheticObserverBuilder qualifier(Annotation qualifierAnnotation); + + // if called multiple times, last call wins + SyntheticObserverBuilder priority(int priority); + + // if called multiple times, last call wins + SyntheticObserverBuilder async(boolean isAsync); + + // if called multiple times, last call wins + // TODO this probably doesn't make sense for synthetic observers? but Portable Extensions have it? + SyntheticObserverBuilder reception(Reception reception); + + // if called multiple times, last call wins + SyntheticObserverBuilder transactionPhase(TransactionPhase transactionPhase); + + // TODO params? ArC doesn't have them for observers, only for beans, do they make sense here? + + SyntheticObserverBuilder observeWith(Class> syntheticObserverClass); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Types.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Types.java new file mode 100644 index 000000000..54ca1ca5d --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Types.java @@ -0,0 +1,61 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import jakarta.enterprise.lang.model.declarations.ClassInfo; +import jakarta.enterprise.lang.model.types.PrimitiveType; +import jakarta.enterprise.lang.model.types.Type; + +// TODO move to jakarta.enterprise.lang.model.types? probably not, it doesn't model any part of Java language +public interface Types { + /** + * Returns a type from given class literal. + * For example: + *

    + *
  • {@code of(void.class)}: same as {@code ofVoid()}
  • + *
  • {@code of(int.class)}: same as {@code ofPrimitive(PrimitiveKind.INT)}
  • + *
  • {@code of(String.class)}: same as {@code ofClass(... ClassInfo for String ...)}
  • + *
  • {@code of(int[].class)}: same as {@code ofArray(ofPrimitive(PrimitiveKind.INT), 1)}
  • + *
  • {@code of(String[][].class)}: same as {@code ofArray(ofClass(... ClassInfo for String ...), 2)}
  • + *
+ * + * @param clazz class literal + * @return {@link Type} object representing the given class literal + */ + Type of(Class clazz); + + Type ofVoid(); + + Type ofPrimitive(PrimitiveType.PrimitiveKind kind); + + Type ofClass(ClassInfo clazz); + + Type ofArray(Type componentType, int dimensions); + + Type parameterized(Class parameterizedType, Class... typeArguments); + + Type parameterized(Class parameterizedType, Type... typeArguments); + + Type parameterized(Type parameterizedType, Type... typeArguments); + + /** + * Equivalent of {@code ? extends upperBound}. + * + * @param upperBound upper bound type + * @return {@link Type} object representing a wildcard type with given upper bound + */ + Type wildcardWithUpperBound(Type upperBound); + + /** + * Equivalent of {@code ? super lowerBound}. + * + * @param lowerBound lower bound type + * @return {@link Type} object representing a wildcard type with given lower bound + */ + Type wildcardWithLowerBound(Type lowerBound); + + /** + * Equivalent of {@code ?}. + * + * @return {@link Type} object representing an unbounded wildcard type + */ + Type wildcardUnbounded(); +} diff --git a/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Validation.java b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Validation.java new file mode 100644 index 000000000..8361f28d1 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/inject/build/compatible/spi/Validation.java @@ -0,0 +1,25 @@ +package jakarta.enterprise.inject.build.compatible.spi; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 5th phase of CDI Lite extension processing. + * Allows custom validation. + *

+ * Methods annotated {@code @Validation} can define parameters of these types: + *

    + *
  • {@link AppArchive}: to find declarations in the application
  • + *
  • {@link AppDeployment}: to find beans and observers in the application
  • + *
  • {@link Messages}: to produce log messages and validation errors
  • + *
+ * If you need to create instances of {@link jakarta.enterprise.lang.model.types.Type Type}, you can also declare + * a parameter of type {@link Types}. It provides factory methods for the void type, primitive types, class types, + * array types, parameterized types and wildcard types. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Validation { +} diff --git a/api/src/main/java/jakarta/enterprise/lang/model/AnnotationAttribute.java b/api/src/main/java/jakarta/enterprise/lang/model/AnnotationAttribute.java new file mode 100644 index 000000000..99c84f354 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/lang/model/AnnotationAttribute.java @@ -0,0 +1,8 @@ +package jakarta.enterprise.lang.model; + +// TODO "attribute" is a colloquial expression, perhaps use something closer to the JLS? AnnotationMember? +public interface AnnotationAttribute { + String name(); + + AnnotationAttributeValue value(); +} diff --git a/api/src/main/java/jakarta/enterprise/lang/model/AnnotationAttributeValue.java b/api/src/main/java/jakarta/enterprise/lang/model/AnnotationAttributeValue.java new file mode 100644 index 000000000..d404c29f2 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/lang/model/AnnotationAttributeValue.java @@ -0,0 +1,111 @@ +package jakarta.enterprise.lang.model; + +import jakarta.enterprise.lang.model.declarations.ClassInfo; +import jakarta.enterprise.lang.model.types.Type; +import java.util.List; + +// TODO "attribute" is a colloquial expression, perhaps use something closer to the JLS? AnnotationMember? +public interface AnnotationAttributeValue { + // TODO is there a better API for this than the is*/as* style? + + enum Kind { + BOOLEAN, + BYTE, + SHORT, + INT, + LONG, + FLOAT, + DOUBLE, + CHAR, + STRING, + ENUM, + CLASS, + ARRAY, + NESTED_ANNOTATION, + } + + Kind kind(); + + default boolean isBoolean() { + return kind() == Kind.BOOLEAN; + } + + default boolean isByte() { + return kind() == Kind.BYTE; + } + + default boolean isShort() { + return kind() == Kind.SHORT; + } + + default boolean isInt() { + return kind() == Kind.INT; + } + + default boolean isLong() { + return kind() == Kind.LONG; + } + + default boolean isFloat() { + return kind() == Kind.FLOAT; + } + + default boolean isDouble() { + return kind() == Kind.DOUBLE; + } + + default boolean isChar() { + return kind() == Kind.CHAR; + } + + default boolean isString() { + return kind() == Kind.STRING; + } + + default boolean isEnum() { + return kind() == Kind.ENUM; + } + + default boolean isClass() { + return kind() == Kind.CLASS; + } + + default boolean isArray() { + return kind() == Kind.ARRAY; + } + + default boolean isNestedAnnotation() { + return kind() == Kind.NESTED_ANNOTATION; + } + + boolean asBoolean(); + + byte asByte(); + + short asShort(); + + int asInt(); + + long asLong(); + + float asFloat(); + + double asDouble(); + + char asChar(); + + String asString(); + + // TODO should this be present? + > E asEnum(); + + ClassInfo asEnumClass(); + + String asEnumValue(); + + Type asClass(); // can be a VoidType, PrimitiveType or ClassType + + List asArray(); + + AnnotationInfo asNestedAnnotation(); +} diff --git a/api/src/main/java/jakarta/enterprise/lang/model/AnnotationInfo.java b/api/src/main/java/jakarta/enterprise/lang/model/AnnotationInfo.java new file mode 100644 index 000000000..b65ab9de4 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/lang/model/AnnotationInfo.java @@ -0,0 +1,76 @@ +package jakarta.enterprise.lang.model; + +import jakarta.enterprise.lang.model.declarations.ClassInfo; +import java.lang.annotation.Repeatable; +import java.util.Collection; + +public interface AnnotationInfo { + /** + * Target of this annotation. + * That is, the declaration, the type parameter or the type use on which this annotation is present. + * TODO what if this annotation is a nested annotation? + * TODO what if this annotation doesn't have a known target (e.g. qualifier of a synthetic bean)? + * + * @return target of this annotation + */ + AnnotationTarget target(); + + /** + * Declaration of this annotation's type. + * + * @return declaration of this annotation + */ + ClassInfo declaration(); + + /** + * Fully qualified name of this annotation. + * Equivalent to {@code declaration().name()}. + * + * @return fully qualified name of this annotation + */ + default String name() { + return declaration().name(); + } + + /** + * Returns whether this annotation is repeatable. In other words, returns whether + * this annotation's type is meta-annotated with {@code @Repeatable}. + * + * @return whether this annotation is repeatable + */ + default boolean isRepeatable() { + return declaration().hasAnnotation(Repeatable.class); + } + + /** + * Whether this annotation has an attribute with given {@code name}. + * + * @param name attribute name + * @return whether this annotation has an attribute with given {@code name} + */ + boolean hasAttribute(String name); + + /** + * Value of this annotation's attribute with given {@code name}. + * TODO what if it doesn't exist? null, exception, or change return type to Optional + * + * @param name attribute name + * @return value of this annotation's attribute with given {@code name} + */ + AnnotationAttributeValue attribute(String name); + + default boolean hasValue() { + return hasAttribute("value"); + } + + default AnnotationAttributeValue value() { + return attribute("value"); + } + + /** + * All attributes of this annotation. + * + * @return all attributes of this annotation + */ + Collection attributes(); +} diff --git a/api/src/main/java/jakarta/enterprise/lang/model/AnnotationTarget.java b/api/src/main/java/jakarta/enterprise/lang/model/AnnotationTarget.java new file mode 100644 index 000000000..74923aec2 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/lang/model/AnnotationTarget.java @@ -0,0 +1,43 @@ +package jakarta.enterprise.lang.model; + +import jakarta.enterprise.lang.model.declarations.DeclarationInfo; +import jakarta.enterprise.lang.model.types.Type; +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.function.Predicate; + +/** + * Annotation target is anything that can be annotated. + * That is: + * + *
    + *
  • a declaration, such as a class, method, field, etc.
  • + *
  • a type parameter, occuring in class declarations and method declarations
  • + *
  • a type use, such as a type of method parameter, a type of field, a type argument, etc.
  • + *
+ */ +public interface AnnotationTarget { + // TODO specify equals/hashCode (for the entire .lang.model hierarchy) + // TODO settle on using Optional everywhere, or allowing null everywhere; it's a mix right now + + boolean isDeclaration(); + + boolean isType(); + + DeclarationInfo asDeclaration(); + + Type asType(); + + boolean hasAnnotation(Class annotationType); + + boolean hasAnnotation(Predicate predicate); + + // TODO what if missing? + AnnotationInfo annotation(Class annotationType); + + Collection repeatableAnnotation(Class annotationType); + + Collection annotations(Predicate predicate); + + Collection annotations(); +} diff --git a/api/src/main/java/jakarta/enterprise/lang/model/declarations/ClassInfo.java b/api/src/main/java/jakarta/enterprise/lang/model/declarations/ClassInfo.java new file mode 100644 index 000000000..0c8acd690 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/lang/model/declarations/ClassInfo.java @@ -0,0 +1,75 @@ +package jakarta.enterprise.lang.model.declarations; + +import jakarta.enterprise.lang.model.types.Type; +import jakarta.enterprise.lang.model.types.TypeVariable; +import java.util.Collection; +import java.util.List; + +/** + * @param the inspected class + */ +public interface ClassInfo extends DeclarationInfo { + // TODO remove the type parameter? + // TODO nested classes don't provide access to enclosing class, but that might be OK for our purposes? + + String name(); + + String simpleName(); + + PackageInfo packageInfo(); + + // empty if class is not parameterized + List typeParameters(); + + // null if this class doesn't have a superclass (e.g. is Object or an interface) + // TODO or Optional? + Type superClass(); + + // null if this class doesn't have a superclass (e.g. is Object or an interface) + // TODO or Optional? + ClassInfo superClassDeclaration(); + + // empty if the class has no superinterfaces + List superInterfaces(); + + // empty if the class has no superinterfaces + List> superInterfacesDeclarations(); + + // TODO better name? + boolean isPlainClass(); + + boolean isInterface(); + + boolean isEnum(); + + boolean isAnnotation(); + + boolean isAbstract(); + + boolean isFinal(); + + int modifiers(); + + // only constructors declared by this class, not inherited ones + // no [static] initializers + Collection> constructors(); + + // only methods declared by this class, not inherited ones + // no constructors nor [static] initializers + Collection> methods(); + + // only fields declared by this class, not inherited ones + Collection> fields(); + + // --- + + @Override + default Kind kind() { + return Kind.CLASS; + } + + @Override + default ClassInfo asClass() { + return this; + } +} diff --git a/api/src/main/java/jakarta/enterprise/lang/model/declarations/DeclarationInfo.java b/api/src/main/java/jakarta/enterprise/lang/model/declarations/DeclarationInfo.java new file mode 100644 index 000000000..7a1b77739 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/lang/model/declarations/DeclarationInfo.java @@ -0,0 +1,91 @@ +package jakarta.enterprise.lang.model.declarations; + +import jakarta.enterprise.lang.model.AnnotationTarget; +import jakarta.enterprise.lang.model.types.Type; + +/** + * Declarations are: + * + *
    + *
  • a package, as declared in {@code package-info.java}
  • + *
  • a class, including interfaces, enums, and annotations
  • + *
  • a field
  • + *
  • a method, including constructors
  • + *
  • a method parameter
  • + *
+ */ +public interface DeclarationInfo extends AnnotationTarget { + // TODO reevaluate the is*/as*/kind() approach (everywhere!); maybe type checks and casts are better, maybe + // something completely different is even better + + @Override + default boolean isDeclaration() { + return true; + } + + @Override + default boolean isType() { + return false; + } + + @Override + default DeclarationInfo asDeclaration() { + return this; + } + + @Override + default Type asType() { + throw new IllegalStateException("Not a type"); + } + + enum Kind { + /** Packages can be annotated in {@code package-info.java}. */ + PACKAGE, + CLASS, + METHOD, + PARAMETER, + FIELD, + } + + Kind kind(); + + default boolean isPackage() { + return kind() == Kind.PACKAGE; + } + + default boolean isClass() { + return kind() == Kind.CLASS; + } + + default boolean isMethod() { + return kind() == Kind.METHOD; + } + + default boolean isParameter() { + return kind() == Kind.PARAMETER; + } + + default boolean isField() { + return kind() == Kind.FIELD; + } + + default PackageInfo asPackage() { + throw new IllegalStateException("Not a package"); + } + + default ClassInfo asClass() { + throw new IllegalStateException("Not a class"); + } + + default MethodInfo asMethod() { + throw new IllegalStateException("Not a method"); + } + + default ParameterInfo asParameter() { + throw new IllegalStateException("Not a parameter"); + } + + default FieldInfo asField() { + throw new IllegalStateException("Not a field"); + } +} diff --git a/api/src/main/java/jakarta/enterprise/lang/model/declarations/FieldInfo.java b/api/src/main/java/jakarta/enterprise/lang/model/declarations/FieldInfo.java new file mode 100644 index 000000000..afbeaf069 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/lang/model/declarations/FieldInfo.java @@ -0,0 +1,34 @@ +package jakarta.enterprise.lang.model.declarations; + +import jakarta.enterprise.lang.model.types.Type; + +/** + * @param type of whomever declares the inspected field + */ +public interface FieldInfo extends DeclarationInfo { + // TODO remove the type parameter? + + String name(); + + Type type(); + + boolean isStatic(); + + boolean isFinal(); + + int modifiers(); + + ClassInfo declaringClass(); + + // --- + + @Override + default Kind kind() { + return Kind.FIELD; + } + + @Override + default FieldInfo asField() { + return this; + } +} diff --git a/api/src/main/java/jakarta/enterprise/lang/model/declarations/MethodInfo.java b/api/src/main/java/jakarta/enterprise/lang/model/declarations/MethodInfo.java new file mode 100644 index 000000000..2f446fe80 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/lang/model/declarations/MethodInfo.java @@ -0,0 +1,53 @@ +package jakarta.enterprise.lang.model.declarations; + +import jakarta.enterprise.lang.model.types.Type; +import jakarta.enterprise.lang.model.types.TypeVariable; +import java.util.List; +import java.util.Optional; + +/** + * @param type of whomever declares the inspected method or constructor + */ +public interface MethodInfo extends DeclarationInfo { + // TODO remove the type parameter? + // TODO split MethodInfo into MethodInfo/ConstructorInfo? a lot of methods here don't make sense for constructors, + // plus existing APIs (Core Reflection, CDI Portable Extensions) also make this distinction + + // TODO what about constructors? + String name(); + + List parameters(); + + // TODO what about constructors? + Type returnType(); + + // TODO return Optional and only return non-empty if receiver parameter is declared, + // or return Type and always return a receiver type, even if not declared (and hence not annotated)? + Optional receiverType(); + + List throwsTypes(); + + List typeParameters(); + + boolean isStatic(); + + boolean isAbstract(); + + boolean isFinal(); + + int modifiers(); + + ClassInfo declaringClass(); + + // --- + + @Override + default Kind kind() { + return Kind.METHOD; + } + + @Override + default MethodInfo asMethod() { + return this; + } +} diff --git a/api/src/main/java/jakarta/enterprise/lang/model/declarations/PackageInfo.java b/api/src/main/java/jakarta/enterprise/lang/model/declarations/PackageInfo.java new file mode 100644 index 000000000..0e54f5ed2 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/lang/model/declarations/PackageInfo.java @@ -0,0 +1,18 @@ +package jakarta.enterprise.lang.model.declarations; + +// TODO is this useful? perhaps `ClassInfo.packageName` returning a `String` would be enough? +public interface PackageInfo extends DeclarationInfo { + String name(); + + // --- + + @Override + default Kind kind() { + return Kind.PACKAGE; + } + + @Override + default PackageInfo asPackage() { + return this; + } +} diff --git a/api/src/main/java/jakarta/enterprise/lang/model/declarations/ParameterInfo.java b/api/src/main/java/jakarta/enterprise/lang/model/declarations/ParameterInfo.java new file mode 100644 index 000000000..c72260833 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/lang/model/declarations/ParameterInfo.java @@ -0,0 +1,23 @@ +package jakarta.enterprise.lang.model.declarations; + +import jakarta.enterprise.lang.model.types.Type; + +public interface ParameterInfo extends DeclarationInfo { + String name(); // TODO doesn't have to be known + + Type type(); + + MethodInfo declaringMethod(); + + // --- + + @Override + default Kind kind() { + return Kind.PARAMETER; + } + + @Override + default ParameterInfo asParameter() { + return this; + } +} diff --git a/api/src/main/java/jakarta/enterprise/lang/model/types/ArrayType.java b/api/src/main/java/jakarta/enterprise/lang/model/types/ArrayType.java new file mode 100644 index 000000000..79aa89eac --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/lang/model/types/ArrayType.java @@ -0,0 +1,23 @@ +package jakarta.enterprise.lang.model.types; + +public interface ArrayType extends Type { + // TODO this model (the ultimate component type + number of dimensions) might not be the best; + // specifically, it doesn't allow access to type-use annotations on individual component types + // (for example: @C int @A [] @B [] f;) + + int dimensions(); + + Type componentType(); + + // --- + + @Override + default Kind kind() { + return Kind.ARRAY; + } + + @Override + default ArrayType asArray() { + return this; + } +} diff --git a/api/src/main/java/jakarta/enterprise/lang/model/types/ClassType.java b/api/src/main/java/jakarta/enterprise/lang/model/types/ClassType.java new file mode 100644 index 000000000..6f208ebbe --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/lang/model/types/ClassType.java @@ -0,0 +1,19 @@ +package jakarta.enterprise.lang.model.types; + +import jakarta.enterprise.lang.model.declarations.ClassInfo; + +public interface ClassType extends Type { + ClassInfo declaration(); + + // --- + + @Override + default Kind kind() { + return Kind.CLASS; + } + + @Override + default ClassType asClass() { + return this; + } +} diff --git a/api/src/main/java/jakarta/enterprise/lang/model/types/ParameterizedType.java b/api/src/main/java/jakarta/enterprise/lang/model/types/ParameterizedType.java new file mode 100644 index 000000000..5bab1f458 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/lang/model/types/ParameterizedType.java @@ -0,0 +1,22 @@ +package jakarta.enterprise.lang.model.types; + +import jakarta.enterprise.lang.model.declarations.ClassInfo; +import java.util.List; + +public interface ParameterizedType extends Type { + ClassInfo declaration(); + + List typeArguments(); + + // --- + + @Override + default Kind kind() { + return Kind.PARAMETERIZED_TYPE; + } + + @Override + default ParameterizedType asParameterizedType() { + return this; + } +} diff --git a/api/src/main/java/jakarta/enterprise/lang/model/types/PrimitiveType.java b/api/src/main/java/jakarta/enterprise/lang/model/types/PrimitiveType.java new file mode 100644 index 000000000..05a92ace5 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/lang/model/types/PrimitiveType.java @@ -0,0 +1,64 @@ +package jakarta.enterprise.lang.model.types; + +public interface PrimitiveType extends Type { + // TODO Kind vs. PrimitiveKind? + + enum PrimitiveKind { + BOOLEAN, + BYTE, + SHORT, + INT, + LONG, + FLOAT, + DOUBLE, + CHAR, + } + + String name(); + + PrimitiveKind primitiveKind(); + + default boolean isBoolean() { + return primitiveKind() == PrimitiveKind.BOOLEAN; + } + + default boolean isByte() { + return primitiveKind() == PrimitiveKind.BYTE; + } + + default boolean isShort() { + return primitiveKind() == PrimitiveKind.SHORT; + } + + default boolean isInt() { + return primitiveKind() == PrimitiveKind.INT; + } + + default boolean isLong() { + return primitiveKind() == PrimitiveKind.LONG; + } + + default boolean isFloat() { + return primitiveKind() == PrimitiveKind.FLOAT; + } + + default boolean isDouble() { + return primitiveKind() == PrimitiveKind.DOUBLE; + } + + default boolean isChar() { + return primitiveKind() == PrimitiveKind.CHAR; + } + + // --- + + @Override + default Kind kind() { + return Kind.PRIMITIVE; + } + + @Override + default PrimitiveType asPrimitive() { + return this; + } +} diff --git a/api/src/main/java/jakarta/enterprise/lang/model/types/Type.java b/api/src/main/java/jakarta/enterprise/lang/model/types/Type.java new file mode 100644 index 000000000..56c47a855 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/lang/model/types/Type.java @@ -0,0 +1,117 @@ +package jakarta.enterprise.lang.model.types; + +import jakarta.enterprise.lang.model.AnnotationTarget; +import jakarta.enterprise.lang.model.declarations.DeclarationInfo; + +/** + * Types are: + * + *
    + *
  • a void type
  • + *
  • a primitive type, such as {@code int}
  • + *
  • a class type, such as {@code String}
  • + *
  • an array type, such as {@code int[]} or {@code String[][]}
  • + *
  • a parameterized type, such as {@code List}
  • + *
  • a type variable, such as {@code T} when used in a class that declares a type parameter {@code T}
  • + *
  • a wildcard type, such as the type argument declared in {@code List}
  • + *
+ */ +public interface Type extends AnnotationTarget { + @Override + default boolean isDeclaration() { + return false; + } + + @Override + default boolean isType() { + return true; + } + + @Override + default DeclarationInfo asDeclaration() { + throw new IllegalStateException("Not a declaration"); + } + + @Override + default Type asType() { + return this; + } + + enum Kind { + /** E.g. when method returns {@code void}. */ + VOID, + /** E.g. when method returns {@code int}. */ + PRIMITIVE, + /** E.g. when method returns {@code String}. */ + CLASS, + /** E.g. when method returns {@code int[]} or {@code String[][]}. */ + ARRAY, + /** E.g. when method returns {@code List}. */ + PARAMETERIZED_TYPE, + /** E.g. when method returns {@code T} and {@code T} is a type parameter of the declaring class. */ + TYPE_VARIABLE, + /** + * E.g. when method returns {@code List}. On the first level, we have a {@code PARAMETERIZED_TYPE}, + * but on the second level, the first (and only) type argument is a {@code WILDCARD_TYPE}. + */ + WILDCARD_TYPE, + } + + Kind kind(); + + default boolean isVoid() { + return kind() == Kind.VOID; + } + + default boolean isPrimitive() { + return kind() == Kind.PRIMITIVE; + } + + default boolean isClass() { + return kind() == Kind.CLASS; + } + + default boolean isArray() { + return kind() == Kind.ARRAY; + } + + default boolean isParameterizedType() { + return kind() == Kind.PARAMETERIZED_TYPE; + } + + default boolean isTypeVariable() { + return kind() == Kind.TYPE_VARIABLE; + } + + default boolean isWildcardType() { + return kind() == Kind.WILDCARD_TYPE; + } + + default VoidType asVoid() { + throw new IllegalStateException("Not a void"); + } + + default PrimitiveType asPrimitive() { + throw new IllegalStateException("Not a primitive"); + } + + default ClassType asClass() { + throw new IllegalStateException("Not a class"); + } + + default ArrayType asArray() { + throw new IllegalStateException("Not an array"); + } + + default ParameterizedType asParameterizedType() { + throw new IllegalStateException("Not a parameterized type"); + } + + default TypeVariable asTypeVariable() { + throw new IllegalStateException("Not a type variable"); + } + + default WildcardType asWildcardType() { + throw new IllegalStateException("Not a wildcard type"); + } +} diff --git a/api/src/main/java/jakarta/enterprise/lang/model/types/TypeVariable.java b/api/src/main/java/jakarta/enterprise/lang/model/types/TypeVariable.java new file mode 100644 index 000000000..c929e6392 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/lang/model/types/TypeVariable.java @@ -0,0 +1,21 @@ +package jakarta.enterprise.lang.model.types; + +import java.util.List; + +public interface TypeVariable extends Type { + String name(); + + List bounds(); + + // --- + + @Override + default Kind kind() { + return Kind.TYPE_VARIABLE; + } + + @Override + default TypeVariable asTypeVariable() { + return this; + } +} diff --git a/api/src/main/java/jakarta/enterprise/lang/model/types/VoidType.java b/api/src/main/java/jakarta/enterprise/lang/model/types/VoidType.java new file mode 100644 index 000000000..14f960f90 --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/lang/model/types/VoidType.java @@ -0,0 +1,18 @@ +package jakarta.enterprise.lang.model.types; + +public interface VoidType extends Type { + // TODO is name() truly needed? + String name(); + + // --- + + @Override + default Kind kind() { + return Kind.VOID; + } + + @Override + default VoidType asVoid() { + return this; + } +} diff --git a/api/src/main/java/jakarta/enterprise/lang/model/types/WildcardType.java b/api/src/main/java/jakarta/enterprise/lang/model/types/WildcardType.java new file mode 100644 index 000000000..7e4a59d5f --- /dev/null +++ b/api/src/main/java/jakarta/enterprise/lang/model/types/WildcardType.java @@ -0,0 +1,39 @@ +package jakarta.enterprise.lang.model.types; + +import java.util.Optional; + +/** + * In case the wildcard type is unbounded (i.e., declared as {@code ?}), both {@code upperBound} + * and {@code lowerBound} are empty. + */ +public interface WildcardType extends Type { + /** + * Present when the wildcard type has a form of {@code ? extends Number}. + * The upper bound in this case is {@code Number}. + * Otherwise empty. + * + * @return upper bound of this wildcard type, if present + */ + Optional upperBound(); + + /** + * Present when the wildcard type has a form of {@code ? super Number}. + * The lower bound in this case is {@code Number}. + * Otherwise empty. + * + * @return lower bound of this wildcard type, if present + */ + Optional lowerBound(); + + // --- + + @Override + default Kind kind() { + return Kind.WILDCARD_TYPE; + } + + @Override + default WildcardType asWildcardType() { + return this; + } +} diff --git a/api/src/main/javadoc/overview.html b/api/src/main/javadoc/overview.html index 5d2bd3e3f..35ee3a4bc 100644 --- a/api/src/main/javadoc/overview.html +++ b/api/src/main/javadoc/overview.html @@ -6,6 +6,12 @@ complementary services that help improve the structure of application code.

+

Core CDI specification is split into Lite and Full. Lite includes +most of the features and is tailored for runtimes that don't intend +to implement the entire Jakarta EE platform. Full includes features +that are not deemed necessary for all CDI implementations. Jakarta EE +implementations are required to support Full.

+
  • A well-defined lifecycle for stateful {@linkplain jakarta.enterprise.inject beans} bound to well-defined @@ -17,24 +23,25 @@ dependencies at either {@linkplain jakarta.inject.Qualifier development} or {@linkplain jakarta.enterprise.inject.Alternative deployment} time, without verbose configuration
  • -
  • Support for Java EE modularity and the Java EE component -architecture—the modular structure of a Java EE application is -taken into account when resolving dependencies between Java EE +
  • Support for Jakarta EE modularity and the Jakarta EE component +architecture—the modular structure of a Jakarta EE application is +taken into account when resolving dependencies between Jakarta EE components
  • {@linkplain jakarta.enterprise.inject Integration} with the Unified Expression Language, allowing any bean to be used directly -within a JSF or JSP page
  • +within a JSF or JSP page (only in Full)
  • The ability to {@linkplain jakarta.decorator decorate} injected -beans
  • +beans (only in Full)
  • The ability to associate {@code jakarta.interceptor.Interceptor} with beans via typesafe {@linkplain jakarta.enterprise.inject interceptor bindings}
  • An {@linkplain jakarta.enterprise.event event} notification model
  • A web {@linkplain jakarta.enterprise.context.ConversationScoped conversation context} in addition to the three standard - web contexts defined by the Java Servlets specification
  • -
  • An {@linkplain jakarta.enterprise.inject.spi SPI} allowing portable -extensions to integrate cleanly with the container
  • + web contexts defined by the Java Servlets specification (only in Full) +
  • A {@linkplain jakarta.enterprise.inject.build.compatible.spi build-compatible SPI} +and a {@linkplain jakarta.enterprise.inject.spi runtime SPI} allowing portable +extensions to integrate cleanly with the container (runtime SPI only in Full)

CDI allows objects to be bound to lifecycle contexts, to be @@ -45,7 +52,7 @@ {@linkplain jakarta.enterprise.inject Various kinds} of objects are injectable, including EJB 3 session beans, managed beans, {@linkplain jakarta.enterprise.inject.Produces producer methods} and - Java EE resources. We refer to these objects in general terms as beans + Jakarta EE resources. We refer to these objects in general terms as beans and to instances of beans that are bound to contexts as contextual instances.