aboutsummaryrefslogtreecommitdiff
path: root/src/launch
diff options
context:
space:
mode:
Diffstat (limited to 'src/launch')
-rw-r--r--src/launch/lombok/launch/AnnotationProcessor.java56
-rw-r--r--src/launch/lombok/launch/ShadowClassLoader.java158
2 files changed, 160 insertions, 54 deletions
diff --git a/src/launch/lombok/launch/AnnotationProcessor.java b/src/launch/lombok/launch/AnnotationProcessor.java
index 35c26b7c..c4f922b9 100644
--- a/src/launch/lombok/launch/AnnotationProcessor.java
+++ b/src/launch/lombok/launch/AnnotationProcessor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 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,19 +21,37 @@
*/
package lombok.launch;
+import java.lang.reflect.Field;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Completion;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+
+import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor;
+
+import sun.misc.Unsafe;
class AnnotationProcessorHider {
+ public static class AstModificationNotifier implements AstModifyingAnnotationProcessor {
+ @Override public boolean isTypeComplete(TypeMirror type) {
+ if (System.getProperty("lombok.disable") != null) return true;
+ return AstModificationNotifierData.lombokInvoked;
+ }
+ }
+
+ static class AstModificationNotifierData {
+ volatile static boolean lombokInvoked = false;
+ }
+
public static class AnnotationProcessor extends AbstractProcessor {
private final AbstractProcessor instance = createWrappedInstance();
@@ -50,10 +68,33 @@ class AnnotationProcessorHider {
}
@Override public void init(ProcessingEnvironment processingEnv) {
+ disableJava9SillyWarning();
+ AstModificationNotifierData.lombokInvoked = true;
instance.init(processingEnv);
super.init(processingEnv);
}
+ // sunapi suppresses javac's warning about using Unsafe; 'all' suppresses eclipse's warning about the unspecified 'sunapi' key. Leave them both.
+ // Yes, javac's definition of the word 'all' is quite contrary to what the dictionary says it means. 'all' does NOT include 'sunapi' according to javac.
+ @SuppressWarnings({"sunapi", "all"})
+ private void disableJava9SillyWarning() {
+ // JVM9 complains about using reflection to access packages from a module that aren't exported. This makes no sense; the whole point of reflection
+ // is to get past such issues. The only comment from the jigsaw team lead on this was some unspecified mumbling about security which makes no sense,
+ // as the SecurityManager is invoked to check such things. Therefore this warning is a bug, so we shall patch java to fix it.
+
+ try {
+ Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
+ theUnsafe.setAccessible(true);
+ Unsafe u = (Unsafe) theUnsafe.get(null);
+
+ Class<?> cls = Class.forName("jdk.internal.module.IllegalAccessLogger");
+ Field logger = cls.getDeclaredField("logger");
+ u.putObjectVolatile(cls, u.staticFieldOffset(logger), null);
+ } catch (Throwable t) {
+ // We shall ignore it; the effect of this code failing is that the user gets to see a warning they remove with various --add-opens magic.
+ }
+ }
+
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return instance.process(annotations, roundEnv);
}
@@ -66,7 +107,7 @@ class AnnotationProcessorHider {
ClassLoader cl = Main.createShadowClassLoader();
try {
Class<?> mc = cl.loadClass("lombok.core.AnnotationProcessor");
- return (AbstractProcessor) mc.newInstance();
+ return (AbstractProcessor) mc.getDeclaredConstructor().newInstance();
} catch (Throwable t) {
if (t instanceof Error) throw (Error) t;
if (t instanceof RuntimeException) throw (RuntimeException) t;
@@ -74,4 +115,15 @@ class AnnotationProcessorHider {
}
}
}
+
+ @SupportedAnnotationTypes("lombok.*")
+ public static class ClaimingProcessor extends AbstractProcessor {
+ @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ return true;
+ }
+
+ @Override public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latest();
+ }
+ }
}
diff --git a/src/launch/lombok/launch/ShadowClassLoader.java b/src/launch/lombok/launch/ShadowClassLoader.java
index 83f64370..72f006e8 100644
--- a/src/launch/lombok/launch/ShadowClassLoader.java
+++ b/src/launch/lombok/launch/ShadowClassLoader.java
@@ -33,10 +33,13 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+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;
@@ -115,12 +118,16 @@ class ShadowClassLoader extends ClassLoader {
SELF_BASE = selfBase;
SELF_BASE_LENGTH = selfBase.length();
} else {
- 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();
+ URL sclClassUrl = ShadowClassLoader.class.getResource("ShadowClassLoader.class");
+ String sclClassStr = sclClassUrl == null ? null : sclClassUrl.toString();
+ if (sclClassStr == null || !sclClassStr.endsWith(SELF_NAME)) {
+ ClassLoader cl = ShadowClassLoader.class.getClassLoader();
+ 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(sclClassUrl.substring(0, SELF_BASE_LENGTH), "UTF-8");
+ decoded = URLDecoder.decode(sclClassStr.substring(0, SELF_BASE_LENGTH), "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new InternalError("UTF-8 not available");
}
@@ -141,62 +148,100 @@ class ShadowClassLoader extends ClassLoader {
}
}
}
-
- private static final String EMPTY_MARKER = new String("--EMPTY JAR--");
- private Map<String, Object> jarContentsCacheTrackers = new HashMap<String, Object>();
- private static WeakHashMap<Object, String> trackerCache = new WeakHashMap<Object, String>();
- private static WeakHashMap<Object, List<String>> jarContentsCache = new WeakHashMap<Object, List<String>>();
-
+
+ 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)
* gets garbage collected if all ShadowClassLoaders that ever tried to request a listing of this jar file, are garbage collected.
*/
- private List<String> getOrMakeJarListing(String absolutePathToJar) {
- List<String> list = retrieveFromCache(absolutePathToJar);
- synchronized (list) {
- if (list.isEmpty()) {
- try {
- JarFile jf = new JarFile(absolutePathToJar);
- try {
- Enumeration<JarEntry> entries = jf.entries();
- while (entries.hasMoreElements()) {
- JarEntry jarEntry = entries.nextElement();
- if (!jarEntry.isDirectory()) list.add(jarEntry.getName());
- }
- } finally {
- jf.close();
- }
- } catch (Exception ignore) {}
- if (list.isEmpty()) list.add(EMPTY_MARKER);
+ private Set<String> getOrMakeJarListing(final String absolutePathToJar) {
+ synchronized (mapTrackerToJarPath) {
+ /*
+ * 1) Check our private instance JarPath-to-Tracker Mappings:
+ */
+ Object ourTracker = mapJarPathToTracker.get(absolutePathToJar);
+ if (ourTracker != null) {
+ /*
+ * Yes, we are already tracking this Jar. Just return its contents...
+ */
+ return mapTrackerToJarContents.get(ourTracker);
}
+
+ /*
+ * 2) Not tracked by us as yet. Check statically whether others have tracked this JarPath:
+ */
+ for (Entry<Object, String> entry : mapTrackerToJarPath.entrySet()) {
+ if (entry.getValue().equals(absolutePathToJar)) {
+ /*
+ * Yes, 3rd party is tracking this jar. We must track too, then return its contents.
+ */
+ Object otherTracker = entry.getKey();
+ mapJarPathToTracker.put(absolutePathToJar, otherTracker);
+ return mapTrackerToJarContents.get(otherTracker);
+ }
+ }
+
+ /*
+ * 3) Not tracked by anyone so far. Build, publish, track & return Jar contents...
+ */
+ Object newTracker = new Object();
+ Set<String> jarMembers = getJarMemberSet(absolutePathToJar);
+
+ mapTrackerToJarContents.put(newTracker, jarMembers);
+ mapTrackerToJarPath.put(newTracker, absolutePathToJar);
+ mapJarPathToTracker.put(absolutePathToJar, newTracker);
+
+ return jarMembers;
}
-
- if (list.size() == 1 && list.get(0) == EMPTY_MARKER) return Collections.emptyList();
- return list;
}
- private List<String> retrieveFromCache(String absolutePathToJar) {
- synchronized (trackerCache) {
- Object tracker = jarContentsCacheTrackers.get(absolutePathToJar);
- if (tracker != null) return jarContentsCache.get(tracker);
+ /**
+ * Return a {@link Set} of members in the Jar identified by {@code absolutePathToJar}.
+ *
+ * @param absolutePathToJar Cache key
+ * @return a Set with the Jar member-names
+ */
+ private Set<String> getJarMemberSet(String absolutePathToJar) {
+ /*
+ * Note:
+ * Our implementation returns a HashSet. initialCapacity and loadFactor are carefully tweaked for speed and RAM optimization purposes.
+ *
+ * Benchmark:
+ * The HashSet implementation is about 10% slower to build (only happens once) than the ArrayList.
+ * The HashSet with shiftBits = 1 was about 33 times(!) faster than the ArrayList for retrievals.
+ */
+ try {
+ int shiftBits = 1; // (fast, but big) 0 <= shiftBits <= 5, say (slower & compact)
+ JarFile jar = new JarFile(absolutePathToJar);
- for (Map.Entry<Object, String> entry : trackerCache.entrySet()) {
- if (entry.getValue().equals(absolutePathToJar)) {
- tracker = entry.getKey();
- break;
+ /*
+ * Find the first power of 2 >= JarSize (as calculated in HashSet constructor)
+ */
+ int jarSizePower2 = Integer.highestOneBit(jar.size());
+ if (jarSizePower2 != jar.size()) jarSizePower2 <<= 1;
+ if (jarSizePower2 == 0) jarSizePower2 = 1;
+
+ Set<String> jarMembers = new HashSet<String>(jarSizePower2 >> shiftBits, 1 << shiftBits);
+ try {
+ Enumeration<JarEntry> entries = jar.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry jarEntry = entries.nextElement();
+ if (jarEntry.isDirectory()) continue;
+ jarMembers.add(jarEntry.getName());
}
+ } catch (Exception ignore) {
+ // ignored; if the jar can't be read, treating it as if the jar contains no classes is just what we want.
+ } finally {
+ jar.close();
}
- List<String> result = null;
- if (tracker != null) result = jarContentsCache.get(tracker);
- if (result != null) return result;
-
- tracker = new Object();
- List<String> list = new ArrayList<String>();
- jarContentsCache.put(tracker, list);
- trackerCache.put(tracker, absolutePathToJar);
- jarContentsCacheTrackers.put(absolutePathToJar, tracker);
- return list;
+ return jarMembers;
+ }
+ catch (Exception newJarFileException) {
+ return Collections.emptySet();
}
}
@@ -228,7 +273,7 @@ class ShadowClassLoader extends ClassLoader {
absoluteFile = location.getAbsoluteFile();
}
}
- List<String> jarContents = getOrMakeJarListing(absoluteFile.getAbsolutePath());
+ Set<String> jarContents = getOrMakeJarListing(absoluteFile.getAbsolutePath());
String absoluteUri = absoluteFile.toURI().toString();
@@ -236,13 +281,17 @@ class ShadowClassLoader extends ClassLoader {
if (jarContents.contains(altName)) {
return new URI("jar:" + absoluteUri + "!/" + altName).toURL();
}
- } catch (Exception e) {}
+ } catch (Exception ignore) {
+ // intentional fallthrough
+ }
try {
if (jarContents.contains(name)) {
return new URI("jar:" + absoluteUri + "!/" + name).toURL();
}
- } catch(Exception e) {}
+ } catch(Exception ignore) {
+ // intentional fallthrough
+ }
return null;
}
@@ -406,7 +455,12 @@ class ShadowClassLoader extends ClassLoader {
Class<?> alreadyDefined = highlanderMap.get(name);
if (alreadyDefined != null) return alreadyDefined;
}
- throw e;
+ try {
+ c = this.findLoadedClass(name);
+ } catch (LinkageError e2) {
+ throw e;
+ }
+ if (c == null) throw e;
}
if (highlanders.contains(name)) {