Skip to content

Commit

Permalink
4.x: Helidon inject collateral 5 (helidon-io#9244)
Browse files Browse the repository at this point in the history
* TypeInfo and TypedElementInfo now have originatingElementValue() that does not return an optional (and replaced usages)
* Annotation now has metaAnnotation information (inherited annotations on the annotation or an annotation it is annotated with)
* All annotation values are validated, and classes modified to TypeName, and enums modified to EnumValue
* New modifiers added.
* Added signature of TypedElementInfo to compare if methods or constructors have the same signature
* Added findInHierarchy to TypeInfo, so we can check if a type extends another type, or implements an interface
* A few small fixes (typos, almost valid regexp in tests etc.)

Signed-off-by: Tomas Langer <tomas.langer@oracle.com>
  • Loading branch information
tomas-langer authored Sep 12, 2024
1 parent 2397b81 commit 5134b93
Show file tree
Hide file tree
Showing 48 changed files with 2,023 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ private void process(RoundContext roundContext, TypeInfo blueprint) {
roundContext.addGeneratedType(prototype,
classModel,
blueprint.typeName(),
blueprint.originatingElement().orElse(blueprint.typeName()));
blueprint.originatingElementValue());

if (typeContext.typeInfo().supportsServiceRegistry() && typeContext.propertyData().hasProvider()) {
for (PrototypeProperty property : typeContext.propertyData().properties()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public interface Annotated {
* <p>
* The returned list does not contain {@link #annotations()}. If a meta-annotation is present on multiple
* annotations, it will be returned once for each such declaration.
* <p>
* This method does not return annotations on super types or interfaces!
*
* @return list of all meta annotations of this element
*/
Expand Down Expand Up @@ -85,11 +87,10 @@ default Optional<Annotation> findAnnotation(TypeName annotationType) {
* @see #findAnnotation(TypeName)
*/
default Annotation annotation(TypeName annotationType) {
return findAnnotation(annotationType).orElseThrow(() -> new NoSuchElementException("Annotation " + annotationType + " "
+ "is not present. Guard "
+ "with hasAnnotation(), or "
+ "use findAnnotation() "
+ "instead"));
return findAnnotation(annotationType)
.orElseThrow(() -> new NoSuchElementException("Annotation " + annotationType + " is not present. "
+ "Guard with hasAnnotation(), "
+ "or use findAnnotation() instead"));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ interface AnnotationBlueprint {
@Option.Singular
Map<String, Object> values();

/**
* A list of inherited annotations (from the whole hierarchy).
*
* @return list of all annotations declared on the annotation type, or inherited from them
*/
@Option.Redundant
@Option.Singular
List<Annotation> metaAnnotations();

/**
* The value property.
*
Expand Down Expand Up @@ -653,4 +662,19 @@ default <T extends Enum<T>> Optional<List<T>> enumValues(String property, Class<
return AnnotationSupport.asEnums(typeName(), values(), property, type);
}

/**
* Check if {@link io.helidon.common.types.Annotation#metaAnnotations()} contains an annotation of the provided type.
* <p>
* Note: we ignore {@link java.lang.annotation.Target}, {@link java.lang.annotation.Inherited},
* {@link java.lang.annotation.Documented}, and {@link java.lang.annotation.Retention}.
*
* @param annotationType type of annotation
* @return {@code true} if the annotation is declared on this annotation, or is inherited from a declared annotation
*/
default boolean hasMetaAnnotation(TypeName annotationType) {
return metaAnnotations()
.stream()
.map(Annotation::typeName)
.anyMatch(annotationType::equals);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,14 @@ private static String asString(TypeName typeName, String property, Object value)
return str;
}

if (value instanceof TypeName tn) {
return tn.fqName();
}

if (value instanceof EnumValue ev) {
return ev.name();
}

if (value instanceof List<?>) {
throw new IllegalArgumentException(typeName.fqName() + " property " + property
+ " is a list, cannot be converted to String");
Expand Down Expand Up @@ -619,22 +627,30 @@ private static Class<?> asClass(TypeName typeName, String property, Object value
if (value instanceof Class<?> theClass) {
return theClass;
}
if (value instanceof String str) {
try {
return Class.forName(str);
} catch (ClassNotFoundException e) {

String className = switch (value) {
case TypeName tn -> tn.name();
case String str -> str;
default -> {
throw new IllegalArgumentException(typeName.fqName() + " property " + property
+ " of type String and value \"" + str + "\""
+ " of type " + value.getClass().getName()
+ " cannot be converted to Class");
}
}
};

throw new IllegalArgumentException(typeName.fqName() + " property " + property
+ " of type " + value.getClass().getName()
+ " cannot be converted to Class");
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(typeName.fqName() + " property " + property
+ " of type String and value \"" + className + "\""
+ " cannot be converted to Class");
}
}

private static TypeName asTypeName(TypeName typeName, String property, Object value) {
if (value instanceof TypeName tn) {
return tn;
}
if (value instanceof Class<?> theClass) {
return TypeName.create(theClass);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates.
*
* 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 io.helidon.common.types;

import java.util.List;

/**
* Signature of a {@link io.helidon.common.types.TypedElementInfo}.
* <p>
* The {@link io.helidon.common.types.TypedElementInfo#signature()} is intended to compare
* fields, methods, and constructors across type hierarchy - for example when looking for a method
* that we override.
* <p>
* The following information is used for equals and hash-code:
* <ul>
* <li>Field: field name</li>
* <li>Constructor: parameter types</li>
* <li>Method: method name, parameter types</li>
* <li>Parameter: this signature is not useful, as we cannot depend on parameter names</li>
* </ul>
*
* The signature has well-defined {@code hashCode} and {@code equals} methods,
* so it can be safely used as a key in a {@link java.util.Map}.
* <p>
* This interface is sealed, an instance can only be obtained
* from {@link io.helidon.common.types.TypedElementInfo#signature()}.
*
* @see #text()
*/
public sealed interface ElementSignature permits ElementSignatures.FieldSignature,
ElementSignatures.MethodSignature,
ElementSignatures.ParameterSignature,
ElementSignatures.NoSignature {
/**
* Type of the element. Resolves as follows:
* <ul>
* <li>Field: type of the field</li>
* <li>Constructor: void</li>
* <li>Method: method return type</li>
* <li>Parameter: parameter type</li>
* </ul>
*
* @return type of this element, never used for equals or hashCode
*/
TypeName type();

/**
* Name of the element. For constructor, this always returns {@code <init>},
* for parameters, this method may return the real parameter name or an index
* parameter name depending on the source of the information (during annotation processing,
* this would be the actual parameter name, when classpath scanning, this would be something like
* {@code param0}.
*
* @return name of this element
*/
String name();

/**
* Types of parameters if this represents a method or a constructor,
* empty {@link java.util.List} otherwise.
*
* @return parameter types
*/
List<TypeName> parameterTypes();

/**
* A text representation of this signature.
*
* <ul>
* <li>Field: field name (such as {@code myNiceField}</li>
* <li>Constructor: comma separated parameter types (no generics) in parentheses (such as
* {@code (java.lang.String,java.util.List)})</li>
* <li>Method: method name, parameter types (no generics) in parentheses (such as
* {@code methodName(java.lang.String,java.util.List)}</li>
* <li>Parameter: parameter name (such as {@code myParameter} or {@code param0} - not very useful, as parameter names
* are not carried over to compiled code in Java</li>
* </ul>
*
* @return text representation
*/
String text();
}
Loading

0 comments on commit 5134b93

Please sign in to comment.