com.totsp.gwittir.rebind.beans.IntrospectorGenerator.java Source code

Java tutorial

Introduction

Here is the source code for com.totsp.gwittir.rebind.beans.IntrospectorGenerator.java

Source

/*
 * IntrospectorGenerator.java
 *
 * Created on July 15, 2007, 2:21 PM
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package com.totsp.gwittir.rebind.beans;

import com.google.gwt.core.ext.Generator;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.JTypeParameter;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

import com.totsp.gwittir.client.beans.SelfDescribed;
import com.totsp.gwittir.client.beans.annotations.Introspectable;

import java.io.IOException;
import java.io.PrintWriter;

import java.net.URL;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

/**
 *
 * @author <a href="mailto:cooper@screaming-penguin.com">Robert "kebernet" Cooper</a>
 */
public class IntrospectorGenerator extends Generator {
    private JType objectType = null;
    private Map<MethodWrapper, Integer> methodWrapperLookup;
    private String implementationName = com.totsp.gwittir.client.beans.Introspector.class.getSimpleName() + "_Impl";
    private String methodsImplementationName = "MethodsList";
    private String packageName = com.totsp.gwittir.client.beans.Introspector.class.getCanonicalName().substring(0,
            com.totsp.gwittir.client.beans.Introspector.class.getCanonicalName().lastIndexOf("."));

    /** Creates a new instance of IntrospectorGenerator */
    public IntrospectorGenerator() {
    }

    public String generate(TreeLogger logger, GeneratorContext context, String typeName)
            throws UnableToCompleteException {
        //.println("Introspector Generate.");
        try {
            this.objectType = context.getTypeOracle().getType("java.lang.Object");
        } catch (NotFoundException ex) {
            logger.log(TreeLogger.ERROR, typeName, ex);

            return null;
        }

        List<BeanResolver> introspectables = this.getIntrospectableTypes(logger, context.getTypeOracle());

        MethodWrapper[] methods = this.findMethods(logger, introspectables);

        ClassSourceFileComposerFactory mcf = new ClassSourceFileComposerFactory(this.packageName,
                this.methodsImplementationName);
        mcf.addImport(com.totsp.gwittir.client.beans.Method.class.getCanonicalName());

        PrintWriter methodsPrintWriter = context.tryCreate(logger, this.packageName,
                this.methodsImplementationName);

        if (methodsPrintWriter != null) {
            SourceWriter methodsWriter = mcf.createSourceWriter(context, methodsPrintWriter);
            this.writeMethods(logger, methods, methodsWriter);
            methodsWriter.println("}");
            context.commit(logger, methodsPrintWriter);
        }

        ClassSourceFileComposerFactory cfcf = new ClassSourceFileComposerFactory(this.packageName,
                this.implementationName);
        cfcf.addImplementedInterface(typeName);
        cfcf.addImport("java.util.HashMap");
        cfcf.addImport(com.totsp.gwittir.client.beans.Method.class.getCanonicalName());
        cfcf.addImport(com.totsp.gwittir.client.beans.Property.class.getCanonicalName());
        cfcf.addImport(com.totsp.gwittir.client.beans.BeanDescriptor.class.getCanonicalName());

        PrintWriter printWriter = context.tryCreate(logger, packageName, implementationName);

        if (printWriter == null) {
            //.println( "Introspector Generate skipped.");
            return packageName + "." + implementationName;
        }

        SourceWriter writer = cfcf.createSourceWriter(context, printWriter);
        this.writeIntrospectables(logger, introspectables, methods, writer);
        this.writeResolver(introspectables, writer);

        writer.println(
                "private HashMap<Class,BeanDescriptor> beanDescriptorLookup = new HashMap<Class,BeanDescriptor>();");
        writer.println();
        writer.println("public BeanDescriptor getDescriptor( Object object ){ ");
        writer.indent();
        writer.println(
                "if( object == null ) throw new NullPointerException(\"Attempt to introspect null object\");");
        writer.println("if( object instanceof " + SelfDescribed.class.getCanonicalName()
                + " ) return ((SelfDescribed)object).__descriptor();");
        writer.println("BeanDescriptor descriptor = beanDescriptorLookup.get(object.getClass());");
        writer.println("if (descriptor!=null){");
        writer.indentln("return descriptor;");
        writer.outdent();
        writer.println("}");

        writer.println("descriptor=_getDescriptor(object);");
        writer.println("beanDescriptorLookup.put(object.getClass(),descriptor);");
        writer.println("return descriptor;");
        writer.outdent();
        writer.println("}");

        writer.println("private BeanDescriptor _getDescriptor( Object object ){ ");
        writer.indent();

        for (BeanResolver resolver : introspectables) {
            writer.println("if( object instanceof " + resolver.getType().getQualifiedSourceName() + " ) {");
            writer.indent();

            String name = resolver.getType().getQualifiedSourceName().replaceAll("\\.", "_");
            logger.log(TreeLogger.DEBUG, "Writing : " + name, null);
            writer.print("return " + name + " == null ? " + name + " = ");
            this.writeBeanDescriptor(logger, resolver, methods, writer);
            writer.print(": " + name + ";");
            writer.outdent();
            writer.println("}");
        }

        writer.println(" throw new IllegalArgumentException(\"Unknown type\" + object.getClass() ); ");
        writer.outdent();
        writer.println("}");
        writer.outdent();
        writer.println("}");

        context.commit(logger, printWriter);

        //.println( "Introspector Generate completed.");
        return packageName + "." + implementationName;
    }

