package com.sora.util.akatsuki;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ElementKind;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;
import com.sora.util.akatsuki.TransformationTemplate.Execution;
import com.sora.util.akatsuki.TypeConverter.DummyTypeConverter;
import com.sora.util.akatsuki.analyzers.ArrayTypeAnalyzer;
import com.sora.util.akatsuki.analyzers.CascadingTypeAnalyzer;
import com.sora.util.akatsuki.analyzers.CascadingTypeAnalyzer.Analysis;
import com.sora.util.akatsuki.analyzers.CollectionTypeAnalyzer;
import com.sora.util.akatsuki.analyzers.ConverterAnalyzer;
import com.sora.util.akatsuki.analyzers.Element;
import com.sora.util.akatsuki.analyzers.GenericTypeAnalyzer;
import com.sora.util.akatsuki.analyzers.NestedTypeAnalyzer;
import com.sora.util.akatsuki.analyzers.ObjectTypeAnalyzer;
import com.sora.util.akatsuki.analyzers.PrimitiveTypeAnalyzer;
import com.sora.util.akatsuki.analyzers.PrimitiveTypeAnalyzer.Type;
import com.sora.util.akatsuki.analyzers.TemplateAnalyzer;
import com.sora.util.akatsuki.models.SourceClassModel;
import com.sora.util.akatsuki.models.SourceTreeModel;
public class TypeAnalyzerResolver {
private final List<TransformationTemplate> templates;
private final List<DeclaredConverterModel> models;
private final SourceTreeModel treeModel;
private final ProcessorContext context;
private final TypeMirror dummyConverter;
private final TransformationContext transformationContext;
public TypeAnalyzerResolver(List<TransformationTemplate> templates,
List<DeclaredConverterModel> models, SourceTreeModel treeModel,
ProcessorContext context) {
this.templates = templates;
this.models = models;
this.treeModel = treeModel;
this.context = context;
this.dummyConverter = context.utils().of(DummyTypeConverter.class);
this.transformationContext = new TransformationContext(context, this);
}
CascadingTypeAnalyzer<?, ? extends TypeMirror, ? extends Analysis> resolve(Element<?> element) {
CascadingTypeAnalyzer<?, ?, ?> strategy;
final TypeMirror mirror = element.refinedMirror();
strategy = element.model().annotation(With.class).map(with -> {
DeclaredType declaredType = context.utils().getClassFromAnnotationMethod(with::value);
// @With defaults to a dummy converter, we don't want that
if (!context.utils().isSameType(declaredType, dummyConverter, true)) {
return new ConverterAnalyzer(transformationContext, declaredType);
}
return null;
}).orElse(null);
if (strategy == null) {
final DeclaredType converterType = models.stream()
.filter(m -> testTypeFilter(element.refinedMirror(), m.filters)).findFirst()
.map(m -> m.converter).orElse(null);
if (converterType != null)
strategy = new ConverterAnalyzer(transformationContext, converterType);
}
if (strategy == null)
strategy = findTransformationTemplates(templates, element, Execution.BEFORE);
if (strategy == null) {
SourceClassModel model = treeModel.findModelWithAssignableMirror(mirror);
// this field is a type that contains the @Retained
// annotation
if (model != null) {
Log.verbose(context, "Nested element found", element.originatingElement());
strategy = new NestedTypeAnalyzer(transformationContext);
}
}
if (strategy == null) {
// TODO consider discarding the switch and move the test
// condition
// into
// every strategy
if (transformationContext.utils().isPrimitive(mirror)) {
strategy = new PrimitiveTypeAnalyzer(transformationContext, Type.UNBOXED);
} else if (transformationContext.utils().isArray(mirror)) {
strategy = new ArrayTypeAnalyzer(transformationContext);
} else if (transformationContext.utils().isAssignable(mirror,
transformationContext.utils().of(Collection.class), true)) {
strategy = new CollectionTypeAnalyzer(transformationContext);
} else if (transformationContext.utils().isAssignable(mirror,
transformationContext.utils().of(Map.class), true)) {
// TODO: ETS phase 2
} else if (transformationContext.utils().isObject(mirror)) {
strategy = new ObjectTypeAnalyzer(transformationContext);
} else if (mirror.getKind().equals(TypeKind.TYPEVAR)) {
// we got a generic type of some bounds
strategy = new GenericTypeAnalyzer(transformationContext);
}
}
if (strategy == null) {
final CascadingTypeAnalyzer<?, ?, Analysis> ignored = findTransformationTemplates(
templates, element, Execution.NEVER);
if (ignored != null)
context.messager().printMessage(Kind.NOTE,
"found matching strategy:" + ignored.getClass() + " but ignored");
}
return strategy;
}
private CascadingTypeAnalyzer<?, ?, Analysis> findTransformationTemplates(
List<TransformationTemplate> templates, Element<?> element, Execution execution) {
return templates.stream().filter(t -> t.execution() == execution)
.filter(template -> testTypeFilter(element.refinedMirror(), template.filters()))
.findFirst().map(t -> new TemplateAnalyzer(transformationContext, t)).orElse(null);
}
private boolean testTypeFilter(TypeMirror mirror, TypeFilter... filters) {
return Arrays.stream(filters).anyMatch(filter -> {
final List<? extends TypeMirror> arguments;
arguments = mirror instanceof DeclaredType ? ((DeclaredType) mirror).getTypeArguments()
: Collections.emptyList();
final TypeConstraint[] parameters = filter.parameters();
// if argument count don't match, short circuit
if (arguments.size() != parameters.length) {
return false;
}
// check our raw type
if (!testTypeConstraint(mirror, filter.type()))
return false;
for (int i = 0; i < arguments.size(); i++) {
TypeMirror m = arguments.get(i);
if (!testTypeConstraint(m, parameters[i])) {
return false;
}
}
return true;
});
}
private boolean testTypeConstraint(TypeMirror mirror, TypeConstraint constraint) {
final DeclaredType type = context.utils().getClassFromAnnotationMethod(constraint::type);
// annotation types are handled differently
if (context.utils().isAssignable(type, context.utils().of(Annotation.class), true)) {
// TODO how do we get the element of PrimitiveType? this
// seems wrong...
final javax.lang.model.element.Element element = (mirror instanceof PrimitiveType)
? context.types().boxedClass((PrimitiveType) mirror)
: context.types().asElement(mirror);
// this happens to array...?
// TODO figure this out
if (element == null) {
return false;
}
List<? extends AnnotationMirror> annotationMirrors;
// bounds have different meanings for annotations as
// they don't have inheritance
switch (constraint.bound()) {
case EXACTLY:
annotationMirrors = element.getAnnotationMirrors();
break;
default:
annotationMirrors = context.elements().getAllAnnotationMirrors(element);
break;
}
if (annotationMirrors.stream()
.anyMatch(m -> context.utils().isSameType(m.getAnnotationType(), type, true)))
return true;
}
switch (constraint.bound()) {
case EXACTLY:
if (context.utils().isSameType(type, mirror, true))
return true;
break;
case EXTENDS:
boolean clazz = type.asElement().getKind() == ElementKind.CLASS;
// classes and interfaces have inverted schematics for isAssignable
// for some reason...
if (context.utils().isAssignable(clazz ? type : mirror, clazz ? mirror : type, true))
return true;
break;
case SUPER:
// swap argument because we are checking whether the mirror is a
// super type of type
if (context.utils().isAssignable(mirror, type, true))
return true;
break;
}
return false;
}
}