package xapi.dev.processor;
import xapi.annotation.reflect.MirroredAnnotation;
import xapi.collect.api.Fifo;
import xapi.collect.impl.SimpleFifo;
import xapi.dev.source.ClassBuffer;
import xapi.dev.source.FieldBuffer;
import xapi.dev.source.MethodBuffer;
import xapi.dev.source.SourceBuilder;
import xapi.util.X_Runtime;
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.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.FileObject;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Set;
/**
* This is the annotation processor for our injection library.
*
* It scans for our injection annotations and writes manifests to speed up
* injection. default implementations go in META-INF/instances or
* META-INF/singletons.
*
* Platform specific injection types go into META-INF/$type/instances,
* META-INF/$type/singletons.
*
* It is included in the core api because it is run on the core api; every jar
* built will include these manifests, whether they are used at runtime or not.
*
* Gwt injection does not look at the manifests, and a bytecode enhancer is in
* the works to further process jre environments, by replacing calls to X_Inject
* with direct access of the injected type.
*
* Final builds (bound to pre-deploy) can also be further enhanced, by replacing
* all references to an injected interface with it's replacement type (changing
* all class references, and changing invokeinterface to invokespecial). This
* may be unnecessary once javac optimize=true after replacing the X_Inject call
* site.
*
*
* @author "James X. Nelson (james@wetheinter.net)"
*
*/
@SupportedAnnotationTypes({ "xapi.annotation.reflect.MirroredAnnotation" })
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AnnotationMirrorProcessor extends AbstractProcessor {
private final HashMap<String, AnnotationManifest> generatedMirrors;
public AnnotationMirrorProcessor() {
generatedMirrors = new HashMap<String, AnnotationManifest>();
}
protected Filer filer;
private TypeMirror annoType;
private TypeMirror stringType;
private TypeMirror classType;
private TypeMirror enumType;
@Override
public final synchronized void init(final ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
final Elements elements = processingEnv.getElementUtils();
final Types types = processingEnv.getTypeUtils();
annoType = elements.getTypeElement(Annotation.class.getName()).asType();
stringType = elements.getTypeElement(String.class.getName()).asType();
classType = types.erasure(elements.getTypeElement(Class.class.getName())
.asType());
enumType = types.erasure(elements.getTypeElement(Enum.class.getName())
.asType());
}
@Override
public boolean process(final Set<? extends TypeElement> annotations,
final RoundEnvironment roundEnv) {
for (final TypeElement anno : annotations) {
for (final Element el : roundEnv.getElementsAnnotatedWith(anno)) {
final MirroredAnnotation mirrorSettings = el.getAnnotation(MirroredAnnotation.class);
final AnnotationManifest manifest = addAnnotation((TypeElement) el);
if (mirrorSettings.generateJavaxLangModelFactory()) {
generateJavaxFactory((TypeElement)el, manifest);
}
if (mirrorSettings.generateXapiBytecodeFactory()) {
generateXapiFactory((TypeElement)el, manifest);
}
if (mirrorSettings.generateReflectionFactory()) {
}
}
}
for (final String key : generatedMirrors.keySet().toArray(
new String[generatedMirrors.size()])) {
try {
final AnnotationManifest mirror = generatedMirrors.get(key);
if (mirror == null) {
continue;
}
final String code = mirror.generated;
log("Generating " + key);
final String genClass = key + "Builder";
// Delete previous class, or build will break.
try {
final int ind = genClass.lastIndexOf('.');
final String pkg = genClass.substring(0, ind);
final String name = genClass.substring(ind+1)+".class";
final FileObject file = filer.getResource(StandardLocation.CLASS_OUTPUT, pkg, name);
final CharSequence content = file.getCharContent(true);
if (code.equals(content.toString())) {
continue;
} else {
file.delete();
}
} catch (final Exception e) {
e.printStackTrace();
}
final JavaFileObject src = filer.createSourceFile(genClass);
final Writer out = src.openWriter();
out.write(code);
generatedMirrors.put(key, null);
out.close();
} catch (final Exception e) {
e.printStackTrace();
log("Unable to write annotation reflection for " + key + ";");
}
}
return roundEnv.processingOver();
}
private void generateJavaxFactory(final TypeElement el, final AnnotationManifest manifest) {
}
private void generateXapiFactory(final TypeElement el, final AnnotationManifest manifest) {
}
private AnnotationManifest addAnnotation(final TypeElement element) {
final String annoName = element.getQualifiedName().toString();
if (generatedMirrors.containsKey(annoName)) {
return generatedMirrors.get(annoName);
}
final AnnotationManifest manifest = new AnnotationManifest(annoName);
generatedMirrors.put(annoName, manifest);
final PackageElement pkg = processingEnv.getElementUtils().getPackageOf(element);
final String simpleName = element.getSimpleName().toString();
final String builderName = simpleName + "Builder";
final SourceBuilder<Object> sb = new SourceBuilder<Object>(
"public class " + builderName);
final ClassBuffer annoBuilder = sb.getClassBuffer();
if (!pkg.isUnnamed()) {
sb.setPackage(pkg.getQualifiedName().toString());
}
// Create an immutable, private class that implements the annotation.
final ClassBuffer immutableAnno = annoBuilder
.addAnnotation("@SuppressWarnings(\"all\")")
.createInnerClass("private static class Immutable" + simpleName)
.addInterfaces(annoName).makeFinal();
immutableAnno
.createMethod(
"public Class<? extends java.lang.annotation.Annotation> annotationType()")
.returnValue(annoName + ".class");
final Fifo<String> requiredFields = new SimpleFifo<String>();
final Fifo<String> ctorParams = new SimpleFifo<String>();
final Types types = processingEnv.getTypeUtils();
final MethodBuffer addValue = annoBuilder.createMethod("public " + builderName + " addValue(String key, Object value)")
.println("switch(key){").indent();
for (final ExecutableElement method : ElementFilter.methodsIn(element
.getEnclosedElements())) {
final TypeMirror returnMirror = method.getReturnType();
final String fieldName = method.getSimpleName().toString();
final String fieldType = annoBuilder.addImport(returnMirror.toString());
final AnnotationValue dflt = method.getDefaultValue();
manifest.addMethod(method.getSimpleName(), returnMirror, dflt);
final FieldBuffer annoField = annoBuilder
.createField(returnMirror.toString(),
method.getSimpleName().toString(),
Modifier.PRIVATE);
int mod = Modifier.PUBLIC | Modifier.FINAL;
annoField.addGetter(mod);
final MethodBuffer setter = annoField.addSetter(mod);
if (fieldType.contains("[]")) {
setter.setParameters(fieldType.replace("[]", " ...") + " "+annoField.getName());
}
addValue
.println("case \"" + annoField.getName()+"\":")
.indentln(setter.getName() +"((" + returnMirror.toString()+ ")value);")
.indentln("break;")
;
final FieldBuffer field = immutableAnno
.createField(returnMirror.toString(),
method.getSimpleName().toString(),
Modifier.PRIVATE | Modifier.FINAL)
.setExactName(true);
field.addGetter(Modifier.PUBLIC | Modifier.FINAL);
final String param = field.getSimpleType() + " " + field.getName();
ctorParams.give(param);
if (dflt == null) {
requiredFields.give(param);
}
switch (returnMirror.getKind()) {
case DECLARED:
if (types.isAssignable(returnMirror, annoType)) {
addAnnotation((TypeElement) ((DeclaredType) returnMirror).asElement());
if (dflt != null) {
final AnnotationMirror value = (AnnotationMirror) dflt.getValue();
// use this annotation mirror to create a suitable factory for defaults
}
manifest.setAnnoType(annoName, fieldName);
} else if (types.isAssignable(returnMirror, classType)) {
} else if (types.isAssignable(returnMirror, stringType)) {
} else if (types.isAssignable(returnMirror, enumType)) {
}
break;
case ARRAY:
final TypeMirror component = ((ArrayType) returnMirror).getComponentType();
// System.out.println(component);
// System.out.println(classType);
if (types.isAssignable(component, annoType)) {
// gross.
addAnnotation((TypeElement) ((DeclaredType) component).asElement());
// manifest.setArrayOfAnnos(name)
} else if (types.isAssignable(component, classType)) {
} else if (types.isAssignable(component, stringType)) {
} else if (types.isAssignable(component, enumType)) {
} else {
switch (component.getKind()) {
case BOOLEAN:
case BYTE:
case CHAR:
case SHORT:
case INT:
case FLOAT:
case LONG:
case DOUBLE:
break;
default:
throw new IllegalArgumentException("Unsupported type: " + component
+ " of " + method);
}
}
break;
case BOOLEAN:
case BYTE:
case CHAR:
case SHORT:
case INT:
case FLOAT:
case LONG:
case DOUBLE:
break;
default:
throw new IllegalArgumentException("Unsupported type: " + returnMirror
+ " of " + method);
}
// field.setInitializer(dflt.toString());
}
addValue.println("default:")
.indent()
.println("assert false : \"Unhandled type \" + key + \" for type \" + getClass();")
.throwException(IllegalArgumentException.class, "\"Invalid key: \"+key")
.outdent()
.println("}")
.returnValue("this")
;
if (requiredFields.size() == 0) {
// With no required fields,
// We can create a zero-arg constructor and static accessor method
annoBuilder.createMethod(
"public static " + builderName + " build" + simpleName + "()")
.returnValue("new " + builderName + "()");
annoBuilder.createMethod("private " + builderName + "()");
} else {
final SimpleFifo<String> joinable = new SimpleFifo<String>();
final MethodBuffer ctor = annoBuilder.createMethod("private " + builderName
+ "(" + requiredFields.join(", ") + ")");
for (String field : requiredFields.forEach()) {
field = field.substring(field.lastIndexOf(' ') + 1);
joinable.give(field);
ctor.println("this." + field + " = " + field + ";");
}
annoBuilder.createMethod(
"public static " + builderName + " build" + simpleName + "("
+ requiredFields.join(", ") + ")").returnValue(
"new " + builderName + "(" + joinable.join(", ") + ")");
}
if (ctorParams.size() > 0) {
final MethodBuffer ctor = immutableAnno.createMethod("private "
+ immutableAnno.getSimpleName() + "()");
final MethodBuffer build = annoBuilder.createMethod("public " + simpleName
+ " build()");
final SimpleFifo<String> fieldRefs = new SimpleFifo<String>();
for (final String param : ctorParams.forEach()) {
final int end = param.lastIndexOf(' ');
ctor.addParameters(param);
final String paramName = param.substring(end + 1);
fieldRefs.give(paramName);
ctor.println("this." + paramName + " = " + paramName + ";");
}
build.returnValue("new Immutable" + simpleName + "("
+ fieldRefs.join(", ") + ")");
}
log(sb.toString());
manifest.generated = sb.toString();
return manifest;
}
private void log(final String string) {
if (X_Runtime.isDebug()) {
System.out.println(string);
}
}
}
/**
* Rather than parse through the clunky javax.lang.model,
* we create a simple, ugly, but straightforward collection of
* all of the fields of a known type into the annotation manifest object.
*
* If a given field type is present, the corresponding type map will have a
* key for the name of the field, and a value string to refine the given
* type (like specifying the generic bounds of a class, the subclass of Enum,
* or, in the case of other annotations, the name of a static, runtime method that
* produces the given annotation value.
*
* @author "James X. Nelson (james@wetheinter.net)"
*
*/
class AnnotationManifest {
AnnotationManifest(final String name) {
this.name = name;
}
public void setAnnoType(final String annoName, final String fieldName) {
if (annotationFields == null) {
annotationFields = new HashMap<String, String>();
}
annotationFields.put(fieldName, annoName);
}
public void addMethod(final Name simpleName, final TypeMirror returnMirror,
final AnnotationValue dflt) {
if (dflt != null) {
defaultValues.put(simpleName.toString(), dflt);
}
}
String name;
String generated;
final HashMap<String, AnnotationValue> defaultValues = new HashMap<String, AnnotationValue>();
HashMap<String, String> annotationFields;
HashMap<String, String> annotationArrayFields;
HashMap<String, String> classFields;
HashMap<String, String> classArrayFields;
HashMap<String, String> enumFields;
HashMap<String, String> enumArrayFields;
HashMap<String, String> stringFields;
HashMap<String, String> stringArrayFields;
HashMap<String, String> primitiveFields;
HashMap<String, String> primitivArrayFields;
}