package co.infinum.ava.annotations.processor;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import co.infinum.ava.annotations.InjectList;
import co.infinum.ava.annotations.ListLayout;
import co.infinum.ava.annotations.ListView;
import co.infinum.ava.annotations.processor.tools.AdapterInjectorCreator;
import co.infinum.ava.annotations.processor.tools.JavaLangUtils;
import co.infinum.ava.annotations.processor.tools.ViewHolderCreator;
import co.infinum.ava.annotations.processor.tools.ViewHolderFieldType;
/**
* Created by ivan on 06/01/14.
*
* TODO doesn't work for interface types
* TODO support for onItemClick listener
*/
@SupportedAnnotationTypes({"co.infinum.ava.annotations.InjectList", "co.infinum.ava.annotations.ListLayout", "co.infinum.ava.annotations.ListView"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class AbstractViewAdapterProcessor extends AbstractProcessor {
protected static final String CLASS_NAME_SUFIX = "$$ViewHolder";
protected static final String INJECTOR_CLASS_NAME_SUFIX = "$$AdapterInjector";
protected static final String STRING_TYPE = "java.lang.String";
protected static final String BITMAP_TYPE = "android.graphics.Bitmap";
protected static final String ACTIVITY_TYPE = "android.app.Activity";
/**
* Used to generate source files.
*/
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Map<String, ViewHolderCreator> adapterMap = findAndParseTargets(roundEnv);
Map<String, AdapterInjectorCreator> adapterInjectorMap = findAndParseInjectors(roundEnv);
generateAdapterSourceFiles(adapterMap);
generateAdapterInjectorSourceFiles(adapterInjectorMap);
return true;
}
private Map<String, ViewHolderCreator> findAndParseTargets(RoundEnvironment env) {
Map<String, ViewHolderCreator> adapterMap = new HashMap<String, ViewHolderCreator>();
for (Element element : env.getElementsAnnotatedWith(ListLayout.class)) {
TypeElement type = (TypeElement) element;
String simpleName = type.getSimpleName().toString();
String objectType = type.getQualifiedName().toString();
String className = type.getQualifiedName().toString() + CLASS_NAME_SUFIX;
String packageName = objectType.substring(0, objectType.length() - simpleName.length() - 1);
ListLayout annotation = element.getAnnotation(ListLayout.class);
int layoutResId = annotation.value();
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "ClassName: " + className);
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "PackageName: " + packageName);
ViewHolderCreator creator = new ViewHolderCreator();
creator.setPackageName(packageName);
creator.setClassName(simpleName + CLASS_NAME_SUFIX);
creator.setObjectType(objectType);
creator.setLayoutId(layoutResId);
adapterMap.put(className, creator);
}
for (Element element : env.getElementsAnnotatedWith(ListView.class)) {
if (element.getKind() != ElementKind.METHOD) {
//TODO throw exception
}
ExecutableElement type = (ExecutableElement) element;
String methodName = type.getSimpleName().toString();
String returnType = type.getReturnType().toString();
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Element: " + element.getClass());
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "ReturnType: " + returnType);
TypeElement parentType = (TypeElement) element.getEnclosingElement();
String className = parentType.getQualifiedName() + CLASS_NAME_SUFIX;
ListView annotation = element.getAnnotation(ListView.class);
int viewResId = annotation.value();
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "MethodName: " + methodName);
ViewHolderCreator creator = adapterMap.get(className);
if (creator == null) {
//TODO throw exception
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "no class creator defined");
}
switch (returnType) {
case STRING_TYPE:
creator.addField(ViewHolderFieldType.TEXT, viewResId, methodName);
break;
case BITMAP_TYPE:
creator.addField(ViewHolderFieldType.IMAGE, viewResId, methodName);
break;
default:
creator.addField(ViewHolderFieldType.TEXT, viewResId, methodName);
}
}
return adapterMap;
}
private Map<String, AdapterInjectorCreator> findAndParseInjectors(RoundEnvironment env) {
Map<String, AdapterInjectorCreator> adapterInjectorMap = new HashMap<>();
for (Element element : env.getElementsAnnotatedWith(InjectList.class)) {
if (element.getKind() != ElementKind.FIELD) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Element '" + element.toString() + "' is not a FIELD.");
}
if (element.getModifiers().contains(Modifier.PROTECTED) ||
element.getModifiers().contains(Modifier.PRIVATE)) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Element '" + element + "' is declared PROTECTED or PRIVATE. Fields annotated with @InjectView can't be declared PROTECTED or PRIVATE");
}
Element parentClass = element.getEnclosingElement();
String parentClassName = parentClass.toString(); //parent class of the field
String injectorClassName = parentClassName + INJECTOR_CLASS_NAME_SUFIX;
if (!adapterInjectorMap.containsKey(injectorClassName)) {
String injectorClassSimpleName = parentClass.getSimpleName().toString() + INJECTOR_CLASS_NAME_SUFIX;
AdapterInjectorCreator injectorCreator = new AdapterInjectorCreator();
injectorCreator.setPackageName(JavaLangUtils.getPackage(parentClass));
injectorCreator.setClassName(injectorClassSimpleName);
injectorCreator.setAdapterClassName(parentClassName);
injectorCreator.setInjectingIntoActivity(JavaLangUtils.checkIfExtends(parentClass, ACTIVITY_TYPE));
adapterInjectorMap.put(injectorClassName, injectorCreator);
}
String adapterFieldName = element.toString();
String objectType = JavaLangUtils.getGenericType(element);
InjectList annotation = element.getAnnotation(InjectList.class);
int viewResId = annotation.value();
AdapterInjectorCreator injectorCreator = adapterInjectorMap.get(injectorClassName);
injectorCreator.addInjection(adapterFieldName, objectType + CLASS_NAME_SUFIX, objectType, viewResId);
}
return adapterInjectorMap;
}
private void parseListLayout(Element element, Map<String, ViewHolderCreator> adapterMap) {
}
private void parseInjectAdapter(RoundEnvironment env) {
}
private void generateAdapterSourceFiles(Map<String, ViewHolderCreator> adapterMap) {
for (String className : adapterMap.keySet()) {
try {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Processing class: " + className);
JavaFileObject adapterSource = filer.createSourceFile(className);
Writer writer = adapterSource.openWriter();
writer.write(adapterMap.get(className).createViewHolderImplementation());
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void generateAdapterInjectorSourceFiles(Map<String, AdapterInjectorCreator> adapterInjectorMap) {
for (String className : adapterInjectorMap.keySet()) {
try {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Processing injector: " + className);
JavaFileObject adapterInjectorSource = filer.createSourceFile(className);
Writer writer = adapterInjectorSource.openWriter();
writer.write(adapterInjectorMap.get(className).createInjectAdapterImplementation());
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
;
}
}
}
}