Skip to content

Commit

Permalink
Fix for issue helidon-io#5608
Browse files Browse the repository at this point in the history
  • Loading branch information
trentjeff committed Dec 16, 2022
1 parent f338512 commit 1a4c6b7
Show file tree
Hide file tree
Showing 9 changed files with 381 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class DefaultTypeInfo implements TypeInfo {
private final String typeKind;
private final List<AnnotationAndValue> annotations;
private final List<TypedElementName> elementInfo;
private final List<TypedElementName> otherElementInfo;
private final TypeInfo superTypeInfo;

/**
Expand All @@ -49,6 +50,7 @@ protected DefaultTypeInfo(Builder b) {
this.typeKind = b.typeKind;
this.annotations = Collections.unmodifiableList(new LinkedList<>(b.annotations));
this.elementInfo = Collections.unmodifiableList(new LinkedList<>(b.elementInfo));
this.otherElementInfo = Collections.unmodifiableList(new LinkedList<>(b.otherElementInfo));
this.superTypeInfo = b.superTypeInfo;
}

Expand Down Expand Up @@ -81,6 +83,11 @@ public List<TypedElementName> elementInfo() {
return elementInfo;
}

@Override
public List<TypedElementName> otherElementInfo() {
return otherElementInfo;
}

@Override
public Optional<TypeInfo> superTypeInfo() {
return Optional.ofNullable(superTypeInfo);
Expand All @@ -99,6 +106,7 @@ public String toString() {
protected String toStringInner() {
return "typeName=" + typeName()
+ ", elementInfo=" + elementInfo()
+ ", annotations=" + annotations()
+ ", superTypeInfo=" + superTypeInfo();
}

Expand All @@ -108,7 +116,7 @@ protected String toStringInner() {
public static class Builder implements io.helidon.common.Builder<Builder, DefaultTypeInfo> {
private final List<AnnotationAndValue> annotations = new ArrayList<>();
private final List<TypedElementName> elementInfo = new ArrayList<>();

private final List<TypedElementName> otherElementInfo = new ArrayList<>();
private TypeName typeName;
private String typeKind;

Expand Down Expand Up @@ -202,6 +210,31 @@ public Builder addElementInfo(TypedElementName val) {
return this;
}

/**
* Sets the otherElementInfo to val.
*
* @param val the value
* @return this fluent builder
*/
public Builder otherElementInfo(Collection<TypedElementName> val) {
Objects.requireNonNull(val);
this.otherElementInfo.clear();
this.otherElementInfo.addAll(val);
return this;
}

/**
* Adds a single otherElementInfo val.
*
* @param val the value
* @return this fluent builder
*/
public Builder addOtherElementInfo(TypedElementName val) {
Objects.requireNonNull(val);
otherElementInfo.add(Objects.requireNonNull(val));
return this;
}

/**
* Sets the superTypeInfo to val.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
import io.helidon.pico.types.TypedElementName;

/**
* Represents the model object for an interface type (e.g., one that was annotated with {@link io.helidon.builder.Builder}).
* Represents the model object for an interface or an abstract type (i.e., one that was annotated with
* {@link io.helidon.builder.Builder}).
*/
public interface TypeInfo {

Expand All @@ -50,12 +51,19 @@ public interface TypeInfo {
List<AnnotationAndValue> annotations();

/**
* The elements that make up the type.
* The elements that make up the type that are "relevant" for processing.
*
* @return the elements that make up the type
* @return the elements that make up the type that are relevant for processing
*/
List<TypedElementName> elementInfo();

/**
* The elements that make up this type that are considered "other", or being skipped because they are irrelevant to processing.
*
* @return the elements that still make up the type, but are otherwise deemed irrelevant for processing
*/
List<TypedElementName> otherElementInfo();

/**
* The parent/super class for this type info.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public class BodyContext {
private final String setType;
private final boolean hasParent;
private final boolean hasAnyBuilderClashingMethodNames;
private final boolean isExtendingAnAbstractClass;
private final TypeName ctorBuilderAcceptTypeName;
private final String genericBuilderClassDecl;
private final String genericBuilderAliasDecl;
Expand Down Expand Up @@ -100,10 +101,15 @@ public class BodyContext {
this.listType = toListImplType(builderTriggerAnnotation, typeInfo);
this.mapType = toMapImplType(builderTriggerAnnotation, typeInfo);
this.setType = toSetImplType(builderTriggerAnnotation, typeInfo);
gatherAllAttributeNames(this, typeInfo);
try {
gatherAllAttributeNames(this, typeInfo);
} catch (Exception e) {
throw new IllegalStateException("Failed while processing: " + typeInfo.typeName(), e);
}
assert (allTypeInfos.size() == allAttributeNames.size());
this.hasParent = Objects.nonNull(parentTypeName.get());
this.hasAnyBuilderClashingMethodNames = determineIfHasAnyClashingMethodNames();
this.isExtendingAnAbstractClass = typeInfo.typeKind().equals("CLASS");
this.ctorBuilderAcceptTypeName = (hasParent)
? typeInfo.typeName()
: (Objects.nonNull(parentAnnotationType.get()) && typeInfo.elementInfo().isEmpty()
Expand Down Expand Up @@ -308,6 +314,15 @@ public boolean hasAnyBuilderClashingMethodNames() {
return hasAnyBuilderClashingMethodNames;
}

/**
* Returns true if this builder is extending an abstract class as a target.
*
* @return true if the target is an abstract class
*/
public boolean isExtendingAnAbstractClass() {
return isExtendingAnAbstractClass;
}

/**
* Returns the streamable accept type of the builder and constructor.
*
Expand Down Expand Up @@ -364,6 +379,28 @@ public Optional<String> interceptorCreateMethod() {
return Optional.ofNullable(interceptorCreateMethod);
}

/**
* Checks whether there is an "other" method that matches the signature.
*
* @param name the method name
* @param typeInfo the type info to check, which will look through the parent chain
* @return true if there is any matches
*/
public boolean hasOtherMethod(String name,
TypeInfo typeInfo) {
for (TypedElementName elem : typeInfo.otherElementInfo()) {
if (elem.elementName().equals(name)) {
return true;
}
}

if (typeInfo.superTypeInfo().isPresent()) {
return hasOtherMethod(name, typeInfo.superTypeInfo().get());
}

return false;
}

/**
* returns the bean attribute name of a particular method.
*
Expand Down Expand Up @@ -574,7 +611,8 @@ private boolean determineIfHasAnyClashingMethodNames() {

private boolean isBuilderClashingMethodName(String beanAttributeName) {
return beanAttributeName.equals("identity")
|| beanAttributeName.equals("get");
|| beanAttributeName.equals("get")
|| beanAttributeName.equals("toStringInner");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@
@Weight(Weighted.DEFAULT_WEIGHT - 1)
public class BuilderTypeTools implements TypeInfoCreatorProvider {

private static final boolean ACCEPT_ABSTRACT_CLASS_TARGETS = true;

/**
* Default constructor.
*/
Expand All @@ -84,8 +86,8 @@ public Optional<TypeInfo> createTypeInfo(AnnotationAndValue annotation,
return Optional.empty();
}

if (element.getKind() != ElementKind.INTERFACE && element.getKind() != ElementKind.ANNOTATION_TYPE) {
String msg = annotation.typeName() + " is intended to be used on interfaces only: " + element;
if (!isAcceptableBuilderTarget(element)) {
String msg = annotation.typeName() + " is not intended to be targeted to this type: " + element;
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg);
throw new IllegalStateException(msg);
}
Expand All @@ -97,48 +99,77 @@ public Optional<TypeInfo> createTypeInfo(AnnotationAndValue annotation,
.filter(it -> !it.getParameters().isEmpty())
.collect(Collectors.toList());
if (!problems.isEmpty()) {
String msg = "only simple getters with 0 args are supported: " + element + ": " + problems;
String msg = "only simple getters with no arguments are supported: " + element + ": " + problems;
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg);
throw new IllegalStateException(msg);
}

Collection<TypedElementName> elementInfo = toElementInfo(element, processingEnv);
Collection<TypedElementName> elementInfo = toElementInfo(element, processingEnv, true);
Collection<TypedElementName> otherElementInfo = toElementInfo(element, processingEnv, false);
return Optional.of(DefaultTypeInfo.builder()
.typeName(typeName)
.typeKind(String.valueOf(element.getKind()))
.annotations(BuilderTypeTools
.createAnnotationAndValueListFromElement(element,
processingEnv.getElementUtils()))
.elementInfo(elementInfo)
.otherElementInfo(otherElementInfo)
.update(it -> toTypeInfo(annotation, element, processingEnv).ifPresent(it::superTypeInfo))
.build());
}

/**
* Determines if the target element with the {@link io.helidon.builder.Builder} annotation is an acceptable element type.
* If it is not acceptable then the caller is expected to throw an exception or log an error, etc.
*
* @param element the element
* @return true if the element is acceptable
*/
protected boolean isAcceptableBuilderTarget(Element element) {
final ElementKind kind = element.getKind();
final Set<Modifier> modifiers = element.getModifiers();
boolean isAcceptable = (kind == ElementKind.INTERFACE
|| kind == ElementKind.ANNOTATION_TYPE
|| (ACCEPT_ABSTRACT_CLASS_TARGETS
&& (kind == ElementKind.CLASS && modifiers.contains(Modifier.ABSTRACT))));
return isAcceptable;
}

/**
* Translation the arguments to a collection of {@link io.helidon.pico.types.TypedElementName}'s.
*
* @param element the typed element (i.e., class)
* @param processingEnv the processing env
* @param element the typed element (i.e., class)
* @param processingEnv the processing env
* @param wantWhatWeCanAccept pass true to get the elements we can accept to process, false for the other ones
* @return the collection of typed elements
*/
protected Collection<TypedElementName> toElementInfo(TypeElement element, ProcessingEnvironment processingEnv) {
protected Collection<TypedElementName> toElementInfo(TypeElement element,
ProcessingEnvironment processingEnv,
boolean wantWhatWeCanAccept) {
return element.getEnclosedElements().stream()
.filter(it -> it.getKind() == ElementKind.METHOD)
.map(ExecutableElement.class::cast)
.filter(this::canAccept)
.filter(it -> (wantWhatWeCanAccept == canAccept(it)))
.map(it -> createTypedElementNameFromElement(it, processingEnv.getElementUtils()))
.collect(Collectors.toList());
}

/**
* Returns true if the executable element passed is acceptable for processing (i.e., not a static and not a default method).
* Returns true if the executable element passed is acceptable for processing (i.e., not a static and not a default method
* on interfaces, and abstract methods on abstract classes).
*
* @param ee the executable element
* @return true if not default and not static
* @param ee the executable element
* @return true if not able to accept
*/
protected boolean canAccept(ExecutableElement ee) {
Set<Modifier> mods = ee.getModifiers();
return !mods.contains(Modifier.DEFAULT) && !mods.contains(Modifier.STATIC);
if (mods.contains(Modifier.ABSTRACT)) {
return true;
}
// if (mods.contains(Modifier.DEFAULT) || mods.contains(Modifier.STATIC)) {
// return false;
// }
return false;
}

private Optional<TypeInfo> toTypeInfo(AnnotationAndValue annotation,
Expand All @@ -147,7 +178,7 @@ private Optional<TypeInfo> toTypeInfo(AnnotationAndValue annotation,
List<? extends TypeMirror> ifaces = element.getInterfaces();
if (ifaces.size() > 1) {
processingEnv.getMessager()
.printMessage(Diagnostic.Kind.ERROR, "currently only supports one parent interface: " + element);
.printMessage(Diagnostic.Kind.ERROR, "only supports one parent interface: " + element);
} else if (ifaces.isEmpty()) {
return Optional.empty();
}
Expand Down
Loading

0 comments on commit 1a4c6b7

Please sign in to comment.