package xapi.javac.dev.processor; import com.github.javaparser.JavaParser; import com.github.javaparser.ParseException; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.plugin.Transformer; import com.sun.source.tree.ClassTree; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; import com.sun.source.util.Trees; import com.sun.tools.javac.code.Attribute.Compound; import com.sun.tools.javac.code.Scope; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.util.List; import xapi.annotation.api.XApi; import xapi.except.NotYetImplemented; import xapi.fu.Pointer; import xapi.javac.dev.api.JavacService; import xapi.javac.dev.model.InjectionBinding; import xapi.javac.dev.model.XApiInjectionConfiguration; import xapi.javac.dev.search.InjectionTargetSearchVisitor; import xapi.javac.dev.template.TemplateTransformer; import xapi.javac.dev.util.ElementUtil; import xapi.source.X_Source; import xapi.util.X_Debug; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.inject.Inject; import javax.lang.model.SourceVersion; 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.util.ElementFilter; import javax.lang.model.util.ElementKindVisitor8; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.Diagnostic.Kind; import javax.tools.FileObject; import javax.tools.JavaFileManager.Location; import javax.tools.JavaFileObject; import javax.tools.StandardLocation; import java.io.IOException; import java.io.Writer; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.Optional; import java.util.Set; /** * @author James X. Nelson (james@wetheinter.net) * Created on 3/13/16. */ @SupportedAnnotationTypes({ "xapi.annotation.api.XApi" }) @SupportedSourceVersion(SourceVersion.RELEASE_8) public class XapiAnnotationProcessor extends AbstractProcessor { private Elements elements; private Filer filer; private Types types; private Messager log; private Set<XApiInjectionConfiguration> annotatedTypes = new LinkedHashSet<>(); private JavacService service; private Trees trees; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); elements = processingEnv.getElementUtils(); filer = processingEnv.getFiler(); types = processingEnv.getTypeUtils(); log = processingEnv.getMessager(); service = JavacService.instanceFor(processingEnv); trees = Trees.instance(processingEnv); } @Override public boolean process( Set<? extends TypeElement> annotations, RoundEnvironment roundEnv ) { annotations.forEach(annotation->roundEnv.getElementsAnnotatedWith(annotation).forEach(element->{ XApi xapi = element.getAnnotation(XApi.class); if (xapi.templates().length > 0) { for (String path : xapi.templates()) { String pkg = ""; if (path.startsWith("./")) { path = path.substring(2); final PackageElement packageElement = elements.getPackageOf(element); if (packageElement != null) { pkg = packageElement.getQualifiedName().toString(); } } int index = path.indexOf('/'); while (index != -1) { String subpath = path.substring(0, index); path = path.substring(index+1); index = path.indexOf('/'); if (pkg.isEmpty()) { pkg = subpath; } else { pkg = pkg + "." + subpath; } } if ("*".equals(path)) { // match everything in this package throw new NotYetImplemented("* file pattern matching not yet supported by XApi annotation processor."); } else { // An actual filename. final Location location = StandardLocation.SOURCE_PATH; final FileObject resource; if (path.indexOf('.') == -1) { path = path + ".xapi"; } try { resource = filer.getResource(location, pkg, path); final CompilationUnit result = JavaParser.parse( resource.openInputStream(), Charset.defaultCharset().name() ); Transformer transformer = new TemplateTransformer(); String source = result.toSource(transformer); final JavaFileObject file = filer.createSourceFile(X_Source.qualifiedName( pkg, path.replace(".xapi", "") // remove file extension ), element); try (Writer out = file.openWriter()) { out.append(source); } } catch (IOException | ParseException e) { throw X_Debug.rethrow(e); } } } } if (Math.random() > -1) { return; } final CompilationUnitTree cup = null; // TODO possibly delete this... :-/ processAnnotation(cup, element); })); if (roundEnv.processingOver()) { // we are done collecting up XApi annotations; lets generate some code! annotatedTypes.forEach(type->generate(null, type)); } return true; } private void processAnnotation(CompilationUnitTree cup, Element element) { final XApi xapi = element.getAnnotation(XApi.class); switch (element.getKind()) { case PACKAGE: // A package-level annotation will be applied, as a default, to all members of that package. // Any @XApi annotated types will override this default for this package throw new UnsupportedOperationException("XApi annotations on packages not yet supported in generator"); case ANNOTATION_TYPE: // This will cause us to become interested in this new annotation type. // To handle this, we will need to add this annotation to a list of types to be scanned; // likely by generating an annotation processor and adding an entry to META-INF/services log.printMessage(Kind.WARNING, "Unhandled XApi annotations on annotation types not yet supported in generator. Element:"+element); break; case CLASS: case INTERFACE: case ENUM: final Name name = elements.getBinaryName((TypeElement) element); log.printMessage(Kind.NOTE, "Adding XApi annotated element "+element); annotatedTypes.add(new XApiInjectionConfiguration(xapi, name.toString(), element, cup)); break; default: throw new UnsupportedOperationException("Unsupported location of XApi annotation ("+element.getKind()+") : " + element); } } private void generate(CompilationUnitTree cup, XApiInjectionConfiguration config) { final Element type = config.getElement(); final ClassTree tr = trees.getTree((TypeElement) type); final java.util.List<InjectionBinding> results = new InjectionTargetSearchVisitor(service, cup) .scan(trees.getTree(type), new ArrayList<>()); trees.getTree(type); ElementFilter.fieldsIn(type.getEnclosedElements()) .stream().filter(ElementUtil.withAnnotation(Inject.class)) .forEach(field->{ // A field annotated with @Inject. Optional<InjectionBinding> binding = service.getInjectionBinding(config, field.asType()); if (binding.isPresent()) { // we have a valid injection result; insert it final List<Compound> attrs = ((VarSymbol) field).getRawAttributes(); Pointer<ExecutableElement> initializer = Pointer.pointer(); ClassSymbol t = (ClassSymbol) type; final Scope members = (((ClassSymbol) type).members()); final TreePath path = trees.getPath(type); if (path != null) path.forEach(tree->{ final Tree.Kind k = tree.getKind(); }); t.members().getElements().forEach(sym->{ log.printMessage(Kind.NOTE, "Symbol " + sym.getClass()+" : " + sym); }); type.accept(new ElementKindVisitor8<Void, Void>(){ @Override public Void visitExecutable(ExecutableElement e, Void aVoid) { initializer.in(e); return null; } }, null); } else { // ignore this field } }); } }