fr.putnami.pwt.core.service.rebind.ServiceBinderCreator.java Source code

Java tutorial

Introduction

Here is the source code for fr.putnami.pwt.core.service.rebind.ServiceBinderCreator.java

Source

/**
 * This file is part of pwt.
 *
 * pwt 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 3 of the
 * License, or (at your option) any later version.
 *
 * pwt 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 pwt. If not,
 * see <http://www.gnu.org/licenses/>.
 */
package fr.putnami.pwt.core.service.rebind;

import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.PropertyOracle;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
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.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.RpcToken;
import com.google.gwt.user.client.rpc.RpcToken.RpcTokenImplementation;
import com.google.gwt.user.client.rpc.RpcTokenException;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.google.gwt.user.rebind.rpc.SerializableTypeOracleBuilder;
import com.google.gwt.user.rebind.rpc.TypeSerializerCreator;

import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;

import fr.putnami.pwt.core.service.client.AbstractServiceBinder;
import fr.putnami.pwt.core.service.client.CallbackAdapter;
import fr.putnami.pwt.core.service.client.CommandController;
import fr.putnami.pwt.core.service.client.CommandParam;
import fr.putnami.pwt.core.service.client.annotation.AsyncHandler;
import fr.putnami.pwt.core.service.client.annotation.LazyCommand;
import fr.putnami.pwt.core.service.client.annotation.QuietCommand;
import fr.putnami.pwt.core.service.shared.domain.CommandDefinition;
import fr.putnami.pwt.core.service.shared.domain.CommandRequest;
import fr.putnami.pwt.core.service.shared.domain.CommandResponse;

public class ServiceBinderCreator {

    private final JClassType serviceType;
    private final JClassType serviceBinderType;
    private final JClassType handlerType;

    private String proxyModelQualifiedName;
    private String serializerTypeName;

    private Collection<JType> imports = Sets.newHashSet();

    public ServiceBinderCreator(JClassType serviceBinderType, JClassType serviceType, JClassType handlerType) {
        this.serviceBinderType = serviceBinderType;
        this.serviceType = serviceType;
        this.handlerType = handlerType;
        this.proxyModelQualifiedName = this.handlerType.getQualifiedSourceName() + "_"
                + this.serviceType.getSimpleSourceName() + "_ServiceBinder";
    }

    public String create(TreeLogger logger, GeneratorContext context)
            throws UnableToCompleteException, NotFoundException {
        PrintWriter printWriter = this.getPrintWriter(logger, context);
        if (printWriter == null) {
            return this.proxyModelQualifiedName;
        }

        this.serializerTypeName = this.createSerializer(logger, context);
        this.collectImports();

        SourceWriter srcWriter = this.getSourceWriter(printWriter, context);

        srcWriter.indent();
        srcWriter.println();
        this.generateSerializer(srcWriter);
        srcWriter.println();
        this.generateServiceImplementation(logger, srcWriter);
        this.generateServiceImplementationWithCallback(logger, srcWriter);

        srcWriter.outdent();
        srcWriter.commit(logger);
        return this.proxyModelQualifiedName;
    }

