1 package org.jetbrains.jps.incremental.java;
3 import com.intellij.compiler.notNullVerification.NotNullVerifyingInstrumenter;
4 import com.intellij.execution.process.BaseOSProcessHandler;
5 import com.intellij.openapi.application.PathManager;
6 import com.intellij.openapi.util.Key;
7 import com.intellij.openapi.util.Ref;
8 import com.intellij.openapi.util.io.FileUtil;
9 import com.intellij.openapi.util.text.StringUtil;
10 import com.intellij.uiDesigner.compiler.*;
11 import com.intellij.uiDesigner.core.GridConstraints;
12 import com.intellij.uiDesigner.lw.CompiledClassPropertiesProvider;
13 import com.intellij.uiDesigner.lw.LwRootContainer;
14 import org.jetbrains.annotations.NotNull;
15 import org.jetbrains.annotations.Nullable;
16 import org.jetbrains.ether.dependencyView.Callbacks;
17 import org.jetbrains.ether.dependencyView.Mappings;
18 import org.jetbrains.jps.Module;
19 import org.jetbrains.jps.ModuleChunk;
20 import org.jetbrains.jps.Project;
21 import org.jetbrains.jps.ProjectPaths;
22 import org.jetbrains.jps.api.GlobalOptions;
23 import org.jetbrains.jps.api.RequestFuture;
24 import org.jetbrains.jps.incremental.*;
25 import org.jetbrains.jps.incremental.messages.BuildMessage;
26 import org.jetbrains.jps.incremental.messages.CompilerMessage;
27 import org.jetbrains.jps.incremental.messages.FileGeneratedEvent;
28 import org.jetbrains.jps.incremental.messages.ProgressMessage;
29 import org.jetbrains.jps.incremental.storage.BuildDataManager;
30 import org.jetbrains.jps.incremental.storage.SourceToFormMapping;
31 import org.jetbrains.jps.javac.*;
32 import org.objectweb.asm.ClassReader;
33 import org.objectweb.asm.ClassWriter;
34 import org.objectweb.asm.Opcodes;
35 import org.objectweb.asm.commons.EmptyVisitor;
39 import java.net.MalformedURLException;
40 import java.net.ServerSocket;
42 import java.net.URLClassLoader;
44 import java.util.concurrent.ExecutionException;
45 import java.util.concurrent.ExecutorService;
48 * @author Eugene Zhuravlev
51 public class JavaBuilder extends ModuleLevelBuilder {
52 public static final String BUILDER_NAME = "java";
53 private static final String JAVA_EXTENSION = ".java";
54 private static final String FORM_EXTENSION = ".form";
55 public static final boolean USE_EMBEDDED_JAVAC = System.getProperty(GlobalOptions.USE_EXTERNAL_JAVAC_OPTION) == null;
57 private static final FileFilter JAVA_SOURCES_FILTER = new FileFilter() {
58 public boolean accept(File file) {
59 return file.getPath().endsWith(JAVA_EXTENSION);
62 private static final FileFilter FORM_SOURCES_FILTER = new FileFilter() {
63 public boolean accept(File file) {
64 return file.getPath().endsWith(FORM_EXTENSION);
68 private static final Key<Callbacks.Backend> DELTA_MAPPINGS_CALLBACK_KEY = Key.create("_dependency_data_");
69 private final ExecutorService myTaskRunner;
70 private final List<ClassPostProcessor> myClassProcessors = new ArrayList<ClassPostProcessor>();
72 public JavaBuilder(ExecutorService tasksExecutor) {
73 myTaskRunner = tasksExecutor;
74 //add here class processors in the sequence they should be executed
75 myClassProcessors.add(new ClassPostProcessor() {
76 public void process(CompileContext context, OutputFileObject out) {
77 final Callbacks.Backend callback = DELTA_MAPPINGS_CALLBACK_KEY.get(context);
78 if (callback != null) {
79 final OutputFileObject.Content content = out.getContent();
80 final File srcFile = out.getSourceFile();
81 if (srcFile != null && content != null) {
82 final String outputPath = FileUtil.toSystemIndependentName(out.getFile().getPath());
83 final String sourcePath = FileUtil.toSystemIndependentName(srcFile.getPath());
84 final RootDescriptor moduleAndRoot = context.getModuleAndRoot(srcFile);
85 final BuildDataManager dataManager = context.getDataManager();
86 if (moduleAndRoot != null) {
88 final String moduleName = moduleAndRoot.module.getName().toLowerCase(Locale.US);
89 dataManager.getSourceToOutputMap(moduleName, context.isCompilingTests()).appendData(sourcePath, outputPath);
92 context.processMessage(new CompilerMessage(BUILDER_NAME, e));
95 final ClassReader reader = new ClassReader(content.getBuffer(), content.getOffset(), content.getLength());
96 //noinspection SynchronizationOnLocalVariableOrMethodParameter
97 synchronized (dataManager.getMappings()) {
98 callback.associate(outputPath, Callbacks.getDefaultLookup(sourcePath), reader);
107 public String getName() {
111 public String getDescription() {
112 return "Java Builder";
115 private static final Key<Set<File>> TEMPORARY_SOURCE_ROOTS_KEY = Key.create("_additional_source_roots_");
117 public static void addTempSourcePathRoot(CompileContext context, File root) {
118 Set<File> roots = TEMPORARY_SOURCE_ROOTS_KEY.get(context);
120 roots = new HashSet<File>();
121 TEMPORARY_SOURCE_ROOTS_KEY.set(context, roots);
126 public ExitCode build(final CompileContext context, final ModuleChunk chunk) throws ProjectBuildException {
128 final Set<File> filesToCompile = new HashSet<File>();
129 final Set<File> formsToCompile = new HashSet<File>();
131 context.processFilesToRecompile(chunk, new FileProcessor() {
132 public boolean apply(Module module, File file, String sourceRoot) throws Exception {
133 if (JAVA_SOURCES_FILTER.accept(file)) {
134 filesToCompile.add(file);
136 else if (FORM_SOURCES_FILTER.accept(file)) {
137 formsToCompile.add(file);
143 // force compilation of bound source file if the form is dirty
144 if (!context.isProjectRebuild()) {
145 for (File form : formsToCompile) {
146 final RootDescriptor descriptor = context.getModuleAndRoot(form);
147 if (descriptor != null) {
148 for (RootDescriptor rd : context.getModuleRoots(descriptor.module)) {
149 final File boundSource = getBoundSource(rd.root, form);
150 if (boundSource != null) {
151 filesToCompile.add(boundSource);
158 // form should be considered dirty if the class it is bound to is dirty
159 final SourceToFormMapping sourceToFormMap = context.getDataManager().getSourceToFormMap();
160 for (File srcFile : filesToCompile) {
161 final String srcPath = srcFile.getPath();
162 final String formPath = sourceToFormMap.getState(srcPath);
163 if (formPath != null) {
164 final File formFile = new File(formPath);
165 if (formFile.exists()) {
166 context.markDirty(formFile);
167 formsToCompile.add(formFile);
169 sourceToFormMap.remove(srcPath);
175 return compile(context, chunk, filesToCompile, formsToCompile);
177 catch (ProjectBuildException e) {
180 catch (Exception e) {
181 String message = e.getMessage();
182 if (message == null) {
183 final ByteArrayOutputStream out = new ByteArrayOutputStream();
184 e.printStackTrace(new PrintStream(out));
185 message = "Internal error: \n" + out.toString();
187 context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, message));
188 throw new ProjectBuildException(message, e);
193 private static File getBoundSource(File srcRoot, File formFile) throws IOException {
194 final String boundClassName = FormsParsing.readBoundClassName(formFile);
195 if (boundClassName == null) {
198 String relPath = boundClassName.replace('.', '/') + JAVA_EXTENSION;
200 final File candidate = new File(srcRoot, relPath);
201 if (candidate.exists()) {
202 return candidate.isFile()? candidate : null;
204 final int index = relPath.lastIndexOf('/');
208 relPath = relPath.substring(0, index) + JAVA_EXTENSION;
212 private ExitCode compile(final CompileContext context, ModuleChunk chunk, Collection<File> files, Collection<File> forms) throws Exception {
213 ExitCode exitCode = ExitCode.OK;
215 final boolean hasSourcesToCompile = !files.isEmpty() || !forms.isEmpty();
217 if (!hasSourcesToCompile && !context.hasRemovedSources()) {
221 final ProjectPaths paths = context.getProjectPaths();
223 final boolean addNotNullAssertions = context.getProject().getCompilerConfiguration().isAddNotNullAssertions();
225 final Collection<File> classpath = paths.getCompilationClasspath(chunk, context.isCompilingTests(), false/*context.isProjectRebuild()*/);
226 final Collection<File> platformCp = paths.getPlatformCompilationClasspath(chunk, context.isCompilingTests(), false/*context.isProjectRebuild()*/);
227 final Map<File, Set<File>> outs = buildOutputDirectoriesMap(context, chunk);
229 // begin compilation round
230 final DiagnosticSink diagnosticSink = new DiagnosticSink(context);
231 final OutputFilesSink outputSink = new OutputFilesSink(context);
232 final Mappings delta = context.createDelta();
233 DELTA_MAPPINGS_CALLBACK_KEY.set(context, delta.getCallback());
235 if (hasSourcesToCompile) {
236 final Set<File> sourcePath = TEMPORARY_SOURCE_ROOTS_KEY.get(context,Collections.<File>emptySet());
238 final boolean compiledOk = compileJava(
239 files, classpath, platformCp, sourcePath, outs, context, diagnosticSink, outputSink
242 final Map<File, String> chunkSourcePath = ProjectPaths.getSourceRootsWithDependents(chunk, context.isCompilingTests());
243 final ClassLoader compiledClassesLoader = createInstrumentationClassLoader(classpath, platformCp, chunkSourcePath, outputSink);
245 if (!forms.isEmpty()) {
247 context.processMessage(new ProgressMessage("Instrumenting forms [" + chunk.getName() + "]"));
248 instrumentForms(context, chunk, chunkSourcePath, compiledClassesLoader, forms, outputSink);
251 context.processMessage(new ProgressMessage("Finished instrumenting forms [" + chunk.getName() + "]"));
255 if (addNotNullAssertions) {
257 context.processMessage(new ProgressMessage("Adding NotNull assertions [" + chunk.getName() + "]"));
258 instrumentNotNull(context, outputSink, compiledClassesLoader);
261 context.processMessage(new ProgressMessage("Finished adding NotNull assertions [" + chunk.getName() + "]"));
265 if (!compiledOk && diagnosticSink.getErrorCount() == 0) {
266 throw new ProjectBuildException("Compilation failed: internal java compiler error");
268 if (diagnosticSink.getErrorCount() > 0) {
269 throw new ProjectBuildException("Compilation failed: errors: " + diagnosticSink.getErrorCount() + "; warnings: " + diagnosticSink.getWarningCount());
274 outputSink.writePendingData();
276 final Set<File> successfullyCompiled = outputSink.getSuccessfullyCompiled();
277 DELTA_MAPPINGS_CALLBACK_KEY.set(context, null);
279 if (updateMappings(context, delta, chunk, files, successfullyCompiled)) {
280 exitCode = ExitCode.ADDITIONAL_PASS_REQUIRED;
284 if (exitCode != ExitCode.ADDITIONAL_PASS_REQUIRED) {
285 final Set<File> tempRoots = TEMPORARY_SOURCE_ROOTS_KEY.get(context);
286 TEMPORARY_SOURCE_ROOTS_KEY.set(context, null);
287 if (tempRoots != null) {
288 for (File root : tempRoots) {
289 FileUtil.delete(root);
296 private boolean compileJava(Collection<File> files, Collection<File> classpath, Collection<File> platformCp, Collection<File> sourcePath, Map<File, Set<File>> outs, CompileContext context, DiagnosticOutputConsumer diagnosticSink, final OutputFileConsumer outputSink) throws Exception {
297 final List<String> options = getCompilationOptions(context);
298 final ClassProcessingConsumer classesConsumer = new ClassProcessingConsumer(context, outputSink);
301 if (USE_EMBEDDED_JAVAC) {
302 rc = JavacMain.compile(options, files, classpath, platformCp, sourcePath, outs, diagnosticSink, classesConsumer, context.getCancelStatus());
305 final JavacServerClient client = ensureJavacServerLaunched(context);
306 final RequestFuture<JavacServerResponseHandler> future = client.sendCompileRequest(options, files, classpath, platformCp, sourcePath, outs, diagnosticSink, classesConsumer);
310 catch (InterruptedException e) {
311 e.printStackTrace(System.err);
313 catch (ExecutionException e) {
314 e.printStackTrace(System.err);
316 rc = future.getResponseHandler().isTerminatedSuccessfully();
321 classesConsumer.ensurePendingTasksCompleted();
325 private static JavacServerClient ensureJavacServerLaunched(CompileContext context) throws Exception {
326 final ExternalJavacDescriptor descriptor = ExternalJavacDescriptor.KEY.get(context);
327 if (descriptor != null) {
328 return descriptor.client;
331 final String vmExecPath = System.getProperty(GlobalOptions.VM_EXE_PATH_OPTION, System.getProperty("java.home") + "/bin/java");
332 final String hostString = System.getProperty(GlobalOptions.HOSTNAME_OPTION, "localhost");
333 final int port = findFreePort();
334 final int heapSize = getJavacServerHeapSize(context);
336 final BaseOSProcessHandler processHandler = JavacServerBootstrap.launchJavacServer(
337 vmExecPath, heapSize, port, Paths.getSystemRoot(), getCompilationVMOptions(context)
339 final JavacServerClient client = new JavacServerClient();
341 client.connect(hostString, port);
343 catch (Throwable ex) {
344 processHandler.destroyProcess();
345 throw new Exception("Failed to connect to external javac process: ", ex);
347 ExternalJavacDescriptor.KEY.set(context, new ExternalJavacDescriptor(processHandler, client));
351 private static int findFreePort() {
353 final ServerSocket serverSocket = new ServerSocket(0);
355 return serverSocket.getLocalPort();
358 //workaround for linux : calling close() immediately after opening socket
359 //may result that socket is not closed
360 synchronized(serverSocket) {
362 serverSocket.wait(1);
364 catch (Throwable ignored) {
367 serverSocket.close();
370 catch (IOException e) {
371 e.printStackTrace(System.err);
372 return JavacServer.DEFAULT_SERVER_PORT;
376 private static int getJavacServerHeapSize(CompileContext context) {
378 final Project project = context.getProject();
379 final Map<String, String> javacOpts = project.getCompilerConfiguration().getJavacOptions();
380 final String hSize = javacOpts.get("MAXIMUM_HEAP_SIZE");
383 heapSize = Integer.parseInt(hSize);
385 catch (NumberFormatException ignored) {
391 private static ClassLoader createInstrumentationClassLoader(Collection<File> classpath, Collection<File> platformCp, Map<File, String> chunkSourcePath, OutputFilesSink outputSink)
392 throws MalformedURLException {
393 final List<URL> urls = new ArrayList<URL>();
394 for (Collection<File> cp : Arrays.asList(platformCp, classpath)) {
395 for (File file : cp) {
396 urls.add(file.toURI().toURL());
399 urls.add(getResourcePath(GridConstraints.class).toURI().toURL()); // forms_rt.jar
400 //urls.add(getResourcePath(CellConstraints.class).toURI().toURL()); // jgoodies-forms
401 for (File file : chunkSourcePath.keySet()) {
402 urls.add(file.toURI().toURL());
404 return new CompiledClassesLoader(outputSink, urls.toArray(new URL[urls.size()]));
407 private static final Key<List<String>> JAVAC_OPTIONS = Key.create("_javac_options_");
408 private static final Key<List<String>> JAVAC_VM_OPTIONS = Key.create("_javac_vm_options_");
410 private static List<String> getCompilationVMOptions(CompileContext context) {
411 List<String> cached = JAVAC_VM_OPTIONS.get(context);
412 if (cached == null) {
413 loadJavacOptions(context);
414 cached = JAVAC_VM_OPTIONS.get(context);
419 private static List<String> getCompilationOptions(CompileContext context) {
420 List<String> cached = JAVAC_OPTIONS.get(context);
421 if (cached == null) {
422 loadJavacOptions(context);
423 cached = JAVAC_OPTIONS.get(context);
428 private static void loadJavacOptions(CompileContext context) {
429 final List<String> options = new ArrayList<String>();
430 final List<String> vmOptions = new ArrayList<String>();
432 options.add("-verbose");
434 final Project project = context.getProject();
435 final Map<String, String> javacOpts = project.getCompilerConfiguration().getJavacOptions();
436 final boolean debugInfo = !"false".equals(javacOpts.get("DEBUGGING_INFO"));
437 final boolean nowarn = "true".equals(javacOpts.get("GENERATE_NO_WARNINGS"));
438 final boolean deprecation = !"false".equals(javacOpts.get("DEPRECATION"));
443 options.add("-deprecation");
446 options.add("-nowarn");
449 final String customArgs = javacOpts.get("ADDITIONAL_OPTIONS_STRING");
450 boolean isEncodingSet = false;
451 if (customArgs != null) {
452 final StringTokenizer tokenizer = new StringTokenizer(customArgs, " \t\r\n");
453 while(tokenizer.hasMoreTokens()) {
454 final String token = tokenizer.nextToken();
455 if ("-g".equals(token) || "-deprecation".equals(token) || "-nowarn".equals(token) || "-verbose".equals(token)){
458 if (token.startsWith("-J-")) {
459 vmOptions.add(token.substring("-J".length()));
464 if ("-encoding".equals(token)) {
465 isEncodingSet = true;
470 if (!isEncodingSet && !StringUtil.isEmpty(project.getProjectCharset())) {
471 options.add("-encoding");
472 options.add(project.getProjectCharset());
475 JAVAC_OPTIONS.set(context, options);
476 JAVAC_VM_OPTIONS.set(context, vmOptions);
479 private static Map<File, Set<File>> buildOutputDirectoriesMap(CompileContext context, ModuleChunk chunk) {
480 final Map<File, Set<File>> map = new HashMap<File, Set<File>>();
481 final boolean compilingTests = context.isCompilingTests();
482 for (Module module : chunk.getModules()) {
483 final String outputPath;
484 final Collection<String> srcPaths;
485 if (compilingTests) {
486 outputPath = module.getTestOutputPath();
487 srcPaths = module.getTestRoots();
490 outputPath = module.getOutputPath();
491 srcPaths = module.getSourceRoots();
493 final Set<File> roots = new HashSet<File>();
494 for (String path : srcPaths) {
495 roots.add(new File(path));
497 map.put(new File(outputPath), roots);
502 // todo: probably instrument other NotNull-like annotations defined in project settings?
503 private static void instrumentNotNull(CompileContext context, OutputFilesSink sink, final ClassLoader loader) {
504 for (final OutputFileObject fileObject : sink.getFileObjects()) {
505 final OutputFileObject.Content originalContent = fileObject.getContent();
506 final ClassReader reader = new ClassReader(originalContent.getBuffer(), originalContent.getOffset(), originalContent.getLength());
507 final int version = getClassFileVersion(reader);
508 if (version >= Opcodes.V1_5) {
509 boolean success = false;
510 final ClassWriter writer = new InstrumenterClassWriter(getAsmClassWriterFlags(version), loader);
512 final NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer);
513 reader.accept(instrumenter, 0);
514 if (instrumenter.isModification()) {
515 fileObject.updateContent(writer.toByteArray());
519 catch (Throwable e) {
520 final StringBuilder msg = new StringBuilder();
521 msg.append("@NotNull instrumentation failed ");
522 final File sourceFile = fileObject.getSourceFile();
523 if (sourceFile != null) {
524 msg.append(" for ").append(sourceFile.getName());
526 msg.append(": ").append(e.getMessage());
527 context.processMessage(
528 new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, msg.toString(), sourceFile != null ? sourceFile.getPath() : null));
532 sink.markError(fileObject);
539 private static void instrumentForms(
540 CompileContext context,
542 final Map<File, String> chunkSourcePath,
543 final ClassLoader loader,
544 Collection<File> formsToInstrument,
545 OutputFilesSink outputSink) throws ProjectBuildException {
547 final Map<String, File> class2form = new HashMap<String, File>();
548 final SourceToFormMapping sourceToFormMap = context.getDataManager().getSourceToFormMap();
550 final Map<String, OutputFileObject> compiledClassNames = new HashMap<String, OutputFileObject>();
551 for (OutputFileObject fileObject : outputSink.getFileObjects()) {
552 compiledClassNames.put(fileObject.getClassName(), fileObject);
555 final MyNestedFormLoader nestedFormsLoader = new MyNestedFormLoader(
556 chunkSourcePath, ProjectPaths.getOutputPathsWithDependents(chunk, context.isCompilingTests())
559 for (File formFile : formsToInstrument) {
560 final LwRootContainer rootContainer;
562 rootContainer = Utils.getRootContainer(formFile.toURI().toURL(), new CompiledClassPropertiesProvider(loader));
564 catch (AlienFormFileException e) {
565 // ignore non-IDEA forms
568 catch (Exception e) {
569 throw new ProjectBuildException("Cannot process form file " + formFile.getAbsolutePath(), e);
572 final String classToBind = rootContainer.getClassToBind();
573 if (classToBind == null) {
577 final OutputFileObject outputClassFile = findClassFile(compiledClassNames, classToBind);
578 if (outputClassFile == null) {
579 context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.WARNING, "Class to bind does not exist: " + classToBind, formFile.getAbsolutePath()));
583 final File alreadyProcessedForm = class2form.get(classToBind);
584 if (alreadyProcessedForm != null) {
585 context.processMessage(new CompilerMessage(
586 BUILDER_NAME, BuildMessage.Kind.WARNING,
587 formFile.getAbsolutePath() + ": The form is bound to the class " + classToBind + ".\nAnother form " + alreadyProcessedForm.getAbsolutePath() + " is also bound to this class",
588 formFile.getAbsolutePath())
593 class2form.put(classToBind, formFile);
595 boolean success = true;
597 final OutputFileObject.Content originalContent = outputClassFile.getContent();
598 final ClassReader classReader = new ClassReader(originalContent.getBuffer(), originalContent.getOffset(), originalContent.getLength());
600 final int version = getClassFileVersion(classReader);
601 final InstrumenterClassWriter classWriter = new InstrumenterClassWriter(getAsmClassWriterFlags(version), loader);
602 final AsmCodeGenerator codeGenerator = new AsmCodeGenerator(rootContainer, loader, nestedFormsLoader, false, classWriter);
603 final byte[] patchedBytes = codeGenerator.patchClass(classReader);
604 if (patchedBytes != null) {
605 outputClassFile.updateContent(patchedBytes);
608 final FormErrorInfo[] warnings = codeGenerator.getWarnings();
609 for (final FormErrorInfo warning : warnings) {
610 context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.WARNING, warning.getErrorMessage(), formFile.getAbsolutePath()));
613 final FormErrorInfo[] errors = codeGenerator.getErrors();
614 if (errors.length > 0) {
616 StringBuilder message = new StringBuilder();
617 for (final FormErrorInfo error : errors) {
618 if (message.length() > 0) {
619 message.append("\n");
621 message.append(formFile.getAbsolutePath()).append(": ").append(error.getErrorMessage());
623 context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, message.toString()));
626 final File sourceFile = outputClassFile.getSourceFile();
627 if (sourceFile != null) {
628 sourceToFormMap.update(sourceFile.getPath(), formFile.getPath());
632 catch (Exception e) {
634 context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, "Forms instrumentation failed" + e.getMessage(), formFile.getAbsolutePath()));
638 outputSink.markError(outputClassFile);
644 private static OutputFileObject findClassFile(Map<String, OutputFileObject> outputs, String classToBind) {
646 final OutputFileObject fo = outputs.get(classToBind);
650 final int dotIndex = classToBind.lastIndexOf('.');
654 classToBind = classToBind.substring(0, dotIndex) + "$" + classToBind.substring(dotIndex + 1);
658 private static int getClassFileVersion(ClassReader reader) {
659 final Ref<Integer> result = new Ref<Integer>(0);
660 reader.accept(new EmptyVisitor() {
661 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
668 private static int getAsmClassWriterFlags(int version) {
669 return version >= Opcodes.V1_6 && version != Opcodes.V1_1 ? ClassWriter.COMPUTE_FRAMES : ClassWriter.COMPUTE_MAXS;
672 private static class DiagnosticSink implements DiagnosticOutputConsumer {
673 private final CompileContext myContext;
674 private volatile int myErrorCount = 0;
675 private volatile int myWarningCount = 0;
677 public DiagnosticSink(CompileContext context) {
681 public void outputLineAvailable(String line) {
683 //System.err.println(line);
684 if (line.startsWith("[") && line.endsWith("]")) {
685 final String message = line.substring(1, line.length() - 1);
686 if (message.startsWith("parsing")) {
687 myContext.processMessage(new ProgressMessage("Parsing sources..."));
690 if (!message.startsWith("total")) {
691 myContext.processMessage(new ProgressMessage(FileUtil.toSystemDependentName(message)));
695 else if (line.contains("java.lang.OutOfMemoryError")) {
696 myContext.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, "OutOfMemoryError: insufficient memory"));
701 public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
702 final CompilerMessage.Kind kind;
703 switch (diagnostic.getKind()) {
705 kind = BuildMessage.Kind.ERROR;
708 case MANDATORY_WARNING:
711 kind = BuildMessage.Kind.WARNING;
715 kind = BuildMessage.Kind.INFO;
717 final JavaFileObject source = diagnostic.getSource();
718 final File sourceFile = source != null? Paths.convertToFile(source.toUri()) : null;
719 final String srcPath = sourceFile != null? FileUtil.toSystemIndependentName(sourceFile.getPath()) : null;
720 myContext.processMessage(new CompilerMessage(
721 BUILDER_NAME, kind, diagnostic.getMessage(Locale.US), srcPath,
722 diagnostic.getStartPosition(), diagnostic.getEndPosition(), diagnostic.getPosition(),
723 diagnostic.getLineNumber(), diagnostic.getColumnNumber()
727 public int getErrorCount() {
731 public int getWarningCount() {
732 return myWarningCount;
736 private static class OutputFilesSink implements OutputFileConsumer {
737 private final CompileContext myContext;
738 private final Set<File> mySuccessfullyCompiled = new HashSet<File>();
739 private final Set<File> myProblematic = new HashSet<File>();
740 private final List<OutputFileObject> myFileObjects = new ArrayList<OutputFileObject>();
741 private final Map<String, OutputFileObject> myCompiledClasses = new HashMap<String, OutputFileObject>();
743 public OutputFilesSink(CompileContext context) {
747 public void save(final @NotNull OutputFileObject fileObject) {
748 final String className = fileObject.getClassName();
749 if (className != null) {
750 final OutputFileObject.Content content = fileObject.getContent();
751 if (content != null) {
752 synchronized (myCompiledClasses) {
753 myCompiledClasses.put(className, fileObject);
758 synchronized (myFileObjects) {
759 myFileObjects.add(fileObject);
764 public OutputFileObject.Content lookupClassBytes(String className) {
765 synchronized (myCompiledClasses) {
766 final OutputFileObject object = myCompiledClasses.get(className);
767 return object != null? object.getContent() : null;
771 public List<OutputFileObject> getFileObjects() {
772 return Collections.unmodifiableList(myFileObjects);
775 public void writePendingData() {
777 if (!myFileObjects.isEmpty()) {
778 final FileGeneratedEvent event = new FileGeneratedEvent();
780 for (OutputFileObject fileObject : myFileObjects) {
782 writeToDisk(fileObject);
783 final File rootFile = fileObject.getOutputRoot();
784 if (rootFile != null) {
785 event.add(rootFile.getPath(), fileObject.getRelativePath());
788 catch (IOException e) {
789 myContext.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, e.getMessage()));
794 myContext.processMessage(event);
799 myFileObjects.clear();
800 myCompiledClasses.clear();
804 public Set<File> getSuccessfullyCompiled() {
805 return Collections.unmodifiableSet(mySuccessfullyCompiled);
808 private void writeToDisk(@NotNull OutputFileObject fileObject) throws IOException {
809 final OutputFileObject.Content content = fileObject.getContent();
810 if (content != null) {
811 FileUtil.writeToFile(fileObject.getFile(), content.getBuffer(), content.getOffset(), content.getLength());
814 throw new IOException("Missing content for file " + fileObject.getFile());
817 final File source = fileObject.getSourceFile();
818 if (source != null && !myProblematic.contains(source)) {
819 mySuccessfullyCompiled.add(source);
820 final String className = fileObject.getClassName();
821 if (className != null) {
822 myContext.processMessage(new ProgressMessage("Compiled " + className));
828 public void markError(OutputFileObject outputClassFile) {
829 final File source = outputClassFile.getSourceFile();
830 if (source != null) {
831 myProblematic.add(source);
836 public static class InstrumenterClassWriter extends ClassWriter {
837 private final ClassLoader myClassLoader;
839 public InstrumenterClassWriter(int flags, final ClassLoader pseudoLoader) {
841 myClassLoader = pseudoLoader;
844 protected String getCommonSuperClass(final String type1, final String type2) {
847 //c = Class.forName(type1.replace('/', '.'), true, myClassLoader);
848 //d = Class.forName(type2.replace('/', '.'), true, myClassLoader);
849 c = myClassLoader.loadClass(type1.replace('/', '.'));
850 d = myClassLoader.loadClass(type2.replace('/', '.'));
852 catch (Exception e) {
853 throw new RuntimeException(e.toString(), e);
855 if (c.isAssignableFrom(d)) {
858 if (d.isAssignableFrom(c)) {
861 if (c.isInterface() || d.isInterface()) {
862 return "java/lang/Object";
866 c = c.getSuperclass();
868 while (!c.isAssignableFrom(d));
870 return c.getName().replace('.', '/');
875 private static class MyNestedFormLoader implements NestedFormLoader {
876 private final Map<File, String> mySourceRoots;
877 private final Collection<File> myOutputRoots;
878 private final HashMap<String, LwRootContainer> myCache = new HashMap<String, LwRootContainer>();
881 * @param sourceRoots all source roots for current module chunk and all dependent recursively
882 * @param outputRoots output roots for this module chunk and all dependent recursively
884 public MyNestedFormLoader(Map<File, String> sourceRoots, Collection<File> outputRoots) {
885 mySourceRoots = sourceRoots;
886 myOutputRoots = outputRoots;
889 public LwRootContainer loadForm(String formFileName) throws Exception {
890 if (myCache.containsKey(formFileName)) {
891 return myCache.get(formFileName);
894 final String relPath = FileUtil.toSystemIndependentName(formFileName);
896 for (Map.Entry<File, String> entry : mySourceRoots.entrySet()) {
897 final File sourceRoot = entry.getKey();
898 final String prefix = entry.getValue();
899 String path = relPath;
900 if (prefix != null && FileUtil.startsWith(path, prefix)) {
901 path = path.substring(prefix.length());
903 final File formFile = new File(sourceRoot, path);
904 if (formFile.exists()) {
905 final BufferedInputStream stream = new BufferedInputStream(new FileInputStream(formFile));
907 return loadForm(formFileName, stream);
915 throw new Exception("Cannot find nested form file " + formFileName);
918 private LwRootContainer loadForm(String formFileName, InputStream resourceStream) throws Exception {
919 final LwRootContainer container = Utils.getRootContainer(resourceStream, null);
920 myCache.put(formFileName, container);
924 public String getClassToBindName(LwRootContainer container) {
925 final String className = container.getClassToBind();
926 for (File outputRoot : myOutputRoots) {
927 final String result = getJVMClassName(outputRoot, className.replace('.', '/'));
928 if (result != null) {
929 return result.replace('/', '.');
937 private static String getJVMClassName(File outputRoot, String className) {
939 final File candidateClass = new File(outputRoot, className + ".class");
940 if (candidateClass.exists()) {
943 final int position = className.lastIndexOf('/');
947 className = className.substring(0, position) + '$' + className.substring(position + 1);
951 private static File getResourcePath(Class aClass) {
952 return new File(PathManager.getResourceRoot(aClass, "/" + aClass.getName().replace('.', '/') + ".class"));
955 private static class CompiledClassesLoader extends URLClassLoader {
956 private final OutputFilesSink mySink;
958 public CompiledClassesLoader(OutputFilesSink sink, URL[] urls) {
963 protected Class findClass(String name) throws ClassNotFoundException {
964 final OutputFileObject.Content content = mySink.lookupClassBytes(name);
965 if (content != null) {
966 return defineClass(name, content.getBuffer(), content.getOffset(), content.getLength());
968 return super.findClass(name);
971 public URL findResource(String name) {
972 return super.findResource(name);
976 private class ClassProcessingConsumer implements OutputFileConsumer {
977 private final CompileContext myCompileContext;
978 private final OutputFileConsumer myDelegateOutputFileSink;
979 private int myTasksInProgress = 0;
980 private final Object myCounterLock = new Object();
982 public ClassProcessingConsumer(CompileContext compileContext, OutputFileConsumer sink) {
983 myCompileContext = compileContext;
984 myDelegateOutputFileSink = sink != null? sink : new OutputFileConsumer() {
985 public void save(@NotNull OutputFileObject fileObject) {
986 throw new RuntimeException("Output sink for compiler was not specified");
991 public void save(@NotNull final OutputFileObject fileObject) {
993 myTaskRunner.submit(new Runnable() {
996 for (ClassPostProcessor processor : myClassProcessors) {
997 processor.process(myCompileContext, fileObject);
1002 myDelegateOutputFileSink.save(fileObject);
1012 private void decTaskCount() {
1013 synchronized (myCounterLock) {
1014 myTasksInProgress = Math.max(0, myTasksInProgress - 1);
1015 if (myTasksInProgress == 0) {
1016 myCounterLock.notifyAll();
1021 private void incTaskCount() {
1022 synchronized (myCounterLock) {
1023 myTasksInProgress++;
1027 public void ensurePendingTasksCompleted() {
1028 synchronized (myCounterLock) {
1029 while (myTasksInProgress > 0) {
1031 myCounterLock.wait();
1033 catch (InterruptedException ignored) {