From eb09843cce285caecd25e1a459e95d8c28dda417 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 14 Jun 2016 03:32:36 +0200 Subject: 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. --- src/launch/lombok/launch/ShadowClassLoader.java | 134 ++++++++++++++++++++++-- 1 file changed, 126 insertions(+), 8 deletions(-) (limited to 'src') 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 fileRootCache = new HashMap(); + 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 jarLocCache = new HashMap(); + 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 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 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 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 en = super.getResources(name); while (en.hasMoreElements()) { candidate = en.nextElement(); - if (!inOwnBase(candidate, name)) return candidate; + if (!partOfShadow(candidate, name)) return candidate; } return null; -- cgit From 4306f978fecb85ad49f5f147db6cf063305bf7b6 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 19 Jun 2018 00:09:48 +0200 Subject: [SCL] update that should make it a little easier to add custom lombok handlers. --- src/launch/lombok/launch/ShadowClassLoader.java | 27 ++++++++----------------- 1 file changed, 8 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/launch/lombok/launch/ShadowClassLoader.java b/src/launch/lombok/launch/ShadowClassLoader.java index 70c58fb6..cd11720a 100644 --- a/src/launch/lombok/launch/ShadowClassLoader.java +++ b/src/launch/lombok/launch/ShadowClassLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2016 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 @@ -33,7 +33,6 @@ 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; @@ -73,6 +72,7 @@ import java.util.zip.ZipInputStream; * * If no overrides are present, the load order is as follows: *
  • First, if the resource is found in our own jar (trying ".SCL.sclSuffix" first for any resource request ending in ".class"), return that. + *
  • Next, check any jar files other than our own, loading them via this classloader, if they have a file META-INF/ShadowClassLoader that contains a line of text with sclSuffix. *
  • Next, ask the parent loader. * * @@ -117,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; @@ -128,12 +126,7 @@ class ShadowClassLoader extends ClassLoader { String sclClassUrl = ShadowClassLoader.class.getResource("ShadowClassLoader.class").toString(); if (!sclClassUrl.endsWith(SELF_NAME)) throw new InternalError("ShadowLoader can't find itself."); SELF_BASE_LENGTH = sclClassUrl.length() - SELF_NAME.length(); - String decoded; - try { - decoded = URLDecoder.decode(sclClassUrl.substring(0, SELF_BASE_LENGTH), "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new InternalError("UTF-8 not available"); - } + String decoded = urlDecode(sclClassUrl.substring(0, SELF_BASE_LENGTH)); SELF_BASE = decoded; } @@ -151,11 +144,11 @@ class ShadowClassLoader extends ClassLoader { } } } - + private final Map mapJarPathToTracker = new HashMap(); private static final Map mapTrackerToJarPath = new WeakHashMap(); private static final Map> mapTrackerToJarContents = new WeakHashMap>(); - + /** * 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) @@ -324,13 +317,9 @@ class ShadowClassLoader extends ClassLoader { private static String urlDecode(String in) { try { - return URLDecoder.decode(in, Charset.defaultCharset().name()); + return URLDecoder.decode(in, "UTF-8"); } catch (UnsupportedEncodingException e) { - try { - return URLDecoder.decode(in, "UTF-8"); - } catch (UnsupportedEncodingException e1) { - return in; - } + throw new InternalError("UTF-8 not supported"); } } -- cgit