    private String createSerializer(TreeLogger logger, GeneratorContext context)
            throws UnableToCompleteException, NotFoundException {

        /* Hack because SerializableTypeOracleBuilder constructor has only two arguments since GWT 2.7 */

        SerializableTypeOracleBuilder typesSentFromBrowser = null;
        SerializableTypeOracleBuilder typesSentToBrowser = null;
        try {
            try {
                Constructor<SerializableTypeOracleBuilder> constructor = SerializableTypeOracleBuilder.class
                        .getConstructor(TreeLogger.class, PropertyOracle.class, GeneratorContext.class);
                typesSentFromBrowser = constructor.newInstance(logger, context.getPropertyOracle(), context);
                typesSentToBrowser = constructor.newInstance(logger, context.getPropertyOracle(), context);
            } catch (NoSuchMethodException exc) {
                try {
                    Constructor<SerializableTypeOracleBuilder> constructor = SerializableTypeOracleBuilder.class
                            .getConstructor(TreeLogger.class, GeneratorContext.class);
                    typesSentFromBrowser = constructor.newInstance(logger, context);
                    typesSentToBrowser = constructor.newInstance(logger, context);
                } catch (NoSuchMethodException ex) {
                    logger.branch(TreeLogger.ERROR, "Unable to find a SerializableTypeOracleBuilder constructor",
                            null);
                    throw new UnableToCompleteException();
                }
            }
        } catch (InstantiationException e) {
            logger.branch(TreeLogger.ERROR, "Unable to invoke SerializableTypeOracleBuilder constructor", null);
            throw new UnableToCompleteException();
        } catch (IllegalAccessException e) {
            logger.branch(TreeLogger.ERROR, "Unable to invoke SerializableTypeOracleBuilder constructor", null);
            throw new UnableToCompleteException();
        } catch (IllegalArgumentException e) {
            logger.branch(TreeLogger.ERROR, "Unable to invoke SerializableTypeOracleBuilder constructor", null);
            throw new UnableToCompleteException();
        } catch (InvocationTargetException e) {
            logger.branch(TreeLogger.ERROR, "Unable to invoke SerializableTypeOracleBuilder constructor", null);
            throw new UnableToCompleteException();
        }
        /* End of Hack */
        JMethod[] methods = this.serviceType.getOverridableMethods();
        TypeOracle typeOracle = context.getTypeOracle();

        JClassType rteType = typeOracle.getType(RpcTokenException.class.getName());
        JClassType rpcTokenClass = typeOracle.getType(RpcToken.class.getName());
        RpcTokenImplementation tokenClassToUse = this.serviceType
                .findAnnotationInTypeHierarchy(RpcTokenImplementation.class);
        if (tokenClassToUse != null) {
            JClassType rpcTokenType = typeOracle.getType(tokenClassToUse.value());
            if (rpcTokenType.isAssignableTo(rpcTokenClass)) {
                typesSentFromBrowser.addRootType(logger, rpcTokenType);
                typesSentToBrowser.addRootType(logger, rteType);
            } else {
                logger.branch(TreeLogger.ERROR, "RPC token class " + tokenClassToUse.value() + " must implement "
                        + RpcToken.class.getName(), null);
                throw new UnableToCompleteException();
            }
        } else {
            JClassType[] rpcTokenSubclasses = rpcTokenClass.getSubtypes();
            for (JClassType rpcTokenSubclass : rpcTokenSubclasses) {
                typesSentFromBrowser.addRootType(logger, rpcTokenSubclass);
            }
            if (rpcTokenSubclasses.length > 0) {
                typesSentToBrowser.addRootType(logger, rteType);
            }
        }
        typesSentFromBrowser.addRootType(logger, typeOracle.getType(CommandDefinition.class.getName()));
        typesSentFromBrowser.addRootType(logger, typeOracle.getType(CommandRequest.class.getName()));
        typesSentToBrowser.addRootType(logger, typeOracle.getType(CommandResponse.class.getName()));

        for (JMethod method : methods) {
            JType returnType = method.getReturnType();
            if (returnType != JPrimitiveType.VOID) {
                typesSentToBrowser.addRootType(logger, returnType);
            }

            JParameter[] params = method.getParameters();
            for (JParameter param : params) {
                JType paramType = param.getType();
                typesSentFromBrowser.addRootType(logger, paramType);
            }

            JType[] exs = method.getThrows();
            if (exs.length > 0) {
                for (JType ex : exs) {
                    typesSentToBrowser.addRootType(logger, ex);
                }
            }
        }

        String serializerSimpleName = this.serviceType.getSimpleSourceName() + "_TypeSerializer";
        String serializeQualifiedName = this.serviceType.getQualifiedSourceName() + "_TypeSerializer";

        TypeSerializerCreator tsc = new TypeSerializerCreator(logger, typesSentFromBrowser.build(logger),
                typesSentToBrowser.build(logger), context, serializeQualifiedName, serializerSimpleName);

        return tsc.realize(logger);
    }

    private void generateSerializer(SourceWriter srcWriter) {
        srcWriter.println("private final %s serializer = new %s();", this.serializerTypeName,
                this.serializerTypeName);
    }

    private static class CallbackMethod {
        String successMethodName;
        String failureMethodName;

