From eb09843cce285caecd25e1a459e95d8c28dda417 Mon Sep 17 00:00:00 2001
From: Reinier Zwitserloot <reinier@zwitserloot.com>
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/launch')

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;
-- 
cgit