package xapi.mojo.model; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Execute; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.ResolutionScope; import xapi.annotation.reflect.MirroredAnnotation; import xapi.bytecode.ClassFile; import xapi.bytecode.impl.BytecodeAdapterService; import xapi.dev.scanner.impl.ClasspathResourceMap; import xapi.dev.source.ClassBuffer; import xapi.dev.source.MethodBuffer; import xapi.dev.source.SourceBuilder; import xapi.log.X_Log; import xapi.mojo.api.AbstractXapiMojo; import xapi.mvn.X_Maven; import xapi.source.X_Modifier; import xapi.source.api.IsAnnotation; import xapi.source.api.IsAnnotationValue; import xapi.source.api.IsClass; import xapi.source.api.IsMethod; import xapi.source.api.IsType; import xapi.source.api.Primitives; import xapi.util.X_Debug; import xapi.util.X_Namespace; import java.io.File; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * This goal will lookup all annotation that are themselves * annotated with {@link MirroredAnnotation}, and then * generate runtime annotation proxy classes to enable * creating instances of annotations at runtime, either * manually through AnnoNameBuilder objects, * or by wrapping {@link IsAnnotation} instances into AnnoNameProxy objects. * * @author <a href="mailto:james@wetheinter.net">James X. Nelson</a> * @version $Id$ */ @Mojo( name="annogen" ,aggregator=true ,defaultPhase = LifecyclePhase.GENERATE_SOURCES ,requiresProject=true ,requiresDependencyCollection=ResolutionScope.COMPILE_PLUS_RUNTIME ,threadSafe = true ) @Execute(phase=LifecyclePhase.PROCESS_CLASSES) public class AnnotationGeneratorMojo extends AbstractXapiMojo{ @Override @SuppressWarnings("unchecked") public void doExecute() throws MojoExecutionException, MojoFailureException { ClasspathResourceMap scanner = X_Maven.compileScopeScanner(getProject(), getSession()); BytecodeAdapterService adapter = X_Maven.compileScopeAdapter(getProject(), getSession()); Map<String, SourceBuilder<IsClass>> generated = new HashMap<String, SourceBuilder<IsClass>>(); X_Log.trace(getClass(), "Scanning compile scope dependencies", scanner); for (ClassFile mirror : scanner.findClassAnnotatedWith(MirroredAnnotation.class)) { X_Log.trace(getClass(), "Adding annotation type", mirror); addType(mirror.getName(), mirror.getPackage(), generated, adapter); } final String[] cp = getAdditionalClasspath(); X_Log.trace(getClass(), "Compiling generated annotations with classpath",cp); ArrayList<Runnable> compileTasks = new ArrayList<Runnable>(); for (String type : generated.keySet()) { final SourceBuilder<IsClass> builder = generated.get(type); final IsClass cls = builder.getPayload(); final String clsName = cls.getSimpleName().replace("[]", ""); ClassBuffer out = builder.getClassBuffer(); MethodBuffer factory = out .createMethod("public static "+clsName+" build()") .addParameters(IsAnnotation.class.getName()+" from") .println(IsAnnotationValue.class.getSimpleName()+" __value;") .println(clsName+"Proxy proxy = new "+clsName+"Proxy(from);") ; MethodBuffer ctor = out.createMethod("private "+clsName+"Proxy()") .addParameters(IsAnnotation.class.getName()+" from"); boolean firstDefault = true; for (IsMethod method : cls.getMethods()) { if (method.getEnclosingType().getQualifiedName().equals("java.lang.Object")) { continue; } String returnType = out.addImport(method.getReturnType().toString()); IsClass returnClass = adapter.toClass(method.getReturnType().getQualifiedName().replace("[]", "")); if (!method.getName().equals("annotationType")) { out .createField(returnType, method.getName()) .setExactName(true) .setModifier(X_Modifier.PRIVATE) .addGetter(X_Modifier.PUBLIC | X_Modifier.FINAL) ; IsAnnotationValue value = method.getDefaultValue(); if (value != null) { // Set field default value if (firstDefault) { ctor.println(IsAnnotationValue.class.getSimpleName()+" __value;"); firstDefault = false; } ctor.println("__value = from.getDefaultValue(from.getMethod(\"" +method.getName()+"\"));"); if (method.getReturnType().getSimpleName().contains("[]")) { // TODO implement array builders } else { } ctor.println("this."+method.getName()+" = "+ getExtractor(out, returnClass, method, returnType)+";"); } // Add field setter in main factory method. // IsAnnotation.getValue() will return the default value if not set. factory.println("__value = from.getValue(from.getMethod(\"" +method.getName()+"\"));"); factory.println("proxy."+method.getName()+" = " + getExtractor(out, returnClass, method, returnType)+";"); } } factory.returnValue("proxy"); // Saves the source file to generated-sources directory, and returns a compile job String javaName = cls.getQualifiedName().replace("[]", "")+"Proxy"; String source = builder.toString(); File file = saveModel(javaName, source, true); // compileTasks.add(prepareCompile(file, javaName, source, true, cp)); } ArrayList<Thread> runningTasks = new ArrayList<Thread>(); // Actually run the compile. We defer the compile until all source is generated. for (Runnable task : compileTasks) { Thread t = new Thread(task); t.setDaemon(true); t.start(); runningTasks.add(t); } final long deadline = System.currentTimeMillis()+10000; while (!runningTasks.isEmpty()) { for (Iterator<Thread> iter = runningTasks.iterator();iter.hasNext();) { if (!iter.next().isAlive()) { iter.remove(); } } try { Thread.sleep(50); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw X_Debug.rethrow(e); } if (System.currentTimeMillis() > deadline) { throw new MojoExecutionException("Failed to compile annotation in 10 seconds."); } } } private String getExtractor(ClassBuffer out, IsClass returnClass, IsMethod method, String returnType) { if (returnClass.isAnnotation()) { String proxyType = out.addImport(returnClass.getQualifiedName()+"Proxy"); return proxyType+".build((IsAnnotation)__value.getRawValue())"; } else if (returnClass instanceof Primitives) { Primitives primitive = (Primitives) returnClass; return "(" +primitive.getObjectName()+")__value.getRawValue()"; } else { return "(" +returnType+")__value.getRawValue()"; } } private String[] getAdditionalClasspath() { return new String[]{ findArtifact("net.wetheinter", "xapi-dev-bytecode", "jar", X_Namespace.XAPI_VERSION) ,findArtifact("net.wetheinter", "xapi-core-reflect", "jar", X_Namespace.XAPI_VERSION) }; } private void addType(String annoName, String annoPackage, Map<String, SourceBuilder<IsClass>> generated, BytecodeAdapterService adapter) { SourceBuilder<IsClass> generator = generated.get(annoName); if (generator == null) { IsClass anno = adapter.toClass(annoName); generator = new SourceBuilder<IsClass>("public final class "+anno.getSimpleName()+"Proxy"); generator .setPackage(annoPackage) .setPayload(anno) .getClassBuffer() .addInterface(annoName) .addImports(Annotation.class, IsAnnotation.class, IsAnnotationValue.class) .addAnnotation("@SuppressWarnings(\"all\")")// Ugly, but we don't need warnings from generated code .addAnnotation(generatedAnnotation()) ; String clsName = generator.getImports().addImport(anno.getQualifiedName()); generated.put(annoName, generator); for (IsMethod method : anno.getMethods()) { if (method.getEnclosingType().getQualifiedName().equals("java.lang.Object")) { continue; } if (method.getName().equals("annotationType")) { generator.getClassBuffer() .createMethod("public Class<? extends Annotation> annotationType()") .setModifier(X_Modifier.PUBLIC_FINAL) .returnValue(clsName+".class"); } IsType ret = method.getReturnType(); IsClass retClass = adapter.toClass(ret.getQualifiedName()); if (retClass.isAnnotation()) { addType(retClass.getQualifiedName().replace("[]", ""), retClass.getPackage(), generated, adapter); } } } } }