        @Override
        public int hashCode() {
            return Objects.hashCode(this.successMethodName, this.failureMethodName);
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof CallbackMethod) {
                CallbackMethod other = (CallbackMethod) obj;
                return Objects.equal(this.successMethodName, other.successMethodName)
                        && Objects.equal(this.failureMethodName, other.failureMethodName);
            }
            return false;
        }
    }

    private Collection<CallbackMethod> listCallbackMethods(TreeLogger logger, JMethod serviceMethod) {
        Collection<CallbackMethod> callbackMethods = Sets.newHashSet();
        for (JMethod handlerMethod : this.handlerType.getOverridableMethods()) {
            AsyncHandler serviceHandlerAnnotation = handlerMethod.getAnnotation(AsyncHandler.class);
            if (serviceHandlerAnnotation == null) {
                continue;
            }
            if (handlerMethod.getParameterTypes() == null || handlerMethod.getParameterTypes().length != 1) {
                logger.log(Type.WARN, "the service handler " + this.handlerType.getSimpleSourceName() + "."
                        + handlerMethod.getName() + " skipped : shall have one parameter");
                continue;
            }
            String methodName = serviceHandlerAnnotation.method();
            if (AsyncHandler.DEFAULT_METHOD_NAME.equals(methodName)) {
                methodName = handlerMethod.getName();
                methodName = methodName.replaceFirst("on", "");
                if (methodName.lastIndexOf("Event") != -1) {
                    methodName = methodName.substring(0, methodName.lastIndexOf("Event"));
                } else if (methodName.lastIndexOf("Thrown") != -1) {
                    methodName = methodName.substring(0, methodName.lastIndexOf("Thrown"));
                }
                methodName = methodName.substring(0, 1).toLowerCase() + methodName.substring(1);
            }

            if (serviceMethod.getName().equals(methodName)) {
                JType paramType = handlerMethod.getParameterTypes()[0];
                String paramQualifiedName = paramType.getQualifiedSourceName();
                JType returnType = serviceMethod.getReturnType();
                String returnQualifiedName = returnType.getQualifiedSourceName();
                if (returnType instanceof JPrimitiveType) {
                    returnQualifiedName = ((JPrimitiveType) returnType).getQualifiedBoxedSourceName();
                }

                if (returnQualifiedName.equals(paramQualifiedName)) {
                    CallbackMethod method = new CallbackMethod();
                    method.successMethodName = handlerMethod.getName();
                    callbackMethods.add(method);
                    continue;
                } else if (Throwable.class.getCanonicalName().equals(paramQualifiedName)) {
                    CallbackMethod method = new CallbackMethod();
                    method.failureMethodName = handlerMethod.getName();
                    callbackMethods.add(method);
                }
            }
        }

        return callbackMethods;
    }

    private void generateServiceImplementationWithCallback(TreeLogger logger, SourceWriter srcWriter)
            throws UnableToCompleteException {
        for (JMethod method : this.serviceBinderType.getMethods()) {
            JParameter[] methodParams = method.getParameters();
            JParameter callbackParam = null;
            if (methodParams != null && methodParams.length > 0) {
                callbackParam = methodParams[methodParams.length - 1];
            }
            if (callbackParam == null) {
                break;
            }
            this.writeStartMethod(srcWriter, method);
            this.writeCommandDefinition(logger, srcWriter, method, callbackParam);
            this.writeCommandParam(srcWriter, method, null, callbackParam);
            this.writeEndMethod(srcWriter, method);
        }
    }

    private void generateServiceImplementation(TreeLogger logger, SourceWriter srcWriter)
            throws UnableToCompleteException {
        Set<String> addedMethods = Sets.newHashSet();
        for (JMethod method : this.serviceType.getOverridableMethods()) {
            if (!addedMethods.contains(method.getName())) {
                addedMethods.add(method.getName());
                this.writeStartMethod(srcWriter, method);
                this.writeCommandDefinition(logger, srcWriter, method, null);
                this.writeCommandParam(srcWriter, method, this.listCallbackMethods(logger, method), null);
                this.writeEndMethod(srcWriter, method);
            }
        }
    }

    private void writeStartMethod(SourceWriter srcWriter, JMethod method) {
        srcWriter.print("public %s %s(", this.typeAsString(method.getReturnType(), false), method.getName());
        int i = 0;
        for (JParameter parameter : method.getParameters()) {
            if (i++ > 0) {
                srcWriter.print(", ");
            }
            srcWriter.print("%s $%d_%s", this.typeAsString(parameter.getType(), false), i, parameter.getName());
        }
        srcWriter.println("){");
        srcWriter.indent();
    }

    private void writeEndMethod(SourceWriter srcWriter, JMethod method) {
        srcWriter.println("CommandController.get().invokeCommand(commandDefinition, commandParam);");
        if (method.getReturnType().equals(JPrimitiveType.BOOLEAN)) {
            srcWriter.println("return false;");
        } else if (method.getReturnType().equals(JPrimitiveType.BYTE)
                || method.getReturnType().equals(JPrimitiveType.CHAR)
                || method.getReturnType().equals(JPrimitiveType.DOUBLE)
                || method.getReturnType().equals(JPrimitiveType.FLOAT)
                || method.getReturnType().equals(JPrimitiveType.INT)
                || method.getReturnType().equals(JPrimitiveType.LONG)
                || method.getReturnType().equals(JPrimitiveType.SHORT)) {
            srcWriter.println("return 0;");
        } else if (!method.getReturnType().equals(JPrimitiveType.VOID)) {
            srcWriter.println("return null;");
        }
        srcWriter.outdent();
        srcWriter.println("}");
    }

    private void writeCommandDefinition(TreeLogger logger, SourceWriter srcWriter, JMethod method,
            JParameter callbackParameter) throws UnableToCompleteException {
        srcWriter.print("CommandDefinition commandDefinition  = new CommandDefinition(");
        srcWriter.print("\"%s\", ", this.serviceType.getQualifiedSourceName());
        srcWriter.print("\"%s\", ", method.getName());
        if (callbackParameter != null) {
            JParameterizedType parameterizedCallback = callbackParameter.getType().isParameterized();
            if (parameterizedCallback != null) {
                srcWriter.print("\"%s\" ", parameterizedCallback.getTypeArgs()[0].getQualifiedSourceName());
            } else {
                logger.branch(TreeLogger.ERROR,
                        "Callback argument type for method " + method.getName() + " is not parametrized", null);
                throw new UnableToCompleteException();
            }

        } else {
            srcWriter.print("\"%s\" ", method.getReturnType().getQualifiedSourceName());
        }
        for (JParameter parameter : method.getParameters()) {
            if (!parameter.equals(callbackParameter)) {
                srcWriter.print(", \"%s\"", parameter.getType().getQualifiedSourceName());
            }
        }
        srcWriter.println(");");
    }

    private void writeCommandParam(SourceWriter srcWriter, JMethod method,
            Collection<CallbackMethod> callbackSuccess, JParameter callbackParameter) {
        boolean lazy = method.getAnnotation(LazyCommand.class) != null;
        boolean quiet = method.getAnnotation(QuietCommand.class) != null;

        srcWriter.print("CommandParam commandParam = new CommandParam(");
        srcWriter.print("%s, ", lazy);
        srcWriter.print("%s, ", quiet);
        srcWriter.print("this.serializer, ");
        srcWriter.print("Lists.newArrayList(Arrays.asList(");
        int i = 0;
        for (JParameter parameter : method.getParameters()) {
            if (!parameter.equals(callbackParameter)) {
                if (i++ > 0) {
                    srcWriter.print(", ");
                }
                srcWriter.print("$%d_%s", i, parameter.getName());
            }
        }
        srcWriter.println(")), ");
        srcWriter.indent();
        srcWriter.println("Lists.newArrayList(");
        srcWriter.indent();
        i = 0;
        if (callbackParameter != null) {
            srcWriter.print("$%d_%s", method.getParameters().length, callbackParameter.getName());
            i++;
        }

        if (callbackSuccess != null) {
            for (CallbackMethod callbackMethod : callbackSuccess) {
                if (i++ > 0) {
                    srcWriter.print(", ");
                }
                srcWriter.println("new CallbackAdapter<%s>(){", this.typeAsString(method.getReturnType(), true));
                srcWriter.indent();
                if (callbackMethod.successMethodName != null) {
                    srcWriter.println("public void onSuccess(%s result){",
                            this.typeAsString(method.getReturnType(), true));
                    srcWriter.indent();
                    srcWriter.println("getHandler().%s(result);", callbackMethod.successMethodName);
                    srcWriter.outdent();
                    srcWriter.println("}");
                }
                if (callbackMethod.failureMethodName != null) {
                    srcWriter.println("public void onFailure(Throwable caught){",
                            this.typeAsString(method.getReturnType(), true));
                    srcWriter.indent();
                    srcWriter.println("getHandler().%s(caught);", callbackMethod.failureMethodName);
                    srcWriter.outdent();
                    srcWriter.println("}");
                }
                srcWriter.outdent();
                srcWriter.print("}");
            }
        }
        srcWriter.outdent();
        srcWriter.println("));");
        srcWriter.outdent();
    }

    private String typeAsString(JType type, boolean translatePrimitives) {
        StringBuilder sb = new StringBuilder();
        if (translatePrimitives && type instanceof JPrimitiveType) {
            sb.append(((JPrimitiveType) type).getQualifiedBoxedSourceName());
        } else {
            sb.append(type.getSimpleSourceName());
            if (type instanceof JParameterizedType) {
                JParameterizedType parameterizedType = (JParameterizedType) type;
                sb.append("<");
                int i = 0;
                for (JType paramType : parameterizedType.getTypeArgs()) {
                    if (i++ > 0) {
                        sb.append(", ");
                    }
                    sb.append(this.typeAsString(paramType, false));
                }
                sb.append(">");
            }
        }
        return sb.toString();
    }

    private void collectImports() {
        Collection<JType> toImports = Sets.newHashSet();
        for (JMethod method : this.serviceType.getOverridableMethods()) {
            toImports.add(method.getReturnType());
            for (JParameter parameter : method.getParameters()) {
                toImports.add(parameter.getType());
            }
        }
        this.imports.addAll(toImports);
        for (JType importType : this.imports) {
            if (importType instanceof JParameterizedType) {
                JParameterizedType parameterizedType = (JParameterizedType) importType;
                for (JType paramType : parameterizedType.getTypeArgs()) {
                    toImports.add(paramType);
                }
            }
        }
        this.imports.addAll(toImports);
    }

    private SourceWriter getSourceWriter(PrintWriter printWriter, GeneratorContext ctx) {

        String packageName = this.handlerType.getPackage().getName();
        String className = this.proxyModelQualifiedName.indexOf('.') == -1 ? this.proxyModelQualifiedName
                : this.proxyModelQualifiedName.substring(this.proxyModelQualifiedName.lastIndexOf('.') + 1,
                        this.proxyModelQualifiedName.length());

        ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName, className);

        composerFactory.addImport(AsyncCallback.class.getName());

        composerFactory.addImport(AbstractServiceBinder.class.getName());
        composerFactory.addImport(Arrays.class.getName());
        composerFactory.addImport(Lists.class.getName());
        composerFactory.addImport(CommandDefinition.class.getName());
        composerFactory.addImport(CommandParam.class.getName());
        composerFactory.addImport(CallbackAdapter.class.getName());
        composerFactory.addImport(CommandController.class.getName());

        composerFactory.addImport(this.serviceType.getQualifiedSourceName());
        composerFactory.addImport(this.serviceBinderType.getQualifiedSourceName());

        for (JType jType : this.imports) {
            if (jType.isPrimitive() != null) {
                continue;
            }
            composerFactory.addImport(jType.getQualifiedSourceName());
        }

        composerFactory.setSuperclass(AbstractServiceBinder.class.getSimpleName() + "<"
                + this.handlerType.getSimpleSourceName() + ", " + this.serviceType.getSimpleSourceName() + ">");

        composerFactory.addImplementedInterface(this.serviceType.getSimpleSourceName());
        composerFactory.addImplementedInterface(this.serviceBinderType.getSimpleSourceName());

        return composerFactory.createSourceWriter(ctx, printWriter);
    }

    private PrintWriter getPrintWriter(TreeLogger logger, GeneratorContext ctx) {
        String packageName = this.proxyModelQualifiedName.indexOf('.') == -1 ? ""
                : this.proxyModelQualifiedName.substring(0, this.proxyModelQualifiedName.lastIndexOf('.'));
        String className = this.proxyModelQualifiedName.indexOf('.') == -1 ? this.proxyModelQualifiedName
                : this.proxyModelQualifiedName.substring(this.proxyModelQualifiedName.lastIndexOf('.') + 1,
                        this.proxyModelQualifiedName.length());

        return ctx.tryCreate(logger, packageName, className);
    }

}