| /* |
| * Copyright 2011 Google Inc. |
| * |
| * 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.google.web.bindery.requestfactory.server; |
| |
| import com.google.gwt.dev.asm.AnnotationVisitor; |
| import com.google.gwt.dev.asm.Attribute; |
| import com.google.gwt.dev.asm.ClassReader; |
| import com.google.gwt.dev.asm.ClassVisitor; |
| import com.google.gwt.dev.asm.ClassWriter; |
| import com.google.gwt.dev.asm.FieldVisitor; |
| import com.google.gwt.dev.asm.Label; |
| import com.google.gwt.dev.asm.MethodVisitor; |
| import com.google.gwt.dev.asm.Opcodes; |
| import com.google.gwt.dev.asm.Type; |
| import com.google.gwt.dev.asm.commons.Method; |
| import com.google.gwt.dev.util.Name; |
| import com.google.gwt.dev.util.Name.SourceOrBinaryName; |
| import com.google.gwt.dev.util.Util; |
| import com.google.web.bindery.event.shared.SimpleEventBus; |
| import com.google.web.bindery.requestfactory.apt.RfValidator; |
| import com.google.web.bindery.requestfactory.apt.ValidationTool; |
| import com.google.web.bindery.requestfactory.gwt.client.RequestBatcher; |
| import com.google.web.bindery.requestfactory.shared.BaseProxy; |
| import com.google.web.bindery.requestfactory.shared.DefaultProxyStore; |
| import com.google.web.bindery.requestfactory.shared.EntityProxy; |
| import com.google.web.bindery.requestfactory.shared.EntityProxyChange; |
| import com.google.web.bindery.requestfactory.shared.EntityProxyId; |
| import com.google.web.bindery.requestfactory.shared.ExtraTypes; |
| import com.google.web.bindery.requestfactory.shared.InstanceRequest; |
| import com.google.web.bindery.requestfactory.shared.JsonRpcContent; |
| import com.google.web.bindery.requestfactory.shared.JsonRpcProxy; |
| import com.google.web.bindery.requestfactory.shared.JsonRpcService; |
| import com.google.web.bindery.requestfactory.shared.JsonRpcWireName; |
| import com.google.web.bindery.requestfactory.shared.Locator; |
| import com.google.web.bindery.requestfactory.shared.LoggingRequest; |
| import com.google.web.bindery.requestfactory.shared.ProxyFor; |
| import com.google.web.bindery.requestfactory.shared.ProxyForName; |
| import com.google.web.bindery.requestfactory.shared.ProxySerializer; |
| import com.google.web.bindery.requestfactory.shared.ProxyStore; |
| import com.google.web.bindery.requestfactory.shared.Receiver; |
| import com.google.web.bindery.requestfactory.shared.Request; |
| import com.google.web.bindery.requestfactory.shared.RequestContext; |
| import com.google.web.bindery.requestfactory.shared.RequestFactory; |
| import com.google.web.bindery.requestfactory.shared.RequestTransport; |
| import com.google.web.bindery.requestfactory.shared.ServerFailure; |
| import com.google.web.bindery.requestfactory.shared.Service; |
| import com.google.web.bindery.requestfactory.shared.ServiceLocator; |
| import com.google.web.bindery.requestfactory.shared.ServiceName; |
| import com.google.web.bindery.requestfactory.shared.ValueProxy; |
| import com.google.web.bindery.requestfactory.shared.WriteOperation; |
| import com.google.web.bindery.requestfactory.vm.RequestFactorySource; |
| import com.google.web.bindery.requestfactory.vm.testing.UrlRequestTransport; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.UnsupportedEncodingException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.BlockingQueue; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentSkipListSet; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.Future; |
| import java.util.concurrent.LinkedBlockingQueue; |
| import java.util.jar.JarOutputStream; |
| import java.util.jar.Manifest; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import java.util.zip.ZipEntry; |
| |
| import javax.annotation.processing.Processor; |
| |
| /** |
| * Used to extract RequestFactory client jars from {@code gwt-user.jar}. |
| */ |
| public class RequestFactoryJarExtractor { |
| /* |
| * The FooProcessor types are ASM visitors that traverse the bytecode, calling |
| * one of the various processFoo() methods. The visitors will also update the |
| * bytecode with the rebased type names that are returned from the |
| * processFoo() methods. |
| */ |
| |
| /** |
| * An implementation of {@link Loader} that uses a {@link ClassLoader} to |
| * retrieve the class files. |
| */ |
| public static class ClassLoaderLoader implements Loader { |
| private final ClassLoader loader; |
| |
| public ClassLoaderLoader(ClassLoader loader) { |
| this.loader = loader; |
| } |
| |
| public boolean exists(String resource) { |
| return loader.getResource(resource) != null; |
| } |
| |
| public InputStream getResourceAsStream(String resource) { |
| return loader.getResourceAsStream(resource); |
| } |
| } |
| |
| /** |
| * Describes a way to emit the contents of a classpath, typically into a JAR |
| * or filesystem directory. |
| */ |
| public interface Emitter { |
| void close() throws IOException; |
| |
| void emit(String path, InputStream contents) throws IOException; |
| } |
| |
| /** |
| * An Emitter implementation that creates a jar file. |
| */ |
| public static class JarEmitter implements Emitter { |
| private int rawByteSize; |
| private final JarOutputStream out; |
| |
| public JarEmitter(File outFile) throws IOException { |
| Manifest m = new Manifest(); |
| m.getMainAttributes().putValue("Created-By", |
| RequestFactoryJarExtractor.class.getCanonicalName()); |
| m.getMainAttributes(); |
| out = new JarOutputStream(new FileOutputStream(outFile), m); |
| } |
| |
| public void close() throws IOException { |
| out.close(); |
| } |
| |
| public void emit(String path, InputStream contents) throws IOException { |
| ZipEntry entry = new ZipEntry(path); |
| out.putNextEntry(entry); |
| byte[] bytes = new byte[4096]; |
| int read; |
| for (;;) { |
| read = contents.read(bytes); |
| if (read == -1) { |
| break; |
| } |
| rawByteSize += read; |
| out.write(bytes, 0, read); |
| } |
| out.closeEntry(); |
| } |
| } |
| |
| /** |
| * Abstracts the mechanism by which class files are loaded. |
| * |
| * @see ClassLoaderLoader |
| */ |
| public interface Loader { |
| /** |
| * Returns true if the specified resource can be loaded. |
| * |
| * @param resource a resource name (e.g. <code>com/example/Foo.class</code>) |
| */ |
| boolean exists(String resource); |
| |
| /** |
| * Returns an InputStream to access the specified resource, or |
| * <code>null</code> if no such resource exists. |
| * |
| * @param resource a resource name (e.g. <code>com/example/Foo.class</code>) |
| */ |
| InputStream getResourceAsStream(String resource); |
| } |
| |
| /** |
| * Controls what is emitted by the tool. |
| */ |
| public enum Mode { |
| BOTH(true, true) { |
| @Override |
| protected boolean matches(String target) { |
| return target.endsWith(CODE_AND_SOURCE); |
| } |
| }, |
| SOURCE(false, true) { |
| @Override |
| protected boolean matches(String target) { |
| return target.endsWith(SOURCE_ONLY); |
| } |
| }, |
| // Order is important, must be last |
| CLASSES(true, false) { |
| @Override |
| protected boolean matches(String target) { |
| return true; |
| } |
| }; |
| |
| public static Mode match(String target) { |
| for (Mode mode : Mode.values()) { |
| if (mode.matches(target)) { |
| return mode; |
| } |
| } |
| return null; |
| } |
| |
| private final boolean emitClasses; |
| private final boolean emitSource; |
| |
| private Mode(boolean emitClasses, boolean emitSource) { |
| this.emitClasses = emitClasses; |
| this.emitSource = emitSource; |
| } |
| |
| public boolean isEmitClasses() { |
| return emitClasses; |
| } |
| |
| public boolean isEmitSource() { |
| return emitSource; |
| } |
| |
| protected abstract boolean matches(String target); |
| } |
| |
| /** |
| * Improves error messages by providing context for the user. |
| * <p> |
| * Visible for testing. |
| */ |
| static class ErrorContext { |
| private static String print(Method method) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(print(method.getReturnType())).append(" ").append(method.getName()).append("("); |
| for (Type t : method.getArgumentTypes()) { |
| sb.append(print(t)).append(" "); |
| } |
| sb.append(")"); |
| return sb.toString(); |
| } |
| |
| private static String print(Type type) { |
| return SourceOrBinaryName.toSourceName(type.getClassName()); |
| } |
| |
| private final Logger logger; |
| private final ErrorContext parent; |
| |
| private Type currentType; |
| |
| private Method currentMethod; |
| |
| public ErrorContext(Logger logger) { |
| this.logger = logger; |
| this.parent = null; |
| } |
| |
| protected ErrorContext(ErrorContext parent) { |
| this.logger = parent.logger; |
| this.parent = parent; |
| } |
| |
| public void poison(String msg, Object... args) { |
| poison(); |
| logger.logp(Level.SEVERE, currentType(), currentMethod(), String.format(msg, args)); |
| } |
| |
| public void poison(String msg, Throwable t) { |
| poison(); |
| logger.logp(Level.SEVERE, currentType(), currentMethod(), msg, t); |
| } |
| |
| public ErrorContext setMethod(Method method) { |
| ErrorContext toReturn = fork(); |
| toReturn.currentMethod = method; |
| return toReturn; |
| } |
| |
| public ErrorContext setType(Type type) { |
| ErrorContext toReturn = fork(); |
| toReturn.currentType = type; |
| return toReturn; |
| } |
| |
| public void spam(String msg, Object... args) { |
| logger.logp(Level.FINEST, currentType(), currentMethod(), String.format(msg, args)); |
| } |
| |
| protected ErrorContext fork() { |
| return new ErrorContext(this); |
| } |
| |
| private String currentMethod() { |
| if (currentMethod != null) { |
| return print(currentMethod); |
| } |
| if (parent != null) { |
| return parent.currentMethod(); |
| } |
| return null; |
| } |
| |
| private String currentType() { |
| if (currentType != null) { |
| return print(currentType); |
| } |
| if (parent != null) { |
| return parent.currentType(); |
| } |
| return null; |
| } |
| |
| /** |
| * Populate {@link RequestFactoryInterfaceValidator#badTypes} with the |
| * current context. |
| */ |
| private void poison() { |
| if (parent != null) { |
| parent.poison(); |
| } |
| } |
| } |
| |
| private class AnnotationProcessor extends AnnotationVisitor { |
| private final String sourceType; |
| private final AnnotationVisitor av; |
| |
| public AnnotationProcessor(String sourceType, AnnotationVisitor av) { |
| // TODO(rluble): should we chain av to super here? |
| super(Opcodes.ASM4); |
| this.sourceType = sourceType; |
| this.av = av; |
| } |
| |
| public void visit(String name, Object value) { |
| value = processConstant(sourceType, value); |
| av.visit(name, value); |
| } |
| |
| public AnnotationVisitor visitAnnotation(String name, String desc) { |
| desc = processDescriptor(sourceType, desc); |
| return new AnnotationProcessor(desc, av.visitAnnotation(name, desc)); |
| } |
| |
| public AnnotationVisitor visitArray(String name) { |
| return new AnnotationProcessor(name, av.visitArray(name)); |
| } |
| |
| public void visitEnd() { |
| av.visitEnd(); |
| } |
| |
| public void visitEnum(String name, String desc, String value) { |
| desc = processDescriptor(sourceType, desc); |
| av.visitEnum(name, desc, value); |
| } |
| } |
| |
| private class ClassProcessor extends ClassVisitor { |
| private State state; |
| private String sourceType; |
| |
| public ClassProcessor(String sourceType, ClassVisitor cv, State state) { |
| super(Opcodes.ASM4, cv); |
| this.sourceType = sourceType; |
| this.state = state; |
| } |
| |
| @Override |
| public void visit(int version, int access, String name, String signature, String superName, |
| String[] interfaces) { |
| name = processInternalName(sourceType, name); |
| superName = processInternalName(sourceType, superName); |
| if (interfaces != null) { |
| for (int i = 0, j = interfaces.length; i < j; i++) { |
| interfaces[i] = processInternalName(sourceType, interfaces[i]); |
| } |
| } |
| super.visit(version, access, name, signature, superName, interfaces); |
| } |
| |
| @Override |
| public AnnotationVisitor visitAnnotation(String desc, boolean visible) { |
| desc = processDescriptor(sourceType, desc); |
| return new AnnotationProcessor(sourceType, super.visitAnnotation(desc, visible)); |
| } |
| |
| @Override |
| public FieldVisitor visitField(int access, String name, String desc, String signature, |
| Object value) { |
| desc = processDescriptor(sourceType, desc); |
| return new FieldProcessor(sourceType, super.visitField(access, name, desc, signature, value)); |
| } |
| |
| @Override |
| public void visitInnerClass(String name, String outerName, String innerName, int access) { |
| name = processInternalName(sourceType, name); |
| outerName = processInternalName(sourceType, outerName); |
| super.visitInnerClass(name, outerName, innerName, access); |
| } |
| |
| @Override |
| public MethodVisitor visitMethod(int access, String name, String desc, String signature, |
| String[] exceptions) { |
| Method method = processMethod(sourceType, name, desc); |
| desc = method.getDescriptor(); |
| if (exceptions != null) { |
| for (int i = 0, j = exceptions.length; i < j; i++) { |
| exceptions[i] = processInternalName(sourceType, exceptions[i]); |
| } |
| } |
| MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); |
| if (mv != null) { |
| mv = new MethodProcessor(sourceType, mv); |
| } |
| return mv; |
| } |
| |
| @Override |
| public void visitOuterClass(String owner, String name, String desc) { |
| owner = processInternalName(sourceType, owner); |
| if (desc != null) { |
| desc = processMethod(sourceType, name, desc).getDescriptor(); |
| } |
| super.visitOuterClass(owner, name, desc); |
| } |
| |
| @Override |
| public void visitSource(String source, String debug) { |
| if (source != null) { |
| state.source = source; |
| } |
| super.visitSource(source, debug); |
| } |
| } |
| |
| private class EmitOneResource implements Callable<Void> { |
| private final byte[] contents; |
| private final String path; |
| |
| private EmitOneResource(String path, byte[] contents) { |
| this.path = path; |
| this.contents = contents; |
| } |
| |
| @Override |
| public Void call() throws Exception { |
| if (mode.isEmitClasses()) { |
| emitter.emit(path, new ByteArrayInputStream(contents)); |
| } |
| return null; |
| } |
| } |
| |
| /** |
| * A unit of work to write one class and its source file into the Emitter. |
| */ |
| private class EmitOneType implements Callable<Void> { |
| private final State state; |
| |
| /** |
| * @param state |
| */ |
| private EmitOneType(State state) { |
| this.state = state; |
| } |
| |
| public Void call() throws Exception { |
| if (mode.isEmitClasses()) { |
| String fileName = state.type.getInternalName(); |
| if (fileName == null) { |
| System.err.println("Got null filename from " + state.type); |
| return null; |
| } |
| fileName += ".class"; |
| emitter.emit(fileName, state.contents); |
| } |
| if (mode.isEmitSource()) { |
| String sourcePath = getPackagePath(state.originalType) + state.source; |
| String destPath = getPackagePath(state.type) + state.source; |
| if (sources.add(sourcePath) && loader.exists(sourcePath)) { |
| String contents = Util.readStreamAsString(loader.getResourceAsStream(sourcePath)); |
| emitter.emit(destPath, new ByteArrayInputStream(Util.getBytes(contents))); |
| } |
| } |
| return null; |
| } |
| } |
| |
| // There is no FieldAdapter type |
| private class FieldProcessor extends FieldVisitor { |
| private final String sourceType; |
| private final FieldVisitor fv; |
| |
| public FieldProcessor(String sourceType, FieldVisitor fv) { |
| // TODO(rluble): Should we chain fv to super here? |
| super(Opcodes.ASM4); |
| this.sourceType = sourceType; |
| this.fv = fv; |
| } |
| |
| public AnnotationVisitor visitAnnotation(String desc, boolean visible) { |
| return new AnnotationProcessor(sourceType, fv.visitAnnotation(desc, visible)); |
| } |
| |
| public void visitAttribute(Attribute attr) { |
| fv.visitAttribute(attr); |
| } |
| |
| public void visitEnd() { |
| fv.visitEnd(); |
| } |
| } |
| |
| private class MethodProcessor extends MethodVisitor { |
| private final String sourceType; |
| |
| public MethodProcessor(String sourceType, MethodVisitor mv) { |
| super(Opcodes.ASM4, mv); |
| this.sourceType = sourceType; |
| } |
| |
| @Override |
| public AnnotationVisitor visitAnnotation(String desc, boolean visible) { |
| desc = processDescriptor(sourceType, desc); |
| return super.visitAnnotation(desc, visible); |
| } |
| |
| @Override |
| public AnnotationVisitor visitAnnotationDefault() { |
| return new AnnotationProcessor(sourceType, super.visitAnnotationDefault()); |
| } |
| |
| @Override |
| public void visitFieldInsn(int opcode, String owner, String name, String desc) { |
| owner = processInternalName(sourceType, owner); |
| desc = processDescriptor(sourceType, desc); |
| super.visitFieldInsn(opcode, owner, name, desc); |
| } |
| |
| @Override |
| public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { |
| for (int i = 0, j = local.length; i < j; i++) { |
| if (local[i] instanceof String) { |
| local[i] = processInternalName(sourceType, (String) local[i]); |
| } |
| } |
| for (int i = 0, j = stack.length; i < j; i++) { |
| if (stack[i] instanceof String) { |
| stack[i] = processInternalName(sourceType, (String) stack[i]); |
| } |
| } |
| super.visitFrame(type, nLocal, local, nStack, stack); |
| } |
| |
| @Override |
| public void visitLdcInsn(Object cst) { |
| cst = processConstant(sourceType, cst); |
| super.visitLdcInsn(cst); |
| } |
| |
| @Override |
| public void visitLocalVariable(String name, String desc, String signature, Label start, |
| Label end, int index) { |
| desc = processDescriptor(sourceType, desc); |
| super.visitLocalVariable(name, desc, signature, start, end, index); |
| } |
| |
| @Override |
| public void visitMethodInsn(int opcode, String owner, String name, String desc) { |
| owner = processInternalName(sourceType, owner); |
| desc = processMethod(sourceType, name, desc).getDescriptor(); |
| super.visitMethodInsn(opcode, owner, name, desc); |
| } |
| |
| @Override |
| public void visitMultiANewArrayInsn(String desc, int dims) { |
| desc = processDescriptor(sourceType, desc); |
| super.visitMultiANewArrayInsn(desc, dims); |
| } |
| |
| @Override |
| public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { |
| desc = processDescriptor(sourceType, desc); |
| return super.visitParameterAnnotation(parameter, desc, visible); |
| } |
| |
| @Override |
| public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { |
| type = processInternalName(sourceType, type); |
| super.visitTryCatchBlock(start, end, handler, type); |
| } |
| |
| @Override |
| public void visitTypeInsn(int opcode, String type) { |
| type = processInternalName(sourceType, type); |
| super.visitTypeInsn(opcode, type); |
| } |
| } |
| |
| /** |
| * Replaces native methods with stub implementations that throw an exception. |
| * This allows any dangling GWT types to be loaded by a JVM without triggering |
| * an {@link UnsatisfiedLinkError}. |
| */ |
| private class NativeMethodDefanger extends ClassVisitor { |
| public NativeMethodDefanger(ClassVisitor cv) { |
| super(Opcodes.ASM4, cv); |
| } |
| |
| @Override |
| public MethodVisitor visitMethod(int access, String name, String desc, String signature, |
| String[] exceptions) { |
| if ((access & Opcodes.ACC_NATIVE) != 0) { |
| MethodVisitor mv = |
| super.visitMethod(access & ~Opcodes.ACC_NATIVE, name, desc, signature, exceptions); |
| if (mv != null) { |
| mv.visitCode(); |
| String exceptionName = Type.getInternalName(RuntimeException.class); |
| // <empty> |
| mv.visitTypeInsn(Opcodes.NEW, exceptionName); |
| // obj |
| mv.visitInsn(Opcodes.DUP); |
| // obj, obj |
| mv.visitLdcInsn(NATIVE_METHOD_ERROR); |
| // obj, obj, string |
| mv.visitMethodInsn(Opcodes.INVOKESPECIAL, exceptionName, "<init>", |
| "(Ljava/lang/String;)V"); |
| // obj |
| mv.visitInsn(Opcodes.ATHROW); |
| |
| // Count argument slots - long and double arguments each take up 2 |
| // slots |
| int numSlots = 0; |
| for (Type t : Type.getArgumentTypes(desc)) { |
| numSlots += t.getSize(); |
| } |
| if ((access & Opcodes.ACC_STATIC) == 0) { |
| // Add one for 'this' reference |
| numSlots++; |
| } |
| mv.visitMaxs(3, numSlots); |
| mv.visitEnd(); |
| } |
| return null; |
| } else { |
| return super.visitMethod(access, name, desc, signature, exceptions); |
| } |
| } |
| } |
| |
| /** |
| * This is the main bytecode-processing entry point. It will read in one |
| * classfile and produce a mutated copy. Any referenced types will be enqueued |
| * via {@link RequestFactoryJarExtractor#processType(String, Type)}. |
| */ |
| private class ProcessOneType implements Callable<State> { |
| |
| private final State state; |
| private final String typeName; |
| |
| public ProcessOneType(Type type) { |
| state = new State(type); |
| typeName = type.getClassName(); |
| } |
| |
| public State call() { |
| ClassWriter writer = new ClassWriter(0); |
| ClassVisitor cv = writer; |
| cv = new ClassProcessor(typeName, cv, state); |
| cv = new NativeMethodDefanger(cv); |
| visit(logger.setType(state.type), loader, state.type.getInternalName(), cv); |
| state.contents = new ByteArrayInputStream(writer.toByteArray()); |
| assert seen.containsKey(state.originalType) : "No type for " + state.type.getClassName(); |
| state.type = seen.get(state.originalType); |
| |
| emit(state); |
| return state; |
| } |
| } |
| |
| /** |
| * Metadata about a single type. |
| */ |
| private static class State { |
| boolean containsNativeMethods; |
| /** |
| * Will contain the data to be written to disk, possibly mutated class data. |
| */ |
| InputStream contents; |
| String source; |
| /** |
| * The possibly rebased type name. |
| */ |
| Type type; |
| final Type originalType; |
| |
| public State(Type type) { |
| this.originalType = this.type = type; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(type.getInternalName()); |
| if (containsNativeMethods) { |
| sb.append(" NATIVE"); |
| } |
| if (source != null) { |
| sb.append(" ").append(source); |
| } |
| return sb.toString(); |
| } |
| } |
| |
| /** |
| * If true, print a trace of dependencies to System.out. |
| */ |
| private static final boolean VERBOSE = false; |
| |
| /** |
| * |
| */ |
| private static final String CODE_AND_SOURCE = "+src"; |
| |
| /** |
| * |
| */ |
| private static final String SOURCE_ONLY = "-src"; |
| |
| private static final String NATIVE_METHOD_ERROR = "Cannot call native method"; |
| |
| /** |
| * A map of target names to the types that target should use as a base. |
| */ |
| private static final Map<String, List<Class<?>>> SEEDS = |
| new LinkedHashMap<String, List<Class<?>>>(); |
| |
| /** |
| * Server public API classes and interfaces. |
| */ |
| private static final Class<?>[] SERVER_CLASSES = { |
| DefaultExceptionHandler.class, ExceptionHandler.class, Logging.class, LoggingRequest.class, |
| RequestFactoryServlet.class, ServiceLayer.class, ServiceLayerDecorator.class, |
| SimpleRequestProcessor.class}; |
| |
| /** |
| * Shared public API classes and interfaces. |
| */ |
| @SuppressWarnings("deprecation") |
| private static final Class<?>[] SHARED_CLASSES = { |
| BaseProxy.class, DefaultProxyStore.class, EntityProxy.class, EntityProxyChange.class, |
| EntityProxyId.class, ExtraTypes.class, InstanceRequest.class, JsonRpcContent.class, |
| JsonRpcProxy.class, JsonRpcService.class, JsonRpcWireName.class, Locator.class, |
| ProxyFor.class, ProxyForName.class, ProxySerializer.class, ProxyStore.class, Receiver.class, |
| Request.class, RequestBatcher.class, RequestContext.class, RequestFactory.class, |
| RequestTransport.class, ServerFailure.class, Service.class, ServiceLocator.class, |
| ServiceName.class, ValueProxy.class, |
| com.google.web.bindery.requestfactory.shared.Violation.class, WriteOperation.class, |
| RequestFactorySource.class, SimpleEventBus.class}; |
| |
| /** |
| * Maximum number of threads to use to run the Extractor. |
| */ |
| private static final int MAX_THREADS = 4; |
| |
| static { |
| List<Class<?>> aptClasses = |
| Collections.unmodifiableList(Arrays.<Class<?>> asList(RfValidator.class, |
| ValidationTool.class)); |
| List<Class<?>> sharedClasses = Arrays.<Class<?>> asList(SHARED_CLASSES); |
| |
| List<Class<?>> clientClasses = new ArrayList<Class<?>>(); |
| clientClasses.addAll(sharedClasses); |
| clientClasses.add(UrlRequestTransport.class); |
| |
| List<Class<?>> serverClasses = new ArrayList<Class<?>>(); |
| serverClasses.addAll(Arrays.<Class<?>> asList(SERVER_CLASSES)); |
| serverClasses.addAll(sharedClasses); |
| |
| SEEDS.put("apt", aptClasses); |
| SEEDS.put("client", Collections.unmodifiableList(clientClasses)); |
| SEEDS.put("server", Collections.unmodifiableList(serverClasses)); |
| |
| Set<Class<?>> all = new LinkedHashSet<Class<?>>(); |
| for (List<Class<?>> value : SEEDS.values()) { |
| all.addAll(value); |
| } |
| SEEDS.put("all", Collections.unmodifiableList(new ArrayList<Class<?>>(all))); |
| |
| for (String target : new ArrayList<String>(SEEDS.keySet())) { |
| SEEDS.put(target + SOURCE_ONLY, SEEDS.get(target)); |
| SEEDS.put(target + CODE_AND_SOURCE, SEEDS.get(target)); |
| } |
| |
| /* |
| * Allows the rebased package to be tested. This is done with a by-name |
| * lookup, since the gwt-user code is compiled separately from its tests. |
| */ |
| try { |
| List<Class<?>> testClasses = |
| Collections.unmodifiableList(Arrays.<Class<?>> asList(Class |
| .forName("com.google.web.bindery.requestfactory.vm.RequestFactoryJreSuite"), Class |
| .forName("com.google.web.bindery.requestfactory.server.SimpleBar"))); |
| SEEDS.put("test", testClasses); |
| SEEDS.put("test" + SOURCE_ONLY, testClasses); |
| } catch (ClassNotFoundException ignored) { |
| } |
| } |
| |
| public static void main(String[] args) throws IOException { |
| if (args.length < 2) { |
| System.err.println("Usage: java -cp gwt-dev.jar:gwt-user.jar:json.jar" |
| + RequestFactoryJarExtractor.class.getCanonicalName() + " <target-name> outfile.jar"); |
| System.err.println("Valid targets:"); |
| for (String target : SEEDS.keySet()) { |
| System.err.println(" " + target); |
| } |
| System.exit(1); |
| } |
| String target = args[0]; |
| List<Class<?>> seeds = SEEDS.get(target); |
| if (seeds == null) { |
| System.err.println("Unknown target: " + target); |
| System.exit(1); |
| } |
| Map<String, byte[]> resources = createResources(seeds); |
| Mode mode = Mode.match(target); |
| Logger errorContext = Logger.getLogger(RequestFactoryJarExtractor.class.getName()); |
| RequestFactoryJarExtractor.ClassLoaderLoader classLoader = |
| new RequestFactoryJarExtractor.ClassLoaderLoader(Thread.currentThread() |
| .getContextClassLoader()); |
| JarEmitter jarEmitter = new JarEmitter(new File(args[1])); |
| RequestFactoryJarExtractor extractor = |
| new RequestFactoryJarExtractor(errorContext, classLoader, jarEmitter, seeds, resources, |
| mode); |
| extractor.run(); |
| System.exit(extractor.isExecutionFailed() ? 1 : 0); |
| } |
| |
| private static Map<String, byte[]> createResources(List<Class<?>> seeds) |
| throws UnsupportedEncodingException, IOException { |
| Map<String, byte[]> resources; |
| if (seeds.contains(RfValidator.class)) { |
| // Add the annotation processor manifest |
| resources = |
| Collections.singletonMap("META-INF/services/" + Processor.class.getCanonicalName(), |
| RfValidator.class.getCanonicalName().getBytes("UTF-8")); |
| } else { |
| resources = Collections.emptyMap(); |
| } |
| return resources; |
| } |
| |
| /** |
| * Given a Type, return a path-prefix based on the type's package. |
| */ |
| private static String getPackagePath(Type t) { |
| String name = t.getInternalName(); |
| return name.substring(0, name.lastIndexOf('/') + 1); |
| } |
| |
| /** |
| * Load the classfile for the given binary name and apply the provided |
| * visitor. |
| * |
| * @return <code>true</code> if the visitor was successfully visited |
| */ |
| private static boolean visit(RequestFactoryJarExtractor.ErrorContext logger, |
| RequestFactoryJarExtractor.Loader loader, String internalName, ClassVisitor visitor) { |
| assert Name.isInternalName(internalName) : "internalName"; |
| logger.spam("Visiting " + internalName); |
| InputStream inputStream = null; |
| try { |
| inputStream = loader.getResourceAsStream(internalName + ".class"); |
| if (inputStream == null) { |
| System.err.println("Could not find class file for " + internalName); |
| logger.poison("Could not find class file for " + internalName); |
| return false; |
| } |
| ClassReader reader = new ClassReader(inputStream); |
| reader.accept(visitor, 0); |
| return true; |
| } catch (IOException e) { |
| logger.poison("Unable to open " + internalName, e); |
| } finally { |
| if (inputStream != null) { |
| try { |
| inputStream.close(); |
| } catch (IOException ignored) { |
| } |
| } |
| } |
| return false; |
| } |
| |
| private boolean executionFailed = false; |
| private final Emitter emitter; |
| private final ExecutorService ex; |
| private final BlockingQueue<Future<?>> inProcess = new LinkedBlockingQueue<Future<?>>(); |
| private final RequestFactoryJarExtractor.ErrorContext logger; |
| private final RequestFactoryJarExtractor.Loader loader; |
| private final Mode mode; |
| private final Map<String, byte[]> resources; |
| private final List<Class<?>> seeds; |
| private final Map<Type, Type> seen = new ConcurrentHashMap<Type, Type>(); |
| private final Set<String> sources = new ConcurrentSkipListSet<String>(); |
| private final ExecutorService writerService; |
| |
| public RequestFactoryJarExtractor(Logger logger, RequestFactoryJarExtractor.Loader loader, |
| Emitter emitter, List<Class<?>> seeds, Map<String, byte[]> resources, Mode mode) { |
| this.logger = new RequestFactoryJarExtractor.ErrorContext(logger); |
| this.loader = loader; |
| this.emitter = emitter; |
| this.resources = resources; |
| this.seeds = seeds; |
| this.mode = mode; |
| |
| int numThreads = Math.min(MAX_THREADS, Runtime.getRuntime().availableProcessors()); |
| ex = Executors.newFixedThreadPool(numThreads); |
| writerService = Executors.newSingleThreadExecutor(); |
| } |
| |
| /** |
| * Blocks until all work has been finished. |
| */ |
| public void run() throws IOException { |
| for (Class<?> seed : seeds) { |
| processType("seeds", Type.getType(seed)); |
| } |
| for (Map.Entry<String, byte[]> entry : resources.entrySet()) { |
| writerService.submit(new EmitOneResource(entry.getKey(), entry.getValue())); |
| } |
| // Wait for all tasks to be completed |
| while (!inProcess.isEmpty()) { |
| try { |
| Future<?> task = inProcess.take(); |
| task.get(); |
| } catch (InterruptedException retry) { |
| } catch (ExecutionException e) { |
| e.getCause().printStackTrace(); |
| executionFailed = true; |
| } |
| } |
| emitter.close(); |
| } |
| |
| /** |
| * Write one type into the output. |
| */ |
| private void emit(final State state) { |
| inProcess.add(writerService.submit(new EmitOneType(state))); |
| } |
| |
| private boolean isExecutionFailed() { |
| return executionFailed; |
| } |
| |
| /** |
| * Look at constant values from the bytecode, processing referenced types. |
| */ |
| private Object processConstant(String sourceType, Object value) { |
| if (value instanceof Type) { |
| value = processType(sourceType, (Type) value); |
| } |
| return value; |
| } |
| |
| /** |
| * Process the type represented by the descriptor, possibly returning a |
| * rebased descriptor. |
| */ |
| private String processDescriptor(String sourceType, String desc) { |
| if (desc == null) { |
| return null; |
| } |
| return processType(sourceType, Type.getType(desc)).getDescriptor(); |
| } |
| |
| /** |
| * Process the type represented by the name, possibly returning a rebased |
| * name. |
| */ |
| private String processInternalName(String sourceType, String internalName) { |
| if (internalName == null) { |
| return null; |
| } |
| return processType(sourceType, Type.getObjectType(internalName)).getInternalName(); |
| } |
| |
| /** |
| * Produce a rebased method declaration, also visiting referenced types. |
| */ |
| private Method processMethod(String sourceType, String name, String desc) { |
| Method method = new Method(name, desc); |
| Type[] argumentTypes = method.getArgumentTypes(); |
| for (int i = 0, j = argumentTypes.length; i < j; i++) { |
| argumentTypes[i] = processType(sourceType, argumentTypes[i]); |
| } |
| method = new Method(name, processType(sourceType, method.getReturnType()), argumentTypes); |
| return method; |
| } |
| |
| /** |
| * Process a type, possibly returning a rebased type. |
| * |
| * @param sourceType TODO |
| */ |
| private Type processType(String sourceType, Type type) { |
| Type toReturn; |
| synchronized (seen) { |
| toReturn = seen.get(type); |
| if (toReturn != null) { |
| return toReturn; |
| } |
| toReturn = Type.getType(type.getDescriptor()); |
| seen.put(type, toReturn); |
| } |
| int sort = type.getSort(); |
| if (sort != Type.OBJECT && sort != Type.ARRAY) { |
| return toReturn; |
| } |
| if (sort == Type.ARRAY) { |
| processType(sourceType, type.getElementType()); |
| return toReturn; |
| } |
| assert type.getInternalName().charAt(0) != 'L'; |
| if (type.getInternalName().startsWith("java/") || type.getInternalName().startsWith("javax/")) { |
| return toReturn; |
| } |
| if (VERBOSE) { |
| System.out.println(sourceType + " -> " + type.getClassName()); |
| } |
| Future<State> future = ex.submit(new ProcessOneType(type)); |
| inProcess.add(future); |
| return toReturn; |
| } |
| } |