/******************************************************************************* * 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.FileReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.javadude.annotation.Access; import com.javadude.annotation.Bean; import com.javadude.annotation.Default; import com.javadude.annotation.Property; import com.javadude.annotation.PropertyKind; import com.javadude.annotation.processors.template.ExpressionException; import com.javadude.annotation.processors.template.Processor; import com.javadude.annotation.processors.template.TemplateReader; import com.sun.mirror.apt.AnnotationProcessor; import com.sun.mirror.apt.AnnotationProcessorEnvironment; import com.sun.mirror.apt.Filer; import com.sun.mirror.declaration.AnnotationMirror; import com.sun.mirror.declaration.AnnotationTypeDeclaration; import com.sun.mirror.declaration.AnnotationTypeElementDeclaration; import com.sun.mirror.declaration.AnnotationValue; import com.sun.mirror.declaration.ClassDeclaration; import com.sun.mirror.declaration.ConstructorDeclaration; import com.sun.mirror.declaration.Declaration; import com.sun.mirror.declaration.EnumConstantDeclaration; import com.sun.mirror.declaration.ExecutableDeclaration; import com.sun.mirror.declaration.InterfaceDeclaration; import com.sun.mirror.declaration.MethodDeclaration; import com.sun.mirror.declaration.Modifier; import com.sun.mirror.declaration.PackageDeclaration; import com.sun.mirror.declaration.ParameterDeclaration; import com.sun.mirror.declaration.TypeDeclaration; import com.sun.mirror.declaration.TypeParameterDeclaration; import com.sun.mirror.type.PrimitiveType; import com.sun.mirror.type.ReferenceType; // does not support standard indexed properties // does not support constrained properties // TODO check delegation -- limit to methods exposed via property type if defined as property // TODO delegation + extractInterface -> must allow superinterfaces to be specified for generated interface // TODO set up generic type mapping for Delegates // TODO check for addPropertyChangeListener et al - should treat it separately // -- if getPropertyChangeSupport() present, can still delegate to it public class BeanAnnotationProcessor implements AnnotationProcessor { private Map<String, AnnotationValue> getAnnotationValues(AnnotationMirror a) { Map<String, AnnotationValue> values = new HashMap<String, AnnotationValue>(); Map<AnnotationTypeElementDeclaration, AnnotationValue> elementValues = a.getElementValues(); for (Map.Entry<AnnotationTypeElementDeclaration, AnnotationValue> entry : elementValues.entrySet()) { values.put(entry.getKey().getSimpleName(), entry.getValue()); } return values; } private Map<String, AnnotationValue> getAnnotationValues(Declaration declaration, String annotationName) { Collection<AnnotationMirror> annotationMirrors = declaration.getAnnotationMirrors(); for (AnnotationMirror annotationMirror : annotationMirrors) { if (annotationName.equals(annotationMirror.getAnnotationType().getDeclaration().getQualifiedName())) { return getAnnotationValues(annotationMirror); } } return null; } private void setValue(Thing data, Map<String, AnnotationValue> values, String name, Object def) { AnnotationValue annotationValue = values.get(name); if (annotationValue == null) { if (def != null) { data.put(name, def); } } else { data.put(name, annotationValue.getValue()); } } private List<AnnotationValue> l(Map<String, AnnotationValue> values, String name) { AnnotationValue annotationValue = values.get(name); if (annotationValue == null) { return Collections.emptyList(); } @SuppressWarnings("unchecked") List<AnnotationValue> value = (List<AnnotationValue>) annotationValue.getValue(); return value; } // private String s(Map<String, AnnotationValue> values, String name, String def) { // AnnotationValue annotationValue = values.get(name); // if (annotationValue == null) { // return def; // } // return (String) annotationValue.getValue(); // } private int i(Map<String, AnnotationValue> values, String name, int def) { AnnotationValue annotationValue = values.get(name); if (annotationValue == null) { return def; } return ((Integer) annotationValue.getValue()).intValue(); } private boolean b(Map<String, AnnotationValue> values, String name) { AnnotationValue annotationValue = values.get(name); if (annotationValue == null) { return false; } return ((Boolean) annotationValue.getValue()).booleanValue(); } private boolean b(AnnotationValue value) { if (value == null) { return false; } return ((Boolean) value.getValue()).booleanValue(); } private static final Set<String> createSet(String... items) { Set<String> set = new HashSet<String>(); for (String item : items) { set.add(item); } return set; } // private static final Set<String> METHODS_TO_SKIP = BeanAnnotationProcessor.createSet("equals", "hashCode", "toString", "wait", "notify", "notifyAll"); private static final Set<String> PRIMITIVE_TYPES = BeanAnnotationProcessor.createSet("byte", "short", "int", "long", "float", "double", "char", "boolean"); private static Processor template; private static final boolean nestedEclipse; private static final String workspace; static { workspace = System.getProperty("workspace"); nestedEclipse = "true".equals(System.getProperty("nested.eclipse", "false")); if (!nestedEclipse) { InputStream stream = BeanAnnotationProcessor.class.getResourceAsStream("/$packageName$/$className$Gen.java"); template = new TemplateReader().readTemplate(new InputStreamReader(stream)); } } // TODO add parameters to METHODS_TO_SKIP and only skip those methods that match those parameters... // private static final Class<?>[] EMPTY_PARAMS = {}; // private static final Object[] EMPTY_ARGS = {}; private final AnnotationProcessorEnvironment env_; public BeanAnnotationProcessor(AnnotationProcessorEnvironment env) { env_ = env; } public void error(AnnotationMirror annotationMirror, String message) { env_.getMessager().printError(annotationMirror.getPosition(), message); } public void error(Declaration declaration, String message) { env_.getMessager().printError(declaration.getPosition(), message); } public void error(AnnotationValue value, String message) { env_.getMessager().printError(value.getPosition(), message); } private String commaSeparate(Collection<?> values) { String result = null; for (Object object : values) { result = addWithCommasBetween(result, object); } if (result == null) { return ""; } return result; } private String addWithCommasBetween(String list, Object item) { if (list == null) { return item.toString(); } else { return list + ", " + item.toString(); } } private String normalizeList(String list) { if (list == null) { return ""; } return list + ' '; } private Thing setupMethod(ExecutableDeclaration declaration, boolean forceNonAbstract) { String name = null; String returnType = null; boolean isAbstract = false; String nullBody = null; String throwsClause = ""; if (declaration instanceof ConstructorDeclaration) { name = "<init>"; } else { MethodDeclaration methodDeclaration = (MethodDeclaration) declaration; name = methodDeclaration.getSimpleName(); returnType = methodDeclaration.getReturnType().toString(); if (!forceNonAbstract) { isAbstract = methodDeclaration.getModifiers().contains(Modifier.ABSTRACT); } if ("void".equals(returnType)) { nullBody = "// null object implementation; do nothing"; } else if ("boolean".equals(returnType)) { nullBody = "false; // null object implementation"; } else if ("char".equals(returnType)) { nullBody = "' '; // null object implementation"; } else if (BeanAnnotationProcessor.PRIMITIVE_TYPES.contains(returnType)) { nullBody = "0; // null object implementation"; } else { nullBody = "null; // null object implementation"; } } Collection<ReferenceType> thrownTypes = declaration.getThrownTypes(); if (!thrownTypes.isEmpty()) { throwsClause = "throws " + commaSeparate(thrownTypes); } String genericDecls = null; String argDecls = null; String args = null; String modifiers = ""; Collection<ParameterDeclaration> parameters = declaration.getParameters(); for (ParameterDeclaration parameterDeclaration : parameters) { argDecls = addWithCommasBetween(argDecls, parameterDeclaration.getType() + " " + parameterDeclaration.getSimpleName()); args = addWithCommasBetween(args, parameterDeclaration.getSimpleName()); } Collection<TypeParameterDeclaration> formalTypeParameters = declaration.getFormalTypeParameters(); for (TypeParameterDeclaration typeParameterDeclaration : formalTypeParameters) { genericDecls = addWithCommasBetween(genericDecls, typeParameterDeclaration); } Collection<Modifier> modifiers2 = declaration.getModifiers(); for (Modifier modifier : modifiers2) { if (!"abstract".equals(modifier.toString())) { modifiers += modifier.toString() + ' '; } } if (genericDecls == null) { genericDecls = ""; } else { genericDecls = '<' + genericDecls + "> "; } Thing method = new Thing("method"); method.put("name", name); method.put("returnType", returnType); method.put("abstract", isAbstract); method.put("nullBody", nullBody); method.put("modifiers", modifiers); // do not normalize; already "" or has ' ' at end method.put("argDecls", normalizeList(argDecls)); method.put("args", normalizeList(args)); method.put("genericDecls", genericDecls); method.put("throwsClause", throwsClause); return method; } @Override public void process() { final AnnotationTypeDeclaration beanAnn = (AnnotationTypeDeclaration) env_.getTypeDeclaration(Bean.class.getName()); for (Declaration declaration : env_.getDeclarationsAnnotatedWith(beanAnn)) { try { if (!(declaration instanceof ClassDeclaration)) { error(declaration, "You can only annotate class declarations with @Bean"); return; } Thing data = new Thing("data"); ClassDeclaration classDeclaration = (ClassDeclaration) declaration; PackageDeclaration packageDeclaration = classDeclaration.getPackage(); Map<String, AnnotationValue> beanValues = getAnnotationValues(classDeclaration, "com.javadude.annotation.Bean"); checkExtends(classDeclaration, packageDeclaration); // check that the class extends XXXGen processSuperclass(data, beanValues); // process the superclass attribute processProperties(data, beanValues); processObservers(data, beanValues); processNullObjects(data, beanValues); processDelegates(data, beanValues); processDefaultMethods(data, classDeclaration); setValue(data, beanValues, "cloneable", false); setValue(data, beanValues, "defineEqualsAndHashCode", false); setValue(data, beanValues, "equalsAndHashCodeCallSuper", false); setValue(data, beanValues, "definePropertyNameConstants", false); setValue(data, beanValues, "defineCreatePropertyMap", false); data.put("date", new Date()); data.put("year", Calendar.getInstance().get(Calendar.YEAR)); data.put("className", classDeclaration.getSimpleName()); data.put("packageName", packageDeclaration.getQualifiedName()); data.checkAllValuesSet(declaration, this); Filer f = env_.getFiler(); PrintWriter pw = f.createSourceFile(classDeclaration.getQualifiedName() + "Gen"); if (nestedEclipse) { // debugging in eclipse -- reread the template each time FileReader fileReader = new FileReader(workspace + "/com.javadude.annotation/template/$packageName$/$className$Gen.java"); template = new TemplateReader().readTemplate(fileReader); } int spacesForLeadingTabs = i(beanValues, "spacesForLeadingTabs", -1); String padding = null; if (spacesForLeadingTabs != -1) { padding = ""; for (int i = 0; i < spacesForLeadingTabs; i++) { padding += ' '; } } template.process(new Symbols(data), pw, -1, padding); pw.close(); } catch (ThreadDeath e) { throw e; } catch (ExpressionException e) { error(declaration, "@Bean generator error: " + e.getMessage()); } catch (Throwable t) { StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); t.printStackTrace(printWriter); printWriter.close(); error(declaration, "Unexpected exception: " + stringWriter.toString()); } } } private void processSuperclass(Thing data, Map<String, AnnotationValue> beanValues) { AnnotationValue value = beanValues.get("superclass"); data.put("superclass", null); data.put("genericDecls", ""); data.put("classModifiers", ""); data.put("propertyNameConstantsInherited", false); data.put("getPropertyChangeSupportInherited", false); data.put("getPropertyChangeSupportModifiers", "protected"); data.put("paramStringInherited", false); data.put("paramStringModifiers", "protected"); data.put("createPropertyMapInherited", false); data.put("createPropertyMapModifiers", "public"); data.put("atLeastOneDouble", false); data.put("atLeastOneBound", false); data.put("atLeastOneObject", false); data.put("atLeastOneDefault", false); if (value == null) { data.setEmpty("superclassConstructors"); } else { if (!(value.getValue() instanceof ClassDeclaration)) { error(value, "superclass must be a class"); return; } ClassDeclaration superclass = (ClassDeclaration) value.getValue(); data.put("superclass", superclass.getQualifiedName()); boolean hasProperties = !l(beanValues, "properties").isEmpty(); // check if getPropertyChangeSupport or some superclass defines bound properties in @Bean checkInheritedMethod(data, "getPropertyChangeSupport", "java.beans.PropertyChangeSupport", superclass, true, new InheritCheck() { @Override public boolean isInherited(Thing d, ClassDeclaration classDeclaration) { Bean beanAnn = classDeclaration.getAnnotation(Bean.class); if (beanAnn != null) { Property[] properties = beanAnn.properties(); for (Property property : properties) { if (property.bound()) { d.put("getPropertyChangeSupportInherited", true); d.put("getPropertyChangeSupportModifiers", "protected"); return true; } } } return false; } }); // check if paramString inherited or some superclass has @Bean checkInheritedMethod(data, "paramString", "java.lang.String", superclass, !hasProperties, new InheritCheck() { @Override public boolean isInherited(Thing d, ClassDeclaration classDeclaration) { if (classDeclaration.getAnnotation(Bean.class) != null) { d.put("paramStringInherited", true); d.put("paramStringModifiers", "protected"); return true; } return false; } }); // check if createPropertyMap inherited or some superclass has @Bean with defineCreatePropertyMap checkInheritedMethod(data, "createPropertyMap", "java.lang.String", superclass, !hasProperties, new InheritCheck() { @Override public boolean isInherited(Thing d, ClassDeclaration classDeclaration) { Bean beanAnn = classDeclaration.getAnnotation(Bean.class); if (beanAnn != null && beanAnn.defineCreatePropertyMap()) { d.put("createPropertyMapInherited", true); d.put("createPropertyMapModifiers", "public"); return true; } return false; } }); String genericDecls = null; Collection<TypeParameterDeclaration> formalTypeParameters2 = superclass.getFormalTypeParameters(); for (TypeParameterDeclaration typeParameterDeclaration : formalTypeParameters2) { genericDecls = addWithCommasBetween(genericDecls, typeParameterDeclaration); } if (genericDecls == null) { genericDecls = ""; } else { genericDecls = '<' + genericDecls + '>'; } data.put("genericDecls", genericDecls); String classModifiers = ""; for (Modifier modifier : superclass.getModifiers()) { if (!"abstract".equals(modifier.toString())) { classModifiers += modifier.toString() + ' '; } } data.put("classModifiers", classModifiers); Collection<ConstructorDeclaration> constructors = superclass.getConstructors(); if (constructors.isEmpty()) { data.setEmpty("superclassConstructors"); } else { for (ConstructorDeclaration constructorDeclaration : constructors) { data.add("superclassConstructors", setupMethod(constructorDeclaration, false)); } } boolean extendPropertyNameConstants = false; if (b(beanValues, "definePropertyNameConstants")) { // if superclass has a PropertyNameConstants interface or a Bean annotation with // definePropertyNameConstants=true, we need to have our PropertyNameConstants // extend it Collection<TypeDeclaration> nestedTypes = superclass.getNestedTypes(); for (TypeDeclaration typeDeclaration : nestedTypes) { if ("PropertyNames".equals(typeDeclaration.getSimpleName()) && (typeDeclaration instanceof InterfaceDeclaration)) { extendPropertyNameConstants = true; } } if (!extendPropertyNameConstants) { // check if the superclass is annotated with Bean Bean annotation = superclass.getAnnotation(Bean.class); if (annotation != null) { extendPropertyNameConstants = annotation.definePropertyNameConstants(); } } } data.put("propertyNameConstantsInherited", extendPropertyNameConstants); } } // private AnnotationValue get(Map<String, AnnotationValue> values, String name, AnnotationValue def) { // AnnotationValue value = values.get(name); // if (value != null) // return value; // return def; // } private void processProperties(Thing data, Map<String, AnnotationValue> beanValues) { boolean atLeastOneBound = false; boolean atLeastOneDouble = false; boolean atLeastOneObject = false; String firstPropertyName = null; AnnotationValue propertiesValue = beanValues.get("properties"); if (propertiesValue == null) { data.setEmpty("properties"); } else { Set<String> propertyNames = new HashSet<String>(); AnnotationValue defaultType = null; AnnotationValue defaultTypeString = null; AnnotationValue defaultKeyType = null; AnnotationValue defaultKeyTypeString = null; AnnotationValue defaultReader = null; AnnotationValue defaultWriter = null; AnnotationValue defaultBound = null; AnnotationValue defaultKind = null; AnnotationValue defaultOmitFromToString = null; AnnotationValue defaultNotNull = null; AnnotationValue defaultIsStatic = null; AnnotationValue defaultIsSynchronized = null; @SuppressWarnings("unchecked") List<AnnotationValue> properties = (List<AnnotationValue>) propertiesValue.getValue(); for (AnnotationValue annotationValue : properties) { AnnotationMirror propertyMirror = (AnnotationMirror) annotationValue.getValue(); Map<String, AnnotationValue> propertyValues = getAnnotationValues(propertyMirror); AnnotationValue name = propertyValues.get("name"); AnnotationValue plural = propertyValues.get("plural"); AnnotationValue type = propertyValues.get("type"); AnnotationValue typeString = propertyValues.get("typeString"); AnnotationValue keyType = propertyValues.get("keyType"); AnnotationValue keyTypeString = propertyValues.get("keyTypeString"); AnnotationValue reader = propertyValues.get("reader"); AnnotationValue writer = propertyValues.get("writer"); AnnotationValue bound = propertyValues.get("bound"); AnnotationValue kind = propertyValues.get("kind"); AnnotationValue omitFromToString = propertyValues.get("omitFromToString"); AnnotationValue notNull = propertyValues.get("notNull"); AnnotationValue isStatic = propertyValues.get("isStatic"); AnnotationValue isSynchronized = propertyValues.get("isSynchronized"); if (Property.DEFAULTS.equals(name.getValue())) { defaultType = type; defaultTypeString = typeString; defaultKeyType = keyType; defaultKeyTypeString = keyTypeString; defaultReader = reader; defaultWriter = writer; defaultBound = bound; defaultKind = kind; defaultOmitFromToString = omitFromToString; defaultNotNull = notNull; defaultIsStatic = isStatic; defaultIsSynchronized = isSynchronized; continue; } // plugin the default values if (type == null && typeString == null) { type = defaultType; typeString = defaultTypeString; } if (keyType == null && keyTypeString == null) { keyType = defaultKeyType; keyTypeString = defaultKeyTypeString; } if (reader == null) { reader = defaultReader; } if (writer == null) { writer = defaultWriter; } if (bound == null) { bound = defaultBound; } if (kind == null) { kind = defaultKind; } if (omitFromToString == null) { omitFromToString = defaultOmitFromToString; } if (notNull == null) { notNull = defaultNotNull; } if (isStatic == null) { isStatic = defaultIsStatic; } if (isSynchronized == null) { isSynchronized = defaultIsSynchronized; } Thing property = new Thing("property"); property.put("name", name.getValue()); if (typeString == null) { if (type == null) { property.put("type", "java.lang.String"); } else { if (type.getValue() instanceof TypeDeclaration) { property.put("type", ((TypeDeclaration) type.getValue()).getQualifiedName()); } else { property.put("type", ((PrimitiveType) type.getValue()).toString()); } } } else { if (type != null) { String message = "@Property cannot have both type and typeString attributes specified"; error(typeString, message); error(type, message); property.put("type", "<ERROR>"); } else { property.put("type", typeString.getValue()); } } PropertyKind propertyKind = PropertyKind.SIMPLE; if (kind != null) { propertyKind = PropertyKind.valueOf(kind.getValue().toString()); } // check for duplicate keytype specifications if (propertyKind.isMap()) { if (keyTypeString == null) { if (keyType == null) { property.put("keyType", "java.lang.String"); } else { property.put("keyType", ((TypeDeclaration) keyType.getValue()).getQualifiedName()); } } else if (keyType != null) { String message = "@Property cannot have both keyType and keyTypeString attributes specified"; error(keyType, message); error(keyTypeString, message); property.put("keyType", "<ERROR>"); } } else { if (keyTypeString != null) { error(keyTypeString, "@Property can only have a keyTypeString attribute if kind is MAP or UNMODIFIABLE_MAP"); } if (keyType != null) { error(keyType, "@Property can only have a keyType attribute if kind is MAP or UNMODIFIABLE_MAP"); } property.put("keyType", "<ERROR>"); } // check for plural names if (propertyKind.isSimple()) { if (plural != null) { error(plural, "@Property cannot have plural specified if kind is SIMPLE"); } property.put("pluralName", null); } else { if (plural == null) { property.put("pluralName", name.getValue() + "s"); } else { property.put("pluralName", plural.getValue()); } } property.put("simple", propertyKind.isSimple()); property.put("list", propertyKind.isList()); property.put("map", propertyKind.isMap()); property.put("set", propertyKind.isSet()); String typeName = (String) property.get("type"); property.put("float", "float".equals(typeName)); property.put("double", "double".equals(typeName)); property.put("boolean", "boolean".equals(typeName)); property.put("char", "char".equals(typeName)); property.put("byte", "byte".equals(typeName)); property.put("long", "long".equals(typeName)); property.put("int", "int".equals(typeName)); property.put("short", "short".equals(typeName)); if (property.containsKey("firstPropertyName")) { property.put("firstPropertyName", name.getValue()); } if (propertyNames.contains(name.getValue())) { error(name, "Duplicate property name '" + name + "' specified for @Bean properties definition"); } else { propertyNames.add((String) name.getValue()); } if (bound != null) { if (isStatic!= null) { error(bound, "Static properties cannot be declared bound"); error(isStatic, "Static properties cannot be declared bound"); } else { atLeastOneBound = true; } } if ("double".equals(property.get("type"))) { data.put("atLeastOneDouble", true); } property.put("kind", propertyKind); property.put("omitFromToString", b(omitFromToString)); data.add("properties", property); // evil hack to get the type, which is a Class boolean isPrimitive = BeanAnnotationProcessor.PRIMITIVE_TYPES.contains(property.get("type")); property.put("primitive", isPrimitive); if (!isPrimitive) { data.put("atLeastOneObject", true); } property.put("bound", b(bound)); if (writer == null) { property.put("writerAccess", Access.PUBLIC.getModifier()); property.put("writeable", true); } else { EnumConstantDeclaration writerValue = (EnumConstantDeclaration) writer.getValue(); Access writerAccess = Access.valueOf(writerValue.toString()); property.put("writerAccess", writerAccess.getModifier()); property.put("writeable", writerAccess != Access.NONE); } if (reader == null) { property.put("readerAccess", Access.PUBLIC.getModifier()); property.put("readable", true); } else { EnumConstantDeclaration readerValue = (EnumConstantDeclaration) reader.getValue(); Access readerAccess = Access.valueOf(readerValue.toString()); property.put("readerAccess", readerAccess.getModifier()); property.put("readable", readerAccess != Access.NONE); } boolean bNotNull = b(notNull); boolean bIsStatic = b(isStatic); boolean bIsSynchronized = b(isSynchronized); property.put("notNull", bNotNull); if (bNotNull && isPrimitive) { error(notNull, "Cannot specify notNull for primitive-typed property " + name.getValue() + " in @Property"); } String extraFieldKeywords = ""; String extraMethodKeywords = ""; if (bIsStatic) { extraFieldKeywords = "static "; extraMethodKeywords = "static "; } if (bIsSynchronized) { if (bIsStatic) { extraMethodKeywords += "synchronized "; } else { extraMethodKeywords = "synchronized "; } } property.put("extraFieldKeywords", extraFieldKeywords); property.put("extraMethodKeywords", extraMethodKeywords); property.checkAllValuesSet(propertiesValue, this); } } data.put("definePropertyChangeSupport", !((Boolean) data.get("getPropertyChangeSupportInherited")) && atLeastOneBound); data.put("atLeastOneDouble", atLeastOneDouble); data.put("atLeastOneObject", atLeastOneObject); data.put("firstPropertyName", firstPropertyName); } private void processDelegates(Thing data, Map<String, AnnotationValue> beanValues) { AnnotationValue delegatesValue = beanValues.get("delegates"); if (delegatesValue == null) { data.setEmpty("delegates"); } else { // if (bean.delegates() != null) { // for (Delegate delegate : bean.delegates()) { // if (delegate == null) { // continue; // } // try { // DelegateSpec delegateSpec = new DelegateSpec(); // delegateSpec.set("accessor", delegate.accessor()); // data.addDelegate(delegateSpec); // String type = selectType(declaration, "@Delegate", delegate, "type", "typeString", null, true); // if (type == null) { // return; // } // delegateSpec.set("name", type); // defineListenerOrDelegate(false, delegateSpec, packageDeclaration, "delegate type", "delegates", packageName); // // // TODO if property doesn't exist, define it // } catch (NoSuchElementException e) { // error(declaration, "Invalid delegate specification; must be type,varName,type,varName..."); // } // } } } private void processNullObjects(Thing data, Map<String, AnnotationValue> beanValues) { AnnotationValue nullObjectsValue = beanValues.get("nullObjects"); if (nullObjectsValue == null) { data.setEmpty("nullObjects"); } else { // if (bean.nullObjects().length > 0 && !"".equals(bean.nullObjects()[0])) { // for (NullObject nullObject : bean.nullObjects()) { // if (nullObject == null) { // continue; // } // Type listener = new Type(); // String type = selectType(declaration, "@NullObject", nullObject, "type", "typeString", null, true); // if (type == null) { // return; // } // listener.set("name", type); // data.addNullObject(listener); // defineListenerOrDelegate(true, listener, classDeclaration, "null implementation class/interface", "nullImplementationName", packageName); // } } } private void processObservers(Thing data, Map<String, AnnotationValue> beanValues) { AnnotationValue observersValue = beanValues.get("observers"); if (observersValue == null) { data.setEmpty("observers"); } else { // for (Observer observer : bean.observers()) { // if (observer == null) { // continue; // } // Type type = new Type(); // data.addObserver(type); // String typeName = selectType(declaration, "@Observer", observer, "type", "typeString", null, true); // if (typeName == null) { // return; // } // type.set("name", typeName); // defineListenerOrDelegate(false, type, packageDeclaration, "listener interface", "eventsets", packageName); // } } } private void processDefaultMethods(Thing data, ClassDeclaration classDeclaration) { // find any methods that have default parameters boolean error = false; for (ConstructorDeclaration constructorDeclaration : classDeclaration.getConstructors()) { Collection<ParameterDeclaration> parameters = constructorDeclaration.getParameters(); for (ParameterDeclaration parameterDeclaration : parameters) { Default annotation = parameterDeclaration.getAnnotation(Default.class); if (annotation != null) { error(parameterDeclaration, "@Default is not legal in constructor parameters"); error = true; } } } if (error) { return; } boolean atLeastOneDefault = false; methods: for (MethodDeclaration methodDeclaration : classDeclaration.getMethods()) { Collection<ParameterDeclaration> parameters = methodDeclaration.getParameters(); boolean seenDefault = false; String[] names = new String[parameters.size()]; String[] types = new String[parameters.size()]; String[] defaults = new String[parameters.size()]; int n = 0; for (ParameterDeclaration parameterDeclaration : parameters) { Default annotation = parameterDeclaration.getAnnotation(Default.class); names[n] = parameterDeclaration.getSimpleName(); types[n] = parameterDeclaration.getType().toString(); if (annotation != null) { seenDefault = true; if ("java.lang.String".equals(types[n])) { defaults[n] = '"' + annotation.value() + '"'; } else { defaults[n] = annotation.value(); } } else if (seenDefault) { error(parameterDeclaration, "All parameters after a parameter annotated with @Default must be annotated with @Default"); continue methods; } n++; } if (seenDefault) { atLeastOneDefault = true; if (methodDeclaration.getModifiers().contains(Modifier.PRIVATE)) { error(methodDeclaration, "Private methods cannot use @Default parameters"); } if (methodDeclaration.getModifiers().contains(Modifier.STATIC)) { error(methodDeclaration, "Static methods cannot use @Default parameters"); } String modifiers3 = ""; if (methodDeclaration.getModifiers().contains(Modifier.PUBLIC)) { modifiers3 = "public "; } else if (methodDeclaration.getModifiers().contains(Modifier.PROTECTED)) { modifiers3 = "protected "; } String throwsClause = getThrowsClause(methodDeclaration); String returnType = methodDeclaration.getReturnType().toString(); String methodName = methodDeclaration.getSimpleName(); String argDecl = ""; String callArgs = ""; for (int i = 0; i < n; i++) { if (defaults[i] != null) { String callArgsWithDefaults = callArgs; for (int j = i; j < n; j++) { if (j > 0) { callArgsWithDefaults += ", "; } callArgsWithDefaults += defaults[j]; } Thing method = new Thing("method"); method.put("name", methodName); method.put("returnType", returnType); method.put("throwsClause", throwsClause); method.put("argDecls", argDecl); method.put("modifiers", modifiers3); method.put("args", callArgsWithDefaults); data.add("defaultMethods", method); } if (i > 0) { argDecl += ", "; callArgs += ", "; } argDecl += types[i] + ' ' + names[i]; callArgs += names[i]; } Thing method = new Thing("method"); method.put("name", methodName); method.put("returnType", returnType); method.put("throwsClause", throwsClause); method.put("modifiers", modifiers3); method.put("abstract", true); method.put("argDecls", argDecl); data.add("defaultMethods", method); } } data.put("atLeastOneDefault", atLeastOneDefault); if (!atLeastOneDefault) { data.setEmpty("defaultMethods"); } } private void checkExtends(ClassDeclaration classDeclaration, PackageDeclaration packageDeclaration) { String scName = classDeclaration.getSuperclass().toString(); int lt = scName.indexOf('<'); if (lt != -1) { scName = scName.substring(0, lt); } int dot = scName.lastIndexOf('.'); if (dot != -1) { String superClassPackageName = scName.substring(0, dot); if (!superClassPackageName.equals(packageDeclaration.getQualifiedName())) { scName = ""; // force error below } else { scName = scName.substring(dot + 1); } } if (!scName.equals(classDeclaration.getSimpleName() + "Gen")) { error(classDeclaration, classDeclaration.getQualifiedName() + " must extend " + classDeclaration.getQualifiedName() + "Gen for @Bean to work properly"); } } private interface InheritCheck { boolean isInherited(Thing data, ClassDeclaration classDeclaration); } private boolean checkInheritedMethod(Thing data, String methodName, String returnType, ClassDeclaration superclass, boolean finalOk, InheritCheck inheritCheck) { if (inheritCheck != null) { if (inheritCheck.isInherited(data, superclass)) { return true; } } for (MethodDeclaration methodDeclaration : superclass.getMethods()) { if (methodName.equals(methodDeclaration.getSimpleName()) && methodDeclaration.getParameters().isEmpty() && returnType.equals(methodDeclaration.getReturnType().toString())) { data.put(methodName + "Inherited", true); Collection<Modifier> modifiers = methodDeclaration.getModifiers(); if ((modifiers.contains(Modifier.FINAL) || modifiers.contains(Modifier.PRIVATE)) && !finalOk) { // TBD TBD TBD - ERROR!!! cannot extend class with superclass method like this - how to report? } else if (modifiers.contains(Modifier.PROTECTED)) { data.put(methodName + "Modifiers", "protected"); } else if (modifiers.contains(Modifier.PUBLIC)) { data.put(methodName + "Modifiers", "public"); } else { data.put(methodName + "Modifiers", ""); } return true; } } if (superclass.getSuperclass() != null) { if (superclass.getSuperclass().getDeclaration() != null) { if (checkInheritedMethod(data, methodName, returnType, superclass.getSuperclass().getDeclaration(), finalOk, inheritCheck)) { return true; } } } return false; } // private TypeDeclaration getType(Declaration declaration, String name, String packageName, String notFoundMessage) { // TypeDeclaration typeDeclaration = env_.getTypeDeclaration(name); // if (typeDeclaration != null) { // return typeDeclaration; // } // // // try it with the package name prepended // typeDeclaration = env_.getTypeDeclaration(packageName + '.' + name); // if (typeDeclaration != null) { // return typeDeclaration; // } // // error(declaration, notFoundMessage); // return null; // } // private void defineListenerOrDelegate(boolean abstractOnly, Thing type, Declaration declaration, String typeOfThing, String partOfBean, String packageName) { // TypeDeclaration typeDeclaration = getType(declaration, (String) type.get("name"), packageName, "Cannot find " + typeOfThing + " " + type.get("name") + " defined as an " + partOfBean + " in @Bean (you probably need to fully-qualify it)"); // if (typeDeclaration == null) { // return; // } // Collection<? extends MethodDeclaration> methods = typeDeclaration.getMethods(); // for (MethodDeclaration methodDeclaration : methods) { // if (methodDeclaration.getModifiers().contains(Modifier.STATIC)) { // continue; // } // if (abstractOnly) { // if (!methodDeclaration.getModifiers().contains(Modifier.ABSTRACT)) { // continue; // } // } else { // if (!methodDeclaration.getModifiers().contains(Modifier.PUBLIC)) { // continue; // } // } // if (BeanAnnotationProcessor.METHODS_TO_SKIP.contains(methodDeclaration.getSimpleName())) { // continue; // } // // type.add("methods", setupMethod(methodDeclaration, true)); // } // } // private String getThrowsClause(MethodDeclaration methodDeclaration) { Collection<ReferenceType> thrownTypes = methodDeclaration.getThrownTypes(); boolean first = true; if (!thrownTypes.isEmpty()) { String throwsClause = " throws "; for (ReferenceType thrownType : thrownTypes) { if (first) { first = false; } else { throwsClause += ", "; } throwsClause += Utils.getTypeName(thrownType); } return throwsClause; } else { return ""; } } }