aboutsummaryrefslogtreecommitdiff
path: root/src/launch/lombok
diff options
context:
space:
mode:
Diffstat (limited to 'src/launch/lombok')
-rw-r--r--src/launch/lombok/launch/Agent.java140
-rw-r--r--src/launch/lombok/launch/Main.java52
-rw-r--r--src/launch/lombok/launch/ShadowClassLoader.java225
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));
+ }
+}