/** * 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.types; import com.farata.dto2extjs.annotations.JSClass; import com.farata.dto2extjs.annotations.JSClassKind; import com.farata.dto2extjs.asap.INameTransformer; import com.farata.dto2extjs.asap.JSAnnotationProcessorOptions; import com.sun.mirror.apt.AnnotationProcessorEnvironment; import com.sun.mirror.declaration.*; import com.sun.mirror.type.*; import com.sun.mirror.util.SourcePosition; import com.sun.mirror.util.Types; import java.util.*; public class JSTypeReflector { final private AnnotationProcessorEnvironment _environment; final private JSClassKind _defaultClassKind; final private JSClassKind _defaultEnumKind; final private IWorkset _workset; final private Types _types; final private boolean _numberAsString; final private INameTransformer _classNameTransformer; public JSTypeReflector(final AnnotationProcessorEnvironment environment, final IWorkset workset, final JSAnnotationProcessorOptions options) { _environment = environment; _workset = workset; _defaultClassKind = options.defaultClassKind(); _defaultEnumKind = options.defaultEnumKind(); _types = environment.getTypeUtils(); _numberAsString = options.numberAsString(); _classNameTransformer = options.classNameTransformer(); } public AnnotationProcessorEnvironment environment() { return _environment; } public boolean isVoid(final TypeMirror type) { return type instanceof VoidType; } public boolean isBoolean(final TypeMirror type) { return type instanceof PrimitiveType && ((PrimitiveType)type).getKind() == PrimitiveType.Kind.BOOLEAN; } public boolean isAbstract(final Declaration declaration) { return declaration instanceof InterfaceDeclaration || declaration.getModifiers().contains( Modifier.ABSTRACT ); } public JSClassKind resolveTypeOf(final TypeDeclaration declaration) throws InvalidJavaTypeException { return resolveTypeOf(declaration, false); } public JSClassKind resolveTypeOf(final TypeDeclaration declaration, final boolean selfCheck) throws InvalidJavaTypeException { final JSClass jsClass = declaration.getAnnotation(JSClass.class); if (null == jsClass) { throw new InvalidJavaTypeException(); } final JSClassKind jsClassKind = jsClass.kind(); switch (jsClassKind) { case EXT_JS: if (declaration instanceof EnumDeclaration) { if (selfCheck) { _environment.getMessager().printWarning( JSTypeReflector.getJSClassAnnotationAttributePosition(declaration, "type"), "Explicit usage of JSClassKind.EXT_JS for enum is deprecated." ); } } return jsClassKind; case STRING_CONSTANTS: if (!(declaration instanceof EnumDeclaration)) { if (selfCheck) { _environment.getMessager().printError( JSTypeReflector.getJSClassAnnotationAttributePosition(declaration, "type"), "Invalid usage of JSClassKind.STRING_CONSTANTS, string constants are allowed only for enum." ); throw new InvalidJavaTypeException(); } } return jsClassKind; case CLASSIC: if (declaration instanceof EnumDeclaration) { if (selfCheck) { _environment.getMessager().printError( JSTypeReflector.getJSClassAnnotationAttributePosition(declaration, "type"), "Invalid usage of JSClassKind.CLASSIC, only DEFAULT or STRING_CONSTANTS kinds are allowed for enum." ); throw new InvalidJavaTypeException(); } else return JSClassKind.EXT_JS; } return jsClassKind; case DEFAULT: default: if (declaration instanceof EnumDeclaration) { if (_defaultEnumKind != JSClassKind.DEFAULT) return _defaultEnumKind; else return JSClassKind.EXT_JS; } else if (_defaultClassKind != JSClassKind.DEFAULT) return _defaultClassKind; else if (null != getAnnotationMirror(declaration, "javax.persistence.Entity")) // ExtJS in any case return JSClassKind.EXT_JS; else return JSClassKind.EXT_JS; } } public static AnnotationMirror getJSClassAnnotationMirror(final TypeDeclaration type) { return JSTypeReflector.getAnnotationMirror(type, JSClass.class.getName()); } public static AnnotationMirror getAnnotationMirror(final Declaration type, final String annotationTypeName) { for (final AnnotationMirror anno : type.getAnnotationMirrors() ) { if ( annotationTypeName.equals( anno.getAnnotationType().getDeclaration().getQualifiedName() ) ) return anno; } return null; } public static SourcePosition getJSClassAnnotationAttributePosition(final TypeDeclaration type, final String attributeName) { return getAnnotationAttributePosition(type, JSClass.class.getName(), attributeName); } public static SourcePosition getAnnotationAttributePosition(final TypeDeclaration type, final String annotationTypeName, final String attributeName) { final AnnotationMirror annotationMirror = getAnnotationMirror(type, annotationTypeName); if (null == annotationMirror) return null; final Map<AnnotationTypeElementDeclaration, AnnotationValue> attributes = annotationMirror.getElementValues(); for (final Map.Entry<AnnotationTypeElementDeclaration, AnnotationValue> e : attributes.entrySet()) { if (attributeName.equals(e.getKey().getSimpleName()) ) { final SourcePosition result = e.getValue().getPosition(); return null == result ? annotationMirror.getPosition() : result; } } return annotationMirror.getPosition(); } public IJSType getJSType(final Class<?> type) { System.out.println(type); final TypeDeclaration t= _environment.getTypeDeclaration(type.getName()); return getJSType(t); } public IJSType getJSType(final TypeDeclaration type) { return getJSType(_types.getDeclaredType(type), type.getPosition()); } public IJSType getJSType(final TypeMirror type, final SourcePosition pos) throws InvalidJavaTypeException { return getJSType(type, pos, false); } public IJSType getJSType(final TypeMirror type, final SourcePosition pos, final boolean tolerateUnknown) throws InvalidJavaTypeException { if ( type instanceof PrimitiveType ) { final PrimitiveType pType = (PrimitiveType)type; switch ( pType.getKind() ) { case BOOLEAN: return JSBuiltinType.BOOLEAN; case BYTE: case SHORT: case INT: return JSBuiltinType.INTEGER; case FLOAT: return JSBuiltinType.FLOAT; case LONG: return JSBuiltinType.NUMBER; case DOUBLE: return _numberAsString ? JSBuiltinType.STRING : JSBuiltinType.NUMBER; case CHAR: return JSBuiltinType.STRING; default: throw new UnsupportedOperationException("Unknown Java primitive type: " + pType.getKind()); } } else if ( type instanceof VoidType ) { return JSBuiltinType.AUTO; } else if ( type instanceof ReferenceType ) { if ( type instanceof ArrayType ) { final ArrayType aType = (ArrayType)type; final TypeMirror componentType = aType.getComponentType(); if ( componentType instanceof PrimitiveType ) { final PrimitiveType pComponentType = (PrimitiveType)componentType; if ( pComponentType.getKind() == PrimitiveType.Kind.BYTE ) return FLASH_BYTE_ARRAY; } else if ( componentType instanceof DeclaredType ) { final DeclaredType dComponentType = (DeclaredType)componentType; final TypeDeclaration tdComponentType = dComponentType.getDeclaration(); if (null == tdComponentType) { // throw error for behavior coherent with non-array types; throw new InvalidJavaTypeException(missingTypeDefiniton(dComponentType)); } final String qName = tdComponentType.getQualifiedName(); if ( "java.lang.Character".equals(qName) ) return JSBuiltinType.STRING; else if ( "java.lang.Byte".equals(qName) ) return FLASH_BYTE_ARRAY; } return JSBuiltinType.AUTO /*new IJSType() { public String id() { return "Array"; } public JSClassKind classKind() { return null; } public boolean isContainer() { return true; } public boolean isEnum() { return false; } public IJSType contentType() { return getJSType(componentType, pos, tolerateUnknown); } }*/; } else if ( type instanceof DeclaredType ) { final DeclaredType dType = (DeclaredType)type; final TypeDeclaration cDeclaration = dType.getDeclaration(); if (null == cDeclaration) { throw new InvalidJavaTypeException(missingTypeDefiniton(dType)); } final String qName = cDeclaration.getQualifiedName(); if ( "java.lang.Object".equals(qName) ) return JSBuiltinType.AUTO; if ( "java.lang.String".equals( qName ) ) return JSBuiltinType.STRING; else if ( "java.lang.Boolean".equals(qName) ) return JSBuiltinType.BOOLEAN; else if ( knownNumberWrappers().contains(qName) ) return _numberAsString ? JSBuiltinType.STRING : JSBuiltinType.NUMBER; else if ( isSubtypeOf(dType, "java.util.Date", false) ) return JSBuiltinType.DATE; else if ( isSubtypeOf(dType, "java.util.Calendar", false) ) return JSBuiltinType.DATE; else if ( isSubtypeOf(dType, "java.util.Map", true)) return JSBuiltinType.AUTO; else if ( isSubtypeOf(dType, "java.util.Collection", true)) { final String jsCollectionClassName; if ( isSubtypeOf(dType, "java.util.List", true) ) jsCollectionClassName = "mx.collections.ArrayCollection"; else jsCollectionClassName = "mx.collections.ICollectionView"; final Collection<TypeMirror> elementTypes = dType.getActualTypeArguments(); // exactly one, we can't guess if more for some ad-hoc collection subclass if (null != elementTypes && elementTypes.size() == 1) { return new JSCustomType( jsCollectionClassName, getJSType(elementTypes.iterator().next(), pos, tolerateUnknown) ); } else { return new JSCustomType(jsCollectionClassName); } } else if ( isSubtypeOf(dType, "java.lang.Number", false) ) return _numberAsString ? JSBuiltinType.STRING : JSBuiltinType.NUMBER; else if ( isSubtypeOf(dType, "org.w3c.dom.Document", true ) ) return JSBuiltinType.STRING; final JSClass jsClass = cDeclaration.getAnnotation(JSClass.class); if (null == jsClass) { _environment.getMessager().printError(pos, unsupportedTypeError(type)); throw new InvalidJavaTypeException(unsupportedTypeError(type)); } else { _workset.enlist( cDeclaration ); if ( cDeclaration instanceof EnumDeclaration && resolveTypeOf(cDeclaration) == JSClassKind.STRING_CONSTANTS ) { return JSBuiltinType.STRING; } else { return new JSCustomType(cDeclaration, _classNameTransformer, resolveTypeOf(cDeclaration) ); } } } } if (tolerateUnknown) return null; else { _environment.getMessager().printError(pos, unsupportedTypeError(type)); throw new InvalidJavaTypeException(unsupportedTypeError(type)); } } private String missingTypeDefiniton(DeclaredType type) { final String MISSING_TYPE = "Can't find class: '%1$s'"; return String.format(MISSING_TYPE, type); } private String unsupportedTypeError(TypeMirror type) { final String UNSUPPORTED_TYPE="Unsupported type: '%1$s' is not annotated with @JSClass"; return String.format(UNSUPPORTED_TYPE, type); } public boolean isSubtypeOf(final DeclaredType type, String targetType, boolean checkInterfaces) { if (null == type) return false; final TypeMirror otherType = _types.getDeclaredType( _environment.getTypeDeclaration( targetType ) ); return _types.isSubtype(type, otherType); } public Set<String> knownNumberWrappers() { return KNOWN_NUMBER_WRAPPERS; } final private static IJSType FLASH_BYTE_ARRAY = new JSCustomType("flash.utils.ByteArray"); final private static Set<String> KNOWN_NUMBER_WRAPPERS = new HashSet<String>( Arrays.asList( "java.lang.Byte", "java.lang.Short", "java.lang.Integer", "java.lang.Long", "java.lang.Float", "java.lang.Double" ) ); }