Java tutorial
/** * Copyright 2011 Colin Alworth * * 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.colinalworth.gwt.websockets.rebind; import com.colinalworth.gwt.websockets.client.impl.ServerImpl; import com.colinalworth.gwt.websockets.shared.Client; import com.colinalworth.gwt.websockets.shared.Server; import com.colinalworth.gwt.websockets.shared.impl.ClientCallbackInvocation; import com.colinalworth.gwt.websockets.shared.impl.ClientInvocation; import com.colinalworth.gwt.websockets.shared.impl.ServerCallbackInvocation; import com.colinalworth.gwt.websockets.shared.impl.ServerInvocation; import com.colinalworth.gwt.websockets.client.ServerBuilder; import com.google.gwt.core.client.Callback; import com.google.gwt.core.client.GWT; 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.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.TypeOracle; import com.google.gwt.dev.util.Name; import com.google.gwt.editor.rebind.model.ModelUtils; import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; import com.google.gwt.user.client.rpc.impl.Serializer; 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; /** * Creates a new instance of the given Server type * */ public class ServerCreator { private final JClassType serverType; /** * */ public ServerCreator(JClassType serverType) { this.serverType = serverType; } public void create(TreeLogger logger, GeneratorContext context) throws UnableToCompleteException { String typeName = this.serverType.getQualifiedSourceName(); String packageName = getPackageName(); String simpleName = getSimpleName(); TypeOracle oracle = context.getTypeOracle(); PrintWriter pw = context.tryCreate(logger, packageName, simpleName); if (pw == null) { return; } JClassType serverType = oracle.findType(Name.getSourceNameForClass(Server.class)); JClassType clientType = ModelUtils.findParameterizationOf(serverType, this.serverType)[1]; ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(packageName, simpleName); factory.setSuperclass(Name.getSourceNameForClass(ServerImpl.class) + "<" + typeName + "," + clientType.getQualifiedSourceName() + ">"); factory.addImplementedInterface(typeName); SourceWriter sw = factory.createSourceWriter(context, pw); //TODO move this check before the printwriter creation can fail, and allow the warn to be optional sw.println("public %1$s(%2$s errorHandler) {", simpleName, Name.getSourceNameForClass(ServerBuilder.ConnectionErrorHandler.class)); RemoteServiceRelativePath path = this.serverType.getAnnotation(RemoteServiceRelativePath.class); if (path == null) { // logger.log(Type.WARN, "@RemoteServiceRelativePath required on " + typeName + " to make a connection to the server without a ServerBuilder"); // throw new UnableToCompleteException(); sw.indentln("super(null);"); sw.indentln( "throw new RuntimeException(\"@RemoteServiceRelativePath annotation required on %1$s to make a connection without a path defined in ServerBuilder\");"); } else { sw.indentln("super(errorHandler, " + "com.google.gwt.user.client.Window.Location.getProtocol().toLowerCase().startsWith(\"https\") ? \"wss://\": \"ws://\", " + "com.google.gwt.user.client.Window.Location.getHost(), \"%1$s\");", path.value()); } sw.println("}"); sw.println("public %1$s(%2$s errorHandler, String url) {", simpleName, Name.getSourceNameForClass(ServerBuilder.ConnectionErrorHandler.class)); sw.indentln("super(errorHandler, url);"); sw.println("}"); //Find all types that may go over the wire // Collect the types the server will send to the client using the Client interface SerializableTypeOracleBuilder serverSerializerBuilder = new SerializableTypeOracleBuilder(logger, context); appendMethodParameters(logger, clientType, Client.class, serverSerializerBuilder); // Also add the wrapper object ClientInvocation serverSerializerBuilder.addRootType(logger, oracle.findType(ClientInvocation.class.getName())); serverSerializerBuilder.addRootType(logger, oracle.findType(ClientCallbackInvocation.class.getName())); // Collect the types the client will send to the server using the Server interface SerializableTypeOracleBuilder clientSerializerBuilder = new SerializableTypeOracleBuilder(logger, context); appendMethodParameters(logger, this.serverType, Server.class, clientSerializerBuilder); // Also add the ServerInvocation wrapper clientSerializerBuilder.addRootType(logger, oracle.findType(ServerInvocation.class.getName())); clientSerializerBuilder.addRootType(logger, oracle.findType(ServerCallbackInvocation.class.getName())); String tsName = simpleName + "_TypeSerializer"; TypeSerializerCreator serializerCreator = new TypeSerializerCreator(logger, clientSerializerBuilder.build(logger), serverSerializerBuilder.build(logger), context, packageName + "." + tsName, tsName); serializerCreator.realize(logger); // Make the newly created Serializer available at runtime sw.println("protected %1$s __getSerializer() {", Serializer.class.getName()); sw.indentln("return %2$s.<%1$s>create(%1$s.class);", tsName, GWT.class.getName()); sw.println("}"); // Build methods that call from the client to the server for (JMethod m : this.serverType.getInheritableMethods()) { if (isRemoteMethod(m, Server.class)) { printServerMethodBody(logger, context, sw, m); } } // Read incoming calls and dispatch them to the correct client method sw.println("protected void __invoke(String method, Object[] params) {"); for (JMethod m : clientType.getInheritableMethods()) { if (isRemoteMethod(m, Client.class)) { JParameter[] params = m.getParameters(); sw.println("if (method.equals(\"%1$s\") && params.length == %2$d) {", m.getName(), params.length); sw.indent(); sw.println("getClient().%1$s(", m.getName()); sw.indent(); for (int i = 0; i < params.length; i++) { if (i != 0) { sw.print(","); } sw.println("(%1$s)params[%2$d]", params[i].getType().getQualifiedSourceName(), i); } sw.outdent(); sw.println(");"); sw.outdent(); sw.println("}"); } } sw.println("}"); sw.println("protected void __onError(Exception error) {"); sw.println("}"); sw.commit(logger); } public String getPackageName() { return serverType.getPackage().getName(); } public String getSimpleName() { return serverType.getName().replace('.', '_') + "_ProxyImpl"; } public String getQualifiedSourceName() { return getPackageName() + "." + getSimpleName(); } /** * Helper method to build up the list of types that can go over the wire * @param logger * @param serviceInterface * @param serviceSuperClass * @param serializerBuilder */ private void appendMethodParameters(TreeLogger logger, JClassType serviceInterface, Class<?> serviceSuperClass, SerializableTypeOracleBuilder serializerBuilder) { TreeLogger l = logger.branch(Type.DEBUG, "Adding params types to " + serviceInterface.getName()); for (JMethod m : serviceInterface.getMethods()) { if (isRemoteMethod(m, serviceSuperClass)) { JParameter[] parameters = m.getParameters(); for (int i = 0; i < parameters.length; i++) { JParameter param = parameters[i]; if (i + 1 != m.getParameters().length || param.getType().isInterface() == null || !param .getType().isInterface().getQualifiedSourceName().equals(Callback.class.getName())) { serializerBuilder.addRootType(l, param.getType()); } } } } } /** * Writes out the method to use to invoke a server call. Mostly derived from RPC's way of building proxy methods * * @param logger * @param context * @param sw * @param m */ private void printServerMethodBody(TreeLogger logger, GeneratorContext context, SourceWriter sw, JMethod m) { sw.println("%1$s {", m.getReadableDeclaration(false, true, true, true, true)); sw.indent(); sw.print("__sendMessage(\"%1$s\"", m.getName()); StringBuilder sb = new StringBuilder(); String callback = "null"; JParameter[] parameters = m.getParameters(); for (int i = 0; i < parameters.length; i++) { JParameter param = parameters[i]; if (i + 1 == parameters.length && param.getType().isInterface() != null && param.getType().isInterface().getQualifiedSourceName().equals(Callback.class.getName())) { callback = param.getName(); } else { sb.append(",\n").append(param.getName()); } } sw.print(","); sw.println(callback); sw.print(sb.toString()); sw.println(");"); sw.outdent(); sw.println("}"); } /** * Checks to see if the given method can be called over the wire. * * * @param m method to check * @param superClass either {@link Server} or {@link Client}, indicating which direction the call will be made * @return */ private boolean isRemoteMethod(JMethod m, Class<?> superClass) { assert superClass == Server.class || superClass == Client.class; return !m.getEnclosingType().getQualifiedSourceName().equals(superClass.getName()); } }