    protected List<BeanResolver> getIntrospectableTypes(TreeLogger logger, TypeOracle oracle) {
        ArrayList<BeanResolver> results = new ArrayList<BeanResolver>();
        HashSet<BeanResolver> resolvers = new HashSet<BeanResolver>();
        HashSet<String> found = new HashSet<String>();

        try {
            JClassType[] types = oracle.getTypes();

            //.println("Found "+types.length +" types.");
            JClassType introspectable = oracle
                    .getType(com.totsp.gwittir.client.beans.Introspectable.class.getCanonicalName());

            for (JClassType type : types) {
                //                if(type.getQualifiedSourceName().endsWith("gwittir.client.ui.TextBox")){
                //                logger.log(
                //                        TreeLogger.WARN,
                //                        type.getQualifiedSourceName() + " is assignable to " + introspectable + " " +
                //                        type.isAssignableTo(introspectable) + " isInterface = " + type.isInterface() +
                //                        "isIntrospectable = "+isIntrospectable(logger,type),
                //                        null
                //                    );
                //                }
                if (!found.contains(type.getQualifiedSourceName())
                        && (isIntrospectable(logger, type) || type.isAssignableTo(introspectable))
                        && (type.isInterface() == null)) {
                    found.add(type.getQualifiedSourceName());
                    resolvers.add(new BeanResolver(logger, type));
                }
            }

            // Do a crazy assed sort to make sure least
            // assignable types are at the bottom of the list
            results.addAll(resolvers);
            results.addAll(this.getFileDeclaredTypes(logger, oracle));

            boolean swap = true;

            //.print("Ordering "+results.size()+" by heirarchy ");
            while (swap) {
                //.print(".");
                swap = false;

                for (int i = results.size() - 1; i >= 0; i--) {
                    BeanResolver type = (BeanResolver) results.get(i);

                    for (int j = i - 1; j >= 0; j--) {
                        BeanResolver check = (BeanResolver) results.get(j);

                        if (type.getType().isAssignableTo(check.getType())) {
                            results.set(i, check);
                            results.set(j, type);

                            type = check;

                            swap = true;
                        }
                    }
                }
            }

            //System.out.println();
        } catch (Exception e) {
            logger.log(TreeLogger.ERROR, "Unable to finad Introspectable types.", e);
        }

        //        for(BeanResolver rs:results){
        //            logger.log(TreeLogger.ERROR, rs.toString());
        //        }
        //System.out.println("Found "+results.size()+" introspectable types.");
        return results;
    }

    private Set<BeanResolver> getFileDeclaredTypes(TreeLogger logger, TypeOracle oralce)
            throws UnableToCompleteException {
        HashSet<BeanResolver> results = new HashSet<BeanResolver>();
        ClassLoader ctxLoader = Thread.currentThread().getContextClassLoader();

        try {
            Enumeration<URL> introspections = ctxLoader.getResources("gwittir-introspection.properties");

            while (introspections.hasMoreElements()) {
                URL propsUrl = introspections.nextElement();
                logger.log(TreeLogger.Type.INFO, "Loading: " + propsUrl.toString());

                Properties props = new Properties();
                props.load(propsUrl.openStream());

                for (Entry entry : props.entrySet()) {
                    String className = entry.getKey().toString();
                    String[] includedProps = entry.getValue().toString().split(",");
                    JClassType type = oralce.findType(className);

                    if (type == null) {
                        logger.log(TreeLogger.Type.ERROR,
                                "Unable to find type " + className + " declared in " + propsUrl);
                        throw new UnableToCompleteException();
                    }

                    results.add(new BeanResolver(logger, type, includedProps));
                }
            }
        } catch (IOException ioe) {
            logger.log(TreeLogger.Type.WARN, "Exception looking for properties files", ioe);
        }

        return results;
    }

