/** * Copyright (c) 2009 Farata Systems http://www.faratasystems.com * * Licensed under The MIT License * Re-distributions of files must retain the above copyright notice. * * @license http://www.opensource.org/licenses/mit-license.php The MIT License * */ package com.farata.dto2extjs.asap.reflect; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.HashSet; import java.util.TreeMap; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; import com.farata.dto2extjs.annotations.JSIgnore; import com.farata.dto2extjs.annotations.JSMetadata; import com.farata.dto2extjs.asap.types.JSTypeReflector; import com.sun.mirror.declaration.AnnotationMirror; import com.sun.mirror.declaration.AnnotationTypeDeclaration; import com.sun.mirror.declaration.FieldDeclaration; import com.sun.mirror.declaration.MemberDeclaration; import com.sun.mirror.declaration.MethodDeclaration; import com.sun.mirror.declaration.Modifier; import com.sun.mirror.declaration.ParameterDeclaration; import com.sun.mirror.declaration.TypeDeclaration; import com.sun.mirror.declaration.ClassDeclaration; import com.sun.mirror.type.ClassType; import com.sun.mirror.type.DeclaredType; import com.sun.mirror.type.TypeMirror; import com.sun.mirror.util.Declarations; public class JSClassDeclarationReflector extends JSTypeDeclarationReflector { public JSClassDeclarationReflector(final ClassDeclaration classDeclaration, final JSTypeReflector typeReflector) { super(classDeclaration, typeReflector); } protected TypeDeclarationVisitor createVisitor() { return new TypeDeclarationVisitor() { protected TypeDeclarationKind getTypeKind() { return TypeDeclarationKind.CLASS; } private boolean isInstancePublic(final MemberDeclaration member) { final Collection<Modifier> modifiers = member.getModifiers(); return !(modifiers.contains(Modifier.STATIC) || modifiers.contains(Modifier.TRANSIENT)) && modifiers.contains(Modifier.PUBLIC); } protected boolean isGetterMethod(final MethodDeclaration method) { if (!isInstancePublic(method)) return false; final String name = method.getSimpleName(); final int nameLength = name.length(); final Collection<ParameterDeclaration> params = method.getParameters(); final TypeMirror returnType = method.getReturnType(); return params.size() == 0 && (nameLength > 3 && name.startsWith("get") && !_types.isVoid(returnType) || nameLength > 2 && name.startsWith("is") && _types.isBoolean(returnType)) ; } protected boolean isSetterMethod(final MethodDeclaration method) { if (!isInstancePublic(method)) return false; final String name = method.getSimpleName(); final Collection<ParameterDeclaration> params = method.getParameters(); final TypeMirror returnType = method.getReturnType(); return name.length() > 3 && name.startsWith("set") && params.size() == 1 && _types.isVoid( returnType ) ; } @Override protected void preprocess() { final TypeDeclaration type = source; final Map<String, IJSPropertyDefinition> properties = _properties; final Set<MemberDeclaration> propertyMembers = _propertyMembers; final Set<MemberDeclaration> propertyDeclaringMembers = _propertyDeclaringMembers; properties.clear(); propertyMembers.clear(); propertyDeclaringMembers.clear(); final Map<String, MethodDeclaration> getters = new TreeMap<String, MethodDeclaration>(); final Map<String, Collection<MethodDeclaration>> setters = new TreeMap<String, Collection<MethodDeclaration>>(); for (final MethodDeclaration method : type.getMethods() ) { final String name = method.getSimpleName(); if (isGetterMethod(method)) { getters.put(name, method); } else if (isSetterMethod(method)) { Collection<MethodDeclaration> settersByName = setters.get(name); if (null == settersByName) { settersByName = new HashSet<MethodDeclaration>(); setters.put(name, settersByName); } settersByName.add(method); } } // Iterate getters, then find corresponding setter // Create read-write property if matching pair found or read-only property for (final Iterator<Map.Entry<String, MethodDeclaration>> i = getters.entrySet().iterator(); i.hasNext(); ) { final Map.Entry<String, MethodDeclaration> entry = i.next(); final String getterName = entry.getKey(); final MethodDeclaration getter = entry.getValue(); final int prefixLength; if ( getterName.startsWith("get") ) prefixLength = 3; else if ( getterName.startsWith("is") ) prefixLength = 2; else prefixLength = 0; if ( prefixLength > 0) { final String setterName = "set" + getterName.substring(prefixLength); final Collection<MethodDeclaration> settersByName = setters.get(setterName); MethodDeclaration setter = null; if (null != settersByName) { boolean oneFound = false; for (final MethodDeclaration nextSetter : settersByName) { if ( nextSetter.getAnnotation(JSIgnore.class) != null ) continue; if (oneFound) { // If more then one setter found then should be an error break; } else { final TypeMirror argumentType = nextSetter.getParameters().iterator().next().getType(); if ( getter.getReturnType().equals(argumentType) ) { setter = nextSetter; oneFound = true; } } } } final boolean superSetter; if (setter != null) { setters.remove(setterName); superSetter = false; } else { setter = findSuperSetterOf(getter, getterName.substring(prefixLength)); superSetter = true; } if ( getter.getAnnotation(JSIgnore.class) != null ) continue; final String propertyName = Character.toLowerCase( getterName.charAt(prefixLength) ) + getterName.substring(prefixLength + 1); enlistProperty(propertyName, getter, false, setter, superSetter, properties); propertyMembers.add( getter ); if (null != setter && !superSetter) propertyMembers.add( setter ); propertyDeclaringMembers.add(getter); } } // Iterate setters without matching getters // and create write-only properties for (final Iterator<Map.Entry<String, Collection<MethodDeclaration>>> i = setters.entrySet().iterator(); i.hasNext(); ) { final Map.Entry<String, Collection<MethodDeclaration>> entry = i.next(); final String name = entry.getKey(); final Collection<MethodDeclaration> settersByName = entry.getValue(); boolean oneFound = false; for (final MethodDeclaration setter : settersByName) { if ( setter.getAnnotation(JSIgnore.class) != null ) continue; if (oneFound) { // If more then one setter found then should be an error break; } else { final String propertyName = Character.toLowerCase( name.charAt(3) ) + name.substring(4); enlistProperty(propertyName, findSuperGetterOf(setter, name.substring(3)), true, setter, false, properties); propertyMembers.add( setter ); propertyDeclaringMembers.add( setter ); oneFound = true; } } } } @Override protected void _processTypeDeclaration() { final ClassDeclaration selfClassDeclaration = (ClassDeclaration)source; final DeclaredType superClass = selfClassDeclaration.getSuperclass(); final boolean declareSuperclass = !superclasses.superClassIgnored(superClass.getDeclaration()); if (declareSuperclass) { processSuperInterfaceOrClass(superClass, "superclass"); } final Map<AnnotationTypeDeclaration, TypeDeclaration> features = collectPropertiesAnnotations(source, false); if (null != features && !features.isEmpty()) { final String featuresTypeQName = NS_DTO2JS + ':' + "features"; final String featureTypeQName = NS_DTO2JS + ':' + "feature"; try { startElement(URI_DTO2JS, "features", featuresTypeQName, new AttributesImpl()); for (final Map.Entry<AnnotationTypeDeclaration, TypeDeclaration> feature : features.entrySet()) { final AttributesImpl attrs = new AttributesImpl(); attrs.addAttribute( "", "name", "feature", "NMTOKEN", feature.getKey().getQualifiedName()); attrs.addAttribute( "", "declared-by", "feature", "NMTOKEN", feature.getValue().getQualifiedName()); startElement(URI_DTO2JS, "feature", featureTypeQName, attrs); endElement(URI_DTO2JS, "feature", featureTypeQName); } endElement(URI_DTO2JS, "features", featuresTypeQName); } catch (final SAXException ex) { throw new SAXRuntimeException(ex); } } for (final FieldDeclaration field : source.getFields() ) processFieldDeclaration(field); } protected void processFieldDeclaration(final FieldDeclaration field) { if (isInstancePublic(field)) { final String propertyName = field.getSimpleName(); // Do not redefine property defined by pair of getter / setter if ( !_properties.containsKey(propertyName) ) { try { declareJSProperty(new JSPropertyDefinition(field, propertyName, field.getType())); } catch (final SAXException ex) { throw new SAXRuntimeException(ex); } } } _processFieldDeclaration(field); } protected void _processFieldDeclaration(final FieldDeclaration field) { final String propertyName = field.getSimpleName(); JSPropertyDefinition prop = (JSPropertyDefinition)_properties.get(propertyName); if ( field.getAnnotation(JSMetadata.class) != null && prop != null) { //TODO : Why prop is null for computed fields? prop.setMetadata( field.getAnnotation(JSMetadata.class).label(), removeCurlyBrackets(field.getAnnotation(JSMetadata.class).resource()), field.getAnnotation(JSMetadata.class).formatString()); } } private String removeCurlyBrackets( final String resourceClass) { if (resourceClass != null && resourceClass.indexOf("{") == 0) return resourceClass.substring(1, resourceClass.length()-1); else return resourceClass; } protected void enlistProperty( final String propertyName, final MethodDeclaration getter, final boolean inheritedGetter, final MethodDeclaration setter, final boolean inheritedSetter, final Map<String, IJSPropertyDefinition> properties) { final MethodDeclaration superGetter = inheritedGetter ? getter : null == getter ? null : superMethodOf(getter); final MethodDeclaration superSetter = inheritedSetter ? setter : null == setter ? null : superMethodOf(setter); final JSMethodDeclarationKind declareGetter; final JSMethodDeclarationKind declareSetter; /* DECLARATION MATRIX -- START */ if (null != superGetter) { if (null == setter) { // Read-only and getter was defined previously declareGetter = JSMethodDeclarationKind.SKIP; declareSetter = JSMethodDeclarationKind.SKIP; } else if (null == superSetter) { // Setter will be ultimately defined, not declared previously declareSetter = JSMethodDeclarationKind.DECLARE; if ( _types.isAbstract(setter) ) { declareGetter = JSMethodDeclarationKind.SKIP; } else { // First full property definition // if getter is not abstract declareGetter = _types.isAbstract(getter) ? JSMethodDeclarationKind.SKIP : JSMethodDeclarationKind.OVERRIDE; } } else { // Both getter and setter are redefined // Declare property only if this is full property definition. final boolean isDef = !_types.isAbstract(getter) && !_types.isAbstract(setter) && (_types.isAbstract(superGetter) || _types.isAbstract(superSetter)); if (isDef) declareGetter = declareSetter = JSMethodDeclarationKind.OVERRIDE; else declareGetter = declareSetter = JSMethodDeclarationKind.SKIP; } } else { // Getter will be ultimately defined, not declared previously declareGetter = null == getter ? JSMethodDeclarationKind.SKIP : JSMethodDeclarationKind.DECLARE; if (null == setter) { declareSetter = JSMethodDeclarationKind.SKIP; } else if (null != superSetter) { if ( null == getter || _types.isAbstract(getter) ) { // Has super setter here and no property definition // while getter is abstract, so skip setter redefinition declareSetter = JSMethodDeclarationKind.SKIP; } else { if ( _types.isAbstract(setter) ) declareSetter = JSMethodDeclarationKind.SKIP; else { // First full property definition declareSetter = JSMethodDeclarationKind.OVERRIDE; } } } else { declareSetter = JSMethodDeclarationKind.DECLARE; } } /* DECLARATION MATRIX -- END */ if (declareGetter != JSMethodDeclarationKind.SKIP || declareSetter != JSMethodDeclarationKind.SKIP) { properties.put(propertyName, new JSPropertyDefinition( null != getter ? getter : setter, propertyName, null != getter ? getter.getReturnType() : setter.getParameters().iterator().next().getType(), declareGetter, declareSetter, JSMethodDeclarationKind.SKIP == declareGetter || JSMethodDeclarationKind.SKIP == declareSetter || _types.isAbstract(getter) || _types.isAbstract(setter) ) ); } } protected Map<AnnotationTypeDeclaration, TypeDeclaration> collectPropertiesAnnotations(final TypeDeclaration type, final boolean startFromSuper) { final Map<AnnotationTypeDeclaration, TypeDeclaration> result = new HashMap<AnnotationTypeDeclaration, TypeDeclaration>(); abstract class CollectAnnotations<M extends MemberDeclaration> implements IMemberVisitor<Map<AnnotationTypeDeclaration, TypeDeclaration>, M> { public boolean visit(final M member) { if (matches(member)) { final TypeDeclaration type = member.getDeclaringType(); for (final AnnotationMirror a : member.getAnnotationMirrors()) { final AnnotationTypeDeclaration decl = a.getAnnotationType().getDeclaration(); if (!"com.farata.dto2extjs.annotations.JSIgnore".equals(decl.getQualifiedName())) { result.put(decl, type); } } } // Drill-down always return false; } public Map<AnnotationTypeDeclaration, TypeDeclaration> result() { return result; } abstract protected boolean matches(M member); } final Map<IGetMemberDeclarations<MemberDeclaration>, CollectAnnotations<MemberDeclaration>> visitors = new LinkedHashMap<IGetMemberDeclarations<MemberDeclaration>, CollectAnnotations<MemberDeclaration>>(); @SuppressWarnings("unchecked") final Map<IGetMemberDeclarations<? extends MemberDeclaration>, CollectAnnotations<? extends MemberDeclaration>> unsafe = (Map<IGetMemberDeclarations<? extends MemberDeclaration>, CollectAnnotations<? extends MemberDeclaration>>)(Map<?, ?>)visitors; unsafe.put(GET_METHODS, new CollectAnnotations<MethodDeclaration>() { public boolean matches(final MethodDeclaration method) { return (isGetterMethod(method) || isSetterMethod(method)) && null == method.getAnnotation(JSIgnore.class) ; } }); unsafe.put(GET_FIELDS, new CollectAnnotations<FieldDeclaration>() { public boolean matches(final FieldDeclaration field) { return isInstancePublic(field) && null == field.getAnnotation(JSIgnore.class) ; } }); visitMembersOf(type, visitors, startFromSuper); return result; } protected MethodDeclaration superMethodOf(final MethodDeclaration method) { final Declarations declarations = apt.getDeclarationUtils(); return visitSuperMethodsOf(method, new MethodLookup(){ public boolean visit(final MethodDeclaration other) { if (declarations.overrides(method, other)) { final Collection<Modifier> otherModifiers = other.getModifiers(); if (otherModifiers.contains(Modifier.PUBLIC) && null == other.getAnnotation(JSIgnore.class) ) _result = other; return true; } return false; } }); } protected MethodDeclaration findSuperSetterOf(final MethodDeclaration getter, final String propertySuffix) { final String setterName = "set" + propertySuffix; final TypeMirror propertyType = getter.getReturnType(); return visitSuperMethodsOf(getter, new MethodLookup(){ public boolean visit(final MethodDeclaration other) { if (setterName.equals(other.getSimpleName()) && _types.isVoid(other.getReturnType()) ) { final Collection<ParameterDeclaration> params = other.getParameters(); if (params != null && params.size() == 1 && propertyType.equals( params.iterator().next().getType() ) ) { final Collection<Modifier> otherModifiers = other.getModifiers(); if (otherModifiers.contains(Modifier.PUBLIC) && null == other.getAnnotation(JSIgnore.class) ) _result = other; return true; } } return false; } }); } protected MethodDeclaration findSuperGetterOf(final MethodDeclaration setter, final String propertySuffix) { final TypeMirror propertyType = setter.getParameters().iterator().next().getType(); final String getterName1 = "get" + propertySuffix; final String getterName2 = _types.isBoolean(propertyType) ? "is" + propertySuffix : ""; return visitSuperMethodsOf(setter, new MethodLookup(){ public boolean visit(final MethodDeclaration other) { final String getterName = other.getSimpleName(); if ( (getterName1.equals(getterName) || getterName2.equals(getterName)) && other.getReturnType().equals(propertyType) ) { final Collection<ParameterDeclaration> params = other.getParameters(); if (params == null || params.size() == 0) { final Collection<Modifier> otherModifiers = other.getModifiers(); if (otherModifiers.contains(Modifier.PUBLIC) && null == other.getAnnotation(JSIgnore.class) ) _result = other; return true; } } return false; } }); } protected <T> T visitSuperMethodsOf(final MethodDeclaration method, final IMethodVisitor<T> visitor) { return visitSuperMethodsOf(method.getDeclaringType(), visitor); } protected <T> T visitSuperMethodsOf(final TypeDeclaration type, final IMethodVisitor<T> visitor) { return visitMethodsOf(type, visitor, true); } protected <T> T visitMethodsOf( final TypeDeclaration type, final IMemberVisitor<T, MethodDeclaration> visitor, final boolean startFromSuper) { return visitMembersOf(type, GET_METHODS, visitor, startFromSuper); } @SuppressWarnings("unused") protected <T> T visitSuperFieldsOf(final MethodDeclaration method, final IFieldVisitor<T> visitor) { return visitSuperFieldsOf(method.getDeclaringType(), visitor); } protected <T> T visitSuperFieldsOf(final TypeDeclaration type, final IFieldVisitor<T> visitor) { return visitFieldsOf(type, visitor, true); } protected <T> T visitFieldsOf( final TypeDeclaration type, final IMemberVisitor<T, FieldDeclaration> visitor, final boolean startFromSuper) { return visitMembersOf(type, GET_FIELDS, visitor, startFromSuper); } protected <T, M extends MemberDeclaration> T visitMembersOf( final TypeDeclaration type, final IGetMemberDeclarations<M> reader, final IMemberVisitor<T, M> visitor, final boolean startFromSuper ) { final Map<IGetMemberDeclarations<M>, IMemberVisitor<T, M>> visitors = Collections.singletonMap(reader, visitor); final Collection<T> results = visitMembersOf(type, visitors, startFromSuper); if (null == results || results.isEmpty()) return null; else return results.iterator().next(); } protected <T, M extends MemberDeclaration> Collection<T> visitMembersOf( final TypeDeclaration type, final Map<IGetMemberDeclarations<M>, ? extends IMemberVisitor<T, M>> visitors, final boolean startFromSuper) { if (type instanceof ClassDeclaration) { final ClassDeclaration selfClass = (ClassDeclaration)type; TypeDeclaration inheritedType = startFromSuper ? selfClass : null; ClassType superClass = selfClass.getSuperclass(); ClassDeclaration superType; if (startFromSuper) superType = null != superClass ? superClass.getDeclaration() : null; else superType = selfClass; while (true) { if (null == superType) return toResults(visitors); final JSSuperclasses childSuperclasses = null == inheritedType ? null : new JSSuperclasses(inheritedType, apt); inheritedType = superType; if (null != childSuperclasses) { if ( childSuperclasses.superClassIgnored(superType) ) return toResults(visitors); } for (final Map.Entry<IGetMemberDeclarations<M>, ? extends IMemberVisitor<T, M>> e : visitors.entrySet()) { final IGetMemberDeclarations<M> reader = e.getKey(); final IMemberVisitor<T, M> visitor = e.getValue(); for (final M member : reader.read(superType) ) { if ( visitor.visit(member) ) return toResults(visitors); } } superClass = superType.getSuperclass(); superType = null != superClass ? superClass.getDeclaration() : null; } } else return toResults(visitors); } private <T> Collection<T> toResults(final Map<?, ? extends IMemberVisitor<T, ?>> visitors) { final Collection<T> results = new ArrayList<T>(); for (final IMemberVisitor<T, ?> visitor : visitors.values()) { results.add(visitor.result()); } return results; } }; } interface IGetMemberDeclarations<M extends MemberDeclaration> { abstract public <C extends TypeDeclaration> Collection<? extends M> read(final C type); } final static IGetMemberDeclarations<MethodDeclaration> GET_METHODS = new IGetMemberDeclarations<MethodDeclaration>() { public <C extends TypeDeclaration> Collection<? extends MethodDeclaration> read(final C type) { return type.getMethods(); } }; final static IGetMemberDeclarations<FieldDeclaration> GET_FIELDS = new IGetMemberDeclarations<FieldDeclaration>() { public <C extends TypeDeclaration> Collection<? extends FieldDeclaration> read(final C type) { return type.getFields(); } }; interface IMemberVisitor<T, M extends MemberDeclaration> { T result(); boolean visit(M member); } interface IMethodVisitor<T> extends IMemberVisitor<T, MethodDeclaration> { } interface IFieldVisitor<T> extends IMemberVisitor<T, FieldDeclaration> { } abstract static class MethodLookup implements IMethodVisitor<MethodDeclaration> { protected MethodDeclaration _result; final public MethodDeclaration result() { return _result; } } }