diff options
Diffstat (limited to 'src/launch/lombok')
-rw-r--r-- | src/launch/lombok/launch/Agent.java | 140 | ||||
-rw-r--r-- | src/launch/lombok/launch/Main.java | 52 | ||||
-rw-r--r-- | src/launch/lombok/launch/ShadowClassLoader.java | 225 |
3 files changed, 417 insertions, 0 deletions
diff --git a/src/launch/lombok/launch/Agent.java b/src/launch/lombok/launch/Agent.java new file mode 100644 index 00000000..fe64e1b6 --- /dev/null +++ b/src/launch/lombok/launch/Agent.java @@ -0,0 +1,140 @@ +/* + * 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.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; + +final class Agent { + public static void agentmain(String agentArgs, Instrumentation instrumentation) throws Throwable { + runAgents(agentArgs, instrumentation, true); + } + + 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 { + 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"; + } + } +} diff --git a/src/launch/lombok/launch/Main.java b/src/launch/lombok/launch/Main.java new file mode 100644 index 00000000..f4b6a788 --- /dev/null +++ b/src/launch/lombok/launch/Main.java @@ -0,0 +1,52 @@ +/* + * 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.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; + } + + 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..66749b94 --- /dev/null +++ b/src/launch/lombok/launch/ShadowClassLoader.java @@ -0,0 +1,225 @@ +/* + * 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.FileInputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URI; +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.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". + * + * In addition, the shadowloader will pick up an alternate (priority) classpath, using normal class files, from the system property {@code shadow.classpath}. + * + * This classloader accomplishes a number of things:<ul> + * <li>Avoid contaminating the namespace of any project using lombok. 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 lombok. 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. + * </ul> + */ +class ShadowClassLoader extends ClassLoader { + private final ClassLoader source; + private final List<File> priority = new ArrayList<File>(); + + private static final int INITIAL_BUFFER_SIZE = 65536; + private static final int MAX_BUFFER_SIZE = 1048576; + + public ShadowClassLoader(ClassLoader source) { + super(source); + this.source = source == null ? ClassLoader.getSystemClassLoader() : source; + } + + private static final ThreadLocal<byte[]> BUFFERS = new ThreadLocal<byte[]>() { + @Override protected byte[] initialValue() { + return new byte[INITIAL_BUFFER_SIZE]; + } + }; + + 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()); + } + } else if (ce.isFile() && ce.canRead()) { + JarFile jf; + JarEntry entry; + + 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(); + if (prioritized == null) prioritized = new ArrayList<URL>(); + prioritized.add(url); + } catch (URISyntaxException ignore) {} + } + } + + 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(); + } + + @Override public URL nextElement() { + if (prim.hasNext()) return prim.next(); + return sec.nextElement(); + } + }; + } + + @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; + + 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) { + } + } + } + + return super.getResource(name); + } + + @Override protected Class<?> findClass(String name) throws ClassNotFoundException { + String rawName, sclName; { + String binName = name.replace(".", "/"); + rawName = binName.concat(".class"); + sclName = binName.concat(".SCL.lombok"); + } + + 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); + + 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); + } + } + } finally { + if (in != null) in.close(); + }} 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]); + return c; + } + + public void addPriorityJarDir(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); + } + + public void addPriorityClasspathEntry(String entry) { + priority.add(new File(entry)); + } +} |