    private boolean isIntrospectable(TreeLogger logger, JType type) {
        if (type == null) {
            return false;
        }

        JClassType ct = type.isClassOrInterface();

        if (ct != null) {
            if (ct.getAnnotation(Introspectable.class) != null) {
                return true;
            }

            for (JClassType iface : ct.getImplementedInterfaces()) {
                if (isIntrospectable(logger, iface)) {
                    return true;
                }
            }

            if (isIntrospectable(logger, ct.getSuperclass())) {
                return true;
            }
        }

        return false;
    }

    private boolean box(JType type, SourceWriter writer) {
        if ((type.isPrimitive() != null) && (type.isPrimitive() == JPrimitiveType.INT)) {
            writer.print("new Integer( ");

            return true;
        }

        if ((type.isPrimitive() != null) && (type.isPrimitive() == JPrimitiveType.LONG)) {
            writer.print("new Long( ");

            return true;
        }

        if ((type.isPrimitive() != null) && (type.isPrimitive() == JPrimitiveType.FLOAT)) {
            writer.print("new Float( ");

            return true;
        }

        if ((type.isPrimitive() != null) && (type.isPrimitive() == JPrimitiveType.DOUBLE)) {
            writer.print("new Double( ");

            return true;
        }

        if ((type.isPrimitive() != null) && (type.isPrimitive() == JPrimitiveType.CHAR)) {
            writer.print("new Character( ");

            return true;
        }

        if ((type.isPrimitive() != null) && (type.isPrimitive() == JPrimitiveType.BYTE)) {
            writer.print("new Byte( ");

            return true;
        }

        if ((type.isPrimitive() != null) && (type.isPrimitive() == JPrimitiveType.BOOLEAN)) {
            writer.print("new Boolean( ");

            return true;
        }

        return false;
    }

    /*because the call to MethodWrapper.toString() used in the comparison is _highly_ expensive
     * (it wanders through the gwt jtype system), this optimisation gives something like a 30x
     * speed improvement
     *
     */
    private int find(MethodWrapper[] search, MethodWrapper match) {
        //the second check (!containsKey) will be hit if doing a recompile
        //in hosted mode with new methods
        if ((methodWrapperLookup == null) || !methodWrapperLookup.containsKey(match)) {
            Map m = new HashMap<MethodWrapper, Integer>();

            for (int i = 0; i < search.length; i++) {
                m.put(search[i], i);
            }

            methodWrapperLookup = m;
        }

        return methodWrapperLookup.get(match);
    }

    private MethodWrapper[] findMethods(TreeLogger logger, List introspectables) {
        HashSet methods = new HashSet();

        for (Iterator it = introspectables.iterator(); it.hasNext();) {
            BeanResolver info = (BeanResolver) it.next();
            logger.branch(TreeLogger.DEBUG, "Method Scanning: " + info.getType().getQualifiedSourceName(), null);

            try {
                if (info.getProperties().size() == 0) {
                    continue;
                }

                Collection<RProperty> pds = info.getProperties().values();

                for (RProperty p : pds) {
                    if (p.getReadMethod() != null) {
                        p.getReadMethod().hashWithType = true;
                        methods.add(p.getReadMethod());
                    }

                    if (p.getWriteMethod() != null) {
                        p.getWriteMethod().hashWithType = true;
                        methods.add(p.getWriteMethod());
                    }
                }
            } catch (Exception e) {
                logger.log(TreeLogger.ERROR, "Unable to introspect class. Is class a bean?", e);
            }
        }

        MethodWrapper[] results = new MethodWrapper[methods.size()];
        Iterator it = methods.iterator();

        for (int i = 0; it.hasNext(); i++) {
            results[i] = (MethodWrapper) it.next();
        }

        return results;
    }

    private int findOld(MethodWrapper[] search, MethodWrapper match) {
        for (int i = 0; i < search.length; i++) {
            if (search[i].equals(match)) {
                return i;
            }
        }

        //System.out.println("NO MATCH FOR: " + match.toString());
        throw new RuntimeException(match.toString());
    }

