/******************************************************************************* * Copyright (c) 2008 Scott Stanchfield. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Scott Stanchfield - initial API and implementation *******************************************************************************/ package com.javadude.annotation.processors; import java.io.PrintWriter; import java.util.Iterator; import java.util.List; public class Generator { private static final String PADDING = " "; private Symbols symbols_ = new Symbols(); private final PrintWriter writer_; private final Data data_; public Generator(PrintWriter writer, Data data) { writer_ = writer; data_ = data; symbols_ = new Symbols(data.createPropertyMap()); } private String fill(String template) { while (true) { int start = template.indexOf("${"); if (start == -1) { break; } int end = template.indexOf('}', start + 2); if (end == -1) { throw new RuntimeException("Template has mismatched ${...}"); } String field = template.substring(start + 2, end); Object value = symbols_.get(field,-1); if (value == null) { throw new RuntimeException("${" + field + "} defined in template not found"); } if (end == template.length()) { template = template.substring(0, start) + value; } else { template = template.substring(0, start) + value + template.substring(end + 1); } } return template; } private void gl(boolean include, String line) { if (include) { gl(line); } } private void gp(boolean include, String line) { if (include) { gp(line); } } private void gl(String line) { gp(line); writer_.println(); } private void gp(String line) { // TODO: check that we have enough room in the PADDING string - increase if necessary if (data_.getSpacesForLeadingTabs() > 0) { int i = 0; while (i < line.length() && line.charAt(i) == '\t') { i++; } if (i > 0) { line = Generator.PADDING.substring(0, i * data_.getSpacesForLeadingTabs()) + line.substring(i); } } writer_.print(fill(line)); } private abstract class ForEach<T extends Pushable> { protected boolean isLast() { return last_; } protected boolean isFirst() { return first_; } private boolean last_ = false; private boolean first_ = true; public ForEach(List<T> items) { for (Iterator<T> i = items.iterator(); i.hasNext();) { try { T item = i.next(); last_ = !i.hasNext(); symbols_.pushScope(item.createPropertyMap()); go(item); first_ = false; } finally { symbols_.popScope(); } } } protected abstract void go(T item); } public void generate() { gl( "// CODE GENERATED BY JAVADUDE BEAN ANNOTATION PROCESSOR"); gl( "// -- DO NOT EDIT - THIS CODE WILL BE REGENERATED! --"); gl( "package ${packageName};"); // class definition gl( "@javax.annotation.Generated("); gl( " value = \"com.javadude.annotation.processors.BeanAnnotationProcessor\","); gl( " date = \"${date}\","); gl( " comments = \"CODE GENERATED BY JAVADUDE BEAN ANNOTATION PROCESSOR; DO NOT EDIT! THIS CODE WILL BE REGENERATED!\")"); gl( "@java.lang.SuppressWarnings(\"all\")"); gp( "${classAccess}abstract class ${className}Gen "); gp(data_.getSuperclass() != null, "extends ${superclass} "); gp(data_.isCloneable(), "implements Cloneable "); gl( "{"); new ForEach<DelegateSpec>(data_.getDelegates()) { @Override protected void go(DelegateSpec item) { gl(item.isNeedToDefine(), " private ${name} ${accessor};"); }}; gl( " public ${className}Gen(${superConstructorArgs}) {"); gl(data_.getSuperConstructorSuperCall() != null," ${superConstructorSuperCall};"); new ForEach<DelegateSpec>(data_.getDelegates()) { @Override protected void go(DelegateSpec item) { gl(item.getInstantiateType() != null, " ${accessor} = new ${instantiateType}();"); // TODO better specification of instance creation - might depend on properties... lazy instantiate? }}; gl( " }"); gl(data_.isCloneable(), " public ${className} clone() {"); gl(data_.isCloneable(), " try {"); gl(data_.isCloneable(), " return (${className}) super.clone();"); gl(data_.isCloneable(), " } catch (CloneNotSupportedException e) {"); gl(data_.isCloneable(), " return null; // cannot happen"); gl(data_.isCloneable(), " }"); gl(data_.isCloneable(), " }"); // If any bound properties exist, add PropertyChangeSupport and delegate methods for it gl(data_.isAtLeastOneBound(), " private java.beans.PropertyChangeSupport propertyChangeSupport_ = new java.beans.PropertyChangeSupport(this);"); gl(data_.isAtLeastOneBound(), " protected java.beans.PropertyChangeSupport getPropertyChangeSupport() {"); gl(data_.isAtLeastOneBound(), " return propertyChangeSupport_;"); gl(data_.isAtLeastOneBound(), " }"); gl(data_.isAtLeastOneBound(), " public void addPropertyChangeListener(java.beans.PropertyChangeListener listener) {"); gl(data_.isAtLeastOneBound(), " getPropertyChangeSupport().addPropertyChangeListener(listener);"); gl(data_.isAtLeastOneBound(), " }"); gl(data_.isAtLeastOneBound(), " public void addPropertyChangeListener(java.lang.String propertyName, java.beans.PropertyChangeListener listener) {"); gl(data_.isAtLeastOneBound(), " getPropertyChangeSupport().addPropertyChangeListener(propertyName, listener);"); gl(data_.isAtLeastOneBound(), " }"); gl(data_.isAtLeastOneBound(), " public void removePropertyChangeListener(java.beans.PropertyChangeListener listener) {"); gl(data_.isAtLeastOneBound(), " getPropertyChangeSupport().removePropertyChangeListener(listener);"); gl(data_.isAtLeastOneBound(), " }"); gl(data_.isAtLeastOneBound(), " public void removePropertyChangeListener(java.lang.String propertyName, java.beans.PropertyChangeListener listener) {"); gl(data_.isAtLeastOneBound(), " getPropertyChangeSupport().removePropertyChangeListener(propertyName, listener);"); gl(data_.isAtLeastOneBound(), " }"); final boolean define = data_.isDefinePropertyNameConstants(); final boolean extend = data_.isExtendPropertyNameConstants(); final String sup = data_.getSuperclass(); gp(define, " public interface PropertyNames"); gp(define && extend && sup != null, " extends " + sup + ".PropertyNames"); gl(define, " {"); new ForEach<PropertySpec>(data_.getProperties()) { @Override protected void go(PropertySpec property) { gl(define, " static final String ${name} = \"${name}\";"); }}; gl(define, " }"); new ForEach<PropertySpec>(data_.getProperties()) { @Override protected void go(PropertySpec property) { boolean readable = property.isReadable(); boolean writeable = property.isWriteable(); boolean notNull = property.isNotNull(); boolean bound = property.isBound(); boolean simple = property.getKind().isSimple(); boolean list = property.getKind().isList(); boolean set = property.getKind().isSet(); boolean map = property.getKind().isMap(); if ("boolean".equals(property.getType())) { symbols_.pushScope("isOrGet", "is"); } else { symbols_.pushScope("isOrGet", "get"); } // field definitions gl(simple, " private ${extraFieldKeywords}${type} ${name}_;"); gl(list, " private ${extraFieldKeywords}final java.util.List<${type}> ${pluralName}_ = new java.util.ArrayList<${type}>();"); gl(set, " private ${extraFieldKeywords}final java.util.Set<${type}> ${pluralName}_ = new java.util.HashSet<${type}>();"); gl(map, " private ${extraFieldKeywords}final java.util.Map<${keyType}, ${type}> ${pluralName}_ = new java.util.HashMap<${keyType}, ${type}>();"); // getter and setter definitions for simple properties gl(simple && readable, " ${readerAccess}${extraMethodKeywords}${type} ${isOrGet}${upper:name}() {"); gl(simple && readable, " return ${name}_;"); gl(simple && readable, " }"); gl(simple && writeable, " ${writerAccess}${extraMethodKeywords}void set${upper:name}(${type} value) {"); gl(simple && writeable && notNull, " if (value == null) throw new IllegalArgumentException(\"${name} cannot be null\");"); gl(simple && writeable && bound, " ${type} oldValue = ${name}_;"); gl(simple && writeable, " ${name}_ = value;"); gl(simple && writeable && bound, " getPropertyChangeSupport().firePropertyChange(\"${name}\", oldValue, value);"); gl(simple && writeable, " }"); // getter and setter definitions for list properties gl(list && readable, " ${readerAccess}${extraMethodKeywords}${type} ${isOrGet}${upper:name}(int i) {"); gl(list && readable, " return ${pluralName}_.get(i);"); gl(list && readable, " }"); gl(list && readable, " ${readerAccess}${extraMethodKeywords}java.util.List<${type}> get${upper:pluralName}() {"); gl(list && readable, " return ${unmodPrefix}${pluralName}_${unmodSuffix};"); gl(list && readable, " }"); gl(list && writeable, " ${writerAccess}${extraMethodKeywords}${type} remove${upper:name}(int i) {"); gl(list && writeable, " ${type} result = ${pluralName}_.remove(i);"); gl(list && writeable && bound, " getPropertyChangeSupport().firePropertyChange(\"${pluralName}\", null, ${pluralName}_);"); gl(list && writeable, " return result;"); gl(list && writeable, " }"); gl(list && writeable, " ${writerAccess}${extraMethodKeywords}void add${upper:name}(int i, ${type} value) {"); gl(list && writeable, " if (value == null) throw new IllegalArgumentException(\"Cannot add null to ${name}\");"); gl(list && writeable, " ${pluralName}_.add(i, value);"); gl(list && writeable && bound, " getPropertyChangeSupport().firePropertyChange(\"${pluralName}\", null, ${pluralName}_);"); gl(list && writeable, " }"); // getter and setter definitions for set properties gl(set && readable, " ${readerAccess}${extraMethodKeywords}java.util.Set<${type}> get${upper:pluralName}() {"); gl(set && readable, " return ${unmodPrefix}${pluralName}_${unmodSuffix};"); gl(set && readable, " }"); // getter and setter definitions for set OR list properties gl((list || set) && readable, " ${readerAccess}${extraMethodKeywords}boolean ${pluralName}Contains(${type} value) {"); gl((list || set) && readable, " return ${pluralName}_.contains(value);"); gl((list || set) && readable, " }"); gl((list || set) && writeable, " ${writerAccess}${extraMethodKeywords}void add${upper:name}(${type} value) {"); gl((list || set) && writeable, " if (value == null) throw new IllegalArgumentException(\"Cannot add null to ${name}\");"); gl((list || set) && writeable, " ${pluralName}_.add(value);"); gl((list || set) && writeable && bound, " getPropertyChangeSupport().firePropertyChange(\"${pluralName}\", null, ${pluralName}_);"); gl((list || set) && writeable, " }"); gl((list || set) && writeable, " ${writerAccess}${extraMethodKeywords}boolean remove${upper:name}(${type} value) {"); gl((list || set) && writeable, " if (value == null) throw new IllegalArgumentException(\"Cannot remove null from ${name}\");"); gl((list || set) && writeable, " boolean result = ${pluralName}_.remove(value);"); gl((list || set) && writeable && bound, " getPropertyChangeSupport().firePropertyChange(\"${pluralName}\", null, ${pluralName}_);"); gl((list || set) && writeable, " return result;"); gl((list || set) && writeable, " }"); gl((list || set) && writeable, " ${writerAccess}${extraMethodKeywords}void clear${upper:pluralName}() {"); gl((list || set) && writeable, " ${pluralName}_.clear();"); gl((list || set) && writeable && bound, " getPropertyChangeSupport().firePropertyChange(\"${pluralName}\", null, ${pluralName}_);"); gl((list || set) && writeable, " }"); // getter and setter definitions for map properties gl(map && readable, " ${readerAccess}${extraMethodKeywords}${type} get${upper:name}(${keyType} key) {"); gl(map && readable, " return ${pluralName}_.get(key);"); gl(map && readable, " }"); gl(map && readable, " ${readerAccess}${extraMethodKeywords}java.util.Map<${keyType}, ${type}> get${upper:pluralName}() {"); gl(map && readable, " return ${unmodPrefix}${pluralName}_${unmodSuffix};"); gl(map && readable, " }"); gl(map && readable, " ${readerAccess}${extraMethodKeywords}boolean ${pluralName}ContainsKey(${keyType} key) {"); gl(map && readable, " return ${pluralName}_.containsKey(key);"); gl(map && readable, " }"); gl(map && readable, " ${readerAccess}${extraMethodKeywords}boolean ${pluralName}ContainsValue(${type} value) {"); gl(map && readable, " return ${pluralName}_.containsValue(value);"); gl(map && readable, " }"); gl(map && writeable, " ${writerAccess}${extraMethodKeywords}void put${upper:name}(${keyType} key, ${type} value) {"); gl(map && writeable, " if (key == null) throw new IllegalArgumentException(\"Cannot put null key in ${name}\");"); gl(map && writeable, " if (value == null) throw new IllegalArgumentException(\"Cannot put null value in ${name}\");"); gl(map && writeable, " ${pluralName}_.put(key, value);"); gl(map && writeable && bound, " getPropertyChangeSupport().firePropertyChange(\"${pluralName}\", null, ${pluralName}_);"); gl(map && writeable, " }"); gl(map && writeable, " ${writerAccess}${extraMethodKeywords}${type} remove${upper:name}(${keyType} key) {"); gl(map && writeable, " if (key == null) throw new IllegalArgumentException(\"Cannot remove null key from ${name}\");"); gl(map && writeable, " ${type} result = ${pluralName}_.remove(key);"); gl(map && writeable && bound, " getPropertyChangeSupport().firePropertyChange(\"${pluralName}\", null, ${pluralName}_);"); gl(map && writeable, " return result;"); gl(map && writeable, " }"); gl(map && writeable, " ${writerAccess}${extraMethodKeywords}void clear${upper:pluralName}() {"); gl(map && writeable, " ${pluralName}_.clear();"); gl(map && writeable && bound, " getPropertyChangeSupport().firePropertyChange(\"${pluralName}\", null, ${pluralName}_);"); gl(map && writeable, " }"); symbols_.popScope(); // pop the isOrGet scope }}; // end foreach property spec // define default methods new ForEach<Method>(data_.getDefaultMethods()) { @Override protected void go(Method method) { boolean returns = !"void".equals(method.getReturnType()); gl(method.isAbstract(), " ${access}abstract ${returnType} ${name}(${argDecls})${throwsClause};"); gl(!method.isAbstract(), " ${access} ${returnType} ${name}(${argDecls})${throwsClause} {"); gp(!method.isAbstract() && returns, " return "); gl(!method.isAbstract(), " ${name}(${args});"); gl(!method.isAbstract(), " }"); }}; // define delegate methods new ForEach<DelegateSpec>(data_.getDelegates()) { @Override protected void go(final DelegateSpec delegate) { new ForEach<Method>(delegate.getMethods()) { @Override protected void go(Method method) { boolean returns = !"void".equals(method.getReturnType()); gl( " public ${returnType} ${name}(${argDecls})${throwsClause} {"); gp( " "); gp(returns, "return "); gl( "${accessor}.${name}(${args});"); // accessor is from DelegateSpec gl( " }"); }}; }}; // define null object implementations new ForEach<Type>(data_.getNullImplementations()) { @Override protected void go(final Type nullObject) { new ForEach<Method>(nullObject.getMethods()) { @Override protected void go(Method method) { gl( " public ${returnType} ${name}(${argDecls})${throwsClause} {"); gl( " ${nullBody}"); gl( " }"); }}; }}; // Define observer management and fire methods // TODO - what if the methods are declared to throw exceptions (like PropertyVetoException)? new ForEach<Type>(data_.getObservers()) { @Override protected void go(final Type observer) { gl( " private java.util.List<${name}> ${stripPackage:lower:name}s_ = new java.util.ArrayList<${name}>();"); gl( " public void add${stripPackage:name}(${name} listener) {"); gl( " synchronized(${stripPackage:lower:name}s_) {"); gl( " ${stripPackage:lower:name}s_.add(listener);"); gl( " }"); gl( " }"); gl( " public void remove${stripPackage:name}(${name} listener) {"); gl( " synchronized(${stripPackage:lower:name}s_) {"); gl( " ${stripPackage:lower:name}s_.remove(listener);"); gl( " }"); gl( " }"); new ForEach<Method>(observer.getMethods()) { @Override protected void go(Method method) { gl( " protected void fire${upper:name}(${argDecls}) {"); gl( " java.util.List<${parent:name}> targets = null;"); gl( " synchronized(${parent:stripPackage:lower:name}s_) {"); gl( " targets = new java.util.ArrayList<${parent:name}>(${parent:stripPackage:lower:name}s_);"); gl( " }"); gl( " for (${parent:name} listener : targets) {"); gl( " listener.${name}(${args});"); gl( " }"); gl( " }"); }}; }}; // Define simple equals() and hashCode() methods if (data_.isDefineSimpleEqualsAndHashCode()) { gl( " protected boolean checkEquals(java.lang.Object o1, java.lang.Object o2) {"); gl( " if (o1 == o2) return true;"); gl( " if (o1 == null || o2 == null) return false;"); gl( " return o1.equals(o2);"); gl( " }"); gl( " public boolean equals(java.lang.Object obj) {"); gl( " if (obj == this) return true;"); gl(data_.isEqualsShouldCheckSuperEquals(), " if (obj == null || obj.getClass() != getClass() || !super.equals(obj)) return false;"); gl(!data_.isEqualsShouldCheckSuperEquals(), " if (obj == null || obj.getClass() != getClass()) return false;"); gl(data_.getProperties().isEmpty(), " return true;"); gl(!data_.getProperties().isEmpty(), " ${className}Gen other = (${className}Gen) obj;"); gl(!data_.getProperties().isEmpty(), " return "); new ForEach<PropertySpec>(data_.getProperties()) { @Override protected void go(PropertySpec property) { boolean simple = property.getKind().isSimple(); boolean primitive = property.isPrimitive(); gp(primitive, " other.${name}_ == ${name}_"); gp(!primitive && !simple, " checkEquals(other.${pluralName}_, ${pluralName}_)"); gp(!primitive && simple, " checkEquals(other.${name}_, ${name}_)"); gl(isLast(), ";"); gl(!isLast(), " &&"); }}; gl( " }"); // TODO: better hashcode algorithm for primitives gl( " public int hashCode() {"); gl(data_.getProperties().isEmpty(), " return super.hashCode();"); gl(!data_.getProperties().isEmpty(), " return super.hashCode() +"); new ForEach<PropertySpec>(data_.getProperties()) { @Override protected void go(PropertySpec property) { boolean primitive = property.isPrimitive(); boolean simple = property.getKind().isSimple(); gp(primitive, " ${intConversion}"); gp(!primitive && simple, " ${name}_.hashCode()"); gp(!primitive && !simple, " ${pluralName}_.hashCode()"); gl(!isLast(), " +"); gl(isLast(), ";"); }}; gl( " }"); } // Define a nice default toString method gl( " public java.lang.String toString() {"); gl( " return getClass().getName() + '[' + paramString() + ']';"); gl( " }"); gl( " protected java.lang.String paramString() {"); gl(data_.getProperties().isEmpty(), " return \"\";"); gl(!data_.getProperties().isEmpty(), " return"); new ForEach<PropertySpec>(data_.getProperties()) { @Override protected void go(PropertySpec property) { boolean simple = property.getKind().isSimple(); boolean omitValue = property.isOmitFromToString(); gp(!omitValue, " \""); gp(!isFirst() && !omitValue, ","); gp(simple && !omitValue, "${name}=\" + ${name}_"); gp(!simple && !omitValue, "${pluralName}=\" + ${pluralName}_"); gp(simple && omitValue, "${name}=\" + '<' + ${name}_.getClass().getName() + '>'"); gp(!simple && omitValue, "${pluralName}=\" + '<' + ${pluralName}_.getClass().getName() + '>'"); gl(!isLast() && !omitValue, " +"); gl(isLast()&& !omitValue, ";"); }}; gl( " }"); if (data_.isCreatePropertyMap()) { gl( " public java.util.Map<java.lang.String, java.lang.Object> createPropertyMap() {"); gl(!data_.isCreatePropertyMapCallsSuper(), " java.util.Map<java.lang.String, java.lang.Object> map = new java.util.HashMap<java.lang.String, java.lang.Object>();"); gl(data_.isCreatePropertyMapCallsSuper(), " java.util.Map<java.lang.String, java.lang.Object> map = super.createPropertyMap();"); new ForEach<PropertySpec>(data_.getProperties()) { @Override protected void go(PropertySpec property) { if (property.isReadable()) { boolean simple = property.getKind().isSimple(); if ("boolean".equals(property.getType())) { symbols_.pushScope("isOrGet", "is"); } else { symbols_.pushScope("isOrGet", "get"); } gl(simple, " map.put(\"${name}\", ${isOrGet}${upper:name}());"); gl(!simple, " map.put(\"${pluralName}\", ${isOrGet}${upper:pluralName}());"); } symbols_.popScope(); }}; gl( " return map;"); gl( " }"); } gl("}"); // close off the class } }