diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/launch/lombok/launch/ShadowClassLoader.java | 145 |
1 files changed, 126 insertions, 19 deletions
diff --git a/src/launch/lombok/launch/ShadowClassLoader.java b/src/launch/lombok/launch/ShadowClassLoader.java index 72f006e8..36dd289c 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-2018 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,9 +21,13 @@ */ 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; @@ -36,14 +40,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. @@ -66,6 +72,7 @@ import java.util.jar.JarFile; * * If no overrides are present, the load order is as follows: * <li>First, if the resource is found in our own jar (trying ".SCL.<em>sclSuffix</em>" first for any resource request ending in ".class"), return that. + * <li>Next, check any jar files other than our own, loading them via this classloader, if they have a file <code>META-INF/ShadowClassLoader</code> that contains a line of text with <em>sclSuffix</em>. * <li>Next, ask the <code>parent</code> loader. * </ul> * @@ -110,9 +117,7 @@ class ShadowClassLoader extends ClassLoader { if (!pe.endsWith("/")) pe = pe + "/"; this.parentExclusion.add(pe); } - if (highlanders != null) for (String hl : highlanders) { - this.highlanders.add(hl); - } + if (highlanders != null) for (String hl : highlanders) this.highlanders.add(hl); if (selfBase != null) { SELF_BASE = selfBase; @@ -125,12 +130,7 @@ class ShadowClassLoader extends ClassLoader { throw new RuntimeException("ShadowLoader can't find itself. SCL loader type: " + (cl == null ? "*NULL*" : cl.getClass().toString())); } SELF_BASE_LENGTH = sclClassStr.length() - SELF_NAME.length(); - String decoded; - try { - decoded = URLDecoder.decode(sclClassStr.substring(0, SELF_BASE_LENGTH), "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new InternalError("UTF-8 not available"); - } + String decoded = urlDecode(sclClassStr.substring(0, SELF_BASE_LENGTH)); SELF_BASE = decoded; } @@ -148,11 +148,11 @@ class ShadowClassLoader extends ClassLoader { } } } - + private final Map<String, Object> mapJarPathToTracker = new HashMap<String, Object>(); private static final Map<Object, String> mapTrackerToJarPath = new WeakHashMap<Object, String>(); private static final Map<Object, Set<String>> mapTrackerToJarContents = new WeakHashMap<Object, Set<String>>(); - + /** * This cache ensures that any given jar file is only opened once in order to determine the full contents of it. * We use 'trackers' to make sure that the bulk of the memory taken up by this cache (the list of strings representing the content of a jar file) @@ -296,6 +296,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. */ @@ -305,6 +309,109 @@ 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, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new InternalError("UTF-8 not supported"); + } + } + + 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; @@ -329,14 +436,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); } } @@ -376,11 +483,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; } @@ -394,12 +501,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; |