    private JType resolveType(final JType type) {
        JType ret = type;
        JParameterizedType pt = type.isParameterized();

        if (pt != null) {
            ret = pt.getRawType();
        }

        JTypeParameter tp = ret.isTypeParameter();

        if (tp != null) {
            ret = tp.getBaseType();
        }

        return ret;
    }

    private boolean unbox(JType type, String reference, SourceWriter writer) {
        if ((type.isPrimitive() != null) && (type.isPrimitive() == JPrimitiveType.INT)) {
            writer.print("((Integer) " + reference + ").intValue()");

            return true;
        }

        if ((type.isPrimitive() != null) && (type.isPrimitive() == JPrimitiveType.LONG)) {
            writer.print("((Long) " + reference + ").longValue()");

            return true;
        }

        if ((type.isPrimitive() != null) && (type.isPrimitive() == JPrimitiveType.FLOAT)) {
            writer.print("((Float) " + reference + ").floatValue()");

            return true;
        }

        if ((type.isPrimitive() != null) && (type.isPrimitive() == JPrimitiveType.DOUBLE)) {
            writer.print("((Double) " + reference + ").doubleValue()");

            return true;
        }

        if ((type.isPrimitive() != null) && (type.isPrimitive() == JPrimitiveType.CHAR)) {
            writer.print("((Character) " + reference + ").charValue()");

            return true;
        }

        if ((type.isPrimitive() != null) && (type.isPrimitive() == JPrimitiveType.BYTE)) {
            writer.print("((Byte) " + reference + ").byteValue()");

            return true;
        }

        if ((type.isPrimitive() != null) && (type.isPrimitive() == JPrimitiveType.BOOLEAN)) {
            writer.print("((Boolean) " + reference + ").booleanValue()");

            return true;
        }

        writer.print("(" + type.getQualifiedSourceName() + ") " + reference);

        return false;
    }

    private void writeBeanDescriptor(TreeLogger logger, BeanResolver info, MethodWrapper[] methods,
            SourceWriter writer) {
        writer.println("new BeanDescriptor() { ");
        writer.indent();
        writer.println("private HashMap lookup;");
        writer.println("private Property[] properties;");
        writer.println("public Property[] getProperties(){");
        writer.indent();

        {
            writer.println("if( this.properties != null ) ");
            writer.indentln("return this.properties;");
            writer.println("this.properties = new Property[" + (info.getProperties().size()) + "];");

            Collection pds = info.getProperties().values();
            String[] propertyNames = new String[pds.size()];
            logger.log(TreeLogger.SPAM, "" + (pds == null), null);

            if (pds != null) {
                int i = 0;

                for (Iterator it = pds.iterator(); it.hasNext(); i++) {
                    RProperty p = (RProperty) it.next();
                    propertyNames[i] = p.getName();
                    writer.println("{");
                    writer.indent();

                    writer.print("Method readMethod = ");

                    if (p.getReadMethod() == null) {
                        writer.println("null;");
                    } else {
                        writer.println(this.packageName + "." + this.methodsImplementationName + ".METHOD_"
                                + +this.find(methods, p.getReadMethod()) + ";");
                    }

                    writer.print("Method writeMethod = ");

                    if (p.getWriteMethod() == null) {
                        writer.println("null;");
                    } else {
                        writer.println(this.packageName + "." + this.methodsImplementationName + ".METHOD_"
                                + +this.find(methods, p.getWriteMethod()) + ";");
                    }

                    logger.log(TreeLogger.DEBUG, p.getName() + " " + p.getType().getQualifiedSourceName(), null);

                    JType ptype = this.resolveType(p.getType());

                    logger.log(TreeLogger.DEBUG, p.getName() + " (Erased) " + ptype.getQualifiedSourceName(), null);
                    writer.println("this.properties[" + (i) + "] = new Property( \"" + p.getName() + "\", "
                            + ((p.getType() != null) ? ptype.getQualifiedSourceName() : "Object")
                            + ".class,  readMethod, writeMethod );");
                    writer.outdent();
                    writer.println("}");
                }
            }

            writer.println("return this.properties;");
        }

        writer.outdent();
        writer.println("} //end getProperties()");
        writer.println("public Property getProperty( String name ) {");
        writer.indent();
        //TODO Rewrite this to a nested if loop using the propertyNames parameter.
        writer.println("Property p = null;");
        writer.println("if( this.lookup != null ) {");
        writer.indentln("p = (Property) lookup.get(name); ");
        writer.println("} else {");
        writer.indent();
        writer.println("this.lookup = new HashMap();");
        writer.println("Property[] props = this.getProperties(); ");
        writer.println("for( int i=0; i < props.length; i++ ) {");
        writer.indent();
        writer.println("this.lookup.put( props[i].getName(), props[i] );");
        writer.outdent();
        writer.println("}");
        writer.println("p = (Property) this.lookup.get(name);");
        writer.outdent();
        writer.println("}");
        writer.println("if( p == null ) throw new RuntimeException(\"Couldn't find property \"+name+\" for "
                + info.getType().getQualifiedSourceName() + "\");");
        writer.println("else return p;");
        writer.outdent();
        writer.println("}");

        writer.outdent();
        writer.print("}");
    }

