package com.sora.util.akatsuki;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.annotation.processing.Filer;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleElementVisitor8;
import com.sora.util.akatsuki.AkatsukiConfig.OptFlags;
import com.sora.util.akatsuki.models.ClassInfo;
import com.sora.util.akatsuki.models.SourceCollectingModel;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeSpec.Builder;
import com.squareup.javapoet.TypeVariableName;
import com.squareup.javapoet.WildcardTypeName;
public class RetainerLUTModel extends SourceCollectingModel<RetainedStateModel> {
private final Collection<? extends Element> rootElements;
protected RetainerLUTModel(ProcessorContext context, List<RetainedStateModel> models,
Collection<? extends Element> rootElements) {
super(context, models);
this.rootElements = rootElements;
}
public TypeSpec createModel() {
final Builder typeBuilder = TypeSpec.classBuilder(Akatsuki.RETAINER_CACHE_NAME)
.addModifiers(Modifier.PUBLIC).addSuperinterface(RetainerCache.class);
// Class
final ClassName classType = ClassName.get(Class.class);
final ClassName bundlerClassName = ClassName.get(BundleRetainer.class);
Function<TypeName, ParameterizedTypeName> valueNameFunction = parameter -> ParameterizedTypeName
.get(classType, WildcardTypeName.subtypeOf(parameter == null ? bundlerClassName
: ParameterizedTypeName.get(bundlerClassName, parameter)));
Supplier<TypeName> keyValueFunction = () -> ClassName.get(String.class);
// Map<Class<?>, Class<? extends BundleRetainer<Object>>>
final ParameterizedTypeName mapType = ParameterizedTypeName.get(ClassName.get(Map.class),
keyValueFunction.get(), valueNameFunction.apply(null));
typeBuilder.addField(FieldSpec
.builder(mapType, "CACHE", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer("new $T<>()", HashMap.class).build());
final CodeBlock.Builder builder = CodeBlock.builder();
Consumer<Map<String, RetainedStateModel>> modelToMapConsumer = m -> {
for (Entry<String, RetainedStateModel> entry : m.entrySet()) {
builder.add("CACHE.put($S, $L);\n", entry.getKey(),
entry.getValue().classInfo().fullyQualifiedClassName() + ".class");
}
};
Map<String, RetainedStateModel> modelMap = mappingModels().stream().collect(
Collectors.toMap(m -> m.classModel().fullyQualifiedName(), Function.identity()));
modelToMapConsumer.accept(modelMap);
if (context.config().optFlags().contains(OptFlags.VECTORIZE_INHERITANCE)) {
// find all implementing class (for inheritance)
for (Element element : rootElements) {
modelToMapConsumer.accept(findAllTypes(element, modelMap));
}
}
typeBuilder.addStaticBlock(builder.build());
// <T>
final TypeVariableName t = TypeVariableName.get("T");
// Class<? extends BundleRetainer<T>>>
final ParameterizedTypeName returnType = valueNameFunction.apply(t);
MethodSpec methodSpec = MethodSpec.methodBuilder("getCached").returns(returnType)
.addParameter(keyValueFunction.get(), "clazz").addModifiers(Modifier.PUBLIC)
.addCode("return ($T) CACHE.get(clazz);\n", returnType).addTypeVariable(t)
.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
.addMember("value", "$S", "unchecked").build())
.build();
typeBuilder.addMethod(methodSpec);
return typeBuilder.build();
}
@Override
public ClassInfo classInfo() {
return new ClassInfo(Akatsuki.RETAINER_CACHE_PACKAGE, Akatsuki.RETAINER_CACHE_NAME);
}
@Override
public void writeToFile(Filer filer) throws IOException {
JavaFile.builder(Akatsuki.RETAINER_CACHE_PACKAGE, createModel()).build().writeTo(filer);
}
private Map<String, RetainedStateModel> findAllTypes(final Element element,
final Map<String, RetainedStateModel> referenceMap) {
Map<String, RetainedStateModel> modelMap = new HashMap<>();
element.accept(new SimpleElementVisitor8<Void, Map<String, RetainedStateModel>>() {
@Override
public Void visitType(TypeElement e, Map<String, RetainedStateModel> map) {
if (e.getKind() == ElementKind.CLASS) {
// only process class that isn't in the map
if (!referenceMap.containsKey(e.getQualifiedName().toString())) {
findInheritedModel(e, referenceMap.values())
.ifPresent(m -> map.put(e.getQualifiedName().toString(), m));
}
e.getEnclosedElements().forEach(ee -> ee.accept(this, map));
}
return null;
}
}, modelMap);
return modelMap;
}
private Optional<RetainedStateModel> findInheritedModel(Element element,
Collection<RetainedStateModel> models) {
if (element == null || element.getKind() != ElementKind.CLASS
|| !(element instanceof TypeElement))
return Optional.empty();
TypeElement type = (TypeElement) element;
TypeMirror superMirror = type.getSuperclass();
return models.stream()
.filter(m -> context.utils().isSameType(m.classModel().mirror(), superMirror, true))
.findFirst().map(Optional::of)
.orElse(findInheritedModel(context.types().asElement(superMirror), models));
}
}