package org.bindgen.processor.generators; import static org.bindgen.processor.CurrentEnv.*; import java.io.IOException; import java.io.Writer; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.annotation.Generated; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic.Kind; import javax.tools.JavaFileObject; import joist.sourcegen.GClass; import joist.sourcegen.GMethod; import joist.util.Copy; 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.Util; /** Generates a <code>XxxBinding</code> class for a given {@link TypeElement}. * * Two classes are generated: one class is an abstract <code>XxxBindingPath</code> * which has a generic parameter <code>R</code> to present one part in * a binding evaluation path rooted at type a type <code>R</code>. * * The second class is the <code>XxxBinding</code> which extends its * <code>XxxBindingPath</code> but provides the type parameter <code>R</code> * as <code>Xxx</code>, meaning that <code>XxxBinding</code> can be * used as the starting point for binding paths rooted at a <code>Xxx</code>. */ public class BindingClassGenerator { private final GenerationQueue queue; private final TypeElement element; private final BoundClass name; private final List<String> foundSubBindings = new ArrayList<String>(); private final Set<Element> sourceElements = new HashSet<Element>(); private GClass pathBindingClass; private GClass rootBindingClass; public BindingClassGenerator(GenerationQueue queue, TypeElement element) { this.queue = queue; this.element = element; this.name = new BoundClass(element); } public void generate() { this.initializePathBindingClass(); this.addGetName(); this.addGetType(); this.addProperties(); this.addGetChildBindings(); this.initializeRootBindingClass(); this.addConstructors(); this.addGetWithRoot(); this.addGetSafelyWithRoot(); this.addGeneratedTimestamp(); this.addSerialVersionUID(); this.saveCode(this.pathBindingClass); this.saveCode(this.rootBindingClass); } private void initializePathBindingClass() { this.pathBindingClass = new GClass(this.name.getBindingPathClassDeclaration()); this.pathBindingClass.baseClassName(this.name.getBindingPathClassSuperClass()); this.pathBindingClass.setAbstract(); this.pathBindingClass.addAnnotation("@SuppressWarnings(\"all\")"); } private void initializeRootBindingClass() { this.rootBindingClass = new GClass(this.name.getBindingRootClassDeclaration()); this.rootBindingClass.baseClassName(this.name.getBindingRootClassSuperClass()); this.rootBindingClass.addAnnotation("@SuppressWarnings(\"all\")"); } private void addGetWithRoot() { GMethod getWithRoot = this.rootBindingClass.getMethod("getWithRoot").argument(this.name.get(), "root").returnType(this.name.get()); getWithRoot.body.line("return root;"); } private void addGetSafelyWithRoot() { GMethod getSafelyWithRoot = this.rootBindingClass.getMethod("getSafelyWithRoot").argument(this.name.get(), "root").returnType(this.name.get()); getSafelyWithRoot.body.line("return root;"); } private void addGeneratedTimestamp() { if (getConfig().skipGeneratedTimestamps()) { return; } String value = Processor.class.getName(); String date = new SimpleDateFormat("dd MMM yyyy hh:mm").format(new Date()); this.pathBindingClass.addImports(Generated.class); this.pathBindingClass.addAnnotation("@Generated(value = \"" + value + "\", date = \"" + date + "\")"); this.rootBindingClass.addImports(Generated.class); this.rootBindingClass.addAnnotation("@Generated(value = \"" + value + "\", date = \"" + date + "\")"); } private void addConstructors() { this.rootBindingClass.getConstructor(); this.rootBindingClass.getConstructor(this.name.get() + " value").body.line("this.set(value);"); } private void addGetName() { GMethod getName = this.pathBindingClass.getMethod("getName").returnType(String.class).addAnnotation("@Override"); getName.body.line("return \"\";"); } private void addGetType() { GMethod getType = this.pathBindingClass.getMethod("getType").returnType("Class<?>").addAnnotation("@Override"); getType.body.line("return {}.class;", this.element.toString()); } private void addProperties() { for (PropertyGenerator pg : this.getPropertyGenerators()) { pg.generate(); this.enqueuePropertyTypeIfNeeded(pg); this.addToSubBindingsIfNeeded(pg); } } private void enqueuePropertyTypeIfNeeded(PropertyGenerator pg) { for (TypeElement te : pg.getPropertyTypeElements()) { if (te != null && getConfig().shouldGenerateBindingFor(te)) { this.queue.enqueueIfNew(te); } } } private void addToSubBindingsIfNeeded(PropertyGenerator pg) { if (pg.hasSubBindings()) { this.foundSubBindings.add(pg.getPropertyName()); } } private void addGetChildBindings() { this.pathBindingClass.addImports(Binding.class, List.class); GMethod children = this.pathBindingClass.getMethod("getChildBindings").returnType("List<Binding<?>>").addAnnotation("@Override"); children.body.line("List<Binding<?>> bindings = new java.util.ArrayList<Binding<?>>();"); for (String foundSubBinding : this.foundSubBindings) { children.body.line("bindings.add(this.{}());", foundSubBinding); } children.body.line("return bindings;"); } private void saveCode(GClass gc) { try { JavaFileObject jfo = getFiler().createSourceFile(gc.getSimpleName(), Copy.array(Element.class, Copy.list(this.sourceElements))); Writer w = jfo.openWriter(); w.write(gc.toCode()); w.close(); this.queue.log("Saved " + gc.getSimpleName()); } catch (IOException io) { getMessager().printMessage(Kind.ERROR, io.getMessage(), this.element); } } private List<PropertyGenerator> getPropertyGenerators() { // factory ordering specifies binding precedence rules List<PropertyGenerator.GeneratorFactory> factories = new ArrayList<PropertyGenerator.GeneratorFactory>(); // these bindings will not mangle their property names factories.add(new MethodPropertyGenerator.Factory(AccessorPrefix.NONE)); factories.add(new MethodCallableGenerator.Factory()); // these bindings will try to drop their prefix and use a shorter name (e.g. getFoo -> foo) factories.add(new MethodPropertyGenerator.Factory(AccessorPrefix.GET)); factories.add(new MethodPropertyGenerator.Factory(AccessorPrefix.HAS)); factories.add(new MethodPropertyGenerator.Factory(AccessorPrefix.IS)); // the field binding will use its name or append Field if it was already taken by get/has/is factories.add(new FieldPropertyGenerator.Factory()); Set<String> namesTaken = new HashSet<String>(); namesTaken.add("getName"); namesTaken.add("getPath"); namesTaken.add("getType"); namesTaken.add("getParentBinding"); namesTaken.add("getChildBindings"); List<Element> elements = this.getAccessibleElements(); List<PropertyGenerator> generators = new ArrayList<PropertyGenerator>(); for (PropertyGenerator.GeneratorFactory f : factories) { for (Iterator<Element> i = elements.iterator(); i.hasNext();) { Element enclosed = i.next(); try { PropertyGenerator pg = f.newGenerator(this.pathBindingClass, this.element, enclosed, namesTaken); if (namesTaken.contains(pg.getPropertyName())) { continue; } else { namesTaken.add(pg.getPropertyName()); } i.remove(); // element is handled, skip any further generators generators.add(pg); this.sourceElements.add(enclosed); } catch (WrongGeneratorException e) { // try next } } } return generators; } private void addSerialVersionUID() { this.rootBindingClass.getField("serialVersionUID").type("long").setStatic().setFinal().initialValue("1L"); this.pathBindingClass.getField("serialVersionUID").type("long").setStatic().setFinal().initialValue("1L"); } private List<Element> getAccessibleElements() { List<Element> elements = new ArrayList<Element>(); for (Element enclosed : getElementUtils().getAllMembers(this.element)) { if (Util.isAccessibleIfGenerated(this.element, enclosed)) { elements.add(enclosed); } } return elements; } }