diff options
Diffstat (limited to 'src/launch')
-rw-r--r-- | src/launch/lombok/launch/AnnotationProcessor.java | 56 | ||||
-rw-r--r-- | src/launch/lombok/launch/ShadowClassLoader.java | 158 |
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)) { |