diff options
-rw-r--r-- | build.xml | 34 | ||||
-rw-r--r-- | src/core/lombok/core/Main.java | 1 | ||||
-rw-r--r-- | src/delombok/lombok/delombok/DelombokApp.java | 16 | ||||
-rw-r--r-- | src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java | 4 | ||||
-rw-r--r-- | src/launch/lombok/launch/Agent.java (renamed from src/core/lombok/core/Agent.java) | 18 | ||||
-rw-r--r-- | src/launch/lombok/launch/Main.java | 52 | ||||
-rw-r--r-- | src/launch/lombok/launch/ShadowClassLoader.java | 225 |
7 files changed, 323 insertions, 27 deletions
@@ -104,7 +104,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr <target name="ensureBuildDeps" depends="config-ivy"> <mkdir dir="lib/openJDK6Environment" /> - <get src="http://projectlombok.org/ivyrepo/langtools/rt-openjdk6.jar" dest="lib/openJDK6Environment/rt-openjdk6.jar" verbose="true" usetimestamp="true" /> +<!-- <get src="http://projectlombok.org/ivyrepo/langtools/rt-openjdk6.jar" dest="lib/openJDK6Environment/rt-openjdk6.jar" verbose="true" usetimestamp="true" />--> <ivy:resolve file="buildScripts/ivy.xml" refresh="true" conf="build, javac7" /> <ivy:retrieve /> </target> @@ -178,6 +178,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr <ivy:compile destdir="build/lombok" source="1.5" target="1.5" includeantruntime="false"> <compilerarg value="-Xbootclasspath/p:lib/openJDK6Environment/rt-openjdk6.jar" /> <compilerarg value="-Xbootclasspath/p:build/stubs" /> + <src path="src/launch" /> <src path="src/core" /> <src path="src/installer" /> <src path="src/eclipseAgent" /> @@ -208,25 +209,31 @@ the common tasks and can be called on to run the main aspects of all the sub-scr <format property="releaseTimestamp" pattern="yyyy-MM-dd" /> </tstamp> <echo file="release-timestamp.txt">${releaseTimestamp}</echo> - <taskdef name="jarjar" classname="com.tonicsystems.jarjar.JarJarTask" classpath="lib/build/com.googlecode.jarjar-jarjar.jar" /> - <jarjar destfile="dist/lombok-${lombok.version}.jar"> - <fileset dir="build/lombok"> - <exclude name="com/sun/tools/javac/**"/> - </fileset> + <zip destfile="dist/lombok-${lombok.version}.jar"> <fileset dir="build" includes="changelog.txt" /> <fileset dir="." includes="LICENSE" /> <fileset dir="." includes="AUTHORS" /> <fileset dir="." includes="release-timestamp.txt" /> - <rule pattern="com.zwitserloot.cmdreader.**" result="lombok.libs.com.zwitserloot.cmdreader.@1" /> - <rule pattern="org.objectweb.asm.**" result="lombok.libs.org.objectweb.asm.@1" /> + <mappedresources> + <fileset dir="build/lombok"> + <exclude name="com/sun/tools/javac/**"/> + </fileset> + <firstmatchmapper> + <globmapper from="lombok/launch/*" to="lombok/launch/*" /> + <globmapper from="*.class" to="*.SCL.lombok" /> + <identitymapper /> + </firstmatchmapper> + </mappedresources> + </zip> + <jar destfile="dist/lombok-${lombok.version}.jar" update="true"> <manifest> - <attribute name="Premain-Class" value="lombok.core.Agent" /> - <attribute name="Agent-Class" value="lombok.core.Agent" /> + <attribute name="Premain-Class" value="lombok.launch.Agent" /> + <attribute name="Agent-Class" value="lombok.launch.Agent" /> <attribute name="Can-Redefine-Classes" value="true" /> - <attribute name="Main-Class" value="lombok.core.Main" /> + <attribute name="Main-Class" value="lombok.launch.Main" /> <attribute name="Lombok-Version" value="${lombok.version}" /> </manifest> - </jarjar> + </jar> <delete file="release-timestamp.txt" /> <copy file="dist/lombok-${lombok.version}.jar" tofile="dist/lombok.jar" /> <property name="lombok.dist.built" value="true" /> @@ -251,6 +258,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr <conf name="test" sources="contrib" /> <module name="lombok" depends="build, test"> <srcdir dir="src/core" /> + <srcdir dir="src/launch" /> <srcdir dir="src/utils" /> <srcdir dir="src/eclipseAgent" /> <srcdir dir="src/installer" /> @@ -280,6 +288,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr <target name="eclipse" depends="-addEclipseDeps, deps, contrib" description="Creates eclipse project files and downloads all dependencies. Open this directory as project in eclipse after running this target. This will NOT let you start a debug session for eclipse; use target 'eclipseForDebugging' instead to do that."> <ivy:eclipsegen source="1.6"> <srcdir dir="src/core" /> + <srcdir dir="src/launch" /> <srcdir dir="src/utils" /> <srcdir dir="src/eclipseAgent" /> <srcdir dir="src/installer" /> @@ -610,6 +619,7 @@ You can also create your own by writing a 'testenvironment.properties' file. The </jar> <jar destfile="dist/lombok-${lombok.version}-sources.jar"> <fileset dir="src/core" /> + <fileset dir="src/launch" /> <fileset dir="src/utils" /> <fileset dir="src/eclipseAgent" /> <fileset dir="src/installer" /> 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/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/EclipsePatcher.java b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java index e14d1367..3fce9626 100644 --- a/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java +++ b/src/eclipseAgent/lombok/eclipse/agent/EclipsePatcher.java @@ -28,7 +28,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import lombok.core.Agent; import lombok.patcher.Hook; import lombok.patcher.MethodTarget; import lombok.patcher.ScriptManager; @@ -44,12 +43,11 @@ 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 { // 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 { String[] args = agentArgs == null ? new String[0] : agentArgs.split(":"); boolean forceEcj = false; diff --git a/src/core/lombok/core/Agent.java b/src/launch/lombok/launch/Agent.java index 49c03bf3..fe64e1b6 100644 --- a/src/core/lombok/core/Agent.java +++ b/src/launch/lombok/launch/Agent.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 @@ -19,11 +19,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package lombok.core; +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; @@ -32,9 +33,7 @@ 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; - +final class Agent { public static void agentmain(String agentArgs, Instrumentation instrumentation) throws Throwable { runAgents(agentArgs, instrumentation, true); } @@ -49,12 +48,15 @@ public abstract class Agent { )); private static void runAgents(String agentArgs, Instrumentation instrumentation, boolean injected) throws Throwable { + ClassLoader cl = Main.createShadowClassLoader(); + for (AgentInfo info : AGENTS) { try { - Class<?> agentClass = Class.forName(info.className()); - Agent agent = (Agent) agentClass.newInstance(); - agent.runAgent(agentArgs, instrumentation, injected); + 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); } } 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)); + } +} |