diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/core/lombok/core/AgentLauncher.java | 86 | ||||
-rw-r--r-- | src/core/lombok/core/PostCompiler.java | 8 | ||||
-rw-r--r-- | src/eclipseAgent/lombok/eclipse/agent/EclipseLoaderPatcher.java | 107 | ||||
-rw-r--r-- | src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java | 14 | ||||
-rw-r--r-- | src/launch/lombok/launch/Agent.java | 115 | ||||
-rw-r--r-- | src/launch/lombok/launch/AnnotationProcessor.java | 77 | ||||
-rw-r--r-- | src/launch/lombok/launch/Main.java | 16 | ||||
-rw-r--r-- | src/launch/lombok/launch/ShadowClassLoader.java | 341 |
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)); } } |