aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorReinier Zwitserloot <reinier@zwitserloot.com>2014-12-04 02:54:22 +0100
committerReinier Zwitserloot <reinier@zwitserloot.com>2014-12-04 02:54:22 +0100
commit2e78a2e03f8fdba4d3d468b2900d30f7e7317641 (patch)
tree617a4f36b044fa7b1f87cd2e525bd7308b6f0163 /src
parent815f7d0fe82df761c038907043abd1a33d491f5d (diff)
parent9e7c75a0fef387c173af289626aa04d5c2942710 (diff)
downloadlombok-2e78a2e03f8fdba4d3d468b2900d30f7e7317641.tar.gz
lombok-2e78a2e03f8fdba4d3d468b2900d30f7e7317641.tar.bz2
lombok-2e78a2e03f8fdba4d3d468b2900d30f7e7317641.zip
Merge branch 'shadowLauncher'
Diffstat (limited to 'src')
-rw-r--r--src/core/lombok/core/AgentLauncher.java (renamed from src/core/lombok/core/Agent.java)74
-rw-r--r--src/core/lombok/core/Main.java1
-rw-r--r--src/core/lombok/core/PostCompiler.java8
-rw-r--r--src/core/lombok/core/Version.java2
-rw-r--r--src/core/lombok/eclipse/handlers/HandlePrintAST.java2
-rw-r--r--src/core/lombok/javac/handlers/HandlePrintAST.java2
-rw-r--r--src/delombok/lombok/delombok/DelombokApp.java16
-rw-r--r--src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java106
-rw-r--r--src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java16
-rw-r--r--src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java3
-rw-r--r--src/installer/lombok/installer/InstallerGUI.java5
-rw-r--r--src/installer/lombok/installer/eclipse/EclipseLocation.java5
-rw-r--r--src/launch/lombok/launch/Agent.java47
-rw-r--r--src/launch/lombok/launch/AnnotationProcessor.java77
-rw-r--r--src/launch/lombok/launch/Main.java40
-rw-r--r--src/launch/lombok/launch/ShadowClassLoader.java399
16 files changed, 714 insertions, 89 deletions
diff --git a/src/core/lombok/core/Agent.java b/src/core/lombok/core/AgentLauncher.java
index 49c03bf3..1d5ab3e6 100644
--- a/src/core/lombok/core/Agent.java
+++ b/src/core/lombok/core/AgentLauncher.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Project Lombok Authors.
+ * 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
@@ -21,45 +21,32 @@
*/
package lombok.core;
-import java.lang.instrument.ClassFileTransformer;
-import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
-import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import javax.swing.JOptionPane;
-import javax.swing.SwingUtilities;
-
-public abstract class Agent {
- protected abstract void runAgent(String agentArgs, Instrumentation instrumentation, boolean injected) throws Exception;
-
- public static void agentmain(String agentArgs, Instrumentation instrumentation) throws Throwable {
- runAgents(agentArgs, instrumentation, true);
+public class AgentLauncher {
+ public interface AgentLaunchable {
+ void runAgent(String agentArgs, Instrumentation instrumentation, boolean injected, Class<?> launchingContext) throws Exception;
}
- public static void premain(String agentArgs, Instrumentation instrumentation) throws Throwable {
- runAgents(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 {
+ 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());
- Agent agent = (Agent) agentClass.newInstance();
- agent.runAgent(agentArgs, instrumentation, injected);
+ 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();
@@ -91,45 +78,6 @@ public abstract class Agent {
}
}
- 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";
diff --git a/src/core/lombok/core/Main.java b/src/core/lombok/core/Main.java
index d62fe3e4..0856d3b3 100644
--- a/src/core/lombok/core/Main.java
+++ b/src/core/lombok/core/Main.java
@@ -38,6 +38,7 @@ public class Main {
));
public static void main(String[] args) throws IOException {
+ Thread.currentThread().setContextClassLoader(Main.class.getClassLoader());
int err = new Main(SpiLoadUtil.readAllFromIterator(
SpiLoadUtil.findServices(LombokApp.class)), Arrays.asList(args)).go();
System.exit(err);
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/core/lombok/core/Version.java b/src/core/lombok/core/Version.java
index 9f810525..02bc1af0 100644
--- a/src/core/lombok/core/Version.java
+++ b/src/core/lombok/core/Version.java
@@ -30,7 +30,7 @@ public class Version {
// ** CAREFUL ** - this class must always compile with 0 dependencies (it must not refer to any other sources or libraries).
// Note: In 'X.Y.Z', if Z is odd, its a snapshot build built from the repository, so many different 0.10.3 versions can exist, for example.
// Official builds always end in an even number. (Since 0.10.2).
- private static final String VERSION = "1.14.9";
+ private static final String VERSION = "1.14.9.shadow";
private static final String RELEASE_NAME = "Edgy Guinea Pig";
// private static final String RELEASE_NAME = "Branching Cobra";
diff --git a/src/core/lombok/eclipse/handlers/HandlePrintAST.java b/src/core/lombok/eclipse/handlers/HandlePrintAST.java
index 0b61bc4d..234e29b8 100644
--- a/src/core/lombok/eclipse/handlers/HandlePrintAST.java
+++ b/src/core/lombok/eclipse/handlers/HandlePrintAST.java
@@ -59,7 +59,7 @@ public class HandlePrintAST extends EclipseAnnotationHandler<PrintAST> {
try {
stream.close();
} catch (Exception e) {
- Lombok.sneakyThrow(e);
+ throw Lombok.sneakyThrow(e);
}
}
}
diff --git a/src/core/lombok/javac/handlers/HandlePrintAST.java b/src/core/lombok/javac/handlers/HandlePrintAST.java
index 9a52b9d9..0826d1d1 100644
--- a/src/core/lombok/javac/handlers/HandlePrintAST.java
+++ b/src/core/lombok/javac/handlers/HandlePrintAST.java
@@ -59,7 +59,7 @@ public class HandlePrintAST extends JavacAnnotationHandler<PrintAST> {
try {
stream.close();
} catch (Exception e) {
- Lombok.sneakyThrow(e);
+ throw Lombok.sneakyThrow(e);
}
}
}
diff --git a/src/delombok/lombok/delombok/DelombokApp.java b/src/delombok/lombok/delombok/DelombokApp.java
index 276bd7de..aa753fc8 100644
--- a/src/delombok/lombok/delombok/DelombokApp.java
+++ b/src/delombok/lombok/delombok/DelombokApp.java
@@ -88,7 +88,7 @@ public class DelombokApp extends LombokApp {
// Since we only read from it, not closing it should not be a problem.
@SuppressWarnings({"resource", "all"}) final JarFile toolsJarFile = new JarFile(toolsJar);
- ClassLoader loader = new ClassLoader() {
+ ClassLoader loader = new ClassLoader(DelombokApp.class.getClassLoader()) {
private Class<?> loadStreamAsClass(String name, boolean resolve, InputStream in) throws ClassNotFoundException {
try {
try {
@@ -107,16 +107,24 @@ public class DelombokApp extends LombokApp {
} finally {
in.close();
}
- } catch (IOException e2) {
+ } catch (Exception e2) {
throw new ClassNotFoundException(name, e2);
}
}
@Override protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
- String rawName = name.replace(".", "/") + ".class";
+ String rawName, altName; {
+ String binName = name.replace(".", "/");
+ rawName = binName + ".class";
+ altName = binName + ".SCL.lombok";
+ }
JarEntry entry = toolsJarFile.getJarEntry(rawName);
if (entry == null) {
- if (name.startsWith("lombok.")) return loadStreamAsClass(name, resolve, super.getResourceAsStream(rawName));
+ if (name.startsWith("lombok.")) {
+ InputStream res = getParent().getResourceAsStream(rawName);
+ if (res == null) res = getParent().getResourceAsStream(altName);
+ return loadStreamAsClass(name, resolve, res);
+ }
return super.loadClass(name, resolve);
}
diff --git a/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java
new file mode 100644
index 00000000..0d21c212
--- /dev/null
+++ b/src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java
@@ -0,0 +1,106 @@
+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().getField("lombok$shadowLoader");
+ ClassLoader shadowLoader = (ClassLoader) shadowLoaderField.get(original);
+ if (shadowLoader == null) {
+ String jarLoc = (String) original.getClass().getField("lombok$location").get(null);
+ 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 e14d1367..96f253f2 100644
--- a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java
+++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java
@@ -28,13 +28,13 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import lombok.core.Agent;
+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;
/**
@@ -44,13 +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 extends Agent {
+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";
- @Override
- 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;
@@ -69,15 +68,14 @@ public class EclipsePatcher extends Agent {
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/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java
index 8eec27fb..44adb333 100644
--- a/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java
+++ b/src/eclipseAgent/lombok/eclipse/agent/PatchExtensionMethod.java
@@ -224,7 +224,8 @@ public class PatchExtensionMethod {
if (methodCall.arguments != null) arguments.addAll(Arrays.asList(methodCall.arguments));
List<TypeBinding> argumentTypes = new ArrayList<TypeBinding>();
for (Expression argument : arguments) {
- argumentTypes.add(argument.resolvedType);
+ if (argument.resolvedType != null) argumentTypes.add(argument.resolvedType);
+ // TODO: Instead of just skipping nulls entirely, there is probably a 'unresolved type' placeholder. THAT is what we ought to be adding here!
}
MethodBinding fixedBinding = scope.getMethod(extensionMethod.declaringClass, methodCall.selector, argumentTypes.toArray(new TypeBinding[0]), methodCall);
if (fixedBinding instanceof ProblemMethodBinding) {
diff --git a/src/installer/lombok/installer/InstallerGUI.java b/src/installer/lombok/installer/InstallerGUI.java
index 5717948a..41832e5d 100644
--- a/src/installer/lombok/installer/InstallerGUI.java
+++ b/src/installer/lombok/installer/InstallerGUI.java
@@ -820,9 +820,8 @@ public class InstallerGUI {
private static final String HOW_I_WORK_EXPLANATION =
"<html><h2>Eclipse</h2><ol>" +
"<li>First, I copy myself (lombok.jar) to your Eclipse install directory.</li>" +
- "<li>Then, I edit the <i>eclipse.ini</i> file to add the following two entries:<br>" +
- "<pre>-Xbootclasspath/a:lombok.jar<br>" +
- "-javaagent:lombok.jar</pre></li></ol>" +
+ "<li>Then, I edit the <i>eclipse.ini</i> file to add the following entry:<br>" +
+ "<pre>-javaagent:lombok.jar</pre></li></ol>" +
"On Mac OS X, eclipse.ini is hidden in<br>" +
"<code>Eclipse.app/Contents/MacOS</code> so that's where I place the jar files.</html>";
diff --git a/src/installer/lombok/installer/eclipse/EclipseLocation.java b/src/installer/lombok/installer/eclipse/EclipseLocation.java
index e347cd98..0fe60c05 100644
--- a/src/installer/lombok/installer/eclipse/EclipseLocation.java
+++ b/src/installer/lombok/installer/eclipse/EclipseLocation.java
@@ -337,8 +337,6 @@ public class EclipseLocation extends IdeLocation {
newContents.append(String.format(
"-javaagent:%s", escapePath(fullPathToLombok + "lombok.jar"))).append(OS_NEWLINE);
- newContents.append(String.format(
- "-Xbootclasspath/a:%s", escapePath(fullPathToLombok + "lombok.jar"))).append(OS_NEWLINE);
FileOutputStream fos = new FileOutputStream(eclipseIniPath);
try {
@@ -360,8 +358,7 @@ public class EclipseLocation extends IdeLocation {
}
return "If you start " + getTypeName() + " with a custom -vm parameter, you'll need to add:<br>" +
- "<code>-vmargs -Xbootclasspath/a:lombok.jar -javaagent:lombok.jar</code><br>" +
- "as parameter as well.";
+ "<code>-vmargs -javaagent:lombok.jar</code><br>as parameter as well.";
}
@Override public URL getIdeIcon() {
diff --git a/src/launch/lombok/launch/Agent.java b/src/launch/lombok/launch/Agent.java
new file mode 100644
index 00000000..7989e51f
--- /dev/null
+++ b/src/launch/lombok/launch/Agent.java
@@ -0,0 +1,47 @@
+/*
+ * 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.lang.instrument.Instrumentation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+final class Agent {
+ public static void agentmain(String agentArgs, Instrumentation instrumentation) throws Throwable {
+ runLauncher(agentArgs, instrumentation, true);
+ }
+
+ public static void premain(String agentArgs, Instrumentation instrumentation) throws Throwable {
+ runLauncher(agentArgs, instrumentation, false);
+ }
+
+ private static void runLauncher(String agentArgs, Instrumentation instrumentation, boolean injected) throws Throwable {
+ ClassLoader cl = Main.createShadowClassLoader();
+ 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
new file mode 100644
index 00000000..63d97d48
--- /dev/null
+++ b/src/launch/lombok/launch/Main.java
@@ -0,0 +1,40 @@
+/*
+ * 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.lang.reflect.InvocationTargetException;
+
+class Main {
+ static ClassLoader createShadowClassLoader() {
+ return new ShadowClassLoader(Main.class.getClassLoader(), "lombok");
+ }
+
+ public static void main(String[] args) throws Throwable {
+ ClassLoader cl = createShadowClassLoader();
+ Class<?> mc = cl.loadClass("lombok.core.Main");
+ try {
+ mc.getMethod("main", String[].class).invoke(null, new Object[] {args});
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ }
+ }
+}
diff --git a/src/launch/lombok/launch/ShadowClassLoader.java b/src/launch/lombok/launch/ShadowClassLoader.java
new file mode 100644
index 00000000..79809b2f
--- /dev/null
+++ b/src/launch/lombok/launch/ShadowClassLoader.java
@@ -0,0 +1,399 @@
+/*
+ * 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.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+import java.util.WeakHashMap;
+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.
+ *
+ * The shadow classloader also serves to link in a project as it is being developed (a 'bin' dir from an IDE for example).
+ * <p>
+ * Classes loaded by the shadowloader use ".SCL.<em>sclSuffix</em>" in addition to ".class". In other words, most of the class files in a given jar end in this suffix, which
+ * serves to hide them from any tool that isn't aware of the suffix (such as IDEs generating auto-complete dialogs, and javac's classpath in general). Only shadowloader can actually
+ * load these classes.
+ * <p>
+ * The shadowloader will pick up an alternate (priority) classpath, using normal class files, from the system property "<code>shadow.override.<em>sclSuffix</em></code>".
+ * This shadow classpath looks just like a normal java classpath; the path separator is applied (semi-colon on windows, colon elsewhere), and entries can consist of directories,
+ * jar files, or directories ending in "/*" to pick up all jars inside it.
+ * <p>
+ * Load order is as follows if at least one override is present:
+ * <li>First, if the resource is found in one of the paths stated in the shadow classpath, find that.
+ * <li>Next, ask the <code>parent</code> loader, which is passed during construction of the ShadowClassLoader.
+ * <li>Notably, this jar's contents are always skipped! (The idea of the shadow classpath is that this jar only functions as a launcher, not as a source of your actual application).
+ * </ul>
+ *
+ * If no overrides are present, the load order is as follows:
+ * <li>First, if the resource is found in our own jar (trying ".SCL.<em>sclSuffix</em>" first for any resource request ending in ".class"), return that.
+ * <li>Next, ask the <code>parent</code> loader.
+ * </ul>
+ *
+ * Use ShadowClassLoader to accomplish the following things:<ul>
+ * <li>Avoid contaminating the namespace of any project using an SCL-based jar. Autocompleters in IDEs will NOT suggest anything other than actual public API.
+ * <li>Like jarjar, allows folding in dependencies such as ASM without foisting these dependencies on projects that use this jar. shadowloader obviates the need for jarjar.
+ * <li>Allows an agent (which MUST be in jar form) to still load everything except this loader infrastructure from class files generated by the IDE, which should
+ * 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 (this is what the {@code shadow.override} feature is for).
+ * </ul>
+ *
+ * Implementation note: {@code lombok.eclipse.agent.EclipseLoaderPatcher} <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. 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. In general, actually passing load control to this loader is a bit tricky
+ * so ensure that this class has zero dependencies on anything except java core classes.
+ */
+class ShadowClassLoader extends ClassLoader {
+ private static final String SELF_NAME = "lombok/launch/ShadowClassLoader.class";
+ private final String SELF_BASE;
+ private final File SELF_BASE_FILE;
+ private final int SELF_BASE_LENGTH;
+
+ private final List<File> override = new ArrayList<File>();
+ private final String sclSuffix;
+ private final List<String> parentExclusion = new ArrayList<String>();
+
+ /**
+ * Calls the {@link ShadowClassLoader(ClassLoader, String, String, String[]) constructor with no exclusions and the source of this class as base.
+ */
+ ShadowClassLoader(ClassLoader source, String sclSuffix) {
+ this(source, sclSuffix, null);
+ }
+
+ /**
+ * @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);
+ }
+
+ 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);
+ }
+
+ if (SELF_BASE.startsWith("jar:file:") && SELF_BASE.endsWith("!/")) SELF_BASE_FILE = new File(SELF_BASE.substring(9, SELF_BASE.length() - 2));
+ else if (SELF_BASE.startsWith("file:")) SELF_BASE_FILE = new File(SELF_BASE.substring(5));
+ else SELF_BASE_FILE = new File(SELF_BASE);
+ 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);
+ }
+ }
+ }
+ }
+
+ private static final String EMPTY_MARKER = new String("--EMPTY JAR--");
+ private Map<String, Object> jarContentsCacheTrackers = new HashMap<String, Object>();
+ private static WeakHashMap<Object, String> trackerCache = new WeakHashMap<Object, String>();
+ private static WeakHashMap<Object, List<String>> jarContentsCache = new WeakHashMap<Object, List<String>>();
+
+ /**
+ * This cache ensures that any given jar file is only opened once in order to determine the full contents of it.
+ * We use 'trackers' to make sure that the bulk of the memory taken up by this cache (the list of strings representing the content of a jar file)
+ * gets garbage collected if all ShadowClassLoaders that ever tried to request a listing of this jar file, are garbage collected.
+ */
+ private List<String> getOrMakeJarListing(String absolutePathToJar) {
+ List<String> list = retrieveFromCache(absolutePathToJar);
+ synchronized (list) {
+ if (list.isEmpty()) {
+ try {
+ JarFile jf = new JarFile(absolutePathToJar);
+ try {
+ Enumeration<JarEntry> entries = jf.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry jarEntry = entries.nextElement();
+ if (!jarEntry.isDirectory()) list.add(jarEntry.getName());
+ }
+ } finally {
+ jf.close();
+ }
+ } catch (Exception ignore) {}
+ if (list.isEmpty()) list.add(EMPTY_MARKER);
+ }
+ }
+
+ if (list.size() == 1 && list.get(0) == EMPTY_MARKER) return Collections.emptyList();
+ return list;
+ }
+
+ private List<String> retrieveFromCache(String absolutePathToJar) {
+ synchronized (trackerCache) {
+ Object tracker = jarContentsCacheTrackers.get(absolutePathToJar);
+ if (tracker != null) return jarContentsCache.get(tracker);
+
+ for (Map.Entry<Object, String> entry : trackerCache.entrySet()) {
+ if (entry.getValue().equals(absolutePathToJar)) {
+ tracker = entry.getKey();
+ break;
+ }
+ }
+ List<String> result = null;
+ if (tracker != null) result = jarContentsCache.get(tracker);
+ if (result != null) return result;
+
+ tracker = new Object();
+ List<String> list = new ArrayList<String>();
+ jarContentsCache.put(tracker, list);
+ trackerCache.put(tracker, absolutePathToJar);
+ jarContentsCacheTrackers.put(absolutePathToJar, tracker);
+ return list;
+ }
+ }
+
+ /**
+ * Looks up {@code altName} in {@code location}, and if that isn't found, looks up {@code name}; {@code altName} can be null in which case it is skipped.
+ */
+ 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();
+ }
+
+ File f = new File(location, name);
+ if (f.isFile() && f.canRead()) return f.toURI().toURL();
+ return null;
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ }
+
+ if (!location.isFile() || !location.canRead()) return null;
+
+ String absolutePath; {
+ try {
+ absolutePath = location.getCanonicalPath();
+ } catch (Exception e) {
+ absolutePath = location.getAbsolutePath();
+ }
+ }
+ List<String> jarContents = getOrMakeJarListing(absolutePath);
+
+ try {
+ if (jarContents.contains(altName)) {
+ return new URI("jar:file:" + absolutePath + "!/" + altName).toURL();
+ }
+ } catch (Exception e) {}
+
+ try {
+ if (jarContents.contains(name)) {
+ return new URI("jar:file:" + absolutePath + "!/" + name).toURL();
+ }
+ } catch(Exception e) {}
+
+ return null;
+ }
+
+ /**
+ * Checks if the stated item is located inside the same classpath root as the jar that hosts ShadowClassLoader.class. {@code item} and {@code name} refer to the same thing.
+ */
+ 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);
+ }
+
+ @Override public Enumeration<URL> getResources(String name) throws IOException {
+ String altName = null;
+ if (name.endsWith(".class")) altName = name.substring(0, name.length() - 6) + ".SCL." + sclSuffix;
+
+ // Vector? 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>();
+
+ for (File ce : override) {
+ URL url = getResourceFromLocation(name, altName, ce);
+ if (url != null) vector.add(url);
+ }
+
+ if (override.isEmpty()) {
+ URL fromSelf = getResourceFromLocation(name, altName, SELF_BASE_FILE);
+ if (fromSelf != null) vector.add(fromSelf);
+ }
+
+ Enumeration<URL> sec = super.getResources(name);
+ while (sec.hasMoreElements()) {
+ URL item = sec.nextElement();
+ if (!inOwnBase(item, name)) vector.add(item);
+ }
+
+ if (altName != null) {
+ Enumeration<URL> tern = super.getResources(altName);
+ while (tern.hasMoreElements()) {
+ URL item = tern.nextElement();
+ if (!inOwnBase(item, altName)) vector.add(item);
+ }
+ }
+
+ return vector.elements();
+ }
+
+ @Override public URL getResource(String name) {
+ 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 {
+ URL res = getResourceSkippingSelf(altName);
+ if (res != null) return res;
+ } catch (IOException ignore) {}
+ }
+
+ try {
+ return getResourceSkippingSelf(name);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ URL url = getResourceFromLocation(name, altName, SELF_BASE_FILE);
+ if (url != null) return url;
+
+ 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 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);
+ }
+
+ byte[] b;
+ int p = 0;
+ try {
+ InputStream in = res.openStream();
+
+ 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();
+ }
+ } catch (IOException e) {
+ throw new ClassNotFoundException("I/O exception reading class " + name, e);
+ }
+
+ Class<?> c = defineClass(name, b, 0, p);
+ if (resolve) resolveClass(c);
+ return c;
+ }
+
+ public void addOverrideJarDir(String dir) {
+ File f = new File(dir);
+ for (File j : f.listFiles()) {
+ if (j.getName().toLowerCase().endsWith(".jar") && j.canRead() && j.isFile()) override.add(j);
+ }
+ }
+
+ public void addOverrideClasspathEntry(String entry) {
+ override.add(new File(entry));
+ }
+}