package org.timepedia.exporter.rebind; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.timepedia.exporter.client.Export; import org.timepedia.exporter.client.ExportClosure; import org.timepedia.exporter.client.ExportConstructor; import org.timepedia.exporter.client.NoExport; import org.timepedia.exporter.client.StructuralType; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.typeinfo.JAbstractMethod; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.JConstructor; import com.google.gwt.core.ext.typeinfo.JField; import com.google.gwt.core.ext.typeinfo.JMethod; import com.google.gwt.core.ext.typeinfo.JType; import com.google.gwt.core.ext.typeinfo.TypeOracle; import com.google.gwt.core.ext.typeinfo.TypeOracleException; /** * */ public class ExportableTypeOracle { public static final String JSO_CLASS = "com.google.gwt.core.client.JavaScriptObject"; static final String EXPORTER_CLASS = "org.timepedia.exporter.client.Exporter"; static final String EXPORTABLE_CLASS = "org.timepedia.exporter.client.Exportable"; static final String EXPORTALL_CLASS = "org.timepedia.exporter.client.ExporterUtil.ExportAll"; static final String EXPORT_OVERLAY_CLASS = "org.timepedia.exporter.client.ExportOverlay"; private static final String STRING_CLASS = "java.lang.String"; private static final String DATE_CLASS = "java.util.Date"; private JClassType exportAllType; public boolean isConstructor(JAbstractMethod method, JExportableClassType type) { return method.isConstructor() != null || method.getAnnotation(ExportConstructor.class) !=null; } public boolean isExportable(JField field) { return field.isStatic() && field.isPublic() && field.isFinal() && ( isExportable(field.getAnnotation(Export.class)) || ( isExportable(field.getEnclosingType()) && !isNotExportable( field.getAnnotation(NoExport.class)))); } public boolean isExportable(JClassType type) { return isExportable(type.getAnnotation(Export.class)) || (type.isInterface() != null && isExportable(type.getAnnotation(ExportClosure.class))); } public static <T> boolean isExportable(Export annotation) { return annotation != null; } public boolean isExportable(JAbstractMethod method, JExportableClassType type) { boolean export = false; // Only public methods are exported if (method.isPublic()) { Export e; if (method instanceof JConstructor && isInstantiable(method.getEnclosingType()) && method.getParameters().length == 0) { // zero-arg constructors always exportable export = true; } else if (isNotExportable(method.getAnnotation(NoExport.class))) { // Do not export methods annotated as NoExport, although the // method is marked as export in an interface or the entire class // is annotated as Export export = false; } else if (isExportable(method.getAnnotation(Export.class))) { // Export this method if has the Export annotation export = true; } else if (isExportable(method.getEnclosingType())) { // Export all method in a class annotated as Export export = true; } else if (type != null && (e = type.getType().getAnnotation(Export.class)) != null && e.all()) { // Export this method if the class has the Export.all attribute set // Filter some generic methods present in Object export = !method.getName().matches("getClass|hashCode|equals|notify|notifyAll|wait"); } else { // Export methods which are annotated in implemented interfaces for (JClassType c : method.getEnclosingType().getImplementedInterfaces()) { for (JMethod m : c.getMethods()) { if (!isNotExportable(m.getAnnotation(NoExport.class)) && (isExportable(c) || isExportable(m.getAnnotation(Export.class))) && m.getName().equals(method.getName())) { if (m.getReadableDeclaration(true, true, false, true, true).equals( ((JMethod) method).getReadableDeclaration(true, true, false, true, true))) { export = true; break; } } } } } } return export; } private static boolean isExportable(ExportClosure annotation) { return annotation != null; } private static boolean isNotExportable(NoExport annotation) { return annotation != null; } private static boolean isExportable(ExportConstructor annotation) { return annotation != null; } public boolean isExportableFactoryMethod(JMethod method, JType retClass) { if (isExportable(method, null) && isExportable(method.getAnnotation(ExportConstructor.class))) { if (method.isStatic() && retClass.equals(method.getReturnType())) { return true; } throw new RuntimeException("Method " + method.getEnclosingType() + " " + method + " ExportConstructor but it is not static or it does not return a " + retClass); } return false; } private TypeOracle typeOracle; private TreeLogger log; private JClassType exportableType = null; private JClassType jsoType = null; private JClassType stringType = null; private JClassType dateType = null; private JClassType exportOverlayType; private Map<String, JExportOverlayClassType> overlayTypes = new HashMap<String, JExportOverlayClassType>(); public ExportableTypeOracle(TypeOracle typeOracle, TreeLogger log) { this.typeOracle = typeOracle; this.log = log; exportableType = typeOracle.findType(EXPORTABLE_CLASS); exportOverlayType = typeOracle.findType(EXPORT_OVERLAY_CLASS); exportAllType = typeOracle.findType(EXPORTALL_CLASS); jsoType = typeOracle.findType(JSO_CLASS); stringType = typeOracle.findType(STRING_CLASS); dateType = typeOracle.findType(DATE_CLASS); assert exportableType != null; assert exportOverlayType != null; assert jsoType != null; assert stringType != null; for (JClassType t : typeOracle.getTypes()) { if (t.isAssignableTo(exportOverlayType) && !t.equals(exportOverlayType)) { JClassType targetType = getExportOverlayType(t); overlayTypes.put(targetType.getQualifiedSourceName(), new JExportOverlayClassType(this, t)); } } } public JExportableClassType findExportableClassType(String requestedClass) { JClassType requestedType = typeOracle.findType(requestedClass); if (requestedType != null) { if (requestedType.isAssignableTo(exportOverlayType)) { return new JExportOverlayClassType(this, requestedType); } else if (requestedType.isAssignableTo(exportableType)) { return new JExportableClassType(this, requestedType); } JExportOverlayClassType exportOverlay = overlayTypes.get(requestedClass); return exportOverlay; } return null; } public JExportableType findExportableType(String paramTypeName) { try { JType type = typeOracle.parse(paramTypeName); JClassType cType = type != null ? type.isClassOrInterface() : null; if (type.isPrimitive() != null) { return new JExportablePrimitiveType(this, type.isPrimitive()); } else if (type.isArray() != null) { return new JExportableArrayType(this, type.isArray()); } else if (overlayTypes.containsKey(paramTypeName)) { return overlayTypes.get(paramTypeName); } else if (cType.isAssignableTo(exportOverlayType)) { return new JExportOverlayClassType(this, type.isClassOrInterface()); } else if (cType != null && (cType.isAssignableTo(exportableType) || cType.isAssignableTo(stringType) || cType.isAssignableTo(dateType) || cType.isAssignableTo(jsoType))) { return new JExportableClassType(this, type.isClassOrInterface()); } else { return null; } } catch (TypeOracleException e) { return null; } } public JClassType getExportOverlayType(JClassType requestedType) { JClassType[] inf = requestedType.getImplementedInterfaces(); for (JClassType i : inf) { if (isExportOverlay(i)) { return i.isParameterized().getTypeArgs()[0]; } } return null; } public boolean isArray(JExportableClassType jExportableClassType) { return jExportableClassType.getType().isArray() != null; } // We maintain a cache with overlay classes exported via export closure static HashMap<String, JExportableClassType> closuresCache = new HashMap<String, JExportableClassType>(); public boolean isClosure(JExportableClassType jExportableClassType) { if (jExportableClassType == null) { return false; } String cType = jExportableClassType.getType().getQualifiedSourceName(); if (closuresCache.containsKey(cType)) { return true; } JClassType rType = jExportableClassType.getRequestedType(); if (rType != null && rType.isAssignableTo(exportableType)) { ExportClosure ann = rType.getAnnotation(ExportClosure.class); if (ann != null && rType.isInterface() != null) { if (rType.getMethods().length > 0) { closuresCache.put(rType.getQualifiedSourceName(), jExportableClassType); closuresCache.put(cType, jExportableClassType); return true; } } } return false; } public boolean isExportOverlay(JClassType i) { return i.isAssignableTo(exportOverlayType); } public boolean isInstantiable(JClassType i) { return !i.isAbstract() && i.isInterface() == null; } public boolean isJavaScriptObject(JExportableClassType type) { return type.getType().isAssignableTo(jsoType); } public boolean isString(JExportableClassType type) { return type.getType().isAssignableTo(stringType); } public boolean isDate(JExportableClassType type) { return type.getType().isAssignableTo(dateType); } public boolean isString(JType type) { return type.isClass() != null && type.isClass().isAssignableTo(stringType); } public boolean isJavaScriptObject(JType type) { return type.isClass() != null && type.isClass().isAssignableTo(jsoType); } public boolean isExportAll(String requestedClass) { return typeOracle.findType(requestedClass).isAssignableTo(exportAllType); } public List<JClassType> findAllExportableTypes() { ArrayList<JClassType> types = new ArrayList<JClassType>(); // Closures should be exported first for (JClassType t : typeOracle.getTypes()) { if (t.isAssignableTo(exportableType) && isClosure(t)) { types.add(t); } } // ExportOverlays should be exported before other classes for (JClassType t : typeOracle.getTypes()) { if (types.contains(t) || t.equals(exportAllType) || t.equals(exportableType) || t.equals(exportOverlayType)) { continue; } if (t.isAssignableTo(exportOverlayType)) { types.add(t); } } // Finally all exportable classes for (JClassType t : typeOracle.getTypes()) { if (types.contains(t) || t.equals(exportAllType) || t.equals(exportableType) || t.equals(exportOverlayType)) { continue; } if (t.isAssignableTo(exportableType)) { if (t.isDefaultInstantiable() && t.isPublic() && new JExportableClassType(this, t).getExportableMethods().length > 0) { types.add(t); } } } return types; } public boolean isClosure(JClassType type) { return type.getAnnotation(ExportClosure.class) != null; } public boolean isStructuralType(JClassType type) { // always false for now until enabled return false && type.getAnnotation(StructuralType.class) != null; } public String getJsTypeOf(JClassType type) { if (type.isAssignableTo(stringType)) { return "string"; } else if (type.isAssignableTo(jsoType)) { return "object"; } return "@" + type.getQualifiedSourceName() + "::class"; } }