package com.sora.util.akatsuki.models; import static com.sora.util.akatsuki.models.SourceTreeModel.enclosingClassValid; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; import javax.lang.model.element.NestingKind; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleElementVisitor8; import com.google.common.base.CharMatcher; import com.google.common.base.MoreObjects; import com.sora.util.akatsuki.ProcessorContext; import com.sora.util.akatsuki.models.FieldModel.Flag; public class SourceClassModel extends BaseModel { private final TypeElement enclosingClass; private SourceClassModel parent; private Set<SourceClassModel> children; final List<FieldModel> fields = new ArrayList<>(); SourceClassModel(ProcessorContext context, TypeElement enclosingClass) { super(context); this.enclosingClass = enclosingClass; if (!enclosingClassValid(context, enclosingClass)) { throw new RuntimeException("Element " + enclosingClass + " is not valid"); } } void linkParent(Map<String, SourceClassModel> map) { TypeElement superClass = enclosingClass; while ((superClass.getKind() == ElementKind.CLASS)) { final Element superElement = context.types().asElement(superClass.getSuperclass()); if (!(superElement instanceof TypeElement)) { break; } superClass = (TypeElement) superElement; final SourceClassModel superModel = map.get(superClass.getQualifiedName().toString()); if (superModel != null) { // we found our closest super class this.parent = superModel; break; } } // this.children.forEach(c -> c.linkParent(map)); } void findChildren(Map<String, SourceClassModel> map, RoundEnvironment environment) { Set<SourceClassModel> models = new HashSet<>(); for (Element root : environment.getRootElements()) { root.accept(new SimpleElementVisitor8<Void, Set<SourceClassModel>>() { @Override public Void visitType(TypeElement element, Set<SourceClassModel> models) { if (element.getKind() == ElementKind.CLASS && element != enclosingClass) { // we're in a class now if (context.utils().isAssignable(element.asType(), mirror(), true)) { // the class extends the source class, add it to the // map SourceClassModel existing = map .get(element.getQualifiedName().toString()); if (existing != null) { // class model was instantiated in the // verification phase if (existing.parent == null) existing.parent = SourceClassModel.this; models.add(existing); } else { // new class found SourceClassModel model = new SourceClassModel(context, element); model.parent = SourceClassModel.this; models.add(model); model.findChildren(map, environment); } } // keep walking element.getEnclosedElements().forEach(e -> e.accept(this, models)); } return null; } }, models); } this.children = Collections.unmodifiableSet(models); } void markHiddenFields() { // set colliding fields with the proper flag fields.stream().filter(model -> parent != null && parent.containsFieldWithSameName(model)) .findAny().ifPresent(m -> m.flags.add(Flag.HIDDEN)); } private boolean containsFieldWithSameName(FieldModel model) { return fields.stream().anyMatch(m -> m.name().equals(model.name())); } public List<FieldModel> fields() { return Collections.unmodifiableList(fields); } public String fullyQualifiedName() { return enclosingClass.getQualifiedName().toString(); } public String simpleName() { // if (enclosingClass.getNestingKind() != NestingKind.TOP_LEVEL) { // String fqn = enclosingClass.getQualifiedName().toString(); // return fqn.substring(fqn.indexOf('.') + 1, fqn.length()); // } else { return enclosingClass.getSimpleName().toString(); // } } public String fullyQualifiedPackageName() { return context.elements().getPackageOf(enclosingClass).toString(); } public boolean containsModifier(Modifier modifier) { return enclosingClass.getModifiers().contains(modifier); } public ClassInfo asClassInfo() { String fqpn = fullyQualifiedPackageName(); String className; if (enclosingClass.getNestingKind() != NestingKind.TOP_LEVEL) { // in case of the static class, we get all the nested classes and // replace '.' with '$' className = CharMatcher.is('.') .replaceFrom(fullyQualifiedName().replace(fqpn + ".", ""), '$'); } else { className = simpleName(); } return new ClassInfo(fqpn, className); } public TypeElement originatingElement() { return enclosingClass; } public <A extends Annotation> Optional<A> annotation(Class<A> annotationClass) { return Optional.ofNullable(enclosingClass.getAnnotation(annotationClass)); } public Optional<SourceClassModel> directSuperModel() { return Optional.ofNullable(parent); } public Optional<SourceClassModel> directSuperModelWithAnnotation( Class<? extends Annotation> annotationClass) { return parent != null && parent.containsAnyAnnotation(annotationClass) ? directSuperModel() : Optional.empty(); } public boolean containsAnyAnnotation(Class<? extends Annotation> annotationClass) { return fields.stream().anyMatch(fm -> fm.annotation(annotationClass).isPresent()); } public Set<SourceClassModel> children() { return children; } public TypeMirror mirror() { return enclosingClass.asType(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SourceClassModel that = (SourceClassModel) o; return enclosingClass.equals(that.enclosingClass); } @Override public int hashCode() { return enclosingClass.hashCode(); } @Override public String toString() { return MoreObjects.toStringHelper(this).add("enclosingClass", enclosingClass) .add("parent", parent != null ? parent.fullyQualifiedName() : null) .add("children", Arrays.toString(children.stream() .map(SourceClassModel::fullyQualifiedName).toArray(String[]::new))) .add("fields", fields).toString(); } }