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 /src | |
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.
Diffstat (limited to 'src')
-rw-r--r-- | src/launch/lombok/launch/ShadowClassLoader.java | 134 |
1 files changed, 126 insertions, 8 deletions
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; |