package com.sora.util.akatsuki.models;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
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.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;
import com.google.common.base.MoreObjects;
import com.sora.util.akatsuki.Log;
import com.sora.util.akatsuki.ProcessorContext;
public class SourceTreeModel extends BaseModel {
private static final Set<Modifier> DISALLOWED_MODIFIERS = EnumSet.of(Modifier.FINAL,
Modifier.STATIC, Modifier.PRIVATE);
private final List<SourceClassModel> models;
private final List<SourceClassModel> flattenedModel;
public enum Arrangement {
FLATTENED, HEAD
}
private SourceTreeModel(ProcessorContext context, List<SourceClassModel> models) {
super(context);
this.models = models;
this.flattenedModel = models.stream()
.flatMap(m -> Stream.concat(m.children().stream(), Stream.of(m))).distinct()
.collect(Collectors.toList());
}
public boolean containsClass(CharSequence fqcn) {
return models.stream().anyMatch(m -> m.fullyQualifiedName().equals(fqcn));
}
private static void collectElements(ArrayList<Element> elements, Element root,
Set<Class<? extends Annotation>> classes) {
if (root.getKind() == ElementKind.CLASS) {
for (Element element : root.getEnclosedElements()) {
collectElements(elements, element, classes);
}
} else if (root.getKind() == ElementKind.FIELD
&& classes.stream().anyMatch(c -> root.getAnnotation(c) != null)) {
elements.add(root);
}
}
// create our model here
public static SourceTreeModel fromRound(ProcessorContext context, RoundEnvironment roundEnv,
Set<Class<? extends Annotation>> classes) {
// final Set<Element> elements = new HashSet<>();
// for (Class<? extends Annotation> clazz : classes) {
// elements.addAll(roundEnv.getElementsAnnotatedWith(clazz));
// }
ArrayList<Element> list = new ArrayList<>();
for (Element root : roundEnv.getRootElements()) {
collectElements(list, root, classes);
}
Map<String, SourceClassModel> classNameMap = new HashMap<>();
int processed = 0;
boolean verifyOnly = false;
for (Element element : list) {
// skip if error
if (!annotatedElementValid(context, element)
|| !enclosingClassValid(context, element)) {
verifyOnly = true;
continue;
}
// >= 1 error has occurred, we're in verify mode
if (!verifyOnly) {
final TypeElement enclosingClass = (TypeElement) element.getEnclosingElement();
if (context.config().fieldAllowed(element)) {
final SourceClassModel model = classNameMap.computeIfAbsent(
enclosingClass.getQualifiedName().toString(),
k -> new SourceClassModel(context, enclosingClass));
Set<Class<? extends Annotation>> annotationClasses = classes.stream()
.filter(c -> element.getAnnotation(c) != null)
.collect(Collectors.toSet());
model.fields.add(new FieldModel((VariableElement) element, annotationClasses));
Log.verbose(context, "Element marked", element);
} else {
Log.verbose(context, "Element skipped", element);
}
}
processed++;
}
Collection<SourceClassModel> models = classNameMap.values();
if (processed != list.size()) {
context.messager().printMessage(Kind.NOTE,
(list.size() - processed)
+ " error(s) occurred, no files are generated after the first "
+ "error has occurred.");
} else {
// stage 2, initialize them all
models.forEach(model -> model.linkParent(classNameMap));
models.forEach(model -> model.findChildren(classNameMap, roundEnv));
models.forEach(SourceClassModel::markHiddenFields);
}
return verifyOnly ? null : new SourceTreeModel(context, new ArrayList<>(models));
}
private static boolean annotatedElementValid(ProcessorContext context, Element element) {
// sanity check
if (!element.getKind().equals(ElementKind.FIELD)) {
context.messager().printMessage(Kind.ERROR, "annotated target must be a field",
element);
return false;
}
// more sanity check
if (!(element instanceof VariableElement)) {
context.messager().printMessage(Kind.ERROR,
"Element is not a variable?! (should not happen at all) ", element);
return false;
}
// check for invalid modifiers, we can only create classes in the
// same package
if (!Collections.disjoint(element.getModifiers(), DISALLOWED_MODIFIERS)) {
context.messager().printMessage(Kind.ERROR,
"field with " + DISALLOWED_MODIFIERS.toString() + " " + "cannot be retained",
element);
return false;
}
return true;
}
private static boolean enclosingClassValid(ProcessorContext context, Element element) {
Element enclosingElement = element.getEnclosingElement();
while (enclosingElement != null) {
// skip until we find a class
if (!enclosingElement.getKind().equals(ElementKind.CLASS))
break;
if (!enclosingElement.getKind().equals(ElementKind.CLASS)) {
context.messager().printMessage(Kind.ERROR,
"enclosing element(" + enclosingElement.toString() + ") is not a class",
element);
return false;
}
TypeElement enclosingClass = (TypeElement) enclosingElement;
// protected, package-private, and public all allow same package
// access
if (enclosingClass.getModifiers().contains(Modifier.PRIVATE)) {
context.messager().printMessage(Kind.ERROR,
"enclosing class (" + enclosingElement.toString() + ") cannot be private",
element);
return false;
}
if (enclosingClass.getNestingKind() != NestingKind.TOP_LEVEL
&& !enclosingClass.getModifiers().contains(Modifier.STATIC)) {
context.messager().printMessage(Kind.ERROR,
"enclosing class is nested but not static", element);
return false;
}
enclosingElement = enclosingClass.getEnclosingElement();
}
return true;
}
static boolean enclosingClassValid(ProcessorContext context, TypeElement enclosingClass){
// protected, package-private, and public all allow same package
// access
if (enclosingClass.getModifiers().contains(Modifier.PRIVATE)) {
context.messager().printMessage(Kind.ERROR,
"class cannot be private",
enclosingClass);
return false;
}
if (enclosingClass.getNestingKind() != NestingKind.TOP_LEVEL
&& !enclosingClass.getModifiers().contains(Modifier.STATIC)) {
context.messager().printMessage(Kind.ERROR,
"class is nested but not static", enclosingClass);
return false;
}
return true;
}
public List<SourceClassModel> classModels(Arrangement arrangement) {
return Collections
.unmodifiableList(arrangement == Arrangement.FLATTENED ? flattenedModel : models);
}
public Collection<SourceClassModel> findModelWithMatchingElement(TypeElement e) {
return models.stream().filter(classModel -> classModel.originatingElement().equals(e))
.collect(Collectors.toSet());
}
public SourceClassModel findModelWithAssignableMirror(TypeMirror mirror) {
return models.stream().filter(model -> model.fullyQualifiedName().equals(mirror.toString()))
.findFirst()
.orElseGet(() -> models.stream()
.filter(m -> context.utils().isAssignable(mirror, m.mirror(), true))
.findFirst().orElse(null));
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("models", models).toString();
}
}