aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/lombok/core/AgentLauncher.java86
-rw-r--r--src/core/lombok/core/PostCompiler.java8
-rw-r--r--src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java107
-rw-r--r--src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java14
-rw-r--r--src/launch/lombok/launch/Agent.java115
-rw-r--r--src/launch/lombok/launch/AnnotationProcessor.java77
-rw-r--r--src/launch/lombok/launch/Main.java16
-rw-r--r--src/launch/lombok/launch/ShadowClassLoader.java341
8 files changed, 504 insertions, 260 deletions
diff --git a/src/core/lombok/core/AgentLauncher.java b/src/core/lombok/core/AgentLauncher.java
new file mode 100644
index 00000000..1d5ab3e6
--- /dev/null
+++ b/src/core/lombok/core/AgentLauncher.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2009-2014 The Project Lombok Authors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.core;
+
+import java.lang.instrument.Instrumentation;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class AgentLauncher {
+ public interface AgentLaunchable {
+ void runAgent(String agentArgs, Instrumentation instrumentation, boolean injected, Class<?> launchingContext) throws Exception;
+ }
+
+ public static void runAgents(String agentArgs, Instrumentation instrumentation, boolean injected, Class<?> launchingContext) throws Throwable {
+ for (AgentInfo info : AGENTS) {
+ try {
+ Class<?> agentClass = Class.forName(info.className());
+ AgentLaunchable agent = (AgentLaunchable) agentClass.newInstance();
+ agent.runAgent(agentArgs, instrumentation, injected, launchingContext);
+ } catch (Throwable t) {
+ info.problem(t, instrumentation);
+ }
+ }
+ }
+
+ private static final List<AgentInfo> AGENTS = Collections.unmodifiableList(Arrays.<AgentInfo>asList(
+ new EclipsePatcherInfo()
+ ));
+
+ private static abstract class AgentInfo {
+ abstract String className();
+
+ /**
+ * Called if an exception occurs while loading the agent represented by this AgentInfo object.
+ *
+ * @param t The throwable.
+ * @param instrumentation In case you want to take an alternative action.
+ */
+ void problem(Throwable t, Instrumentation instrumentation) throws Throwable {
+ if (t instanceof ClassNotFoundException) {
+ //That's okay - this lombok evidently is a version with support for something stripped out.
+ return;
+ }
+
+ if (t instanceof ClassCastException) {
+ throw new InternalError("Lombok bug. Class: " + className() + " is not an implementation of lombok.core.Agent");
+ }
+
+ if (t instanceof IllegalAccessError) {
+ throw new InternalError("Lombok bug. Class: " + className() + " is not public");
+ }
+
+ if (t instanceof InstantiationException) {
+ throw new InternalError("Lombok bug. Class: " + className() + " is not concrete or has no public no-args constructor");
+ }
+
+ throw t;
+ }
+ }
+
+ private static class EclipsePatcherInfo extends AgentInfo {
+ @Override String className() {
+ return "lombok.eclipse.agent.EclipsePatcher";
+ }
+ }
+}
diff --git a/src/core/lombok/core/PostCompiler.java b/src/core/lombok/core/PostCompiler.java
index 11091cb8..237867cb 100644
--- a/src/core/lombok/core/PostCompiler.java
+++ b/src/core/lombok/core/PostCompiler.java
@@ -24,6 +24,8 @@ package lombok.core;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.Collections;
import java.util.List;
@@ -55,10 +57,12 @@ public final class PostCompiler {
transformations = SpiLoadUtil.readAllFromIterator(SpiLoadUtil.findServices(PostCompilerTransformation.class, PostCompilerTransformation.class.getClassLoader()));
} catch (IOException e) {
transformations = Collections.emptyList();
- diagnostics.addWarning("Could not load post-compile transformers: " + e.getMessage());
+ StringWriter sw = new StringWriter();
+ e.printStackTrace(new PrintWriter(sw, true));
+ diagnostics.addWarning("Could not load post-compile transformers: " + e.getMessage() + "\n" + sw.toString());
}
}
-
+
public static OutputStream wrapOutputStream(final OutputStream originalStream, final String fileName, final DiagnosticsReceiver diagnostics) throws IOException {
if (System.getProperty("lombok.disablePostCompiler", null) != null) return originalStream;
return new ByteArrayOutputStream() {
diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java
new file mode 100644
index 00000000..eb633406
--- /dev/null
+++ b/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java
@@ -0,0 +1,107 @@
+package lombok.eclipse.agent;
+
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+import lombok.patcher.ClassRootFinder;
+import lombok.patcher.Hook;
+import lombok.patcher.MethodTarget;
+import lombok.patcher.ScriptManager;
+import lombok.patcher.StackRequest;
+import lombok.patcher.scripts.ScriptBuilder;
+
+public class EclipseLoaderPatcher {
+ public static boolean overrideLoadDecide(ClassLoader original, String name, boolean resolve) {
+ return name.startsWith("lombok.");
+ }
+
+ public static Class<?> overrideLoadResult(ClassLoader original, String name, boolean resolve) throws ClassNotFoundException {
+ try {
+ Field shadowLoaderField = original.getClass().getDeclaredField("lombok$shadowLoader");
+ ClassLoader shadowLoader = (ClassLoader) shadowLoaderField.get(original);
+ if (shadowLoader == null) {
+ String jarLoc = (String) original.getClass().getDeclaredField("lombok$location").get(null);
+ System.out.println(jarLoc);
+ JarFile jf = new JarFile(jarLoc);
+ InputStream in = null;
+ try {
+ ZipEntry entry = jf.getEntry("lombok/launch/ShadowClassLoader.class");
+ in = jf.getInputStream(entry);
+ byte[] bytes = new byte[65536];
+ int len = 0;
+ while (true) {
+ int r = in.read(bytes, len, bytes.length - len);
+ if (r == -1) break;
+ len += r;
+ if (len == bytes.length) throw new IllegalStateException("lombok.launch.ShadowClassLoader too large.");
+ }
+ in.close();
+ Class<?> shadowClassLoaderClass; {
+ Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
+ defineClassMethod.setAccessible(true);
+ shadowClassLoaderClass = (Class<?>) defineClassMethod.invoke(original, "lombok.launch.ShadowClassLoader", bytes, 0, len);
+ }
+ Constructor<?> constructor = shadowClassLoaderClass.getDeclaredConstructor(ClassLoader.class, String.class, String.class, String[].class);
+ constructor.setAccessible(true);
+ shadowLoader = (ClassLoader) constructor.newInstance(original, "lombok", jarLoc, new String[] {"lombok."});
+ shadowLoaderField.set(original, shadowLoader);
+ } finally {
+ if (in != null) in.close();
+ jf.close();
+ }
+ }
+
+ if (resolve) {
+ Method m = shadowLoader.getClass().getDeclaredMethod("loadClass", String.class, boolean.class);
+ m.setAccessible(true);
+ return (Class<?>) m.invoke(shadowLoader, name, true);
+ } else {
+ return shadowLoader.loadClass(name);
+ }
+ } catch (Exception ex) {
+ Throwable t = ex;
+ if (t instanceof InvocationTargetException) t = t.getCause();
+ if (t instanceof RuntimeException) throw (RuntimeException) t;
+ if (t instanceof Error) throw (Error) t;
+ throw new RuntimeException(t);
+ }
+ }
+
+ private static final String SELF_NAME = "lombok.eclipse.agent.EclipseLoaderPatcher";
+
+ public static void patchEquinoxLoaders(ScriptManager sm, Class<?> launchingContext) {
+ sm.addScript(ScriptBuilder.exitEarly()
+ .target(new MethodTarget("org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader", "loadClass",
+ "java.lang.Class", "java.lang.String", "boolean"))
+ .target(new MethodTarget("org.eclipse.osgi.framework.adapter.core.AbstractClassLoader", "loadClass",
+ "java.lang.Class", "java.lang.String", "boolean"))
+ .target(new MethodTarget("org.eclipse.osgi.internal.loader.ModuleClassLoader", "loadClass",
+ "java.lang.Class", "java.lang.String", "boolean"))
+ .decisionMethod(new Hook(SELF_NAME, "overrideLoadDecide", "boolean", "java.lang.ClassLoader", "java.lang.String", "boolean"))
+ .valueMethod(new Hook(SELF_NAME, "overrideLoadResult", "java.lang.Class", "java.lang.ClassLoader", "java.lang.String", "boolean"))
+ .transplant()
+ .request(StackRequest.THIS, StackRequest.PARAM1, StackRequest.PARAM2).build());
+
+ sm.addScript(ScriptBuilder.addField().setPublic()
+ .fieldType("Ljava/lang/ClassLoader;")
+ .fieldName("lombok$shadowLoader")
+ .targetClass("org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader")
+ .targetClass("org.eclipse.osgi.framework.adapter.core.AbstractClassLoader")
+ .targetClass("org.eclipse.osgi.internal.loader.ModuleClassLoader")
+ .build());
+
+ sm.addScript(ScriptBuilder.addField().setPublic().setStatic().setFinal()
+ .fieldType("Ljava/lang/String;")
+ .fieldName("lombok$location")
+ .targetClass("org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader")
+ .targetClass("org.eclipse.osgi.framework.adapter.core.AbstractClassLoader")
+ .targetClass("org.eclipse.osgi.internal.loader.ModuleClassLoader")
+ .value(ClassRootFinder.findClassRootOfClass(launchingContext))
+ .build());
+ }
+}
diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java
index 3fce9626..96f253f2 100644
--- a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java
+++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java
@@ -28,12 +28,13 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import lombok.core.AgentLauncher;
+
import lombok.patcher.Hook;
import lombok.patcher.MethodTarget;
import lombok.patcher.ScriptManager;
import lombok.patcher.StackRequest;
import lombok.patcher.TargetMatcher;
-import lombok.patcher.equinox.EquinoxClassLoader;
import lombok.patcher.scripts.ScriptBuilder;
/**
@@ -43,12 +44,12 @@ import lombok.patcher.scripts.ScriptBuilder;
* classes in this package for more information about which classes are transformed and how they are
* transformed.
*/
-public class EclipsePatcher {
+public class EclipsePatcher implements AgentLauncher.AgentLaunchable {
// At some point I'd like the agent to be capable of auto-detecting if its on eclipse or on ecj. This class is a sure sign we're not in ecj but in eclipse. -ReinierZ
@SuppressWarnings("unused")
private static final String ECLIPSE_SIGNATURE_CLASS = "org/eclipse/core/runtime/adaptor/EclipseStarter";
- public void runAgent(String agentArgs, Instrumentation instrumentation, boolean injected) throws Exception {
+ @Override public void runAgent(String agentArgs, Instrumentation instrumentation, boolean injected, Class<?> launchingContext) throws Exception {
String[] args = agentArgs == null ? new String[0] : agentArgs.split(":");
boolean forceEcj = false;
boolean forceEclipse = false;
@@ -67,15 +68,14 @@ public class EclipsePatcher {
else if (forceEclipse) ecj = false;
else ecj = injected;
- registerPatchScripts(instrumentation, injected, ecj);
+ registerPatchScripts(instrumentation, injected, ecj, launchingContext);
}
- private static void registerPatchScripts(Instrumentation instrumentation, boolean reloadExistingClasses, boolean ecjOnly) {
+ private static void registerPatchScripts(Instrumentation instrumentation, boolean reloadExistingClasses, boolean ecjOnly, Class<?> launchingContext) {
ScriptManager sm = new ScriptManager();
sm.registerTransformer(instrumentation);
if (!ecjOnly) {
- EquinoxClassLoader.addPrefix("lombok.");
- EquinoxClassLoader.registerScripts(sm);
+ EclipseLoaderPatcher.patchEquinoxLoaders(sm, launchingContext);
}
if (!ecjOnly) {
diff --git a/src/launch/lombok/launch/Agent.java b/src/launch/lombok/launch/Agent.java
index fe64e1b6..7989e51f 100644
--- a/src/launch/lombok/launch/Agent.java
+++ b/src/launch/lombok/launch/Agent.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2014 The Project Lombok Authors.
+ * Copyright (C) 2014 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -21,120 +21,27 @@
*/
package lombok.launch;
-import java.lang.instrument.ClassFileTransformer;
-import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.InvocationTargetException;
-import java.security.ProtectionDomain;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import javax.swing.JOptionPane;
-import javax.swing.SwingUtilities;
+import java.lang.reflect.Method;
final class Agent {
public static void agentmain(String agentArgs, Instrumentation instrumentation) throws Throwable {
- runAgents(agentArgs, instrumentation, true);
+ runLauncher(agentArgs, instrumentation, true);
}
public static void premain(String agentArgs, Instrumentation instrumentation) throws Throwable {
- runAgents(agentArgs, instrumentation, false);
+ runLauncher(agentArgs, instrumentation, false);
}
- private static final List<AgentInfo> AGENTS = Collections.unmodifiableList(Arrays.asList(
- new NetbeansPatcherInfo(),
- new EclipsePatcherInfo()
- ));
-
- private static void runAgents(String agentArgs, Instrumentation instrumentation, boolean injected) throws Throwable {
+ private static void runLauncher(String agentArgs, Instrumentation instrumentation, boolean injected) throws Throwable {
ClassLoader cl = Main.createShadowClassLoader();
-
- for (AgentInfo info : AGENTS) {
- try {
- Class<?> agentClass = cl.loadClass(info.className());
- Object agent = agentClass.newInstance();
- agentClass.getMethod("runAgent", String.class, Instrumentation.class, boolean.class).invoke(agent, agentArgs, instrumentation, injected);
- } catch (Throwable t) {
- if (t instanceof InvocationTargetException) t = t.getCause();
- info.problem(t, instrumentation);
- }
- }
- }
-
- private static abstract class AgentInfo {
- abstract String className();
-
- /**
- * Called if an exception occurs while loading the agent represented by this AgentInfo object.
- *
- * @param t The throwable.
- * @param instrumentation In case you want to take an alternative action.
- */
- void problem(Throwable t, Instrumentation instrumentation) throws Throwable {
- if (t instanceof ClassNotFoundException) {
- //That's okay - this lombok evidently is a version with support for something stripped out.
- return;
- }
-
- if (t instanceof ClassCastException) {
- throw new InternalError("Lombok bug. Class: " + className() + " is not an implementation of lombok.core.Agent");
- }
-
- if (t instanceof IllegalAccessError) {
- throw new InternalError("Lombok bug. Class: " + className() + " is not public");
- }
-
- if (t instanceof InstantiationException) {
- throw new InternalError("Lombok bug. Class: " + className() + " is not concrete or has no public no-args constructor");
- }
-
- throw t;
- }
- }
-
- private static class NetbeansPatcherInfo extends AgentInfo {
- @Override String className() {
- return "lombok.netbeans.agent.NetbeansPatcher";
- }
-
- @Override void problem(Throwable in, Instrumentation instrumentation) throws Throwable {
- try {
- super.problem(in, instrumentation);
- } catch (InternalError ie) {
- throw ie;
- } catch (Throwable t) {
- final String error;
-
- if (t instanceof UnsupportedClassVersionError) {
- error = "Lombok only works on netbeans if you start netbeans using a 1.6 or higher JVM.\n" +
- "Change your platform's default JVM, or edit etc/netbeans.conf\n" +
- "and explicitly tell netbeans your 1.6 JVM's location.";
- } else {
- error = "Lombok disabled due to error: " + t;
- }
-
- instrumentation.addTransformer(new ClassFileTransformer() {
- @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
- if ("org/netbeans/modules/java/source/parsing/JavacParser".equals(className)) {
- //If that class gets loaded, this is definitely a netbeans(-esque) environment, and thus we SHOULD tell the user that lombok is not in fact loaded.
- SwingUtilities.invokeLater(new Runnable() {
- @Override public void run() {
- JOptionPane.showMessageDialog(null, error, "Lombok Disabled", JOptionPane.ERROR_MESSAGE);
- }
- });
- }
-
- return null;
- }
- });
- }
- }
- }
-
- private static class EclipsePatcherInfo extends AgentInfo {
- @Override String className() {
- return "lombok.eclipse.agent.EclipsePatcher";
+ try {
+ Class<?> c = cl.loadClass("lombok.core.AgentLauncher");
+ Method m = c.getDeclaredMethod("runAgents", String.class, Instrumentation.class, boolean.class, Class.class);
+ m.invoke(null, agentArgs, instrumentation, injected, Agent.class);
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
}
}
}
diff --git a/src/launch/lombok/launch/AnnotationProcessor.java b/src/launch/lombok/launch/AnnotationProcessor.java
new file mode 100644
index 00000000..35c26b7c
--- /dev/null
+++ b/src/launch/lombok/launch/AnnotationProcessor.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2014 The Project Lombok Authors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.launch;
+
+import java.util.Set;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Completion;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+
+class AnnotationProcessorHider {
+ public static class AnnotationProcessor extends AbstractProcessor {
+ private final AbstractProcessor instance = createWrappedInstance();
+
+ @Override public Set<String> getSupportedOptions() {
+ return instance.getSupportedOptions();
+ }
+
+ @Override public Set<String> getSupportedAnnotationTypes() {
+ return instance.getSupportedAnnotationTypes();
+ }
+
+ @Override public SourceVersion getSupportedSourceVersion() {
+ return instance.getSupportedSourceVersion();
+ }
+
+ @Override public void init(ProcessingEnvironment processingEnv) {
+ instance.init(processingEnv);
+ super.init(processingEnv);
+ }
+
+ @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ return instance.process(annotations, roundEnv);
+ }
+
+ @Override public Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText) {
+ return instance.getCompletions(element, annotation, member, userText);
+ }
+
+ private static AbstractProcessor createWrappedInstance() {
+ ClassLoader cl = Main.createShadowClassLoader();
+ try {
+ Class<?> mc = cl.loadClass("lombok.core.AnnotationProcessor");
+ return (AbstractProcessor) mc.newInstance();
+ } catch (Throwable t) {
+ if (t instanceof Error) throw (Error) t;
+ if (t instanceof RuntimeException) throw (RuntimeException) t;
+ throw new RuntimeException(t);
+ }
+ }
+ }
+}
diff --git a/src/launch/lombok/launch/Main.java b/src/launch/lombok/launch/Main.java
index f4b6a788..63d97d48 100644
--- a/src/launch/lombok/launch/Main.java
+++ b/src/launch/lombok/launch/Main.java
@@ -21,23 +21,11 @@
*/
package lombok.launch;
-import java.io.File;
import java.lang.reflect.InvocationTargetException;
class Main {
- public static ClassLoader createShadowClassLoader() {
- ShadowClassLoader cl = new ShadowClassLoader(Main.class.getClassLoader());
- String scl = System.getProperty("shadow.classpath");
- if (scl == null || scl.isEmpty()) return cl;
- for (String part : scl.split("\\s*" + (File.pathSeparatorChar == ';' ? ";" : ":") + "\\s*")) {
- if (part.endsWith("/*") || part.endsWith(File.separator + "*")) {
- cl.addPriorityJarDir(part.substring(0, part.length() - 2));
- } else {
- cl.addPriorityClasspathEntry(part);
- }
- }
-
- return cl;
+ static ClassLoader createShadowClassLoader() {
+ return new ShadowClassLoader(Main.class.getClassLoader(), "lombok");
}
public static void main(String[] args) throws Throwable {
diff --git a/src/launch/lombok/launch/ShadowClassLoader.java b/src/launch/lombok/launch/ShadowClassLoader.java
index 66749b94..ab36d6b4 100644
--- a/src/launch/lombok/launch/ShadowClassLoader.java
+++ b/src/launch/lombok/launch/ShadowClassLoader.java
@@ -22,8 +22,6 @@
package lombok.launch;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
@@ -32,15 +30,15 @@ import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
-import java.util.Iterator;
import java.util.List;
+import java.util.Vector;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* The shadow classloader serves to completely hide almost all classes in a given jar file by using a different file ending.
*
- * Classes loaded by the shadowloader use ".SCL.lombok" instead of ".class".
+ * Classes loaded by the shadowloader use ".SCL.<em>sclSuffix</em>" instead of ".class".
*
* In addition, the shadowloader will pick up an alternate (priority) classpath, using normal class files, from the system property {@code shadow.classpath}.
*
@@ -51,175 +49,252 @@ import java.util.jar.JarFile;
* considerably help debugging, as you can now rely on the IDE's built-in auto-recompile features instead of having to run a full build everytime, and it should help
* with hot code replace and the like.
* </ul>
+ *
+ * Implementation note: {@code lombok.patcher} <em>relies</em> on this class having no dependencies on any other class except the JVM boot class, notably
+ * including any other classes in this package, <strong>including</strong> inner classes of this very class. So, don't write closures, anonymous inner class literals,
+ * enums, or anything else that could cause the compilation of this file to produce more than 1 class file.
*/
class ShadowClassLoader extends ClassLoader {
- private final ClassLoader source;
- private final List<File> priority = new ArrayList<File>();
+ private static final String SELF_NAME = "lombok/launch/ShadowClassLoader.class";
+ private final String SELF_BASE;
+ private final int SELF_BASE_LENGTH;
- private static final int INITIAL_BUFFER_SIZE = 65536;
- private static final int MAX_BUFFER_SIZE = 1048576;
+ private final List<File> override = new ArrayList<File>();
+ private final String sclSuffix;
+ private final List<String> parentExclusion = new ArrayList<String>();
- public ShadowClassLoader(ClassLoader source) {
- super(source);
- this.source = source == null ? ClassLoader.getSystemClassLoader() : source;
+ ShadowClassLoader(ClassLoader source, String sclSuffix) {
+ this(source, sclSuffix, null);
}
- private static final ThreadLocal<byte[]> BUFFERS = new ThreadLocal<byte[]>() {
- @Override protected byte[] initialValue() {
- return new byte[INITIAL_BUFFER_SIZE];
+ /**
+ * @param source The 'parent' classloader.
+ * @param sclSuffix The suffix of the shadowed class files in our own jar. For example, if this is {@code lombok}, then the class files in your jar should be {@code foo/Bar.SCL.lombok} and not {@code foo/Bar.class}.
+ * @param selfBase The (preferably absolute) path to our own jar. This jar will be searched for class/SCL.sclSuffix files.
+ * @param parentExclusion For example {@code "lombok."}; upon invocation of loadClass of this loader, the parent loader ({@code source}) will NOT be invoked if the class to be loaded begins with anything in the parent exclusion list. No exclusion is applied for getResource(s).
+ */
+ ShadowClassLoader(ClassLoader source, String sclSuffix, String selfBase, String... parentExclusion) {
+ super(source);
+ this.sclSuffix = sclSuffix;
+ if (parentExclusion != null) for (String pe : parentExclusion) {
+ pe = pe.replace(".", "/");
+ if (!pe.endsWith("/")) pe = pe + "/";
+ this.parentExclusion.add(pe);
}
- };
-
- public Enumeration<URL> getResources(String name) throws IOException {
- List<URL> prioritized = null;
- for (File ce : priority) {
- if (ce.isDirectory()) {
- File f = new File(ce, name);
- if (f.isFile() && f.canRead()) {
- if (prioritized == null) prioritized = new ArrayList<URL>();
- prioritized.add(f.toURI().toURL());
+
+ if (selfBase != null) {
+ SELF_BASE = selfBase;
+ SELF_BASE_LENGTH = selfBase.length();
+ } else {
+ String sclClassUrl = ShadowClassLoader.class.getResource("ShadowClassLoader.class").toString();
+ if (!sclClassUrl.endsWith(SELF_NAME)) throw new InternalError("ShadowLoader can't find itself.");
+ SELF_BASE_LENGTH = sclClassUrl.length() - SELF_NAME.length();
+ SELF_BASE = sclClassUrl.substring(0, SELF_BASE_LENGTH);
+ }
+
+ String scl = System.getProperty("shadow.override." + sclSuffix);
+ if (scl != null && !scl.isEmpty()) {
+ for (String part : scl.split("\\s*" + (File.pathSeparatorChar == ';' ? ";" : ":") + "\\s*")) {
+ if (part.endsWith("/*") || part.endsWith(File.separator + "*")) {
+ addOverrideJarDir(part.substring(0, part.length() - 2));
+ } else {
+ addOverrideClasspathEntry(part);
}
- } else if (ce.isFile() && ce.canRead()) {
- JarFile jf;
- JarEntry entry;
-
- try {
- jf = new JarFile(ce);
- entry = jf.getJarEntry(name);
- } catch (IOException ignore) {
- continue;
+ }
+ }
+ }
+
+ private URL getResourceFromLocation(String name, String altName, File location) {
+ if (location.isDirectory()) {
+ try {
+ if (altName != null) {
+ File f = new File(location, altName);
+ if (f.isFile() && f.canRead()) return f.toURI().toURL();
}
- if (entry != null) try {
- // TODO: This needs work, this feels a bit hacky. Is there an API way to create these URLs?
- URL url = new URI("jar:" + ce.toURI().toString() + "!/" + name).toURL();
- if (prioritized == null) prioritized = new ArrayList<URL>();
- prioritized.add(url);
- } catch (URISyntaxException ignore) {}
+ File f = new File(location, name);
+ if (f.isFile() && f.canRead()) return f.toURI().toURL();
+ return null;
+ } catch (MalformedURLException e) {
+ return null;
}
}
- if (prioritized == null) return super.getResources(name);
- final Iterator<URL> prim = prioritized.iterator();
- final Enumeration<URL> sec = super.getResources(name);
- return new Enumeration<URL>() {
- @Override public boolean hasMoreElements() {
- if (prim.hasNext()) return true;
- return sec.hasMoreElements();
+ if (!location.isFile() || !location.canRead()) return null;
+
+ JarFile jf = null;
+ JarEntry entry = null;
+
+ try {
+ jf = new JarFile(location);
+ if (altName != null) entry = jf.getJarEntry(altName);
+ if (entry == null) entry = jf.getJarEntry(name);
+ if (entry == null) return null;
+ return new URI("jar:file:" + location.getAbsolutePath() + "!/" + entry.getName()).toURL();
+ } catch (IOException e) {
+ return null;
+ } catch (URISyntaxException e) {
+ return null;
+ } finally {
+ if (jf != null) try {
+ jf.close();
+ } catch (Exception ignore) {}
+ }
+ }
+
+ private boolean inOwnBase(URL item, String name) {
+ if (item == null) return false;
+ String itemString = item.toString();
+ return (itemString.length() == SELF_BASE_LENGTH + name.length()) && SELF_BASE.regionMatches(0, itemString, 0, SELF_BASE_LENGTH);
+ }
+
+ public Enumeration<URL> getResources(String name) throws IOException {
+ String altName = null;
+ if (name.endsWith(".class")) altName = name.substring(0, name.length() - 6) + ".SCL." + sclSuffix;
+ List<URL> overrides = null;
+ for (File ce : override) {
+ URL url = getResourceFromLocation(name, altName, ce);
+ if (url != null) {
+ if (overrides == null) overrides = new ArrayList<URL>();
+ overrides.add(url);
}
-
- @Override public URL nextElement() {
- if (prim.hasNext()) return prim.next();
- return sec.nextElement();
+ }
+
+ // Vector????!!???WTFBBQ??? Yes, we need one:
+ // * We can NOT make inner classes here (this class is loaded with special voodoo magic in eclipse, as a one off, it's not a full loader.
+ // * We need to return an enumeration.
+ // * We can't make one on the fly.
+ // * ArrayList can't make these.
+
+ Vector<URL> vector = new Vector<URL>();
+ if (overrides != null) vector.addAll(overrides);
+ Enumeration<URL> sec = super.getResources(name);
+ while (sec.hasMoreElements()) {
+ URL item = sec.nextElement();
+ if (override.isEmpty() || !inOwnBase(item, name)) vector.add(item);
+ }
+
+ if (altName != null) {
+ Enumeration<URL> tern = super.getResources(altName);
+ while (tern.hasMoreElements()) {
+ URL item = tern.nextElement();
+ if (override.isEmpty() || !inOwnBase(item, altName)) vector.add(item);
}
- };
+ }
+
+ return vector.elements();
}
@Override public URL getResource(String name) {
- for (File ce : priority) {
- if (ce.isDirectory()) {
- File f = new File(ce, name);
- if (f.isFile() && f.canRead()) try {
- return f.toURI().toURL();
- } catch (MalformedURLException ignore) {}
- } else if (ce.isFile() && ce.canRead()) {
- JarFile jf;
- JarEntry entry;
-
+ return getResource_(name, false);
+ }
+
+ private URL getResource_(String name, boolean noSuper) {
+ String altName = null;
+ if (name.endsWith(".class")) altName = name.substring(0, name.length() - 6) + ".SCL." + sclSuffix;
+ for (File ce : override) {
+ URL url = getResourceFromLocation(name, altName, ce);
+ if (url != null) return url;
+ }
+
+ if (!override.isEmpty()) {
+ if (noSuper) return null;
+ if (altName != null) {
try {
- jf = new JarFile(ce);
- entry = jf.getJarEntry(name);
- } catch (IOException ignore) {
- continue;
- }
-
- if (entry != null) try {
- // TODO: This needs work, this feels a bit hacky. Is there an API way to create these URLs?
- URL url = new URI("jar:" + ce.toURI().toString() + "!/" + name).toURL();
- return url;
- } catch (URISyntaxException ignore) {
- } catch (MalformedURLException ignore) {
- }
+ URL res = getResourceSkippingSelf(altName);
+ if (res != null) return res;
+ } catch (IOException ignore) {}
+ }
+
+ try {
+ return getResourceSkippingSelf(name);
+ } catch (IOException e) {
+ return null;
}
}
- return super.getResource(name);
+ if (altName != null) {
+ URL res = super.getResource(altName);
+ if (res != null && (!noSuper || inOwnBase(res, altName))) return res;
+ }
+
+ URL res = super.getResource(name);
+ if (res != null && (!noSuper || inOwnBase(res, name))) return res;
+ return null;
+ }
+
+ private boolean exclusionListMatch(String name) {
+ for (String pe : parentExclusion) {
+ if (name.startsWith(pe)) return true;
+ }
+ return false;
+ }
+
+ private URL getResourceSkippingSelf(String name) throws IOException {
+ URL candidate = super.getResource(name);
+ if (candidate == null) return null;
+ if (!inOwnBase(candidate, name)) return candidate;
+
+
+ Enumeration<URL> en = super.getResources(name);
+ while (en.hasMoreElements()) {
+ candidate = en.nextElement();
+ if (!inOwnBase(candidate, name)) return candidate;
+ }
+
+ return null;
}
- @Override protected Class<?> findClass(String name) throws ClassNotFoundException {
- String rawName, sclName; {
- String binName = name.replace(".", "/");
- rawName = binName.concat(".class");
- sclName = binName.concat(".SCL.lombok");
+ @Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ {
+ Class<?> alreadyLoaded = findLoadedClass(name);
+ if (alreadyLoaded != null) return alreadyLoaded;
+ }
+
+ String fileNameOfClass = name.replace(".", "/") + ".class";
+ URL res = getResource_(fileNameOfClass, true);
+ if (res == null) {
+ if (!exclusionListMatch(fileNameOfClass)) return super.loadClass(name, resolve);
+ throw new ClassNotFoundException(name);
}
- InputStream in = null;
byte[] b;
int p = 0;
-
- try { try {
- for (File ce : priority) {
- if (ce.isDirectory()) {
- File f = new File(ce, rawName);
- if (f.isFile() && f.canRead()) {
- in = new FileInputStream(f);
- break;
- }
- } else if (ce.isFile() && ce.canRead()) {
- JarFile jf;
- JarEntry entry;
-
- try {
- jf = new JarFile(ce);
- entry = jf.getJarEntry(rawName);
- } catch (IOException ignore) {
- continue;
- }
-
- if (entry != null) {
- in = jf.getInputStream(entry);
- break;
- }
- }
- }
-
- if (in == null) in = source.getResourceAsStream(sclName);
- if (in == null) in = source.getResourceAsStream(rawName);
- if (in == null) throw new ClassNotFoundException(name);
+ try {
+ InputStream in = res.openStream();
- b = BUFFERS.get();
- while (true) {
- int r = in.read(b, p, b.length - p);
- if (r == -1) break;
- p += r;
- if (p == b.length) {
- byte[] nb = new byte[b.length * 2];
- System.arraycopy(b, 0, nb, 0, p);
- b = nb;
- BUFFERS.set(nb);
+ try {
+ b = new byte[65536];
+ while (true) {
+ int r = in.read(b, p, b.length - p);
+ if (r == -1) break;
+ p += r;
+ if (p == b.length) {
+ byte[] nb = new byte[b.length * 2];
+ System.arraycopy(b, 0, nb, 0, p);
+ b = nb;
+ }
}
+ } finally {
+ in.close();
}
- } finally {
- if (in != null) in.close();
- }} catch (IOException e) {
+ } catch (IOException e) {
throw new ClassNotFoundException("I/O exception reading class " + name, e);
}
Class<?> c = defineClass(name, b, 0, p);
- if (b.length > MAX_BUFFER_SIZE) BUFFERS.set(new byte[INITIAL_BUFFER_SIZE]);
+ if (resolve) resolveClass(c);
return c;
}
- public void addPriorityJarDir(String dir) {
+ public void addOverrideJarDir(String dir) {
File f = new File(dir);
- for (File j : f.listFiles(new FilenameFilter() {
- @Override public boolean accept(File dir, String name) {
- return name.toLowerCase().endsWith(".jar");
- }
- })) if (j.canRead() && j.isFile()) priority.add(j);
+ for (File j : f.listFiles()) {
+ if (j.getName().toLowerCase().endsWith(".jar") && j.canRead() && j.isFile()) override.add(j);
+ }
}
- public void addPriorityClasspathEntry(String entry) {
- priority.add(new File(entry));
+ public void addOverrideClasspathEntry(String entry) {
+ override.add(new File(entry));
}
}