/** * 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.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; import org.xml.sax.helpers.XMLFilterImpl; import com.farata.dto2extjs.annotations.JSClass; import com.farata.dto2extjs.annotations.JSIgnore; import com.farata.dto2extjs.annotations.JSManyToOne; import com.farata.dto2extjs.annotations.JSOneToMany; import com.farata.dto2extjs.asap.types.JSTypeReflector; import com.farata.dto2extjs.asap.types.IJSType; import com.farata.dto2extjs.asap.types.InvalidJavaTypeException; import com.sun.mirror.apt.AnnotationProcessorEnvironment; import com.sun.mirror.declaration.Declaration; import com.sun.mirror.declaration.MemberDeclaration; import com.sun.mirror.declaration.MethodDeclaration; import com.sun.mirror.declaration.Modifier; import com.sun.mirror.declaration.TypeDeclaration; import com.sun.mirror.type.DeclaredType; import com.sun.mirror.type.EnumType; import com.sun.mirror.type.InterfaceType; import com.sun.mirror.util.SimpleDeclarationVisitor; abstract public class JSTypeDeclarationReflector extends XMLFilterImpl { final protected TypeDeclaration _declaration; final protected JSTypeReflector _types; public JSTypeDeclarationReflector(final TypeDeclaration declaration, final JSTypeReflector types) { _declaration = declaration; _types = types; } abstract protected TypeDeclarationVisitor createVisitor(); @Override public void setFeature(final String name, final boolean value) {} @Override public void parse(final InputSource ignore) throws SAXException { startDocument(); try { _processor.init( _declaration, _types.environment() ); _declaration.accept(new SimpleDeclarationVisitor() { @Override public void visitTypeDeclaration(final TypeDeclaration type) { _processor.processTypeDeclaration(type); } }); } catch (final SAXRuntimeException ex) { throw ex.saxException(); } endDocument(); } protected static class SAXRuntimeException extends RuntimeException { SAXRuntimeException(final SAXException cause) { super(cause); } SAXException saxException() { return (SAXException)getCause(); } final private static long serialVersionUID = 1L; } final private TypeDeclarationVisitor _processor = createVisitor(); abstract public class TypeDeclarationVisitor { final protected Map<String, IJSPropertyDefinition> _properties = new HashMap<String, IJSPropertyDefinition>(); final protected Set<MemberDeclaration> _propertyMembers = new HashSet<MemberDeclaration>(); final protected Set<MemberDeclaration> _propertyDeclaringMembers = new HashSet<MemberDeclaration>(); protected AnnotationProcessorEnvironment apt; protected TypeDeclaration source; protected JSSuperclasses superclasses; protected JSKeysBuilder keysBuilder; final void init(final TypeDeclaration source, final AnnotationProcessorEnvironment apt) { this.source = source; this.apt = apt; superclasses = new JSSuperclasses(source, apt); keysBuilder = new JSKeysBuilder(apt); preprocess(); } abstract protected TypeDeclarationKind getTypeKind(); protected void preprocess() {} protected void processTypeDeclaration(final TypeDeclaration type) { try { if ( !superclasses.validate(apt) ) return; final AttributesImpl attrs = new AttributesImpl(); attrs.addAttribute("", "javaClass", "javaClass", "NMTOKEN", type.getQualifiedName() ); final Collection<Modifier> modifiers = type.getModifiers(); if ( modifiers.contains(Modifier.ABSTRACT) ) attrs.addAttribute("", "abstract", "abstract", "NMTOKEN", "true"); if ( modifiers.contains(Modifier.FINAL) ) attrs.addAttribute("", "final", "final", "NMTOKEN", "true"); final IJSType jsType = _types.getJSType(type); attrs.addAttribute("", "name", "name", "NMTOKEN", jsType.id() ); attrs.addAttribute("", "kind", "kind", "NMTOKEN", jsType.classKind().name().toLowerCase() ); final TypeDeclarationKind declarationKind = getTypeKind(); final String typeQName = NS_DTO2JS + ':' + declarationKind.id(); startElement(URI_DTO2JS, declarationKind.id(), typeQName, attrs); //processInheritance(); processSuperinterfaces(); _processTypeDeclaration(); for (final MethodDeclaration method : type.getMethods() ) processMethodDeclaration(method); keysBuilder.commit(); if (null != keysBuilder.syntheticKey()) { final char[] syntheticKey = keysBuilder.syntheticKey().toCharArray(); final String syntheticKeyQName = NS_DTO2JS + ':' + "synthetic-key"; startElement(URI_DTO2JS, "synthetic-key", syntheticKeyQName, NO_ATTRS); characters(syntheticKey, 0, syntheticKey.length); endElement(URI_DTO2JS, "synthetic-key", syntheticKeyQName); } if (null != keysBuilder.semanticKey()) { final String semanticKeyQName = NS_DTO2JS + ':' + "semantic-key"; final String semanticKeyPartQName = NS_DTO2JS + ':' + "semantic-key-part"; startElement(URI_DTO2JS, "semantic-key", semanticKeyQName, NO_ATTRS); for (final String partName : keysBuilder.semanticKey()) { final char[] partChars = partName.toCharArray(); startElement(URI_DTO2JS, "semantic-key-part", semanticKeyPartQName, NO_ATTRS); characters(partChars, 0, partChars.length); endElement(URI_DTO2JS, "semantic-key-part", semanticKeyPartQName); } endElement(URI_DTO2JS, "semantic-key", semanticKeyQName); } endElement(URI_DTO2JS, declarationKind.id(), typeQName); } catch (final SAXException ex) { throw new SAXRuntimeException(ex); } } protected void _processTypeDeclaration() {} /* protected void processInheritance() throws SAXException { final JSClass jsClass = source.getAnnotation( JSClass.class ); if (null == jsClass) return; try { final String qname = NS_DTO2extjs + ':' + "inheritance"; startElement(URI_DTO2extjs, "inheritance", qname, NO_ATTRS); for (final DeclaredType type : superclasses.inheritance() ) { processSuperInterfaceOrClass( type ); } endElement(URI_DTO2extjs, "inheritance", qname); } catch (final SAXException ex) { throw new SAXRuntimeException(ex); } } */ protected void processSuperinterfaces() throws SAXException { final JSClass jsClass = source.getAnnotation( JSClass.class ); if (null == jsClass) return; try { final String qname = NS_DTO2JS + ':' + "interfaces"; startElement(URI_DTO2JS, "interfaces", qname, NO_ATTRS); for (final InterfaceType intf : source.getSuperinterfaces() ) { processSuperInterfaceOrClass( intf ); } endElement(URI_DTO2JS, "interfaces", qname); } catch (final SAXException ex) { throw new SAXRuntimeException(ex); } } protected void processSuperInterfaceOrClass(final DeclaredType baseDeclaredType) { processSuperInterfaceOrClass(baseDeclaredType, null); } protected void processSuperInterfaceOrClass(final DeclaredType baseDeclaredType, final String suggestedElementType) { final TypeDeclaration decl = baseDeclaredType.getDeclaration(); if ( superclasses.superInterfaceIgnored(decl) ) return; final IJSType type; try { type = _types.getJSType(baseDeclaredType, source.getPosition()); } catch (final InvalidJavaTypeException ex) { return; } final AttributesImpl attrs = new AttributesImpl(); attrs.addAttribute("", "name", "name", "NMTOKEN", type.id() ); attrs.addAttribute("", "javaClass", "javaClass", "NMTOKEN", decl.getQualifiedName() ); attrs.addAttribute("", "kind", "kind", "NMTOKEN", type.classKind().name().toLowerCase()); if (_types.isAbstract(baseDeclaredType.getDeclaration())) attrs.addAttribute("", "abstract", "abstract", "NMTOKEN", "true"); final String elementType; if (null != suggestedElementType) elementType = suggestedElementType; else if (decl instanceof InterfaceType) elementType = "interface"; else if (decl instanceof EnumType) elementType = "enum"; else elementType = "class"; final String elementTypeQName = NS_DTO2JS + ':' + elementType; try { startElement(URI_DTO2JS, elementType, elementTypeQName, attrs); _processSuperInterfaceOrClass(baseDeclaredType); endElement(URI_DTO2JS, elementType, elementTypeQName); } catch (final SAXException ex) { throw new SAXRuntimeException(ex); } } protected void _processSuperInterfaceOrClass(final DeclaredType intf) {} protected void processMethodDeclaration(final MethodDeclaration method) { try { final JSIgnore ignore = method.getAnnotation( JSIgnore.class ); if (null != ignore) return; final Collection<Modifier> modifiers = method.getModifiers(); if ( !modifiers.contains(Modifier.PUBLIC) || modifiers.contains(Modifier.STATIC) ) return; if (_propertyMembers.contains(method)) { final String propertyName = propertyNameByMethod(method); final IJSPropertyDefinition propertyDefinition = null != propertyName ? _properties.get( propertyName ) : null; if (null != propertyDefinition) declareJSProperty(propertyDefinition); else { // Nothing } } else { // Action definition final AttributesImpl actionAttrs = new AttributesImpl(); if ( modifiers.contains(Modifier.ABSTRACT)) actionAttrs.addAttribute("", "abstract", "abstract", "NMTOKEN", "true" ); if ( modifiers.contains(Modifier.FINAL)) actionAttrs.addAttribute("", "final", "final", "NMTOKEN", "true" ); actionAttrs.addAttribute("", "name", "name", "NMTOKEN", method.getSimpleName()); final String actionQName = NS_DTO2JS + ':' + "action"; startElement(URI_DTO2JS, "action", actionQName, actionAttrs); _processActionDeclaration(method); endElement(URI_DTO2JS, "action", actionQName); } } catch (final SAXException ex) { throw new SAXRuntimeException(ex); } } protected String propertyNameByMethod(final MemberDeclaration method) { final String name = method.getSimpleName(); boolean declareProperty = false; int prefixLength = 3; if ( name.startsWith("get") && name.length() > 3 ) { declareProperty = true; } else if ( name.startsWith("is") && name.length() > 2 ) { declareProperty = true; prefixLength = 2; } else if ( _propertyDeclaringMembers.contains(method) && name.startsWith("set") && name.length() > 3 ) { declareProperty = true; prefixLength = 3; } else declareProperty = false; if (declareProperty) { final String propertyName = Character.toLowerCase( name.charAt(prefixLength) ) + name.substring(prefixLength + 1); return propertyName; } else return null; } protected void _processPropertyDeclaration( final String propertyName, final Declaration origin) throws SAXException { JSOneToMany oneToManyAnnotation = origin.getAnnotation(JSOneToMany.class); if (oneToManyAnnotation != null) { final AttributesImpl propertyAttrs = new AttributesImpl(); final String propertyQName = NS_DTO2JS + ':' + "OneToMany"; final String storeType = oneToManyAnnotation.storeType(); propertyAttrs.addAttribute("", "storeType", "storeType", "NMTOKEN", storeType); final String storeConfig = oneToManyAnnotation.storeConfig(); propertyAttrs.addAttribute("", "storeConfig", "storeConfig", "NMTOKEN", storeConfig); final boolean autoLoad = oneToManyAnnotation.autoLoad(); propertyAttrs.addAttribute("", "autoLoad", "autoLoad", "NMTOKEN", autoLoad?"true":"false"); final String getter = oneToManyAnnotation.getter(); propertyAttrs.addAttribute("", "getter", "getter", "NMTOKEN", getter); final String primaryKey = oneToManyAnnotation.primaryKey(); propertyAttrs.addAttribute("", "primaryKey", "primaryKey", "NMTOKEN", primaryKey); final String foreignKey = oneToManyAnnotation.foreignKey(); propertyAttrs.addAttribute("", "foreignKey", "foreignKey", "NMTOKEN", foreignKey); final JSOneToMany.SyncType sync = oneToManyAnnotation.sync(); propertyAttrs.addAttribute("", "sync", "sync", "NMTOKEN", sync.toString()); final int ranking = oneToManyAnnotation.ranking(); propertyAttrs.addAttribute("", "ranking", "ranking", "NMTOKEN", ""+ranking); startElement(URI_DTO2JS, "OneToMany", propertyQName, propertyAttrs); endElement(URI_DTO2JS, "OneToMany", propertyQName); } JSManyToOne manyToOneAnnotation = origin.getAnnotation(JSManyToOne.class); if (manyToOneAnnotation != null) { final AttributesImpl propertyAttrs = new AttributesImpl(); final String propertyQName = NS_DTO2JS + ':' + "ManyToOne"; /* IJSType parentType; try { final Class<?> parentClass = manyToOneAnnotation.parent(); parentType = _types.getJSType(parentClass); } catch (final MirroredTypeException ex) { parentType = _types.getJSType(ex.getTypeMirror(), origin.getPosition()); } if (parentType != null) { propertyAttrs.addAttribute("", "parent", "parent", "NMTOKEN", parentType.id()); } */ final String primaryKey = manyToOneAnnotation.primaryKey(); propertyAttrs.addAttribute("", "primaryKey", "primaryKey", "NMTOKEN", primaryKey); final String foreignKey = manyToOneAnnotation.foreignKey(); propertyAttrs.addAttribute("", "foreignKey", "foreignKey", "NMTOKEN", foreignKey); startElement(URI_DTO2JS, "ManyToOne", propertyQName, propertyAttrs); endElement(URI_DTO2JS, "ManyToOne", propertyQName); } } protected void _processActionDeclaration(final MethodDeclaration method) throws SAXException {} protected void declareJSProperty(final IJSPropertyDefinition property) throws SAXException { keysBuilder.enlist(property); if ( property.declareGetter() == JSMethodDeclarationKind.SKIP && property.declareSetter() == JSMethodDeclarationKind.SKIP ) { return; } final IJSType type; try { type = _types.getJSType(property.type(), property.origin().getPosition()); } catch (final InvalidJavaTypeException ex) { return; } final AttributesImpl propertyAttrs = new AttributesImpl(); propertyAttrs.addAttribute("", "name", "name", "NMTOKEN", property.name()); propertyAttrs.addAttribute("", "label", "label", "NMTOKEN", property.label()); propertyAttrs.addAttribute("", "resource", "resource", "NMTOKEN", property.resource()); propertyAttrs.addAttribute("", "formatString", "formatString", "NMTOKEN", property.formatString()); propertyAttrs.addAttribute("", "type", "type", "NMTOKEN", type.id() ); if ( type.isContainer() ) propertyAttrs.addAttribute("", "contentType", "contentType", "NMTOKEN", type.contentType().id() ); if ( type.isEnum() ) propertyAttrs.addAttribute("", "enum", "enum", "NMTOKEN", "true" ); if ( property.isAbstract() ) propertyAttrs.addAttribute("", "abstract", "abstract", "NMTOKEN", "true" ); switch (property.declareGetter()) { case OVERRIDE: propertyAttrs.addAttribute("", "override-getter", "override-get", "NMTOKEN", "true" ); case DECLARE: propertyAttrs.addAttribute("", "declare-getter", "readable", "NMTOKEN", "true" ); } switch (property.declareSetter()) { case OVERRIDE: propertyAttrs.addAttribute("", "override-setter", "override-set", "NMTOKEN", "true" ); case DECLARE: propertyAttrs.addAttribute("", "declare-setter", "writeable", "NMTOKEN", "true" ); } /* if ( modifiers.contains(Modifier.FINAL)) propertyAttrs.addAttribute("", "final", "final", "NMTOKEN", "true" ); */ final String propertyQName = NS_DTO2JS + ':' + "property"; startElement(URI_DTO2JS, "property", propertyQName, propertyAttrs); _processPropertyDeclaration(property.name(), property.origin()); endElement(URI_DTO2JS, "property", propertyQName); } }; final protected static String NS_DTO2JS = "dto2extjs"; final protected static String URI_DTO2JS = "http://dto2extjs.faratasystems.com/"; final protected static Attributes NO_ATTRS = new AttributesImpl(); }