com.artemis.gwtref.gen.ReflectionCacheSourceCreator.java Source code

Java tutorial

Introduction

Here is the source code for com.artemis.gwtref.gen.ReflectionCacheSourceCreator.java

Source

/*******************************************************************************
 * Copyright 2011 See AUTHORS file.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package com.artemis.gwtref.gen;

import com.google.gwt.core.ext.BadPropertyValueException;
import com.google.gwt.core.ext.ConfigurationProperty;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.typeinfo.*;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

public class ReflectionCacheSourceCreator {
    private static final List<String> PRIMITIVE_TYPES = Collections.unmodifiableList(
            Arrays.asList(new String[] { "char", "int", "long", "byte", "short", "float", "double", "boolean" }));
    final TreeLogger logger;
    final GeneratorContext context;
    final JClassType type;
    final String simpleName;
    final String packageName;
    SourceWriter sw;
    StringBuffer source = new StringBuffer();
    List<JType> types = new ArrayList<JType>();
    List<SetterGetterStub> setterGetterStubs = new ArrayList<SetterGetterStub>();
    List<MethodStub> methodStubs = new ArrayList<MethodStub>();
    int nextId = 0;

    class SetterGetterStub {
        int getter;
        int setter;
        String name;
        String enclosingType;
        String type;
        boolean isStatic;
        boolean isFinal;
        boolean unused;
    }

    class MethodStub {
        String enclosingType;
        String returnType;
        List<String> parameterTypes = new ArrayList<String>();
        String jnsi;
        int methodId;
        boolean isStatic;
        boolean isAbstract;
        boolean isFinal;
        boolean isNative;
        boolean isConstructor;
        boolean isMethod;
        boolean isPublic;
        String name;
        boolean unused;
    }

    public ReflectionCacheSourceCreator(TreeLogger logger, GeneratorContext context, JClassType type) {
        this.logger = logger;
        this.context = context;
        this.type = type;
        this.packageName = type.getPackage().getName();
        this.simpleName = type.getSimpleSourceName() + "Generated";
        logger.log(Type.INFO, type.getQualifiedSourceName());
    }

    private int nextId() {
        return nextId++;
    }

    public String create() {
        ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(packageName, simpleName);
        composer.addImplementedInterface("com.artemis.gwtref.client.IReflectionCache");
        imports(composer);
        PrintWriter printWriter = context.tryCreate(logger, packageName, simpleName);
        if (printWriter == null) {
            return packageName + "." + simpleName;
        }
        sw = composer.createSourceWriter(context, printWriter);

        generateLookups();

        getKnownTypesC();
        forNameC();
        newArrayC();

        getArrayLengthT();
        getArrayElementT();
        setArrayElementT();

        getF();
        setF();

        invokeM();

        sw.commit(logger);
        createProxy(type);
        return packageName + "." + simpleName;
    }

    private void createProxy(JClassType type) {
        ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(type.getPackage().getName(),
                type.getSimpleSourceName() + "Proxy");
        PrintWriter printWriter = context.tryCreate(logger, packageName, simpleName);
        if (printWriter == null) {
            return;
        }
        SourceWriter writer = composer.createSourceWriter(context, printWriter);
        writer.commit(logger);
    }

    private void generateLookups() {
        p("Map<String, Type> types = new HashMap<String, Type>();");

        TypeOracle typeOracle = context.getTypeOracle();
        JPackage[] packages = typeOracle.getPackages();

        // gather all types from wanted packages
        for (JPackage p : packages) {
            for (JClassType t : p.getTypes()) {
                gatherTypes(t.getErasedType(), types);
            }
        }

        gatherTypes(typeOracle.findType("java.util.List").getErasedType(), types);
        gatherTypes(typeOracle.findType("java.util.ArrayList").getErasedType(), types);
        gatherTypes(typeOracle.findType("java.util.HashMap").getErasedType(), types);
        gatherTypes(typeOracle.findType("java.util.Map").getErasedType(), types);
        gatherTypes(typeOracle.findType("java.lang.String").getErasedType(), types);
        gatherTypes(typeOracle.findType("java.lang.Boolean").getErasedType(), types);
        gatherTypes(typeOracle.findType("java.lang.Byte").getErasedType(), types);
        gatherTypes(typeOracle.findType("java.lang.Long").getErasedType(), types);
        gatherTypes(typeOracle.findType("java.lang.Character").getErasedType(), types);
        gatherTypes(typeOracle.findType("java.lang.Short").getErasedType(), types);
        gatherTypes(typeOracle.findType("java.lang.Integer").getErasedType(), types);
        gatherTypes(typeOracle.findType("java.lang.Float").getErasedType(), types);
        gatherTypes(typeOracle.findType("java.lang.CharSequence").getErasedType(), types);
        gatherTypes(typeOracle.findType("java.lang.Double").getErasedType(), types);
        gatherTypes(typeOracle.findType("java.lang.Object").getErasedType(), types);

        // sort the types so the generated output will be stable between runs
        Collections.sort(types, new Comparator<JType>() {
            public int compare(JType o1, JType o2) {
                return o1.getQualifiedSourceName().compareTo(o2.getQualifiedSourceName());
            }
        });

        // generate Type lookup generator methods.
        int id = 0;
        for (JType t : types) {
            String typeGen = createTypeGenerator(t);
            p("private void c" + (id++) + "() {");
            p(typeGen);
            p("}\n");
        }

        // generate constructor that calls all the type generators
        // that populate the map.
        p("public " + simpleName + "() {");
        for (int i = 0; i < id; i++) {
            p("c" + i + "();");
        }
        p("}");

        // sort the stubs so the generated output will be stable between runs
        Collections.sort(setterGetterStubs, new Comparator<SetterGetterStub>() {
            @Override
            public int compare(SetterGetterStub o1, SetterGetterStub o2) {
                return new Integer(o1.setter).compareTo(o2.setter);
            }
        });

        // generate field setters/getters
        for (SetterGetterStub stub : setterGetterStubs) {
            String stubSource = generateSetterGetterStub(stub);
            if (stubSource.equals(""))
                stub.unused = true;
            p(stubSource);
        }

        // sort the stubs so the generated output will be stable between runs
        Collections.sort(methodStubs, new Comparator<MethodStub>() {
            @Override
            public int compare(MethodStub o1, MethodStub o2) {
                return new Integer(o1.methodId).compareTo(o2.methodId);
            }
        });

        // generate methods
        for (MethodStub stub : methodStubs) {
            String stubSource = generateMethodStub(stub);
            if (stubSource.equals(""))
                stub.unused = true;
            p(stubSource);
        }

        logger.log(Type.INFO, types.size() + " types reflected");
    }

    private void out(String message, int nesting) {
        for (int i = 0; i < nesting; i++)
            System.out.print("  ");
        System.out.println(message);
    }

    int nesting = 0;

    private void gatherTypes(JType type, List<JType> types) {
        nesting++;
        // came here from a type that has no super class
        if (type == null) {
            nesting--;
            return;
        }
        // package info
        if (type.getQualifiedSourceName().contains("-")) {
            nesting--;
            return;
        }

        // not visible
        if (!isVisible(type)) {
            nesting--;
            return;
        }

        // filter reflection scope based on configuration in gwt xml module
        boolean keep = false;
        String name = type.getQualifiedSourceName();
        try {
            ConfigurationProperty prop;
            keep |= !name.contains(".");
            prop = context.getPropertyOracle().getConfigurationProperty("artemis.reflect.include");
            for (String s : prop.getValues())
                keep |= name.contains(s);
            prop = context.getPropertyOracle().getConfigurationProperty("artemis.reflect.exclude");
            for (String s : prop.getValues())
                keep &= !name.equals(s);
        } catch (BadPropertyValueException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        if (!keep) {
            nesting--;
            return;
        }

        // already visited this type
        if (types.contains(type.getErasedType())) {
            nesting--;
            return;
        }
        types.add(type.getErasedType());
        out(type.getErasedType().getQualifiedSourceName(), nesting);

        if (type instanceof JPrimitiveType) {
            // nothing to do for a primitive type
            nesting--;
            return;
        } else {
            // gather fields
            JClassType c = (JClassType) type;
            JField[] fields = c.getFields();
            if (fields != null) {
                for (JField field : fields) {
                    gatherTypes(field.getType().getErasedType(), types);
                }
            }

            // gather super types & interfaces
            gatherTypes(c.getSuperclass(), types);
            JClassType[] interfaces = c.getImplementedInterfaces();
            if (interfaces != null) {
                for (JClassType i : interfaces) {
                    gatherTypes(i.getErasedType(), types);
                }
            }

            // gather method parameter & return types
            JMethod[] methods = c.getMethods();
            if (methods != null) {
                for (JMethod m : methods) {
                    gatherTypes(m.getReturnType().getErasedType(), types);
                    if (m.getParameterTypes() != null) {
                        for (JType p : m.getParameterTypes()) {
                            gatherTypes(p.getErasedType(), types);
                        }
                    }
                }
            }

            // gather inner classes
            JClassType[] inner = c.getNestedTypes();
            if (inner != null) {
                for (JClassType i : inner) {
                    gatherTypes(i.getErasedType(), types);
                }
            }
        }
        nesting--;
    }

    private String generateMethodStub(MethodStub stub) {
        buffer.setLength(0);

        if (stub.enclosingType == null) {
            logger.log(Type.INFO, "method '" + stub.name + "' of invisible class is not invokable");
            return "";
        }

        if ((stub.enclosingType.startsWith("java") && !stub.enclosingType.startsWith("java.util"))
                || stub.enclosingType.contains("google")) {
            logger.log(Type.INFO, "not emitting code for accessing method " + stub.name + " in class '"
                    + stub.enclosingType + ", either in java.* or GWT related class");
            return "";
        }

        if (stub.enclosingType.contains("[]")) {
            logger.log(Type.INFO, "method '" + stub.name + "' of class '" + stub.enclosingType
                    + "' is not invokable because the class is an array type");
            return "";
        }

        for (int i = 0; i < stub.parameterTypes.size(); i++) {
            String paramType = stub.parameterTypes.get(i);
            if (paramType == null) {
                logger.log(Type.INFO, "method '" + stub.name + "' of class '" + stub.enclosingType
                        + "' is not invokable because one of its argument types is not visible");
                return "";
            } else if (paramType.startsWith("long") || paramType.contains("java.lang.Long")) {
                logger.log(Type.INFO, "method '" + stub.name + "' of class '" + stub.enclosingType
                        + " has long parameter, prohibited in JSNI");
                return "";
            } else {
                stub.parameterTypes.set(i, paramType.replace(".class", ""));
            }
        }
        if (stub.returnType == null) {
            logger.log(Type.INFO, "method '" + stub.name + "' of class '" + stub.enclosingType
                    + "' is not invokable because its return type is not visible");
            return "";
        }
        if (stub.returnType.startsWith("long") || stub.returnType.contains("java.lang.Long")) {
            logger.log(Type.INFO, "method '" + stub.name + "' of class '" + stub.enclosingType
                    + " has long return type, prohibited in JSNI");
            return "";
        }

        stub.enclosingType = stub.enclosingType.replace(".class", "");
        stub.returnType = stub.returnType.replace(".class", "");

        if (stub.isMethod) {
            boolean isVoid = stub.returnType.equals("void");
            pbn("private native " + (isVoid ? "Object" : stub.returnType) + " m" + stub.methodId + "(");
            if (!stub.isStatic)
                pbn(stub.enclosingType + " obj" + (stub.parameterTypes.size() > 0 ? ", " : ""));
            int i = 0;
            for (String paramType : stub.parameterTypes) {
                pbn(paramType + " p" + i + (i < stub.parameterTypes.size() - 1 ? "," : ""));
                i++;
            }
            pbn(") /*-{");

            if (!isVoid)
                pbn("return ");
            if (stub.isStatic)
                pbn("@" + stub.enclosingType + "::" + stub.name + "(" + stub.jnsi + ")(");
            else
                pbn("obj.@" + stub.enclosingType + "::" + stub.name + "(" + stub.jnsi + ")(");

            for (i = 0; i < stub.parameterTypes.size(); i++) {
                pbn("p" + i + (i < stub.parameterTypes.size() - 1 ? ", " : ""));
            }
            pbn(");");
            if (isVoid)
                pbn("return null;");
            pbn("}-*/;");
        } else {
            pbn("private static " + stub.returnType + " m" + stub.methodId + "(");
            int i = 0;
            for (String paramType : stub.parameterTypes) {
                pbn(paramType + " p" + i + (i < stub.parameterTypes.size() - 1 ? "," : ""));
                i++;
            }
            pbn(") {");

            pbn("return new " + stub.returnType + "(");
            for (i = 0; i < stub.parameterTypes.size(); i++) {
                pbn("p" + i + (i < stub.parameterTypes.size() - 1 ? ", " : ""));
            }
            pbn(")");
            if (!stub.isPublic) {
                // Access non-public constructors through an anonymous class
                pbn("{}");
            }
            pbn(";");

            pbn("}");
        }

        return buffer.toString();
    }

    private String generateSetterGetterStub(SetterGetterStub stub) {
        buffer.setLength(0);
        if (stub.enclosingType == null || stub.type == null) {
            logger.log(Type.INFO, "field '" + stub.name + "' in class '" + stub.enclosingType
                    + "' is not accessible as its type '" + stub.type + "' is not public");
            return "";
        }
        if (stub.enclosingType.startsWith("java") || stub.enclosingType.contains("google")) {
            logger.log(Type.INFO, "not emitting code for accessing field " + stub.name + " in class '"
                    + stub.enclosingType + ", either in java.* or GWT related class");
            return "";
        }

        if (stub.type.startsWith("long") || stub.type.contains("java.lang.Long")) {
            logger.log(Type.INFO, "not emitting code for accessing field " + stub.name + " in class '"
                    + stub.enclosingType + " as its of type long which can't be used with JSNI");
            return "";
        }

        stub.enclosingType = stub.enclosingType.replace(".class", "");
        stub.type = stub.type.replace(".class", "");

        pb("// " + stub.enclosingType + "#" + stub.name);
        pbn("private native " + stub.type + " g" + stub.getter + "(" + stub.enclosingType + " obj) /*-{");
        if (stub.isStatic)
            pbn("return @" + stub.enclosingType + "::" + stub.name + ";");
        else
            pbn("return obj.@" + stub.enclosingType + "::" + stub.name + ";");
        pb("}-*/;");

        if (!stub.isFinal) {
            pbn("private native void s" + stub.setter + "(" + stub.enclosingType + " obj, " + stub.type
                    + " value)  /*-{");
            if (stub.isStatic)
                pbn("@" + stub.enclosingType + "::" + stub.name + " = value");
            else
                pbn("obj.@" + stub.enclosingType + "::" + stub.name + " = value;");
            pb("}-*/;");
        }

        return buffer.toString();
    }

    private boolean isVisible(JType type) {
        if (type == null)
            return false;

        if (type instanceof JClassType) {
            if (type instanceof JArrayType) {
                JType componentType = ((JArrayType) type).getComponentType();
                while (componentType instanceof JArrayType) {
                    componentType = ((JArrayType) componentType).getComponentType();
                }
                if (componentType instanceof JClassType) {
                    return ((JClassType) componentType).isPublic();
                }
            } else {
                return ((JClassType) type).isPublic();
            }
        }
        return true;
    }

    private Map<String, Integer> typeNames2typeIds = new HashMap<String, Integer>();

    private String createTypeGenerator(JType t) {
        buffer.setLength(0);
        String varName = "t";
        if (t instanceof JPrimitiveType)
            varName = "p";
        int id = nextId();
        typeNames2typeIds.put(t.getErasedType().getQualifiedSourceName(), id);
        pb("Type " + varName + " = new Type();");
        pb(varName + ".name = \"" + t.getErasedType().getQualifiedSourceName() + "\";");
        pb(varName + ".id = " + id + ";");
        pb(varName + ".clazz = " + t.getErasedType().getQualifiedSourceName() + ".class;");
        if (t instanceof JClassType) {
            JClassType c = (JClassType) t;
            if (isVisible(c.getSuperclass()))
                pb(varName + ".superClass = " + c.getSuperclass().getErasedType().getQualifiedSourceName()
                        + ".class;");
            if (c.getFlattenedSupertypeHierarchy() != null) {
                pb("Set<Class> " + varName + "Assignables = new HashSet<Class>();");
                for (JType i : c.getFlattenedSupertypeHierarchy()) {
                    if (!isVisible(i))
                        continue;
                    pb(varName + "Assignables.add(" + i.getErasedType().getQualifiedSourceName() + ".class);");
                }
                pb(varName + ".assignables = " + varName + "Assignables;");
            }
            if (c.isInterface() != null)
                pb(varName + ".isInterface = true;");
            if (c.isEnum() != null)
                pb(varName + ".isEnum = true;");
            if (c.isArray() != null)
                pb(varName + ".isArray = true;");
            if (c.isMemberType())
                pb(varName + ".isMemberClass = true;");
            pb(varName + ".isStatic = " + c.isStatic() + ";");
            pb(varName + ".isAbstract = " + c.isAbstract() + ";");

            if (c.getFields() != null) {
                pb(varName + ".fields = new Field[] {");
                for (JField f : c.getFields()) {
                    String enclosingType = getType(c);
                    String fieldType = getType(f.getType());
                    int setter = nextId();
                    int getter = nextId();
                    String elementType = getElementTypes(f);
                    String annotations = getAnnotations(f.getDeclaredAnnotations());

                    pb("new Field(\"" + f.getName() + "\", " + enclosingType + ", " + fieldType + ", " + f.isFinal()
                            + ", " + f.isDefaultAccess() + ", " + f.isPrivate() + ", " + f.isProtected() + ", "
                            + f.isPublic() + ", " + f.isStatic() + ", " + f.isTransient() + ", " + f.isVolatile()
                            + ", " + getter + ", " + setter + ", " + elementType + ", " + annotations + "), ");

                    SetterGetterStub stub = new SetterGetterStub();
                    stub.name = f.getName();
                    stub.enclosingType = enclosingType;
                    stub.type = fieldType;
                    stub.isStatic = f.isStatic();
                    stub.isFinal = f.isFinal();
                    if (enclosingType != null && fieldType != null) {
                        stub.getter = getter;
                        stub.setter = setter;
                    }
                    setterGetterStubs.add(stub);
                }
                pb("};");
            }

            printMethods(c, varName, "Method", c.getMethods());
            if (c.isPublic() && !c.isAbstract() && (c.getEnclosingType() == null || c.isStatic())) {
                printMethods(c, varName, "Constructor", c.getConstructors());
            } else {
                logger.log(Type.INFO, c.getName() + " can't be instantiated. Constructors not generated");
            }

            if (c.isArray() != null) {
                pb(varName + ".componentType = " + getType(c.isArray().getComponentType()) + ";");
            }
            if (c.isEnum() != null) {
                JEnumConstant[] enumConstants = c.isEnum().getEnumConstants();
                if (enumConstants != null) {
                    pb(varName + ".enumConstants = new Object[" + enumConstants.length + "];");
                    for (int i = 0; i < enumConstants.length; i++) {
                        pb(varName + ".enumConstants[" + i + "] = " + c.getErasedType().getQualifiedSourceName()
                                + "." + enumConstants[i].getName() + ";");
                    }
                }
            }

            Annotation[] annotations = c.getDeclaredAnnotations();
            if (annotations != null && annotations.length > 0) {
                pb(varName + ".annotations = " + getAnnotations(annotations) + ";");
            }
        } else if (t.isAnnotation() != null) {
            pb(varName + ".isAnnotation = true;");
        } else {
            pb(varName + ".isPrimitive = true;");
        }

        pb("types.put(\"" + t.getErasedType().getQualifiedSourceName() + "\", " + varName + ");");
        return buffer.toString();
    }

    private String getAnnotations(Annotation[] annotations) {
        if (annotations != null && annotations.length > 0) {
            int numValidAnnotations = 0;
            final Class<?>[] ignoredAnnotations = { Deprecated.class, Retention.class };
            StringBuilder b = new StringBuilder();
            b.append("new java.lang.annotation.Annotation[] {");
            for (Annotation annotation : annotations) {
                Class<?> type = annotation.annotationType();
                // skip ignored types, assuming we are not interested in those at runtime
                boolean ignoredType = false;
                for (int i = 0; !ignoredType && i < ignoredAnnotations.length; i++) {
                    ignoredType = ignoredAnnotations[i].equals(type);
                }
                if (ignoredType) {
                    continue;
                }
                // skip if not annotated with RetentionPolicy.RUNTIME
                Retention retention = type.getAnnotation(Retention.class);
                if (retention == null || retention.value() != RetentionPolicy.RUNTIME) {
                    continue;
                }
                numValidAnnotations++;
                // anonymous class
                b.append(" new ").append(type.getCanonicalName()).append("() {");
                // override all methods
                Method[] methods = type.getDeclaredMethods();
                for (Method method : methods) {
                    Class<?> returnType = method.getReturnType();
                    b.append(" @Override public");
                    b.append(" ").append(returnType.getCanonicalName());
                    b.append(" ").append(method.getName()).append("() { return");
                    if (returnType.isArray()) {
                        b.append(" new ").append(returnType.getCanonicalName()).append(" {");
                    }
                    // invoke the annotation method
                    Object invokeResult = null;
                    try {
                        invokeResult = method.invoke(annotation);
                    } catch (IllegalAccessException e) {
                        logger.log(Type.ERROR, "Error invoking annotation method.");
                    } catch (InvocationTargetException e) {
                        logger.log(Type.ERROR, "Error invoking annotation method.");
                    }
                    // write result as return value
                    if (invokeResult != null) {
                        if (returnType.equals(String[].class)) {
                            // String[]
                            for (String s : (String[]) invokeResult) {
                                b.append(" \"").append(s).append("\",");
                            }
                        } else if (returnType.equals(String.class)) {
                            // String
                            b.append(" \"").append((String) invokeResult).append("\"");
                        } else if (returnType.equals(Class[].class)) {
                            // Class[]
                            for (Class c : (Class[]) invokeResult) {
                                b.append(" ").append(c.getCanonicalName()).append(".class,");
                            }
                        } else if (returnType.equals(Class.class)) {
                            // Class
                            b.append(" ").append(((Class) invokeResult).getCanonicalName()).append(".class");
                        } else if (returnType.isArray() && returnType.getComponentType().isEnum()) {
                            // enum[]
                            String enumTypeName = returnType.getComponentType().getCanonicalName();
                            int length = Array.getLength(invokeResult);
                            for (int i = 0; i < length; i++) {
                                Object e = Array.get(invokeResult, i);
                                b.append(" ").append(enumTypeName).append(".").append(e.toString()).append(",");
                            }
                        } else if (returnType.isEnum()) {
                            // enum
                            b.append(" ").append(returnType.getCanonicalName()).append(".")
                                    .append(invokeResult.toString());
                        } else if (returnType.isArray() && returnType.getComponentType().isPrimitive()) {
                            // primitive []
                            Class<?> primitiveType = returnType.getComponentType();
                            int length = Array.getLength(invokeResult);
                            for (int i = 0; i < length; i++) {
                                Object n = Array.get(invokeResult, i);
                                b.append(" ").append(n.toString());
                                if (primitiveType.equals(float.class)) {
                                    b.append("f");
                                }
                                b.append(",");
                            }
                        } else if (returnType.isPrimitive()) {
                            // primitive
                            b.append(" ").append(invokeResult.toString());
                            if (returnType.equals(float.class)) {
                                b.append("f");
                            }
                        } else {
                            logger.log(Type.ERROR, "Return type not supported (or not yet implemented).");
                        }
                    }
                    if (returnType.isArray()) {
                        b.append(" }");
                    }
                    b.append("; ");
                    b.append("}");
                }
                // must override annotationType()
                b.append(
                        " @Override public Class<? extends java.lang.annotation.Annotation> annotationType() { return ");
                b.append(type.getCanonicalName());
                b.append(".class; }");
                b.append("}, ");
            }
            b.append("}");
            return (numValidAnnotations > 0) ? b.toString() : "null";
        }
        return "null";
    }

    private void printMethods(JClassType c, String varName, String methodType, JAbstractMethod[] methodTypes) {
        if (methodTypes != null) {
            pb(varName + "." + methodType.toLowerCase() + "s = new " + methodType + "[] {");
            for (JAbstractMethod m : methodTypes) {
                MethodStub stub = new MethodStub();
                stub.isPublic = m.isPublic();
                stub.enclosingType = getType(c);
                if (m.isMethod() != null) {
                    stub.isMethod = true;
                    stub.returnType = getType(m.isMethod().getReturnType());
                    stub.isStatic = m.isMethod().isStatic();
                    stub.isAbstract = m.isMethod().isAbstract();
                    stub.isNative = m.isMethod().isAbstract();
                    stub.isFinal = m.isMethod().isFinal();

                } else {
                    if (m.isPrivate() || m.isDefaultAccess()) {
                        logger.log(Type.INFO, "Skipping non-visible constructor for class " + c.getName());
                        continue;
                    }
                    if (m.getEnclosingType().isFinal() && !m.isPublic()) {
                        logger.log(Type.INFO, "Skipping non-public constructor for final class" + c.getName());
                        continue;
                    }
                    stub.isConstructor = true;
                    stub.returnType = stub.enclosingType;
                }

                stub.jnsi = "";
                stub.methodId = nextId();
                stub.name = m.getName();
                methodStubs.add(stub);

                String methodAnnotations = getAnnotations(m.getDeclaredAnnotations());

                pb("new " + methodType + "(\"" + m.getName() + "\", ");
                pb(stub.enclosingType + ", ");
                pb(stub.returnType + ", ");

                pb("new Parameter[] {");
                if (m.getParameters() != null) {
                    for (JParameter p : m.getParameters()) {
                        stub.parameterTypes.add(getType(p.getType()));
                        stub.jnsi += p.getType().getErasedType().getJNISignature();
                        pb("new Parameter(\"" + p.getName() + "\", " + getType(p.getType()) + ", \""
                                + p.getType().getJNISignature() + "\"), ");
                    }
                }
                pb("}, ");

                pb(stub.isAbstract + ", " + stub.isFinal + ", " + stub.isStatic + ", " + m.isDefaultAccess() + ", "
                        + m.isPrivate() + ", " + m.isProtected() + ", " + m.isPublic() + ", " + stub.isNative + ", "
                        + m.isVarArgs() + ", " + stub.isMethod + ", " + stub.isConstructor + ", " + stub.methodId
                        + ", " + methodAnnotations + "),");
            }
            pb("};");
        }
    }

    private String getElementTypes(JField f) {
        StringBuilder b = new StringBuilder();
        JParameterizedType params = f.getType().isParameterized();
        if (params != null) {
            JClassType[] typeArgs = params.getTypeArgs();
            b.append("new Class[] {");
            for (JClassType typeArg : typeArgs) {
                if (typeArg.isWildcard() != null)
                    b.append("Object.class");
                else if (!isVisible(typeArg))
                    b.append("null");
                else if (typeArg.isClassOrInterface() != null)
                    b.append(typeArg.isClassOrInterface().getQualifiedSourceName()).append(".class");
                else if (typeArg.isParameterized() != null)
                    b.append(typeArg.isParameterized().getQualifiedBinaryName()).append(".class");
                else
                    b.append("null");
                b.append(", ");
            }
            b.append("}");
            return b.toString();
        }
        return "null";
    }

    private String getType(JType type) {
        if (!isVisible(type))
            return null;
        return type.getErasedType().getQualifiedSourceName() + ".class";
    }

    private void imports(ClassSourceFileComposerFactory composer) {
        composer.addImport("java.security.AccessControlException");
        composer.addImport("java.util.*");
        composer.addImport("com.artemis.gwtref.client.*");
    }

    private void invokeM() {
        p("public Object invoke(Method m, Object obj, Object[] params) {");
        SwitchedCodeBlock pc = new SwitchedCodeBlock("m.methodId");
        int subN = 0;
        int nDispatch = 0;

        for (MethodStub stub : methodStubs) {
            if (stub.enclosingType == null)
                continue;
            if (stub.enclosingType.contains("[]"))
                continue;
            if (stub.returnType == null)
                continue;
            if (stub.unused)
                continue;
            boolean paramsOk = true;
            for (String paramType : stub.parameterTypes) {
                if (paramType == null) {
                    paramsOk = false;
                    break;
                }
            }

            if (!paramsOk)
                continue;

            buffer.setLength(0);
            pbn("return m" + stub.methodId + "(");
            addParameters(stub);
            pbn(");");
            pc.add(stub.methodId, buffer.toString());
            nDispatch++;
            if (nDispatch > 1000) {
                pc.print();
                pc = new SwitchedCodeBlock("m.methodId");
                subN++;
                p("   return invoke" + subN + "(m, obj, params);");
                p("}");
                p("public Object invoke" + subN + "(Method m, Object obj, Object[] params) {");
                nDispatch = 0;
            }
        }

        pc.print();
        p("   throw new IllegalArgumentException(\"Missing method-stub \" + m.methodId + \" for method \" + m.name);");
        p("}");
    }

    private void addParameters(MethodStub stub) {
        if (!stub.isStatic && !stub.isConstructor)
            pbn("(" + stub.enclosingType + ")obj" + (stub.parameterTypes.size() > 0 ? "," : ""));
        for (int i = 0; i < stub.parameterTypes.size(); i++) {
            pbn(cast(stub.parameterTypes.get(i), "params[" + i + "]")
                    + (i < stub.parameterTypes.size() - 1 ? ", " : ""));
        }
    }

    private String cast(String paramType, String arg) {
        if (paramType.equals("byte") || paramType.equals("short") || paramType.equals("int")
                || paramType.equals("long") || paramType.equals("float") || paramType.equals("double")) {
            return "((Number)" + arg + ")." + paramType + "Value()";
        } else if (paramType.equals("boolean")) {
            return "((Boolean)" + arg + ")." + paramType + "Value()";
        } else if (paramType.equals("char")) {
            return "((Character)" + arg + ")." + paramType + "Value()";
        } else {
            return "((" + paramType + ")" + arg + ")";
        }
    }

    private void setF() {
        p("public void set(Field field, Object obj, Object value) throws IllegalAccessException {");
        SwitchedCodeBlock pc = new SwitchedCodeBlock("field.setter");
        for (SetterGetterStub stub : setterGetterStubs) {
            if (stub.enclosingType == null || stub.type == null || stub.isFinal || stub.unused)
                continue;
            pc.add(stub.setter, "s" + stub.setter + "(" + cast(stub.enclosingType, "obj") + ", "
                    + cast(stub.type, "value") + "); return;");
        }
        pc.print();
        p("   throw new IllegalArgumentException(\"Missing setter-stub \" + field.setter + \" for field \" + field.name);");
        p("}");
    }

    private void getF() {
        p("public Object get(Field field, Object obj) throws IllegalAccessException {");
        SwitchedCodeBlock pc = new SwitchedCodeBlock("field.getter");
        for (SetterGetterStub stub : setterGetterStubs) {
            if (stub.enclosingType == null || stub.type == null || stub.unused)
                continue;
            pc.add(stub.getter, "return g" + stub.getter + "(" + cast(stub.enclosingType, "obj") + ");");
        }
        pc.print();
        p("   throw new IllegalArgumentException(\"Missing getter-stub \" + field.getter + \" for field \" + field.name);");
        p("}");
    }

    private static boolean isInstantiableWithNewOperator(JClassType t) {
        if (!t.isDefaultInstantiable() || t instanceof JArrayType || t instanceof JEnumType)
            return false;
        try {
            JConstructor constructor = t.getConstructor(new JType[0]);
            return constructor != null && constructor.isPublic();
        } catch (NotFoundException e) {
            return false;
        }
    }

    private void setArrayElementT() {
        p("public void setArrayElement(Type type, Object obj, int i, Object value) {");
        SwitchedCodeBlock pc = new SwitchedCodeBlock("type.id");

        for (String s : PRIMITIVE_TYPES) {
            if (!typeNames2typeIds.containsKey(s + "[]"))
                continue;
            pc.add(typeNames2typeIds.get(s + "[]"), "((" + s + "[])obj)[i] = " + cast(s, "value") + "; return;");
        }

        pc.print();
        p("   ((Object[])obj)[i] = value;");
        p("}");
    }

    private void getArrayElementT() {
        p("public Object getArrayElement(Type type, Object obj, int i) {");
        SwitchedCodeBlock pc = new SwitchedCodeBlock("type.id");

        for (String s : PRIMITIVE_TYPES) {
            if (!typeNames2typeIds.containsKey(s + "[]"))
                continue;
            pc.add(typeNames2typeIds.get(s + "[]"), "return ((" + s + "[])obj)[i];");
        }

        pc.print();
        p("   return ((Object[])obj)[i];");
        p("}");
    }

    private void getArrayLengthT() {
        p("public int getArrayLength(Type type, Object obj) {");
        SwitchedCodeBlock pc = new SwitchedCodeBlock("type.id");

        for (String s : PRIMITIVE_TYPES) {
            if (!typeNames2typeIds.containsKey(s + "[]"))
                continue;
            pc.add(typeNames2typeIds.get(s + "[]"), "return ((" + s + "[])obj).length;");
        }

        pc.print();
        p("   return ((Object[])obj).length;");
        p("}");
    }

    private void newArrayC() {
        p("public Object newArray (Class componentType, int size) {");
        p("   Type t = forName(componentType.getName().replace('$', '.'));");
        p("   if (t != null) {");
        SwitchedCodeBlock pc = new SwitchedCodeBlock("t.id");
        for (JType type : types) {
            if (type.getQualifiedSourceName().equals("void"))
                continue;
            if (type.getQualifiedSourceName().endsWith("Void"))
                continue;
            String arrayType = type.getErasedType().getQualifiedSourceName() + "[size]";
            if (arrayType.contains("[]")) {
                arrayType = type.getErasedType().getQualifiedSourceName();
                arrayType = arrayType.replaceFirst("\\[\\]", "[size]") + "[]";
            }
            pc.add(typeNames2typeIds.get(type.getQualifiedSourceName()), "return new " + arrayType + ";");
        }
        pc.print();
        p("   }");
        p("   throw new RuntimeException(\"Couldn't create array with element type \" + componentType.getName());");
        p("}");
    }

    private void forNameC() {
        p("public Type forName(String name) {");
        p("   return types.get(name);");
        p("}");
    }

    private void getKnownTypesC() {
        p("public Collection<Type> getKnownTypes() {");
        p("   return types.values();");
        p("}");
    }

    void p(String line) {
        sw.println(line);
        source.append(line);
        source.append("\n");
    }

    void pn(String line) {
        sw.print(line);
        source.append(line);
    }

    StringBuffer buffer = new StringBuffer();

    void pb(String line) {
        buffer.append(line);
        buffer.append("\n");
    }

    private void pbn(String line) {
        buffer.append(line);
    }

    class SwitchedCodeBlock {
        private List<KeyedCodeBlock> blocks = new ArrayList<KeyedCodeBlock>();
        private final String switchStatement;

        SwitchedCodeBlock(String switchStatement) {
            this.switchStatement = switchStatement;
        }

        void add(int key, String codeBlock) {
            KeyedCodeBlock b = new KeyedCodeBlock();
            b.key = key;
            b.codeBlock = codeBlock;
            blocks.add(b);
        }

        void print() {
            if (blocks.isEmpty())
                return;

            p("   switch(" + switchStatement + ") {");
            for (KeyedCodeBlock b : blocks) {
                p("   case " + b.key + ": " + b.codeBlock);
            }
            p("}");
        }

        class KeyedCodeBlock {
            int key;
            String codeBlock;
        }
    }
}