diff options
author | Reinier Zwitserloot <reinier@zwitserloot.com> | 2016-06-14 03:32:36 +0200 |
---|---|---|
committer | Reinier Zwitserloot <reinier@zwitserloot.com> | 2016-06-14 03:32:36 +0200 |
commit | eb09843cce285caecd25e1a459e95d8c28dda417 (patch) | |
tree | 6826afb51c34b67f2a8175e1beeb272ca343a55f | |
parent | ddd4e1feaee9fc4e450c7bed7e7938ff22455756 (diff) | |
download | lombok-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.markdown | 1 | ||||
-rw-r--r-- | src/launch/lombok/launch/ShadowClassLoader.java | 134 |
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; |