001: package Schmortopf.OutputManager;
002:
003: /**
004: * The Runnable, which carries out compilation with SUN's JavaC compiler.
005: * Called by outputManager methods of this package.
006: * The CompilerRunnable will compile the files complete or
007: * to the first 10 errors.
008: *
009: * When it ends, it will set the attributes of the outputManager :
010: * - classesWereCompiledAFirstTime
011: * - classesWereCompiledWithoutErrors
012: * and reenable the starterbutton
013: *
014: * It is designed to be called in the ThreadEngine threadcontext.
015: * The ThreadEngine guarantees that the CompilerRunnable and other
016: * tasks (for example the ones, which scan dependencies) are carried out
017: * in sequence. This prevents race conditions.
018: *
019: */
020:
021: import javax.swing.*;
022: import java.io.*;
023: import java.util.*;
024:
025: import Schmortopf.Main.IDE_ProjectFrameProvider;
026: import Schmortopf.Utility.SchmortopfConstants;
027: import Language.Language;
028: import Schmortopf.OutputManager.Processes.ProcessManager;
029: import Schmortopf.Utility.io.FileUtilities;
030: import Schmortopf.Utility.CommentedBoolean;
031: import Schmortopf.Utility.StringUtilities;
032: import Shared.Logging.Log;
033:
034: public class CompilerRunnableForJavaC implements Runnable {
035:
036: // Notice regarding GC assistance :
037: // This is quite easy here, because except the run() method,
038: // all others are private and called from the inside of the run method.
039: // Therefore we just can set all references of bigger objects to null
040: // at the end of the run() method, and the GC should scan the dependencies
041: // faster and therefore faster see, when this object isn't referenced anymore.
042:
043: private OutputManager outputManager; // GC: set to null at the end of the run() method
044: private IDE_ProjectFrameProvider projectFrameProvider; // GC: set to null at the end of the run() method
045:
046: private final String javacExePath;
047: private final String sourceDirectoryPath;
048: private final String targetClassDirectoryPath;
049: private String[] sourceFilePathNames;
050: private final String mainClassFilePath;
051: private final String jdkBasisPath;
052: private final JButton compileButton;
053: private final JButton executeButton;
054: private final boolean isProcessingProject; // single file, if false
055: private final boolean launchAfterCompilation;
056: private final String[] additionalLibraryPathes;
057:
058: // Set after a complete compiler run
059: private boolean compilingWasSuccessful = true;
060:
061: // Contains the number of all found and printed errors
062: // during the run.
063: private int numberOfErrors = 0;
064:
065: private long startTime;
066:
067: private int decodeErrorMessagesCalls = 0; // debug attribute
068:
069: // The errorbuffer is used on failures of the mechanism itself.
070: // In some situations, the decoder fails and the IDE prints an
071: // Internal Error message. In this case, we at least can show
072: // the content of this buffer for the user, so he can see the
073: // compiler error messages ( in text format )
074: private Vector errorLinesBuffer = new Vector(); // GC: set to null at the end of the run() method
075:
076: // The number of all files, which were compiled by JavaC
077: // on its way through the import dependency chain.
078: // This is equal or (usually much) bigger than the number of
079: // changed files by the programmer.
080: private int totalNumberOfCompiledFiles = 0;
081:
082: // Additional compiler output count statistics:
083: private int numberOfLoadedFiles = 0;
084: private int numberOfParsedFiles = 0;
085: private int numberOfCheckedFiles = 0;
086: private int numberOfWrittenFiles = 0;
087:
088: // When this flag isn't set, the compiler is started in normal mode
089: // and compiles all projectfiles. If it's set, the compiler is started
090: // in selective mode and only will see java sources from changed files
091: // and files, which in any way depend on changed files.
092: private boolean classesWereCompiledAFirstTime;
093:
094: // The value of this depends on the compiler mode:
095: // In normal mode, this is the same as the passed sourceDirectoryPath,
096: // but in selective mode, its the path to the temp directory, to where
097: // the files to be compiled are written.
098: // It is used by the method decodeSingleErrorMessage().
099: private String selectedSourceDirectoryPath = "";
100: private boolean selectiveCompileModeIsSet = false;
101:
102: private String compilerVersion;
103:
104: public CompilerRunnableForJavaC(final OutputManager outputManager,
105: final String javacExePath,
106: final String sourceDirectoryPath,
107: final String targetClassDirectoryPath,
108: final String[] sourceFilePathNames,
109: final String mainClassFilePath, final String jdkBasisPath,
110: final String[] additionalLibraryPathes,
111: final boolean classesWereCompiledAFirstTime,
112: final JButton compileButton, final JButton executeButton,
113: final boolean isProcessingProject,
114: final boolean launchAfterCompilation, final long startTime,
115: final IDE_ProjectFrameProvider projectFrameProvider,
116: final String compilerVersion) {
117: this .outputManager = outputManager;
118: this .javacExePath = javacExePath;
119: this .sourceDirectoryPath = sourceDirectoryPath;
120: this .targetClassDirectoryPath = targetClassDirectoryPath;
121: this .sourceFilePathNames = sourceFilePathNames;
122: this .mainClassFilePath = mainClassFilePath;
123: this .jdkBasisPath = jdkBasisPath;
124: this .additionalLibraryPathes = additionalLibraryPathes;
125: this .classesWereCompiledAFirstTime = classesWereCompiledAFirstTime;
126: this .compileButton = compileButton;
127: this .executeButton = executeButton;
128: this .isProcessingProject = isProcessingProject;
129: this .launchAfterCompilation = launchAfterCompilation;
130: this .startTime = startTime;
131: this .projectFrameProvider = projectFrameProvider;
132: this .compilerVersion = compilerVersion;
133: } // Constructor
134:
135: /**
136: * run method, which starts javac one time for all files.
137: */
138: public void run() {
139: // Debug time measurement for this run() method:
140: final long runMethodStartTime = System.currentTimeMillis();
141: try {
142: // For memory reasons, generate subarrays with maxFilesPerCall elements.
143: // Compiling >> maxFilesPerCall files per call can easily cause out of memory errors.
144: // Already compiled files of each subarray are subtracted from
145: // the list, so they aren't compiled again unnecessarily.
146: // maxFilesPerCall is set depending on the memory, which was verified
147: // on the startup of Schmortopf. (faster, than doing it each time).
148: // Note that on the call this.compileFileList() to the compiler,
149: // the javac application again is started with -J-Xms and -J-Xmx options,
150: // which reserve additional memory for javac proportional to
151: // the number of filesPerCall.
152: int maxMemOnStartup = this .projectFrameProvider
153: .getMainFrameProvider()
154: .getVerifiedAvailableMemoryMB();
155: // filesPerCall : 125 files upto 50 MB free initially
156: // 500 files for > 100 MB free initially
157: // 1000 files for > 150 MB free initially
158: // 2000 files for > 200 MB free initially
159: int maxFilesPerCall = 125;
160: if (maxMemOnStartup > 50) {
161: maxFilesPerCall = maxMemOnStartup * maxMemOnStartup
162: / 20;
163: }
164: // collect now :
165: Vector filesToBeCompiled = new Vector();
166: for (int i = 0; i < this .sourceFilePathNames.length; i++) {
167: filesToBeCompiled
168: .addElement(this .sourceFilePathNames[i]);
169: }
170: boolean listCompiledSuccessfully = true; // break on errors
171: int numberOfAlreadyCompiledFiles = 0;
172: while ((filesToBeCompiled.size() > 0)
173: && (listCompiledSuccessfully)) {
174: int partialListNumber = filesToBeCompiled.size();
175: if (partialListNumber > maxFilesPerCall)
176: partialListNumber = maxFilesPerCall;
177: String[] partialFileList = new String[partialListNumber];
178: int partialFileListIndex = 0;
179: for (int i = 0; i < partialListNumber; i++) {
180: partialFileList[i] = (String) filesToBeCompiled
181: .elementAt(0); // always the first
182: filesToBeCompiled.removeElementAt(0);
183: }
184:
185: // If ALL classes already were compiled a first time,
186: // and if we can compile all in one run, we can feed javac only with the
187: // sourcefiles, which really need to be compiled, and only let it
188: // see the class files [of the previous run] of all other files :
189: boolean selectiveCompileModeIsAllowed = ((this .classesWereCompiledAFirstTime) && (filesToBeCompiled
190: .size() == 0));
191:
192: /* Debug
193: if( filesToBeCompiled.size() == 0 ) // enough memory for all files
194: {
195: outputManager.printLine( "Starting JavaC compile task for " +
196: partialListNumber + " files. [in 1 run]");
197: }
198: else // piecewise compiling, cause memory low
199: {
200: outputManager.printLine( "Starting JavaC compile task for " +
201: partialListNumber + " files and " +
202: filesToBeCompiled.size() + " files left.");
203: }
204: End Debug */
205:
206: /* Debug
207: outputManager.printLine("Files passed to JavaC for compiling [max 20 printed out] are :");
208: for( int i=0; i < partialFileList.length; i++ )
209: {
210: int nr = i+1;
211: outputManager.printLine( " " + nr + ") " + partialFileList[i] );
212: if( i > 19 )
213: {
214: outputManager.printLine("Output stopped after 20 files...");
215: break;
216: }
217: }
218: outputManager.printLine(" ");
219: End Debug */
220:
221: // Start the compile task and get the compiled files :
222: String[] compiledClassFiles = this .compileFileList(
223: partialFileList, numberOfAlreadyCompiledFiles,
224: selectiveCompileModeIsAllowed);
225:
226: listCompiledSuccessfully = !(compiledClassFiles == null);
227: if (listCompiledSuccessfully) {
228: numberOfAlreadyCompiledFiles += compiledClassFiles.length;
229: // Remove all pathnames of compiled files, which are found
230: // in the filesToBeCompiled Vector :
231: for (int ri = 0; ri < compiledClassFiles.length; ri++) {
232: // The compiledClassFiles have no ending, but the path is the
233: // output class path, whereas the filepathes in the filesToBeCompiled
234: // are in the projectfile directory.
235: // For identifying already compiled files, we must transform the pathes
236: // of the compiledFiles into the project directory :
237: int startCut = this .targetClassDirectoryPath
238: .length();
239: String relativeFilePath = compiledClassFiles[ri]
240: .substring(startCut);
241: String projectFilePath = this .sourceDirectoryPath
242: + relativeFilePath + ".java";
243: this .removeStringInStringVector(
244: filesToBeCompiled, projectFilePath);
245: }
246: }
247: } // while
248: this .compilingWasSuccessful = listCompiledSuccessfully;
249: } catch (final Exception anyEx) {
250: this .compilingWasSuccessful = false;
251: System.gc();
252: SwingUtilities.invokeLater(new Runnable() {
253: public void run() {
254: outputManager
255: .printErrorLine("Compiler Exception. Unable to compile all files.");
256: Log
257: .Error("Compiler Exception. Unable to compile all files.");
258: Log.Error(anyEx);
259: }
260: });
261: } catch (final Error anyError) {
262: this .compilingWasSuccessful = false;
263: System.gc();
264: SwingUtilities.invokeLater(new Runnable() {
265: public void run() {
266: outputManager
267: .printErrorLine("Compiler Error. Unable to compile all files.");
268: Log
269: .Error("Compiler Error. Unable to compile all files.");
270: Log.Error(anyError);
271: }
272: });
273: }
274:
275: SwingUtilities.invokeLater(new Runnable() {
276: public void run() {
277: if (compilingWasSuccessful) {
278: if (sourceFilePathNames.length > 0) {
279: long elapsedTimeOfCompiling = (System
280: .currentTimeMillis() - startTime) / 1000;
281: final String compileMessage = Language
282: .Translate(
283: "Compiled %1 files successfully in %2 sec",
284: "" + sourceFilePathNames.length,
285: "" + elapsedTimeOfCompiling);
286: outputManager.setCompilerInfoLabel(
287: compileMessage, false);
288: outputManager.printLine(compileMessage + " "
289: + makeLowLevelFileStatisticsLine());
290: // Tell it the outputmanager :
291: outputManager
292: .setClassesWereCompiledAFirstTime(true);
293: outputManager
294: .setClassesWereCompiledWithoutErrors(true);
295: } else {
296: outputManager
297: .setCompilerInfoLabel(
298: Language
299: .Translate("Compiler: All classes are up to date."),
300: false);
301: }
302: if (launchAfterCompilation) {
303: outputManager.doExecute(mainClassFilePath);
304: }
305:
306: /* Debug: totalNumberOfCompiledFiles should equal sourceFilePathNames.length:
307: final String testMessage = "Total number compiled by JavaC = " + totalNumberOfCompiledFiles;
308: outputManager.printLine(testMessage);
309: */
310:
311: } else {
312: String message = Language
313: .Translate("Compiling stopped: ");
314: if (numberOfErrors == 0) // Should not occure.
315: {
316: message += Language
317: .Translate("Internal error."); // -> examine std output... ide problem.
318:
319: /*
320: // Debug output for this case:
321: Log.Error("------------ Compiler: Internal error ---------------");
322: Log.Error("--- decodeErrorMessagesCalls= " + decodeErrorMessagesCalls );
323: Log.Error("------------ Compiler: Internal error ---------------");
324: // We at least can output the unprocessed error lines, if they have been any :
325: for( int i=0; i < errorLinesBuffer.size(); i++ )
326: {
327: Log.Error( (String)errorLinesBuffer.elementAt(i) );
328: }
329: */
330:
331: } else if (numberOfErrors == 1) {
332: message += Language
333: .Translate("An error has been found.");
334: } else {
335: message += Language.Translate(
336: "% errors have been found.", ""
337: + numberOfErrors);
338: }
339: outputManager.setCompilerInfoLabel(message, false);
340: // Tell it to the outputmanager :
341: outputManager
342: .setClassesWereCompiledWithoutErrors(false);
343: // Additionally, *IF* there are no processes running (which can write on the
344: // output area anytime, we don't need chronological order and do the
345: // user a favour by scrolling the view to the top where the error lines
346: // do start in this case:
347: if (outputManager.getProcessManager()
348: .getNumberOfRunningProcesses() == 0) {
349: outputManager.scrollUpEditorTextPane(); // No EDT required here
350: }
351: }
352: compileButton.setEnabled(true);
353: executeButton.setEnabled(true);
354: }
355: });
356:
357: // Free associations for helping the GC.
358: // Because some actions above are processed delayed, we must delay this too :
359: SwingUtilities.invokeLater(new Runnable() {
360: public void run() {
361: outputManager = null;
362: projectFrameProvider = null;
363: errorLinesBuffer.setSize(0);
364: errorLinesBuffer = null;
365: sourceFilePathNames = null;
366: }
367: });
368:
369: /* Debug: Time measurement:
370: long runMethodElapsedTime = System.currentTimeMillis() - runMethodStartTime;
371: Log.Info(">>> CompilerRunnableForJavaC.run() time for run()= " +
372: runMethodElapsedTime + " [ms]" );
373: */
374:
375: } // run
376:
377: /**
378: * Compiles the passed file list. returns true, if all was compiled.
379: * Returns an array which consists of the pathnames of all compiled files,
380: * which also can have zero length.
381: * If the compiler stopped with an error, null is returned to indicate that.
382: */
383: private String[] compileFileList(
384: final String[] raw_SourceFilePathNamesPartList,
385: final int numberOfAlreadyCompiledFiles,
386: final boolean selectiveCompileModeIsAllowed)
387: throws Exception, Error {
388: long compileFileListStartTime = System.currentTimeMillis();
389:
390: String[] compiledFiles = null; // is the returned attribute
391: // Defaults for normal (non selective) compile mode:
392: this .selectedSourceDirectoryPath = this .sourceDirectoryPath;
393: String[] sourceFilePathNamesPartList = raw_SourceFilePathNamesPartList;
394:
395: // Test and set selectiveCompileMode:
396: // If selectiveCompileModeIsAllowed is set, and if we have less than 500 files for
397: // compiling, we try to do it in selectiveCompileMode:
398: // In this case, we copy the files for compiling into a separate temp directory
399: // and let javac see only these sourcefiles, and all class files in the
400: // output directory. Condition for this is, that all files have been compiled
401: // one time before.
402: this .selectiveCompileModeIsSet = false;
403: if ((selectiveCompileModeIsAllowed)
404: && (sourceFilePathNamesPartList.length < 500)) {
405: // Try to copy all to the temp directory and get their pathes:
406: String tempSourceDirPath = ""; // Used on error, when we fallback for user information
407: try {
408: File outputDirectory = new File(
409: this .targetClassDirectoryPath);
410: String outputDirectoryName = outputDirectory.getName();
411: if (outputDirectory.exists()) {
412: if (outputDirectory.isDirectory()) {
413: File parentDirectory = outputDirectory
414: .getParentFile();
415: // We must keep it unique, so concurrent compiler runs won't interfere:
416: String tempSourceDirName = outputDirectoryName
417: + "SRC.temp";
418: File tempSourceDirectory = new File(
419: parentDirectory, tempSourceDirName);
420: tempSourceDirPath = tempSourceDirectory
421: .getPath();
422: boolean success = true;
423: if (tempSourceDirectory.exists()) {
424: // Remove anything in it:
425: success = FileUtilities
426: .MakeDirectoryEmpty(tempSourceDirectory);
427: } else {
428: tempSourceDirectory.mkdirs();
429: }
430: if (success) {
431: // Copy all source files into the temp directory,
432: // keep project relative subfolders associated with the package pathes
433: // and overwrite the sourceFilePathNamesPartList elements with the
434: // destination names, once all has succeeded.
435: int skipLength = sourceDirectoryPath
436: .length();
437:
438: String[] selectiveModeSourceFilePathNamesPartList = new String[sourceFilePathNamesPartList.length];
439: String relativeSourcePath = null;
440: String targetPath = null;
441: File targetFile = null;
442: File parentDir = null;
443: for (int fileIndex = 0; fileIndex < sourceFilePathNamesPartList.length; fileIndex++) {
444: relativeSourcePath = sourceFilePathNamesPartList[fileIndex]
445: .substring(skipLength);
446: targetPath = tempSourceDirectory
447: .getPath()
448: + relativeSourcePath;
449:
450: //this.outputManager.printLine(">src= " + sourceFilePathNamesPartList[fileIndex]);
451: //this.outputManager.printLine(">relative src= " + relativeSourcePath);
452: //this.outputManager.printLine(">target= " + targetPath);
453:
454: selectiveModeSourceFilePathNamesPartList[fileIndex] = targetPath;
455:
456: // Create the parentdirectory:
457: targetFile = new File(targetPath);
458: parentDir = targetFile.getParentFile();
459: parentDir.mkdirs();
460: // And copy:
461: final CommentedBoolean fileCopyResult = FileUtilities
462: .FileCopy(
463: sourceFilePathNamesPartList[fileIndex],
464: targetPath);
465: if (!fileCopyResult.isTrue) {
466: throw new Exception(
467: "FileCopy failed to "
468: + targetPath);
469: }
470: relativeSourcePath = null;
471: targetPath = null;
472: targetFile = null;
473: parentDir = null;
474: } // for
475: // If we come here, no exception was thrown, so we can continue:
476: // Overwrite the source array with the one containing the selected files only,
477: // and set the new path and the ok flag:
478:
479: //this.outputManager.printLine("selectiveCompileModeIsSet = true OUTCOMMENTED");
480: this .selectedSourceDirectoryPath = tempSourceDirectory
481: .getPath();
482: sourceFilePathNamesPartList = selectiveModeSourceFilePathNamesPartList;
483: this .selectiveCompileModeIsSet = true;
484: }
485: parentDirectory = null;
486: tempSourceDirectory = null;
487: tempSourceDirName = null;
488: }
489: }
490: outputDirectory = null;
491: outputDirectoryName = null;
492: } catch (Exception anyEx) {
493: // fallback to normal mode.
494: this .outputManager
495: .printLine(">------------------------------------------");
496: this .outputManager
497: .printLine(">Compiler: Selective mode could not be set.");
498: this .outputManager.printLine(">Compiler: Reason: "
499: + anyEx.getMessage());
500: this .outputManager
501: .printLine(">Compiler: Check, if you have enough disk space on "
502: + tempSourceDirPath);
503: this .outputManager
504: .printLine(">------------------------------------------");
505: }
506: } // selectedSourceDirectoryPath
507:
508: /* Debug:
509: if( this.selectiveCompileModeIsSet )
510: {
511: this.outputManager.printLine(">Compiler: Runs in selective mode.");
512: }
513: else
514: {
515: this.outputManager.printLine(">Compiler: Runs in normal mode.");
516: }
517: */
518:
519: // Debug:
520: //final long elapsed1 = System.currentTimeMillis() - compileFileListStartTime;
521: //Log.Info("compileFileList: elapsed1= " + elapsed1);
522: // Write the temporary file which contains the source files to be compiled :
523: // Location independent of compile mode.
524: String sourcesFilePathName = this .sourceDirectoryPath
525: + SchmortopfConstants.OSDelimiter + "compiling.txt";
526: File sourcesFile = new File(sourcesFilePathName);
527: FileOutputStream fileOut = new FileOutputStream(sourcesFile);
528: BufferedOutputStream bufOut = new BufferedOutputStream(fileOut);
529: String this Line;
530: for (int i = 0; i < sourceFilePathNamesPartList.length; i++) {
531: this Line = sourceFilePathNamesPartList[i] + "\n";
532: bufOut.write(this Line.getBytes());
533: }
534: bufOut.flush();
535: bufOut.close();
536: bufOut = null;
537: fileOut = null;
538: this Line = null;
539: sourcesFile = null;
540: // Build the compiler arguments.
541: // Pass xmx and xms commands to the javac jvm, which reserve
542: // additional memory proportional to the number of files to be compiled.
543: String[] args;
544: int specialMemory_MB = sourceFilePathNamesPartList.length / 4;
545: // Divider was 6 upto 1.3_07, but this caused javac 1.4.2_01 to
546: // go outofmemory seldomly, but javac 1.4.2_03 always goes outofmemory
547: // for 1500+ files. Therefore set to 4 for version 1.3_08 Jan. 2004.
548: // 250 MB for 1000 source files to be compiled in one call.
549: if (specialMemory_MB < 64)
550: specialMemory_MB = 64;
551: // Max JVM memory set to specialMemory_MB: (would be 64MB default)
552: // Note that just setting a high value wouldn't work, at least on
553: // some Mac OSX computers, which seemed to try to reserve the xmx
554: // memory on startup. There it failed, if the system didnt have
555: // that amount of REAL memory (virtual memory didnt count..).
556: String xmxArg = "-J-Xmx" + Integer.toString(specialMemory_MB)
557: + "m";
558: // Startsize set to 16MB: (would be 2MB default)
559: String xmsArg = "-J-Xms" + Integer.toString(16) + "m";
560: // Note that SUN changed src.jar to src.zip in jdk 1.4
561:
562: // Add all library pathes to the sourceLibrariesPathNames string :
563: final StringBuffer sourceLibrariesPathNames = new StringBuffer(
564: "");
565:
566: // Add the directory containing the class files, if selective mode is set:
567: if (this .selectiveCompileModeIsSet) {
568: sourceLibrariesPathNames
569: .append(this .targetClassDirectoryPath);
570: if (additionalLibraryPathes.length > 0) {
571: sourceLibrariesPathNames.append(";");
572: }
573: }
574:
575: if (additionalLibraryPathes.length > 0) {
576: for (int libIndex = 0; libIndex < additionalLibraryPathes.length; libIndex++) {
577: sourceLibrariesPathNames
578: .append(additionalLibraryPathes[libIndex]);
579: if (libIndex < additionalLibraryPathes.length - 1) {
580: sourceLibrariesPathNames.append(";");
581: }
582: } // for
583: }
584:
585: // For the 1.4, 1.5 compiler, we add the -source=1.x switch, so
586: // assert directives are recognized and compiled by 1.4 and
587: // generics,static imports.. are compiled by 1.5.
588: if (compilerVersion.startsWith("1.4")
589: || compilerVersion.startsWith("1.5")) {
590: String compilerVersionTag = compilerVersion
591: .startsWith("1.4") ? "1.4" : "1.5";
592: if (sourceLibrariesPathNames.length() > 0) {
593: // include libs in special classpath:
594: args = new String[] { this .javacExePath, "-d",
595: this .targetClassDirectoryPath, xmsArg, xmxArg,
596: "-classpath",
597: sourceLibrariesPathNames.toString(),
598: "-sourcepath",
599: this .selectedSourceDirectoryPath, "-source",
600: compilerVersionTag, "-verbose", // this is !*NEEDED*! always
601: "-nowarn", // make this an option in future
602: "@" + sourcesFilePathName };
603: } else {
604: // no special classpath :
605: args = new String[] { this .javacExePath, "-d",
606: this .targetClassDirectoryPath, xmsArg, xmxArg,
607: "-sourcepath",
608: this .selectedSourceDirectoryPath, "-source",
609: compilerVersionTag, "-verbose", // this is !*NEEDED*! always
610: "-nowarn", // make this an option in future
611: "@" + sourcesFilePathName };
612: }
613: } else {
614: // Usual case for 1.3 jdks :
615: if (sourceLibrariesPathNames.length() > 0) {
616: // include libs in special classpath:
617: args = new String[] { this .javacExePath, "-d",
618: this .targetClassDirectoryPath, xmsArg, xmxArg,
619: "-classpath",
620: sourceLibrariesPathNames.toString(),
621: "-sourcepath",
622: this .selectedSourceDirectoryPath, "-verbose", // this is !*NEEDED*! always
623: "-nowarn", // make this an option in future
624: "@" + sourcesFilePathName };
625: } else {
626: // no special classpath :
627: args = new String[] { this .javacExePath, "-d",
628: this .targetClassDirectoryPath, xmsArg, xmxArg,
629: "-sourcepath",
630: this .selectedSourceDirectoryPath, "-verbose", // this is !*NEEDED*! always
631: "-nowarn", // make this an option in future
632: "@" + sourcesFilePathName };
633: }
634: } // else
635:
636: // Debug:
637: //final long elapsed2 = System.currentTimeMillis() - compileFileListStartTime;
638: //Log.Info("compileFileList: elapsed2= " + elapsed2);
639:
640: this .outputManager.printLine("Calling compiler "
641: + this .javacExePath);
642:
643: // The stream, which buffers the whole output (stdout and err)
644: // and updates the compiler display in realtime on one side
645: // as well as serves as source for parsing the output for
646: // errormessages at the end. (This stream also has a compilerinfo thread
647: // which processes info updates for the compiler info label. This thread
648: // must be stopped by calling compilerOutputStream.terminate(), when we
649: // are through all :
650: final CompilerOutputStreamForJavaC compilerOutputStream = new CompilerOutputStreamForJavaC(
651: this .outputManager, numberOfAlreadyCompiledFiles);
652: // Start the java compiler now :
653: final Runtime rt = Runtime.getRuntime();
654: final Process compilerProcess = rt.exec(args);
655:
656: // Debug:
657: //final long elapsed3 = System.currentTimeMillis() - compileFileListStartTime;
658: //Log.Info("compileFileList: elapsed3= " + elapsed3);
659:
660: // Redirect both javac outputstreams to the CompilerOutputStream :
661: // JavaC writes verbose messages to the error outputstream.
662: final CompilerOutputProcessor compilerOutputProcessor = new CompilerOutputProcessor(
663: compilerProcess.getInputStream(), compilerOutputStream);
664: compilerOutputProcessor.start();
665: final CompilerOutputProcessor compilerErrorOutputProcessor = new CompilerOutputProcessor(
666: compilerProcess.getErrorStream(), compilerOutputStream);
667: compilerErrorOutputProcessor.start();
668:
669: // Debug:
670: //final long elapsed4 = System.currentTimeMillis() - compileFileListStartTime;
671: //Log.Info("compileFileList: elapsed4= " + elapsed4);
672:
673: // Pass the process to the processmanager, so it will display it
674: // and gives the possibility for the user to kill the process by a mouseclick:
675: this .outputManager.getProcessManager().addToRunningProcesses(
676: ProcessManager.COMPILER_PROCESS, compilerProcess,
677: this .javacExePath, "");
678:
679: // If javac pops up a systemframe, bring our frame to front.
680: // We test that tree times :
681: JFrame theDevelopFrame = this .outputManager.getProjectFrame()
682: .getParentFrameForChildren();
683: theDevelopFrame.toFront();
684: Thread.sleep(80);
685: theDevelopFrame.toFront();
686: Thread.sleep(80);
687: theDevelopFrame.toFront();
688:
689: // Debug:
690: //final long elapsed5 = System.currentTimeMillis() - compileFileListStartTime;
691: //Log.Info("compileFileList: elapsed5= " + elapsed5);
692:
693: // Wait for termination and get the return value :
694: final int exitValue = compilerProcess.waitFor();
695: boolean listWasCompiledSuccessfully = (exitValue == 0);
696:
697: // Debug:
698: //final long elapsed6 = System.currentTimeMillis() - compileFileListStartTime;
699: //Log.Info("compileFileList: elapsed6= " + elapsed6);
700:
701: // Tell the outputmanager to remove this process from its list:
702: this .outputManager.getProcessManager()
703: .removeFromRunningProcesses(compilerProcess);
704: if (listWasCompiledSuccessfully) {
705: // Remove all files from the compilerlist :
706: for (int i = 0; i < sourceFilePathNamesPartList.length; i++) {
707: String pathToBeRemoved = sourceFilePathNamesPartList[i];
708: // An additional work, if the compiler works in selective mode:
709: // In this case, the sourcefiles to be compiled have been copied
710: // to a temp directory this.selectedSourceDirectoryPath, which
711: // must be replaced by the original file basis directory, which is
712: // this.sourceDirectoryPath, otherwise the error buttons won't work:
713: if (this .selectiveCompileModeIsSet) {
714: pathToBeRemoved = pathToBeRemoved
715: .substring(this .selectedSourceDirectoryPath
716: .length());
717: pathToBeRemoved = this .sourceDirectoryPath
718: + pathToBeRemoved;
719: }
720: this .outputManager
721: .removeSourceFilePathFromCompilerList(pathToBeRemoved);
722: pathToBeRemoved = null; // GC assistance
723: } // for
724: compiledFiles = compilerOutputStream
725: .getPathNamesOfCompiledClasses();
726:
727: /* Start Debug: Print out the first 60 lines of the compiler output :
728: outputManager.printLine(" ");
729: outputManager.printLine("----------- START First 60 lines of the javac parsing output --------");
730: String[] lines = StringUtilities.SplitString(compilerOutputStream.toString(),"\n");
731: int writtenTestLines = 0;
732: for( int lineIndex=0; lineIndex < lines.length; lineIndex++ )
733: {
734: if( lines[lineIndex].startsWith("[parsing started") )
735: {
736: writtenTestLines++;
737: outputManager.printLine( "" + writtenTestLines + ") " + lines[lineIndex] );
738: if( writtenTestLines > 60 ) break;
739: }
740: }
741: outputManager.printLine("----------- END First 60 lines of the compiler output --------");
742: outputManager.printLine(" ");
743: End Debug */
744:
745: } else {
746: // Make sure, the compiler output text area is or becomes visible for the user :
747: this .outputManager.getProjectFrame()
748: .checkPopupCompilerOutputPanel();
749: // and print the error text and button output :
750: final String compilerOutput = compilerOutputStream
751: .toString();
752: this .decodeErrorMessages(compilerOutput);
753: compiledFiles = null; // indicates the error, as this is returned
754: }
755:
756: // Debug:
757: //final long elapsed7 = System.currentTimeMillis() - compileFileListStartTime;
758: //Log.Info("compileFileList: elapsed7= " + elapsed7);
759:
760: // Now we wait, until both compilerOutputProcessor have
761: // processed their buffers and have shut down, or until
762: // a security time has passed :
763: long startTime = System.currentTimeMillis();
764: while (true) {
765: if (compilerOutputProcessor.getHasTerminated()
766: && compilerErrorOutputProcessor.getHasTerminated()) {
767: // This is the normal operation - the 2 processor threads
768: // automatically were shut down, when javac terminated.
769: break;
770: }
771: Thread.sleep(100);
772: if (System.currentTimeMillis() - startTime > 10000) {
773: Log.Warn("*** Caution: Unable to shut down some");
774: Log.Warn("*** output processing tasks. This could");
775: Log.Warn("*** slow down the program execution.");
776: break;
777: }
778: }
779:
780: // Debug:
781: //final long elapsed8 = System.currentTimeMillis() - compileFileListStartTime;
782: //Log.Info("compileFileList: elapsed8= " + elapsed8);
783:
784: // Update counters for this list:
785: this .numberOfLoadedFiles += compilerErrorOutputProcessor
786: .getNumberOfLoadedFiles();
787: this .numberOfParsedFiles += compilerErrorOutputProcessor
788: .getNumberOfParsedFiles();
789: this .numberOfCheckedFiles += compilerErrorOutputProcessor
790: .getNumberOfCheckedFiles();
791: this .numberOfWrittenFiles += compilerErrorOutputProcessor
792: .getNumberOfWrittenFiles();
793: this .totalNumberOfCompiledFiles += compilerOutputStream
794: .getNumberOfCompiledFiles();
795: // Shut down the compilerOutputStreams info updater too :
796: compilerOutputStream.terminate();
797: sourceFilePathNamesPartList = null;
798: args = null;
799: xmxArg = null;
800: xmsArg = null;
801: return compiledFiles;
802: } // compileFileList
803:
804: /**
805: * Returns a lowlevel counter statistics about number of files
806: * loaded,parsed,checked and written by the compiler.
807: * I call it lowlevel, because there can be multiple files
808: * associated to one java source file, if this file contains multiple
809: * inner classes or anonymous classes.
810: */
811: private String makeLowLevelFileStatisticsLine() {
812: final StringBuffer buf = new StringBuffer(" ( ");
813: buf.append(Language.Translate("Disk") + ":");
814: buf.append(" " + this .numberOfLoadedFiles + " "
815: + Language.Translate("loaded") + ",");
816: buf.append(" " + this .numberOfParsedFiles + " "
817: + Language.Translate("parsed") + ",");
818: buf.append(" " + this .numberOfCheckedFiles + " "
819: + Language.Translate("checked") + ",");
820: buf.append(" " + this .numberOfWrittenFiles + " "
821: + Language.Translate("written") + " )");
822: return buf.toString();
823: } // makeLowLevelFileStatisticsLine
824:
825: private void decodeErrorMessages(final String compilerOutput) {
826:
827: this .decodeErrorMessagesCalls++;
828: this .errorLinesBuffer.addElement(compilerOutput);
829:
830: // Decompose the lines, but skip all lines, which start with a "[" sign,
831: // because these are usual compiler infos in the "-verbose" mode, which
832: // is used ALWAYS.
833: String[] tokens = StringUtilities.SplitString(compilerOutput,
834: "\n");
835: final Vector linesVector = new Vector();
836: for (int i = 0; i < tokens.length; i++) {
837: // Exclude file check,load and wrote messages, which all start
838: // with a [ sign :
839: if (!tokens[i].startsWith("[")) {
840: linesVector.addElement(tokens[i]);
841: }
842: }
843: final String[] lines = new String[linesVector.size()];
844: linesVector.copyInto(lines);
845:
846: // Intercept java.lang.OutOfMemoryError messages right here -
847: // as this makes further things senseless :
848: for (int i = 0; i < lines.length; i++) {
849: if (lines[i].indexOf("java.lang.OutOfMemoryError") >= 0) {
850: try {
851: System.gc();
852: Thread.sleep(300);
853: System.gc();
854: } catch (Exception any234765) {
855: }
856: JOptionPane
857: .showMessageDialog(
858: this .projectFrameProvider
859: .getParentFrameForChildren(),
860: Language
861: .Translate("An out-of-memory error has occured.\nYour system has not enough free memory\nfor compiling the files.")
862: + "\n");
863: return;
864: }
865: }
866:
867: /* Start Debug: just output all lines :
868: this.outputManager.printLine("|-------- rawoutput start ------------------------- ");
869: for( int i=0; i < lines.length; i++ )
870: {
871: this.outputManager.printLine("| line" + i + " = $" + lines[i] + "$" );
872: }
873: this.outputManager.printLine("|-------- rawoutput end -------------------------");
874: End Debug */
875:
876: // For JavaC, each error description starts with a line, which contains
877: // the sequences ".java" and ": ".
878: // It contains 3 or more lines each.
879: // One line should contain a ^ sign, which is the locationpointer.
880: final Vector singleMessageVector = new Vector();
881: for (int i = 0; i < lines.length; i++) {
882: if ((lines[i].indexOf(".java") > 0)
883: && (lines[i].indexOf(": ") > 0)) {
884: // new single message starts :
885: if (singleMessageVector.size() > 0) {
886: // send what we have until now :
887: final String[] singleMessageLines = new String[singleMessageVector
888: .size()];
889: singleMessageVector.copyInto(singleMessageLines);
890: this .decodeSingleErrorMessage(singleMessageLines);
891: }
892: singleMessageVector.removeAllElements();
893: }
894: singleMessageVector.addElement(lines[i]);
895: } // for
896: // Test the rest message: If it too starts with a valid error descriptor,
897: // send it too :
898: if (singleMessageVector.size() > 0) {
899: // send what we have until now :
900: final String[] singleMessageLines = new String[singleMessageVector
901: .size()];
902: singleMessageVector.copyInto(singleMessageLines);
903: if ((singleMessageLines[0].indexOf(".java") > 0)
904: && (singleMessageLines[0].indexOf(": ") > 0)) {
905: this .decodeSingleErrorMessage(singleMessageLines);
906: } else {
907: // Obviously we couldnt parse this correctly, so we at least
908: // print it as it is :
909: for (int i = 0; i < singleMessageLines.length; i++) {
910: this .outputManager
911: .printErrorLine(singleMessageLines[i]);
912: }
913: }
914: }
915: } // decodeErrorMessages
916:
917: private void decodeSingleErrorMessage(final String[] errorLines) {
918: this .numberOfErrors++;
919:
920: final String[] parsedErrorLines = new String[errorLines.length];
921: for (int i = 0; i < parsedErrorLines.length; i++) {
922: parsedErrorLines[i] = "";
923: }
924:
925: // The first one is the descriptor. Extract the filePath :
926: final String descriptor = errorLines[0]; // exists always
927: int numberEndIndex = descriptor.indexOf(": ");
928: if (numberEndIndex > 0) {
929: // go back to previous : sign :
930: int numberStartIndex = numberEndIndex - 1;
931: while ((descriptor.charAt(numberStartIndex) != ':')
932: && (numberStartIndex > 0)) {
933: numberStartIndex--;
934: }
935: if (numberStartIndex > 0) {
936: String filePath = descriptor.substring(0,
937: numberStartIndex);
938: final String lineNumberString = descriptor.substring(
939: numberStartIndex + 1, numberEndIndex);
940: // Get the lineNumber
941: int lineNumber = -1;
942: try {
943: lineNumber = Integer.parseInt(lineNumberString);
944: } catch (Exception anyEx) {
945: }
946: // The linenumber must be decremented by one :
947: lineNumber -= 1;
948: // Get the first pased errorline :
949: parsedErrorLines[0] = descriptor.substring(
950: numberEndIndex + 2, descriptor.length() - 1);
951: // Move the other ones and catch the locationPointer line,
952: // which consists of spaces and one ^ sign :
953: int column = -1;
954: for (int i = 1; i < errorLines.length; i++) {
955: parsedErrorLines[i] = errorLines[i];
956: final int c = errorLines[i].indexOf('^');
957: if (c >= 0) {
958: column = c;
959: }
960: }
961:
962: // An additional work, if the compiler works in selective mode:
963: // In this case, the sourcefiles to be compiled have been copied
964: // to a temp directory this.selectedSourceDirectoryPath, which
965: // must be replaced by the original file basis directory, which is
966: // this.sourceDirectoryPath, otherwise the error buttons won't work:
967: if (this .selectiveCompileModeIsSet) {
968: filePath = filePath
969: .substring(this .selectedSourceDirectoryPath
970: .length());
971: filePath = this .sourceDirectoryPath + filePath;
972: }
973: outputManager.appendErrorDescriptionForJavaC(filePath,
974: parsedErrorLines, lineNumber, column);
975: }
976: }
977: } // decodeSingleErrorMessage
978:
979: private void removeStringInStringVector(Vector vector, String string) {
980: int vIndex = 0;
981: while (vIndex < vector.size()) {
982: String this Element = (String) vector.elementAt(vIndex);
983: if (this Element.equals(string)) {
984: vector.removeElementAt(vIndex);
985: } else {
986: vIndex++;
987: }
988: }
989: }
990:
991: } // CompilerRunnableForJavaC
|