com.google.web.bindery.requestfactory.server.RequestFactoryJarExtractor Maven / Gradle / Ivy
/*
* 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.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 org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.Method;
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. com/example/Foo.class
)
*/
boolean exists(String resource);
/**
* Returns an InputStream to access the specified resource, or
* null
if no such resource exists.
*
* @param resource a resource name (e.g. com/example/Foo.class
)
*/
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.
*
* 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.ASM5);
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.ASM5, 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 {
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 {
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.ASM5);
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.ASM5, 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, boolean dintf) {
owner = processInternalName(sourceType, owner);
desc = processMethod(sourceType, name, desc).getDescriptor();
super.visitMethodInsn(opcode, owner, name, desc, dintf);
}
@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.ASM5, 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);
//
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, "",
"(Ljava/lang/String;)V", false);
// 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 {
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>> SEEDS =
new LinkedHashMap>>();
/**
* 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> aptClasses =
Collections.unmodifiableList(Arrays.> asList(RfValidator.class,
ValidationTool.class));
List> sharedClasses = Arrays.> asList(SHARED_CLASSES);
List> clientClasses = new ArrayList>();
clientClasses.addAll(sharedClasses);
clientClasses.add(UrlRequestTransport.class);
List> serverClasses = new ArrayList>();
serverClasses.addAll(Arrays.> asList(SERVER_CLASSES));
serverClasses.addAll(sharedClasses);
SEEDS.put("apt", aptClasses);
SEEDS.put("client", Collections.unmodifiableList(clientClasses));
SEEDS.put("server", Collections.unmodifiableList(serverClasses));
Set> all = new LinkedHashSet>();
for (List> value : SEEDS.values()) {
all.addAll(value);
}
SEEDS.put("all", Collections.unmodifiableList(new ArrayList>(all)));
for (String target : new ArrayList(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> testClasses =
Collections.unmodifiableList(Arrays.> 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() + " outfile.jar");
System.err.println("Valid targets:");
for (String target : SEEDS.keySet()) {
System.err.println(" " + target);
}
System.exit(1);
}
String target = args[0];
List> seeds = SEEDS.get(target);
if (seeds == null) {
System.err.println("Unknown target: " + target);
System.exit(1);
}
Map 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 createResources(List> seeds)
throws UnsupportedEncodingException, IOException {
Map 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 true
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> inProcess = new LinkedBlockingQueue>();
private final RequestFactoryJarExtractor.ErrorContext logger;
private final RequestFactoryJarExtractor.Loader loader;
private final Mode mode;
private final Map resources;
private final List> seeds;
private final Map seen = new ConcurrentHashMap();
private final Set sources = new ConcurrentSkipListSet();
private final ExecutorService writerService;
public RequestFactoryJarExtractor(Logger logger, RequestFactoryJarExtractor.Loader loader,
Emitter emitter, List> seeds, Map 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 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 future = ex.submit(new ProcessOneType(type));
inProcess.add(future);
return toReturn;
}
}