cleanup (inspection "Java | Class structure | Utility class is not 'final'")
[idea/community.git] / jps / jps-builders-6 / src / org / jetbrains / jps / javac / JavacMain.java
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package org.jetbrains.jps.javac;
3
4 import com.intellij.util.BooleanFunction;
5 import org.jetbrains.annotations.NotNull;
6 import org.jetbrains.annotations.Nullable;
7 import org.jetbrains.jps.api.CanceledStatus;
8 import org.jetbrains.jps.builders.impl.java.JavacCompilerTool;
9 import org.jetbrains.jps.builders.java.CannotCreateJavaCompilerException;
10 import org.jetbrains.jps.builders.java.JavaCompilingTool;
11 import org.jetbrains.jps.builders.java.JavaSourceTransformer;
12 import org.jetbrains.jps.incremental.LineOutputWriter;
13
14 import javax.tools.*;
15 import java.io.File;
16 import java.io.IOException;
17 import java.lang.reflect.*;
18 import java.util.*;
19
20 /**
21  * @author Eugene Zhuravlev
22  */
23 public final class JavacMain {
24   private static final String JAVA_VERSION = System.getProperty("java.version", "");
25
26   //private static final boolean ECLIPSE_COMPILER_SINGLE_THREADED_MODE = Boolean.parseBoolean(System.getProperty("jdt.compiler.useSingleThread", "false"));
27   private static final Set<String> FILTERED_OPTIONS = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
28     "-d", "-classpath", "-cp", "--class-path", "-bootclasspath", "--boot-class-path"
29   )));
30   private static final Set<String> FILTERED_SINGLE_OPTIONS = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
31     /*javac options*/  "-verbose", "-proc:only", "-implicit:class", "-implicit:none", "-Xprefer:newer", "-Xprefer:source"
32   )));
33   private static final Set<String> FILE_MANAGER_EARLY_INIT_OPTIONS = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
34     "-encoding", "-extdirs", "-endorseddirs", "-processorpath", "--processor-path", "--processor-module-path", "-s", "-d", "-h"
35   )));
36
37   public static final String JAVA_RUNTIME_VERSION = System.getProperty("java.runtime.version");
38
39   public static boolean compile(Collection<String> options,
40                                 final Collection<? extends File> sources,
41                                 Collection<? extends File> classpath,
42                                 Collection<? extends File> platformClasspath,
43                                 ModulePath modulePath,
44                                 Collection<? extends File> upgradeModulePath,
45                                 Collection<? extends File> sourcePath,
46                                 final Map<File, Set<File>> outputDirToRoots,
47                                 final DiagnosticOutputConsumer diagnosticConsumer,
48                                 final OutputFileConsumer outputSink,
49                                 CanceledStatus canceledStatus, @NotNull JavaCompilingTool compilingTool) {
50     JavaCompiler compiler;
51     try {
52       compiler = compilingTool.createCompiler();
53     }
54     catch (CannotCreateJavaCompilerException e) {
55       diagnosticConsumer.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, e.getMessage()));
56       return false;
57     }
58
59     for (File outputDir : outputDirToRoots.keySet()) {
60       outputDir.mkdirs();
61     }
62
63     final boolean usingJavac = compilingTool instanceof JavacCompilerTool;
64     final boolean javacBefore9 = isJavacBefore9(compilingTool);
65     final JpsJavacFileManager fileManager = new JpsJavacFileManager(
66       new ContextImpl(compiler, diagnosticConsumer, outputSink, modulePath, canceledStatus), javacBefore9, JavaSourceTransformer.getTransformers()
67     );
68     if (!platformClasspath.isEmpty()) {
69       // for javac6 this will prevent lazy initialization of Paths.bootClassPathRtJar
70       // and thus usage of symbol file for resolution, when this file is not expected to be used
71       fileManager.handleOption("-bootclasspath", Collections.singleton("").iterator());
72       fileManager.handleOption("-extdirs", Collections.singleton("").iterator()); // this will clear cached stuff
73       fileManager.handleOption("-endorseddirs", Collections.singleton("").iterator()); // this will clear cached stuff
74     }
75
76     final Collection<String> _options = prepareOptions(options, compilingTool);
77
78     try {
79       // to be on the safe side, we'll have to apply all options _before_ calling any of manager's methods
80       // i.e. getJavaFileObjectsFromFiles()
81       // This way the manager will be properly initialized. Namely, the encoding will be set correctly
82       // Note that due to lazy initialization in various components inside javac, handleOption() should be called before setLocation() and others
83       // update: for some options their repetitive initialization would be considered as error: e.g. '--patch-module',
84       //  therefore we do the trick only for those options that may influence FileManager's state initialization before passing it to getTask() method
85       for (Iterator<String> iterator = _options.iterator(); iterator.hasNext(); ) {
86         final String option = iterator.next();
87         if (FILE_MANAGER_EARLY_INIT_OPTIONS.contains(option)) {
88           fileManager.handleOption(option, iterator);
89         }
90       }
91
92       try {
93         fileManager.setOutputDirectories(outputDirToRoots);
94       }
95       catch (IOException e) {
96         fileManager.getContext().reportMessage(Diagnostic.Kind.ERROR, e.getMessage());
97         return false;
98       }
99
100       if (!platformClasspath.isEmpty()) {
101         try {
102           fileManager.handleOption("-bootclasspath", Collections.singleton("").iterator()); // this will clear cached stuff
103           fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, buildPlatformClasspath(platformClasspath, _options));
104         }
105         catch (IOException e) {
106           fileManager.getContext().reportMessage(Diagnostic.Kind.ERROR, e.getMessage());
107           return false;
108         }
109       }
110
111       if (!upgradeModulePath.isEmpty()) {
112         try {
113           setLocation(fileManager, "UPGRADE_MODULE_PATH", upgradeModulePath);
114         }
115         catch (IOException e) {
116           fileManager.getContext().reportMessage(Diagnostic.Kind.ERROR, e.getMessage());
117           return false;
118         }
119       }
120
121       if (!modulePath.isEmpty()) {
122         try {
123           setLocation(fileManager, "MODULE_PATH", modulePath.getPath());
124           if (isAnnotationProcessingEnabled(_options) &&
125             getLocation(fileManager, "ANNOTATION_PROCESSOR_MODULE_PATH") == null &&
126             fileManager.getLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH) == null) {
127             // default annotation processing discovery path to module path if not explicitly set
128             setLocation(fileManager, "ANNOTATION_PROCESSOR_MODULE_PATH", JpsJavacFileManager.filter(modulePath.getPath(), new BooleanFunction<File>() {
129               @Override
130               public boolean fun(File file) {
131                 return !outputDirToRoots.containsKey(file);
132               }
133             }));
134           }
135         }
136         catch (IOException e) {
137           fileManager.getContext().reportMessage(Diagnostic.Kind.ERROR, e.getMessage());
138           return false;
139         }
140       }
141
142       if (!classpath.isEmpty()) {
143         // because module path has priority if present, initialize classpath after the module path
144         try {
145           fileManager.setLocation(StandardLocation.CLASS_PATH, classpath);
146           if (!usingJavac &&
147               isAnnotationProcessingEnabled(_options) &&
148               !_options.contains("-processorpath") &&
149               (javacBefore9 || (!_options.contains("--processor-module-path") && getLocation(fileManager, "ANNOTATION_PROCESSOR_MODULE_PATH") == null))) {
150             // for non-javac file manager ensure annotation processor path defaults to classpath
151             fileManager.setLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH, classpath);
152           }
153         }
154         catch (IOException e) {
155           fileManager.getContext().reportMessage(Diagnostic.Kind.ERROR, e.getMessage());
156           return false;
157         }
158       }
159
160       if (javacBefore9 || !sourcePath.isEmpty() || modulePath.isEmpty()) {
161         try {
162           // ensure the source path is set;
163           // otherwise, if not set, javac attempts to search both classes and sources in classpath;
164           // so if some classpath jars contain sources, it will attempt to compile them
165           // starting from javac9 it seems that setting empty source path may affect module compilation logic, so starting from javac9
166           // we avoid forcing empty sourcepath
167           fileManager.setLocation(StandardLocation.SOURCE_PATH, sourcePath);
168         }
169         catch (IOException e) {
170           fileManager.getContext().reportMessage(Diagnostic.Kind.ERROR, e.getMessage());
171           return false;
172         }
173       }
174
175       final LineOutputWriter out = new LineOutputWriter() {
176         @Override
177         protected void lineAvailable(String line) {
178           if (usingJavac) {
179             diagnosticConsumer.outputLineAvailable(line);
180           }
181           else {
182             // todo: filter too verbose eclipse output?
183           }
184         }
185       };
186
187       final StandardJavaFileManager fm = wrapWithCallDispatcher(StandardJavaFileManager.class, fileManager, fileManager.getClass().getSuperclass(), fileManager.getStdManager());
188       final JavaCompiler.CompilationTask task = tryInstallClientCodeWrapperCallDispatcher(compiler.getTask(
189         out, fm, diagnosticConsumer, _options, null, fileManager.getJavaFileObjectsFromFiles(sources)
190       ), fm);
191       for (JavaCompilerToolExtension extension : JavaCompilerToolExtension.getExtensions()) {
192         try {
193           extension.beforeCompileTaskExecution(compilingTool, task, _options, diagnosticConsumer);
194         }
195         catch (Throwable e) {
196           fileManager.getContext().reportMessage(Diagnostic.Kind.MANDATORY_WARNING, extension.getClass() + " : " + e.getMessage());
197           e.printStackTrace(System.err);
198         }
199       }
200
201       //if (!IS_VM_6_VERSION) { //todo!
202       //  // Do not add the processor for JDK 1.6 because of the bugs in javac
203       //  // The processor's presence may lead to NPE and resolve bugs in compiler
204       //  final JavacASTAnalyser analyzer = new JavacASTAnalyser(outConsumer, !annotationProcessingEnabled);
205       //  task.setProcessors(Collections.singleton(analyzer));
206       //}
207       return task.call();
208     }
209     catch(IllegalArgumentException e) {
210       diagnosticConsumer.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, e.getMessage()));
211     }
212     catch(IllegalStateException e) {
213       diagnosticConsumer.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, e.getMessage()));
214     }
215     catch (CompilationCanceledException ignored) {
216       handleCancelException(diagnosticConsumer);
217     }
218     catch (RuntimeException e) {
219       final Throwable cause = e.getCause();
220       if (cause != null) {
221         if (cause instanceof CompilationCanceledException) {
222           handleCancelException(diagnosticConsumer);
223         }
224         else {
225           diagnosticConsumer.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, buildCompilerErrorMessage(e)));
226           throw e;
227         }
228       }
229       else {
230         throw e;
231       }
232     }
233     finally {
234       fileManager.close();
235       if (usingJavac) {
236         cleanupJavacNameTable();
237       }
238     }
239     return false;
240   }
241
242   private static String buildCompilerErrorMessage(Throwable e) {
243     return new Object() {
244       final StringBuilder buf = new StringBuilder();
245       String collectAllMessages(Throwable e, Set<Throwable> processed) {
246         if (e != null && processed.add(e)) {
247           final String msg = e.getMessage();
248           if (msg != null && !msg.trim().isEmpty() && buf.indexOf(msg) < 0) {
249             if (buf.length() > 0) {
250               buf.append("\n");
251             }
252             buf.append(msg);
253           }
254           return collectAllMessages(e.getCause(), processed);
255         }
256         return buf.toString();
257       }
258     }.collectAllMessages(e, new HashSet<Throwable>());
259   }
260
261
262   // Workaround for javac bug:
263   // the internal ClientCodeWrapper class may not implement some interface-declared methods
264   // which throw UnsupportedOperationException instead of delegating to our JpsFileManager instance
265   private static JavaCompiler.CompilationTask tryInstallClientCodeWrapperCallDispatcher(JavaCompiler.CompilationTask task, StandardJavaFileManager delegateTo) {
266     try {
267       final Class<? extends JavaCompiler.CompilationTask> taskClass = task.getClass();
268       final Field contextField = findField(taskClass, new BooleanFunction<Field>() {
269         private final Class<?> contextClass = Class.forName("com.sun.tools.javac.util.Context", true, taskClass.getClassLoader());
270         @Override
271         public boolean fun(Field field) {
272           return contextClass.equals(field.getType());
273         }
274       });
275       if (contextField != null) {
276         final Object contextObject = contextField.get(task);
277         final Method getMethod = contextObject.getClass().getMethod("get", Class.class);
278         final Object currentManager = getMethod.invoke(contextObject, JavaFileManager.class);
279         if (isClientCodeWrapper(currentManager, delegateTo)) {
280           final Method putMethod = contextObject.getClass().getMethod("put", Class.class, Object.class);
281           putMethod.invoke(contextObject, JavaFileManager.class, null);  // must clear previous value first
282           putMethod.invoke(contextObject, JavaFileManager.class, wrapWithCallDispatcher(
283             StandardJavaFileManager.class, (StandardJavaFileManager)currentManager, Object.class, delegateTo)
284           );
285         }
286         else {
287           installCallDispatcherRecursively(currentManager, delegateTo, new HashSet<Object>());
288         }
289       }
290     }
291     catch (Throwable ignored) {
292     }
293     return task;
294   }
295
296   private static void installCallDispatcherRecursively(final Object obj, final StandardJavaFileManager delegateTo, final Set<Object> visited) {
297     if (obj instanceof JavaFileManager && visited.add(obj)) {
298       forEachField(obj.getClass(), new BooleanFunction<Field>() {
299         @Override
300         public boolean fun(Field field) {
301           try {
302             if (JavaFileManager.class.isAssignableFrom(field.getType())) {
303               final Object value = field.get(obj);
304               if (isClientCodeWrapper(value, delegateTo)) {
305                 field.set(obj, wrapWithCallDispatcher(StandardJavaFileManager.class, (StandardJavaFileManager)value, Object.class, delegateTo));
306               }
307               else {
308                 installCallDispatcherRecursively(value, delegateTo, visited);
309               }
310             }
311           }
312           catch (Throwable ignored) {
313           }
314           return true;
315         }
316       });
317     }
318   }
319
320   private static boolean isClientCodeWrapper(final Object obj, final StandardJavaFileManager delegateTo) {
321     return obj instanceof StandardJavaFileManager && findField(obj.getClass(), new BooleanFunction<Field>() {
322       @Override
323       public boolean fun(Field f) {
324         try {
325           return f.get(obj) == delegateTo;
326         }
327         catch (Throwable ignored) {
328           return false;
329         }
330       }
331     }) != null;
332   }
333
334   private static Field findField(final Class<?> aClass, final BooleanFunction<Field> cond) {
335     final Field[] res = new Field[]{null};
336     forEachField(aClass, new BooleanFunction<Field>() {
337       @Override
338       public boolean fun(Field field) {
339         if (!cond.fun(field)) {
340           return true; // continue
341         }
342         res[0] = field;
343         return false; // stop
344       }
345     });
346     return res[0];
347   }
348
349   private static void forEachField(final Class<?> aClass, final BooleanFunction<Field> func) {
350     for (Class<?> from = aClass; from != null && !Object.class.equals(from); from = from.getSuperclass()) {
351       for (Field field : from.getDeclaredFields()) {
352         try {
353           if (!field.isAccessible()) {
354             field.setAccessible(true);
355           }
356           if (!func.fun(field)) {
357             return;
358           }
359         }
360         catch (Throwable ignored) {
361         }
362       }
363     }
364   }
365
366   private static void setLocation(JpsJavacFileManager fileManager, String locationId, Iterable<? extends File> path) throws IOException {
367     JavaFileManager.Location location = StandardLocation.locationFor(locationId);
368     if (location != null) { // if this option is supported
369       fileManager.setLocation(location, path);
370     }
371   }
372
373   private static Iterable<? extends File> getLocation(JpsJavacFileManager fileManager, String locationId) {
374     final JavaFileManager.Location location = StandardLocation.locationFor(locationId);
375     return location != null? fileManager.getLocation(location) : null;
376   }
377
378   // methods added to newer versions of StandardJavaFileManager interfaces have default implementations that
379   // do not delegate to corresponding methods of FileManager's base implementation
380   // this proxy object makes sure the calls, not implemented in our file manager, are dispatched further to the base file manager implementation
381   private static <T> T wrapWithCallDispatcher(final Class<T> ifaceClass, final T targetObject, final Class<?> parentToTopSearchAt, final T delegateTo) {
382     //return fileManager;
383     return ifaceClass.cast(Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), new Class[]{ifaceClass}, new InvocationHandler() {
384       private final Map<Method, Boolean> ourImplStatus = Collections.synchronizedMap(new HashMap<Method, Boolean>());
385       @Override
386       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
387         try {
388           return method.invoke(getApiCallHandler(method, parentToTopSearchAt), args);
389         }
390         catch (InvocationTargetException e) {
391           final Throwable cause = e.getCause();
392           throw cause != null? cause : e;
393         }
394       }
395
396       private T getApiCallHandler(Method method, Class<?> parentToTopSearchAt) {
397         Boolean isImplemented = ourImplStatus.get(method);
398         if (isImplemented == null) {
399           isImplemented = Boolean.FALSE;
400           // important: look for implemented methods starting from the actual class
401           Class<?> aClass = targetObject.getClass();
402           while (!(parentToTopSearchAt.equals(aClass) || Object.class.equals(aClass))) {
403             try {
404               aClass.getDeclaredMethod(method.getName(), method.getParameterTypes());
405               isImplemented = Boolean.TRUE;
406               break;
407             }
408             catch (NoSuchMethodException e) {
409               aClass = aClass.getSuperclass();
410             }
411           }
412           ourImplStatus.put(method, isImplemented);
413         }
414         return isImplemented ? targetObject : delegateTo;
415       }
416     }));
417   }
418
419   private static boolean isJavacBefore9(JavaCompilingTool compilingTool) {
420     // since java 9 internal API's used by the optimizedFileManager have changed
421     return compilingTool instanceof JavacCompilerTool && (JAVA_RUNTIME_VERSION.startsWith("1.8.") || JAVA_RUNTIME_VERSION.startsWith("1.7.") || JAVA_RUNTIME_VERSION.startsWith("1.6."));
422   }
423
424   private static void handleCancelException(DiagnosticOutputConsumer diagnosticConsumer) {
425     diagnosticConsumer.report(new JpsInfoDiagnostic("Compilation was canceled"));
426   }
427
428   private static boolean isAnnotationProcessingEnabled(final Collection<String> options) {
429     return !options.contains("-proc:none");
430   }
431
432   private static Collection<String> prepareOptions(final Collection<String> options, @NotNull JavaCompilingTool compilingTool) {
433     final List<String> result = new ArrayList<String>(compilingTool.getDefaultCompilerOptions());
434     boolean skip = false;
435     for (String option : options) {
436       if (FILTERED_OPTIONS.contains(option)) {
437         skip = true;
438         continue;
439       }
440       if (!skip) {
441         if (!FILTERED_SINGLE_OPTIONS.contains(option) && !compilingTool.getDefaultCompilerOptions().contains(option)) {
442           result.add(option);
443         }
444       }
445       skip = false;
446     }
447     compilingTool.preprocessOptions(result);
448     return result;
449   }
450
451   private static Collection<? extends File> buildPlatformClasspath(Collection<? extends File> platformClasspath, Collection<String> options) {
452     final Map<PathOption, String> argsMap = new HashMap<PathOption, String>();
453     for (Iterator<String> iterator = options.iterator(); iterator.hasNext(); ) {
454       final String arg = iterator.next();
455       for (PathOption pathOption : PathOption.values()) {
456         if (pathOption.parse(argsMap, arg, iterator)) {
457           break;
458         }
459       }
460     }
461     if (argsMap.isEmpty()) {
462       return platformClasspath;
463     }
464
465     final List<File> result = new ArrayList<File>();
466     appendFiles(argsMap, PathOption.PREPEND_CP, result, false);
467     appendFiles(argsMap, PathOption.ENDORSED, result, true);
468     appendFiles(argsMap, PathOption.D_ENDORSED, result, true);
469     result.addAll(platformClasspath);
470     appendFiles(argsMap, PathOption.APPEND_CP, result, false);
471     appendFiles(argsMap, PathOption.EXTDIRS, result, true);
472     appendFiles(argsMap, PathOption.D_EXTDIRS, result, true);
473     return result;
474   }
475
476   private static void appendFiles(Map<PathOption, String> args, PathOption option, Collection<? super File> container, boolean listDir) {
477     final String path = args.get(option);
478     if (path == null) {
479       return;
480     }
481     final StringTokenizer tokenizer = new StringTokenizer(path, File.pathSeparator, false);
482     while (tokenizer.hasMoreTokens()) {
483       final File file = new File(tokenizer.nextToken());
484       if (listDir) {
485         final File[] files = file.listFiles();
486         if (files != null) {
487           for (File f : files) {
488             final String fName = f.getName();
489             if (fName.endsWith(".jar") || fName.endsWith(".zip")) {
490               container.add(f);
491             }
492           }
493         }
494       }
495       else {
496         container.add(file);
497       }
498     }
499   }
500
501   enum PathOption {
502     PREPEND_CP("-Xbootclasspath/p:"),
503     ENDORSED("-endorseddirs"), D_ENDORSED("-Djava.endorsed.dirs="),
504     APPEND_CP("-Xbootclasspath/a:"),
505     EXTDIRS("-extdirs"), D_EXTDIRS("-Djava.ext.dirs=");
506
507     private final String myArgName;
508     private final boolean myIsSuffix;
509
510     PathOption(String name) {
511       myArgName = name;
512       myIsSuffix = name.endsWith("=") || name.endsWith(":");
513     }
514
515     public boolean parse(Map<PathOption, String> container, String arg, Iterator<String> rest) {
516       if (myIsSuffix) {
517         if (arg.startsWith(myArgName)) {
518           container.put(this, arg.substring(myArgName.length()));
519           return true;
520         }
521       }
522       else {
523         if (arg.equals(myArgName)) {
524           if (rest.hasNext()) {
525             container.put(this, rest.next());
526           }
527           return true;
528         }
529       }
530       return false;
531     }
532   }
533
534   private static class ContextImpl implements JpsJavacFileManager.Context {
535     private final StandardJavaFileManager myStdManager;
536     private final DiagnosticOutputConsumer myOutConsumer;
537     private final OutputFileConsumer myOutputFileSink;
538     private final ModulePath myModulePath;
539     private final CanceledStatus myCanceledStatus;
540
541     ContextImpl(@NotNull JavaCompiler compiler,
542                 @NotNull DiagnosticOutputConsumer outConsumer,
543                 @NotNull OutputFileConsumer sink,
544                 @NotNull ModulePath modulePath,
545                 CanceledStatus canceledStatus) {
546       myOutConsumer = outConsumer;
547       myOutputFileSink = sink;
548       myModulePath = modulePath;
549       myCanceledStatus = canceledStatus;
550       myStdManager = compiler.getStandardFileManager(outConsumer, Locale.US, null);
551     }
552
553     @Nullable
554     @Override
555     public String getExplodedAutomaticModuleName(File pathElement) {
556       return myModulePath.getModuleName(pathElement);
557     }
558
559     @Override
560     public boolean isCanceled() {
561       return myCanceledStatus.isCanceled();
562     }
563
564     @NotNull
565     @Override
566     public StandardJavaFileManager getStandardFileManager() {
567       return myStdManager;
568     }
569
570     @Override
571     public void reportMessage(final Diagnostic.Kind kind, String message) {
572       myOutConsumer.report(new PlainMessageDiagnostic(kind, message));
573     }
574
575     @Override
576     public void consumeOutputFile(@NotNull final OutputFileObject cls) {
577       myOutputFileSink.save(cls);
578     }
579   }
580
581   private static final class NameTableCleanupDataHolder {
582     static final Object emptyList;
583     static final Field freelistField;
584
585     static {
586       try {
587         final ClassLoader loader = ToolProvider.getSystemToolClassLoader();
588         if (loader == null) {
589           throw new RuntimeException("no tools provided");
590         }
591
592         final Class<?> listClass = Class.forName("com.sun.tools.javac.util.List", true, loader);
593         final Method nilMethod = listClass.getDeclaredMethod("nil");
594         emptyList = nilMethod.invoke(null);
595
596         Field freelistRef;
597         try {
598           // trying jdk 6
599           freelistRef = Class.forName("com.sun.tools.javac.util.Name$Table", true, loader).getDeclaredField("freelist");
600         }
601         catch (Exception e) {
602           // trying jdk 7
603           freelistRef = Class.forName("com.sun.tools.javac.util.SharedNameTable", true, loader).getDeclaredField("freelist");
604         }
605         freelistRef.setAccessible(true);
606         freelistField = freelistRef;
607       }
608       catch(RuntimeException e) {
609         throw e;
610       }
611       catch (Exception e) {
612         throw new RuntimeException(e);
613       }
614     }
615   }
616
617   private static void cleanupJavacNameTable() {
618     try {
619       final Field freelistField = NameTableCleanupDataHolder.freelistField;
620       final Object emptyList = NameTableCleanupDataHolder.emptyList;
621       // both parameters should be non-null if properly initialized
622       if (freelistField != null && emptyList != null) {
623         // the access to static 'freeList' field is synchronized inside javac, so we must use "synchronized" too
624         synchronized (freelistField.getDeclaringClass()) {
625           freelistField.set(null, emptyList);
626         }
627       }
628     }
629     catch (Throwable ignored) {
630     }
631   }
632
633   private static final class ZipFileIndexCleanupDataHolder {
634     @Nullable
635     static final Method cacheInstanceGetter;
636     @Nullable
637     static final Method cacheClearMethod;
638
639     static {
640       Method getterMethod = null;
641       Method clearMethod = null;
642       try {
643         //trying JDK 6
644         clearMethod = Class.forName("com.sun.tools.javac.zip.ZipFileIndex").getDeclaredMethod("clearCache");
645         clearMethod.setAccessible(true);
646       }
647       catch (Throwable e) {
648         try {
649           final Class<?> cacheClass = Class.forName("com.sun.tools.javac.file.ZipFileIndexCache");
650           clearMethod = cacheClass.getDeclaredMethod("clearCache");
651           getterMethod = cacheClass.getDeclaredMethod("getSharedInstance");
652           clearMethod.setAccessible(true);
653           getterMethod.setAccessible(true);
654         }
655         catch (Throwable ignored2) {
656           clearMethod = null;
657           getterMethod = null;
658         }
659       }
660
661       cacheInstanceGetter = getterMethod;
662       cacheClearMethod = clearMethod;
663     }
664   }
665
666   private static volatile boolean zipCacheCleanupPossible = true;
667
668   public static void clearCompilerZipFileCache() {
669     if (zipCacheCleanupPossible) {
670       final Method clearMethod = ZipFileIndexCleanupDataHolder.cacheClearMethod;
671       if (clearMethod != null) {
672         final Method getter = ZipFileIndexCleanupDataHolder.cacheInstanceGetter;
673         try {
674           Object instance = getter != null? getter.invoke(null) : null;
675           clearMethod.invoke(instance);
676         }
677         catch (Throwable e) {
678           zipCacheCleanupPossible = false;
679         }
680       }
681       else {
682         zipCacheCleanupPossible = false;
683       }
684     }
685   }
686 }