All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.jbock.annotated.ExecutableElementsFinder Maven / Gradle / Ivy

There is a newer version: 5.18
Show newest version
package net.jbock.annotated;

import io.jbock.util.Either;
import net.jbock.common.ValidationFailure;
import net.jbock.processor.SourceElement;
import net.jbock.validate.ValidateScope;

import javax.inject.Inject;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static io.jbock.util.Either.right;
import static io.jbock.util.Eithers.toOptionalList;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.partitioningBy;
import static java.util.stream.Collectors.toList;
import static javax.lang.model.element.ElementKind.INTERFACE;
import static javax.lang.model.element.Modifier.ABSTRACT;
import static net.jbock.common.Annotations.methodLevelAnnotations;
import static net.jbock.common.TypeTool.AS_DECLARED;
import static net.jbock.common.TypeTool.AS_TYPE_ELEMENT;

@ValidateScope
public class ExecutableElementsFinder {

    private final SourceElement sourceElement;

    @Inject
    ExecutableElementsFinder(SourceElement sourceElement) {
        this.sourceElement = sourceElement;
    }

    /**
     * Returns a Right-Either containing all annotated parameterless abstract
     * methods, including methods that are inherited from interfaces and
     * abstract ancestor classes.
     *
     * 

If one of the annotated parameterless abstract methods is overridden * by a non-abstract method, or by another annotated method, * a Left-Either is returned. * *

If one of the unannotated parameterless abstract methods does not have * an annotated ancestor, a Left-Either is returned. * * @return all annotated parameterless abstract methods, including inherited, * or a nonempty list of validation failures */ Either, AnnotatedMethodsBuilder.Step2> findExecutableElements() { List methods = findParameterlessMethodsIn(sourceElement.element().asType()); Map> partitions = methods.stream() .collect(partitioningBy(m -> m.getModifiers().contains(ABSTRACT))); Set nonAbstractNames = partitions.get(false).stream() .map(ExecutableElement::getSimpleName) .collect(Collectors.toSet()); List allAbstract = partitions.get(true); Map> allAbstractByName = allAbstract.stream() .collect(groupingBy(ExecutableElement::getSimpleName, LinkedHashMap::new, toList())); return validateAbstractMethods(allAbstractByName, nonAbstractNames) ., List>>map(Either::left) .orElseGet(() -> right(allAbstract.stream() .flatMap(this::createExecutable) .collect(toList()))) .map(AnnotatedMethodsBuilder::builder) .map(step -> step.sourceElement(sourceElement)); } private List findParameterlessMethodsIn(TypeMirror mirror) { Map> methodsInInterfaces = findMethodsInInterfaces(mirror); List acc = new ArrayList<>(); methodsInInterfaces.values().forEach(acc::addAll); while (true) { Optional element = asAbstractTypeElement(mirror); if (element.isEmpty()) { return acc; } TypeElement el = element.orElseThrow(); List methods = parameterlessMethodsIn(el.getEnclosedElements()); acc.addAll(methods); mirror = el.getSuperclass(); } } private Optional asAbstractTypeElement(TypeMirror mirror) { return AS_DECLARED.visit(mirror) .map(DeclaredType::asElement) .flatMap(AS_TYPE_ELEMENT::visit) // interfaces are handled separately .filter(typeElement -> typeElement.getKind() != INTERFACE) // not abstract -> no relevant methods .filter(typeElement -> typeElement.getModifiers().contains(ABSTRACT)); } private Map> findMethodsInInterfaces(TypeMirror mirror) { return AS_DECLARED.visit(mirror) .map(DeclaredType::asElement) .flatMap(AS_TYPE_ELEMENT::visit) .map(typeElement -> { List methods = typeElement.getKind() == INTERFACE ? parameterlessMethodsIn(typeElement.getEnclosedElements()) : List.of(); Map> acc = new HashMap<>(); acc.put(typeElement.getQualifiedName(), methods); for (TypeMirror superInterface : typeElement.getInterfaces()) { acc.putAll(findMethodsInInterfaces(superInterface)); // recursion } return acc; }).orElse(Map.of()); } private List parameterlessMethodsIn(List elements) { return ElementFilter.methodsIn(elements).stream() .filter(m -> m.getParameters().isEmpty()) .collect(toList()); } private Optional> validateAbstractMethods( Map> allAbstractByName, Set nonAbstractNames) { Set names = allAbstractByName.keySet(); return names.stream() .filter(name -> !nonAbstractNames.contains(name) && missingAnnotation(allAbstractByName.get(name))) .map(m -> new ValidationFailure(missingAnnotationError(), allAbstractByName.get(m).get(0))) .collect(toOptionalList()) .or(() -> names.stream() .filter(name -> nonAbstractNames.contains(name) && allAbstractByName.get(name).stream().anyMatch(this::hasAnnotation) || multiAnnotation(allAbstractByName.get(name))) .map(m -> new ValidationFailure("annotated method is overridden", allAbstractByName.get(m).get(0))) .collect(toOptionalList())); } private boolean multiAnnotation(List homonyms) { return homonyms.stream() .filter(this::hasAnnotation) .count() >= 2; } private boolean missingAnnotation(List homonyms) { return homonyms.stream() .filter(this::hasAnnotation) .findAny() .isEmpty(); } private boolean hasAnnotation(ExecutableElement method) { return methodLevelAnnotations().stream() .anyMatch(a -> method.getAnnotation(a) != null); } private Stream createExecutable(ExecutableElement method) { return methodLevelAnnotations().stream() .flatMap(a -> method.getAnnotation(a) != null ? Stream.of(Executable.create(method, method.getAnnotation(a))) : Stream.empty()); } private String missingAnnotationError() { return "add one of these annotations: " + methodLevelAnnotations().stream() .map(ann -> "@" + ann.getSimpleName()) .collect(Collectors.joining(", ")); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy