All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.web.bindery.requestfactory.server.RequestFactoryJarExtractor Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * 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.ClassAdapter;
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.MethodAdapter;
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. 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 implements AnnotationVisitor { private final String sourceType; private final AnnotationVisitor av; public AnnotationProcessor(String sourceType, AnnotationVisitor av) { 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 ClassAdapter { private State state; private String sourceType; public ClassProcessor(String sourceType, ClassVisitor cv, State state) { super(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 implements FieldVisitor { private final String sourceType; private final FieldVisitor fv; public FieldProcessor(String sourceType, FieldVisitor fv) { 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 MethodAdapter { private final String sourceType; public MethodProcessor(String sourceType, MethodVisitor mv) { super(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 ClassAdapter { public NativeMethodDefanger(ClassVisitor cv) { super(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"); // 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy