/*******************************************************************************
* 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
}
}