    private void writeIntrospectables(TreeLogger logger, List introspectables, MethodWrapper[] methods,
            SourceWriter writer) {
        for (Iterator it = introspectables.iterator(); it.hasNext();) {
            BeanResolver bean = (BeanResolver) it.next();

            logger.branch(TreeLogger.DEBUG, "Introspecting: " + bean.getType().getQualifiedSourceName(), null);

            try {
                if (bean.getProperties().size() == 0) {
                    continue;
                }

                writer.print("private static BeanDescriptor ");
                writer.print(bean.getType().getQualifiedSourceName().replaceAll("\\.", "_"));

                writer.println(" = null;");
            } catch (Exception e) {
                logger.log(TreeLogger.ERROR, "Unable to introspect class. Is class a bean?", e);
            }
        }
    }

    private void writeMethod(TreeLogger logger, MethodWrapper method, SourceWriter writer) {
        JType ptype = this.resolveType(method.getDeclaringType());

        writer.println("new Method(){ ");
        writer.indent();
        writer.println("public String getName() {");
        writer.indentln("return \"" + method.getBaseMethod().getName() + "\";");
        writer.println(" }");
        writer.println("public Object invoke( Object target, Object[] args ) throws Exception {");
        writer.indent();
        writer.println(ptype.getQualifiedSourceName() + " casted =");
        writer.println("(" + ptype.getQualifiedSourceName() + ") target;");
        logger.log(TreeLogger.SPAM, "Method: " + method.getBaseMethod().getName() + " "
                + method.getBaseMethod().getReturnType().getQualifiedSourceName(), null);

        if (!(method.getBaseMethod().getReturnType().isPrimitive() == JPrimitiveType.VOID)) {
            writer.print("return ");
        }

        JType type = this.resolveType(method.getBaseMethod().getReturnType());

        boolean boxed = this.box(type, writer);
        writer.print("casted." + method.getBaseMethod().getName() + "(");

        if (method.getBaseMethod().getParameters() != null) {
            for (int j = 0; j < method.getBaseMethod().getParameters().length; j++) {
                JType arg = this.resolveType(method.getBaseMethod().getParameters()[j].getType());

                this.unbox(arg, "args[" + j + "]", writer);

                if (j != (method.getBaseMethod().getParameters().length - 1)) {
                    writer.print(", ");
                }
            }
        }

        writer.print(")");

        if (boxed) {
            writer.print(")");
        }

        writer.println(";");

        if (method.getBaseMethod().getReturnType().getQualifiedSourceName().equals("void")) {
            writer.println("return null;");
        }

        writer.outdent();
        writer.println("}");
        writer.outdent();
        writer.println("};");
    }

    private void writeMethods(TreeLogger logger, MethodWrapper[] methods, SourceWriter writer) {
        for (int i = 0; i < methods.length; i++) {
            writer.print("public static final Method METHOD_" + i + " = ");
            writeMethod(logger, methods[i], writer);
        }
    }

    private void writeResolver(List introspectables, SourceWriter writer) {
        writer.println("public Class resolveClass(Object object){");
        writer.indent();

        for (Iterator it = introspectables.iterator(); it.hasNext();) {
            BeanResolver type = (BeanResolver) it.next();
            writer.println("if( object instanceof " + type.getType().getQualifiedSourceName() + " ) return "
                    + type.getType().getQualifiedSourceName() + ".class;");
        }

        writer.println("throw new RuntimeException( \"Object \"+object+\"could not be resolved.\" );");
        writer.outdent();
        writer.println("}");
    }
}