aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorReinier Zwitserloot <reinier@zwitserloot.com>2016-06-14 03:32:36 +0200
committerReinier Zwitserloot <reinier@zwitserloot.com>2016-06-14 03:32:36 +0200
commiteb09843cce285caecd25e1a459e95d8c28dda417 (patch)
tree6826afb51c34b67f2a8175e1beeb272ca343a55f
parentddd4e1feaee9fc4e450c7bed7e7938ff22455756 (diff)
downloadlombok-eb09843cce285caecd25e1a459e95d8c28dda417.tar.gz
lombok-eb09843cce285caecd25e1a459e95d8c28dda417.tar.bz2
lombok-eb09843cce285caecd25e1a459e95d8c28dda417.zip
ShadowClassLoader is now friendlier to trying to extend lombok.
Your (separate) jar/dir should have a file named META-INF/ShadowClassLoader. This file should contain the string 'lombok'. If you have that, any classes in that jar/dir will be loaded in the same space as lombok classes. You can also rename the class files to .SCL.lombok to avoid other loaders from finding them.
-rw-r--r--doc/changelog.markdown1
-rw-r--r--src/launch/lombok/launch/ShadowClassLoader.java134
2 files changed, 127 insertions, 8 deletions
diff --git a/doc/changelog.markdown b/doc/changelog.markdown
index 9bff6d56..393cbe7d 100644
--- a/doc/changelog.markdown
+++ b/doc/changelog.markdown
@@ -4,6 +4,7 @@ Lombok Changelog
### v1.16.9 "Edgy Guinea Pig"
* FEATURE: Added support for JBoss logger [Issue #1103](https://github.com/rzwitserloot/lombok/issues/1103)
* ENHANCEMENT: Running `javac -Xlint:all` would generate a warning about unclaimed annotations [Issue #1117](https://github.com/rzwitserloot/lombok/issues/1117)
+* FEATURE: Adding custom extensions to lombok now easier. [More information](https://projectlombok.org/features/extending.html).
### v1.16.8 (March 7th, 2016)
diff --git a/src/launch/lombok/launch/ShadowClassLoader.java b/src/launch/lombok/launch/ShadowClassLoader.java
index 37c479ee..70c58fb6 100644
--- a/src/launch/lombok/launch/ShadowClassLoader.java
+++ b/src/launch/lombok/launch/ShadowClassLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014-2015 The Project Lombok Authors.
+ * Copyright (C) 2014-2016 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,14 +21,19 @@
*/
package lombok.launch;
+import java.io.BufferedReader;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLDecoder;
+import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
@@ -36,14 +41,16 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;
import java.util.WeakHashMap;
-import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
/**
* The shadow classloader serves to completely hide almost all classes in a given jar file by using a different file ending.
@@ -292,6 +299,10 @@ class ShadowClassLoader extends ClassLoader {
return null;
}
+ private boolean partOfShadow(URL item, String name) {
+ return inOwnBase(item, name) || isPartOfShadowSuffix(item, name, sclSuffix);
+ }
+
/**
* 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.
*/
@@ -301,6 +312,113 @@ class ShadowClassLoader extends ClassLoader {
return (itemString.length() == SELF_BASE_LENGTH + name.length()) && SELF_BASE.regionMatches(0, itemString, 0, SELF_BASE_LENGTH);
}
+ private static boolean sclFileContainsSuffix(InputStream in, String suffix) throws IOException {
+ BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
+ for (String line = br.readLine(); line != null; line = br.readLine()) {
+ line = line.trim();
+ if (line.isEmpty() || line.charAt(0) == '#') continue;
+ if (line.equals(suffix)) return true;
+ }
+ return false;
+ }
+
+ private static String urlDecode(String in) {
+ try {
+ return URLDecoder.decode(in, Charset.defaultCharset().name());
+ } catch (UnsupportedEncodingException e) {
+ try {
+ return URLDecoder.decode(in, "UTF-8");
+ } catch (UnsupportedEncodingException e1) {
+ return in;
+ }
+ }
+ }
+
+ private Map<String, Boolean> fileRootCache = new HashMap<String, Boolean>();
+ private boolean isPartOfShadowSuffixFileBased(String fileRoot, String suffix) {
+ String key = fileRoot + "::" + suffix;
+ Boolean existing = fileRootCache.get(key);
+ if (existing != null) return existing.booleanValue();
+
+ File f = new File(fileRoot + "/META-INF/ShadowClassLoader");
+ try {
+ FileInputStream fis = new FileInputStream(f);
+ try {
+ boolean v = sclFileContainsSuffix(fis, suffix);
+ fileRootCache.put(key, v);
+ return v;
+ } finally {
+ fis.close();
+ }
+ } catch (FileNotFoundException fnfEx) {
+ fileRootCache.put(key, false);
+ return false;
+ } catch (IOException e) {
+ fileRootCache.put(key, false);
+ return false; // *unexpected*
+ }
+ }
+
+ private Map<String, Boolean> jarLocCache = new HashMap<String, Boolean>();
+ private boolean isPartOfShadowSuffixJarBased(String jarLoc, String suffix) {
+ String key = jarLoc + "::" + suffix;
+ Boolean existing = jarLocCache.get(key);
+ if (existing != null) return existing.booleanValue();
+
+ if (jarLoc.startsWith("file:/")) jarLoc = urlDecode(jarLoc.substring(5));
+ try {
+ FileInputStream jar = new FileInputStream(jarLoc);
+ try {
+ ZipInputStream zip = new ZipInputStream(jar);
+ while (true) {
+ ZipEntry entry = zip.getNextEntry();
+ if (entry == null) {
+ jarLocCache.put(key, false);
+ return false;
+ }
+ if (!"META-INF/ShadowClassLoader".equals(entry.getName())) continue;
+ boolean v = sclFileContainsSuffix(zip, suffix);
+ jarLocCache.put(key, v);
+ return v;
+ }
+ } finally {
+ jar.close();
+ }
+ } catch (FileNotFoundException fnfEx) {
+ jarLocCache.put(key, false);
+ return false;
+ } catch (IOException ex) {
+ jarLocCache.put(key, false);
+ return false; // *unexpected*
+ }
+ }
+
+ private boolean isPartOfShadowSuffix(URL item, String name, String suffix) {
+ // Instead of throwing an exception or logging, weird, unexpected cases just return false.
+ // This is better than throwing an exception, because exceptions would make your build tools unusable.
+ // Such cases are marked with the comment: // *unexpected*
+ if (item == null) return false;
+ String url = item.toString();
+ if (url.startsWith("file:/")) {
+ url = urlDecode(url.substring(5));
+ if (url.length() <= name.length() || !url.endsWith(name) || url.charAt(url.length() - name.length() - 1) != '/') {
+ return false; // *unexpected*
+ }
+
+ String fileRoot = url.substring(0, url.length() - name.length() - 1);
+ return isPartOfShadowSuffixFileBased(fileRoot, suffix);
+ } else if (url.startsWith("jar:")) {
+ int sep = url.indexOf('!');
+ if (sep == -1) {
+ return false; // *unexpected*
+ }
+ String jarLoc = url.substring(4, sep);
+ return isPartOfShadowSuffixJarBased(jarLoc, suffix);
+ }
+
+ return false;
+ }
+
@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;
@@ -325,14 +443,14 @@ class ShadowClassLoader extends ClassLoader {
Enumeration<URL> sec = super.getResources(name);
while (sec.hasMoreElements()) {
URL item = sec.nextElement();
- if (!inOwnBase(item, name)) vector.add(item);
+ if (!partOfShadow(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);
+ if (!partOfShadow(item, altName)) vector.add(item);
}
}
@@ -372,11 +490,11 @@ class ShadowClassLoader extends ClassLoader {
if (altName != null) {
URL res = super.getResource(altName);
- if (res != null && (!noSuper || inOwnBase(res, altName))) return res;
+ if (res != null && (!noSuper || partOfShadow(res, altName))) return res;
}
URL res = super.getResource(name);
- if (res != null && (!noSuper || inOwnBase(res, name))) return res;
+ if (res != null && (!noSuper || partOfShadow(res, name))) return res;
return null;
}
@@ -390,12 +508,12 @@ class ShadowClassLoader extends ClassLoader {
private URL getResourceSkippingSelf(String name) throws IOException {
URL candidate = super.getResource(name);
if (candidate == null) return null;
- if (!inOwnBase(candidate, name)) return candidate;
+ if (!partOfShadow(candidate, name)) return candidate;
Enumeration<URL> en = super.getResources(name);
while (en.hasMoreElements()) {
candidate = en.nextElement();
- if (!inOwnBase(candidate, name)) return candidate;
+ if (!partOfShadow(candidate, name)) return candidate;
}
return null;