package org.bindgen.processor.generators; import static org.bindgen.processor.CurrentEnv.*; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Writer; import java.util.Set; import java.util.TreeSet; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.tools.Diagnostic.Kind; import javax.tools.FileObject; import javax.tools.JavaFileObject; import javax.tools.StandardLocation; import joist.sourcegen.GClass; import joist.sourcegen.GMethod; import joist.util.Join; import org.bindgen.Binding; import org.bindgen.processor.GenerationQueue; import org.bindgen.processor.Processor; import org.bindgen.processor.util.BoundClass; import org.bindgen.processor.util.ClassName; /** Generates a BindKeyword class with "bind" static helper methods. * * The idea is that by static importing <code>BindKeyword.bind</code>, * client code could use the short cut <code>bind(someInstance)</code> * to get a {@link Binding} for that object without typing out the full * <code>new SomeInstanceBinding(someInstance)</code>. * * This requires some gymnastics because in the Eclipse environment, * the {@link Processor} instance is recreated for * each save/processing cycle. So we have to cache all of the bindings * we've seen on the file system we can persist them across processor * instances. */ public class BindKeywordGenerator { private static final String PACKAGE_NAME = "org.bindgen"; private static final String CLASS_NAME = "BindKeyword"; private final GenerationQueue queue; private final GClass bindClass = new GClass(PACKAGE_NAME + "." + CLASS_NAME); private final Set<String> classNames = new TreeSet<String>(); /** @param queue the {@link GenerationQueue} only used for logging */ public BindKeywordGenerator(GenerationQueue queue) { this.queue = queue; } /** Adds/updates <code>bind</code> methods to the <code>BindKeyword</code> class for each of the <code>newlyWritten</code> class names. */ public void generate(Set<String> newlyWritten) { this.readClassNamesFromBindKeywordFileIfExists(); this.classNames.addAll(newlyWritten); this.addBindMethods(); this.addSuppressWarnings(); this.writeBindKeywordFile(); this.writeBindKeywordClass(); } private void addSuppressWarnings() { this.bindClass.addAnnotation("@SuppressWarnings(\"all\")"); } private void addBindMethods() { for (String className : this.classNames) { TypeElement e = getElementUtils().getTypeElement(className); if (e == null) { continue; } if (getElementUtils().getPackageOf(e).isUnnamed()) { continue; } this.addBindMethod(className, (DeclaredType) e.asType()); } } private void addBindMethod(String className, DeclaredType type) { ClassName bindingType = new BoundClass((TypeElement) getTypeUtils().asElement(type)).getBindingClassName(); this.queue.log("Adding " + className + ", " + type + ", " + bindingType.get()); if (type.getTypeArguments().size() > 0) { GMethod method = this.bindClass.getMethod("bind({}<{}> o)", className, Join.commaSpace(bindingType.getGenericPartWithoutBrackets())); method.returnType("{}", bindingType); method.typeParameters(Join.commaSpace(new ClassName(type.toString()).getGenericsWithBounds())); method.setStatic(); method.body.line("return new {}(o);", bindingType); } else { GMethod method = this.bindClass.getMethod("bind({} o)", className); method.returnType(bindingType.toString()); method.setStatic(); method.body.line("return new {}(o);", bindingType); } } /** Finds class names cached in <code>SOURCE_OUTPUT/BindKeyword.txt</code>, if it exists, and adds them to <code>this.classNames</code>. */ private void readClassNamesFromBindKeywordFileIfExists() { try { this.queue.log("READING " + CLASS_NAME + ".txt"); FileObject fo = getFiler().getResource(StandardLocation.SOURCE_OUTPUT, PACKAGE_NAME, CLASS_NAME + ".txt"); if (fo.getLastModified() > 0) { String line; BufferedReader input = new BufferedReader(new InputStreamReader(fo.openInputStream())); while ((line = input.readLine()) != null) { this.classNames.add(line); } input.close(); this.queue.log("WAS THERE"); } else { this.queue.log("NOT THERE"); } } catch (IOException io) { getMessager().printMessage(Kind.ERROR, io.getMessage()); } } private void writeBindKeywordFile() { try { this.queue.log("WRITING " + CLASS_NAME + ".txt"); FileObject fo = getFiler().createResource(StandardLocation.SOURCE_OUTPUT, PACKAGE_NAME, CLASS_NAME + ".txt"); OutputStream output = fo.openOutputStream(); for (String className : this.classNames) { output.write(className.getBytes()); output.write("\n".getBytes()); } output.close(); } catch (IOException io) { this.queue.log("ERROR: " + io.getMessage()); getMessager().printMessage(Kind.ERROR, io.getMessage()); } } private void writeBindKeywordClass() { try { this.queue.log("WRITING " + CLASS_NAME + ".java"); JavaFileObject jfo = getFiler().createSourceFile(this.bindClass.getSimpleName()); Writer w = jfo.openWriter(); w.write(this.bindClass.toCode()); w.close(); } catch (IOException io) { this.queue.log("ERROR: " + io.getMessage()); getMessager().printMessage(Kind.ERROR, io.getMessage()); } } }