Clover coverage report - DrJava Test Coverage (drjava-20120304-r5456)
Coverage timestamp: Sun Mar 4 2012 03:13:23 CST
file stats: LOC: 699   Methods: 36
NCLOC: 386   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
DefaultCompilerModel.java 26.1% 38.5% 52.8% 36.7%
coverage coverage
 1    /*BEGIN_COPYRIGHT_BLOCK
 2    *
 3    * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu)
 4    * All rights reserved.
 5    *
 6    * Redistribution and use in source and binary forms, with or without
 7    * modification, are permitted provided that the following conditions are met:
 8    * * Redistributions of source code must retain the above copyright
 9    * notice, this list of conditions and the following disclaimer.
 10    * * Redistributions in binary form must reproduce the above copyright
 11    * notice, this list of conditions and the following disclaimer in the
 12    * documentation and/or other materials provided with the distribution.
 13    * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
 14    * names of its contributors may be used to endorse or promote products
 15    * derived from this software without specific prior written permission.
 16    *
 17    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18    * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19    * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20    * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 21    * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 22    * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 23    * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 24    * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 25    * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 26    * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28    *
 29    * This software is Open Source Initiative approved Open Source Software.
 30    * Open Source Initative Approved is a trademark of the Open Source Initiative.
 31    *
 32    * This file is part of DrJava. Download the current version of this project
 33    * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
 34    *
 35    * END_COPYRIGHT_BLOCK*/
 36   
 37    package edu.rice.cs.drjava.model.compiler;
 38   
 39    import java.io.File;
 40    import java.io.IOException;
 41   
 42    import javax.swing.JButton;
 43    import javax.swing.AbstractAction;
 44    import java.awt.event.ActionEvent;
 45    import javax.swing.JOptionPane;
 46    import java.util.*;
 47   
 48    import edu.rice.cs.drjava.DrJava;
 49    import edu.rice.cs.drjava.config.OptionConstants;
 50    import edu.rice.cs.drjava.config.Option;
 51    import edu.rice.cs.drjava.model.DJError;
 52    import edu.rice.cs.drjava.model.GlobalModel;
 53    import edu.rice.cs.drjava.model.OpenDefinitionsDocument;
 54    import edu.rice.cs.drjava.model.DrJavaFileUtils;
 55    import edu.rice.cs.drjava.model.definitions.InvalidPackageException;
 56    import edu.rice.cs.plt.io.IOUtil;
 57    import edu.rice.cs.plt.iter.IterUtil;
 58    import edu.rice.cs.plt.collect.CollectUtil;
 59    import edu.rice.cs.util.FileOps;
 60    import edu.rice.cs.util.UnexpectedException;
 61    import edu.rice.cs.util.swing.Utilities;
 62    import edu.rice.cs.javalanglevels.*;
 63    import edu.rice.cs.javalanglevels.parser.*;
 64    import edu.rice.cs.javalanglevels.tree.*;
 65    import edu.rice.cs.util.swing.ScrollableListDialog;
 66   
 67    import static edu.rice.cs.plt.debug.DebugUtil.debug;
 68   
 69    /** Default implementation of the CompilerModel interface. This implementation is used for normal DrJava execution
 70    * (as opposed to testing DrJava). TO DO: convert edu.rice.cs.util.Pair to edu.rice.cs.plt.tuple.Pair; requires
 71    * making the same conversion in javalanglevels.
 72    * @version $Id: DefaultCompilerModel.java 5443 2011-08-17 04:58:50Z rcartwright $
 73    */
 74    public class DefaultCompilerModel implements CompilerModel {
 75   
 76    /** for logging debug info */
 77    private static edu.rice.cs.util.Log _log = new edu.rice.cs.util.Log("DefaultCompilerModel.txt", false);
 78   
 79    /** The available compilers */
 80    private final List<CompilerInterface> _compilers;
 81   
 82    /** Current compiler -- one of _compilers, or a NoCompilerAvailable */
 83    private CompilerInterface _active;
 84   
 85    /** Manages listeners to this model. */
 86    private final CompilerEventNotifier _notifier = new CompilerEventNotifier();
 87   
 88    /** The global model to which this compiler model belongs. */
 89    private final GlobalModel _model;
 90   
 91    /** The error model containing all current compiler errors. */
 92    private CompilerErrorModel _compilerErrorModel;
 93   
 94    /** The lock providing mutual exclustion between compilation and unit testing */
 95    private Object _compilerLock = new Object();
 96   
 97    /** The LanguageLevelStackTraceMapper that helps translate .java line
 98    * numbers to .dj* line numbers when an error is thrown */
 99    public LanguageLevelStackTraceMapper _LLSTM;
 100   
 101    /** Main constructor.
 102    * @param m the GlobalModel that is the source of documents for this CompilerModel
 103    * @param compilers The compilers to use. The first will be made active; all are assumed
 104    * to be available. An empty list is acceptable.
 105    */
 106  157 public DefaultCompilerModel(GlobalModel m, Iterable<? extends CompilerInterface> compilers) {
 107   
 108  157 _compilers = new ArrayList<CompilerInterface>();
 109  157 ArrayList<String> compilerNames = new ArrayList<String>();
 110   
 111  471 for (CompilerInterface i : compilers) { _compilers.add(i); compilerNames.add(i.getName());}
 112   
 113  157 OptionConstants.COMPILER_PREFERENCE_CONTROL.setList(compilerNames); // populates the compiler list for preference panel
 114   
 115  157 String dCompName = DrJava.getConfig().getSetting(OptionConstants.DEFAULT_COMPILER_PREFERENCE);
 116   
 117  157 if (_compilers.size() > 0) {
 118  157 if (! dCompName.equals(OptionConstants.COMPILER_PREFERENCE_CONTROL.NO_PREFERENCE) &&
 119    compilerNames.contains(dCompName))
 120  0 _active = _compilers.get(compilerNames.indexOf(dCompName));
 121    else
 122  157 _active = _compilers.get(0);
 123    }
 124    else
 125  0 _active = NoCompilerAvailable.ONLY;
 126   
 127  157 _model = m;
 128  157 _compilerErrorModel = new CompilerErrorModel(new DJError[0], _model);
 129  157 _LLSTM = new LanguageLevelStackTraceMapper(_model);
 130    }
 131   
 132   
 133    //--------------------------------- Locking -------------------------------//
 134   
 135    /** Returns the lock used to prevent simultaneous compilation and JUnit testing */
 136  18 public Object getCompilerLock() { return _compilerLock; }
 137   
 138    //-------------------------- Listener Management --------------------------//
 139   
 140    /** Adds a CompilerListener to the model. This operation is synchronized by the readers/writers protocol in
 141    * EventNotifier<T>.
 142    * @param listener A listener that reacts to compiler events.
 143    */
 144  317 public void addListener(CompilerListener listener) { _notifier.addListener(listener); }
 145   
 146    /** Removes a CompilerListener from the model. If the listener is not installed, this method has no effect.
 147    * @param listener a listener that reacts to compiler events
 148    * This operation is synchronized by the readers/writers protocol in EventNotifier<T>.
 149    */
 150  3 public void removeListener(CompilerListener listener) { _notifier.removeListener(listener); }
 151   
 152    /** Removes all CompilerListeners from this model. */
 153  0 public void removeAllListeners() { _notifier.removeAllListeners(); }
 154   
 155    //-------------------------------- Triggers --------------------------------//
 156   
 157   
 158    /** Compile all open documents.
 159    * <p>Before compiling, all unsaved and untitled documents are saved, and compilation ends if the user cancels this
 160    * step. The compilation classpath and sourcepath includes the build directory (if it exists), the source roots,
 161    * the project "extra classpath" (if it exists), the global "extra classpath", and the current JVM's classpath
 162    * (which includes drjava.jar, containing JUnit classes).</p>
 163    * This method formerly only compiled documents which were out of sync with their class file, as a performance
 164    * optimization. However, bug #634386 pointed out that unmodified files could depend on modified files, in which
 165    * case this command would not recompile a file in some situations when it should. Since we value correctness over
 166    * performance, we now always compile all open documents.</p>
 167    * @throws IOException if a filesystem-related problem prevents compilation
 168    */
 169  11 public void compileAll() throws IOException {
 170  11 if (_prepareForCompile()) { _doCompile(_model.getOpenDefinitionsDocuments()); }
 171  0 else _notifier.compileAborted(new UnexpectedException("Some modified open files are unsaved"));
 172    }
 173   
 174    /** Compiles all documents in the project source tree. Assumes DrJava currently contains an active project.
 175    * <p>Before compiling, all unsaved and untitled documents are saved, and compilation ends if the user cancels this
 176    * step. The compilation classpath and sourcepath includes the build directory (if it exists), the source roots,
 177    * the project "extra classpath" (if it exists), the global "extra classpath", and the current JVM's classpath
 178    * (which includes drjava.jar, containing JUnit classes).</p>
 179    * This method formerly only compiled documents which were out of sync with their class file, as a performance
 180    * optimization. However, bug #634386 pointed out that unmodified files could depend on modified files, in which
 181    * case this command would not recompile a file in some situations when it should. Since we value correctness over
 182    * performance, we now always compile all open documents.</p>
 183    * @throws IOException if a filesystem-related problem prevents compilation
 184    */
 185  0 public void compileProject() throws IOException {
 186  0 if (! _model.isProjectActive())
 187  0 throw new UnexpectedException("compileProject invoked when DrJava is not in project mode");
 188   
 189  0 if (_prepareForCompile()) { _doCompile(_model.getProjectDocuments()); }
 190  0 else _notifier.compileAborted(new UnexpectedException("Project contains unsaved modified files"));
 191    }
 192   
 193    /** Compiles all of the given files.
 194    * <p>Before compiling, all unsaved and untitled documents are saved, and compilation ends if the user cancels this
 195    * step. The compilation classpath and sourcepath includes the build directory (if it exists), the source roots,
 196    * the project "extra classpath" (if it exists), the global "extra classpath", and the current JVM's classpath
 197    * (which includes drjava.jar, containing JUnit classes).</p>
 198    * This method formerly only compiled documents which were out of sync with their class file, as a performance
 199    * optimization. However, bug #634386 pointed out that unmodified files could depend on modified files, in which
 200    * case this command would not recompile a file in some situations when it should. Since we value correctness over
 201    * performance, we now always compile all open documents.</p>
 202    * @throws IOException if a filesystem-related problem prevents compilation
 203    */
 204  0 public void compile(List<OpenDefinitionsDocument> defDocs) throws IOException {
 205  0 if (_prepareForCompile()) { _doCompile(defDocs); }
 206  0 else _notifier.compileAborted(new UnexpectedException("The files to be compiled include unsaved modified files"));
 207    }
 208   
 209    /** Compiles the given file.
 210    * <p>Before compiling, all unsaved and untitled documents are saved, and compilation ends if the user cancels this
 211    * step. The compilation classpath and sourcepath includes the build directory (if it exists), the source roots,
 212    * the project "extra classpath" (if it exists), the global "extra classpath", and the current JVM's classpath
 213    * (which includes drjava.jar, containing JUnit classes).</p>
 214    * This method formerly only compiled documents which were out of sync with their class file, as a performance
 215    * optimization. However, bug #634386 pointed out that unmodified files could depend on modified files, in which
 216    * case this command would not recompile a file in some situations when it should. Since we value correctness over
 217    * performance, we now always compile all open documents.</p>
 218    * @throws IOException if a filesystem-related problem prevents compilation
 219    */
 220  48 public void compile(OpenDefinitionsDocument doc) throws IOException {
 221  46 if (_prepareForCompile()) { _doCompile(Arrays.asList(doc)); }
 222  2 else _notifier.compileAborted(new UnexpectedException(doc + "is modified but unsaved"));
 223    }
 224   
 225    /** Check that there are no unsaved or untitled files currently open.
 226    * @return @code{true} iff compilation should continue
 227    */
 228  59 private boolean _prepareForCompile() {
 229  6 if (_model.hasModifiedDocuments()) _notifier.saveBeforeCompile();
 230    // If user cancelled save, abort compilation
 231  59 return ! _model.hasModifiedDocuments();
 232    }
 233   
 234    /** Compile the given documents. */
 235  57 private void _doCompile(List<OpenDefinitionsDocument> docs) throws IOException {
 236  57 _LLSTM.clearCache();
 237  57 final ArrayList<File> filesToCompile = new ArrayList<File>();
 238  57 final ArrayList<File> excludedFiles = new ArrayList<File>();
 239  57 final ArrayList<DJError> packageErrors = new ArrayList<DJError>();
 240  57 for (OpenDefinitionsDocument doc : docs) {
 241  71 if (doc.isSourceFile()) {
 242  61 File f = doc.getFile();
 243    // Check for null in case the file is untitled (not sure this is the correct check)
 244  60 if (f != null && f != FileOps.NULL_FILE) { filesToCompile.add(f); }
 245  60 doc.setCachedClassFile(FileOps.NULL_FILE); // clear cached class file
 246   
 247  60 try { doc.getSourceRoot(); }
 248    catch (InvalidPackageException e) {
 249  0 packageErrors.add(new DJError(f, e.getMessage(), false));
 250    }
 251    }
 252  10 else excludedFiles.add(doc.getFile());
 253    }
 254   
 255  56 Utilities.invokeLater(new Runnable() { public void run() { _notifier.compileStarted(); } });
 256  56 try {
 257  0 if (! packageErrors.isEmpty()) { _distributeErrors(packageErrors); }
 258    else {
 259  56 try {
 260  56 File buildDir = _model.getBuildDirectory();
 261  56 if (buildDir != null && buildDir != FileOps.NULL_FILE && ! buildDir.exists() && ! buildDir.mkdirs()) {
 262  0 throw new IOException("Could not create build directory: " + buildDir);
 263    }
 264   
 265    // File workDir = _model.getWorkingDirectory();
 266    // if (workDir == FileOps.NULL_FILE) workDir = null;
 267    // if (workDir != null && ! workDir.exists() && ! workDir.mkdirs()) {
 268    // throw new IOException("Could not create working directory: " + workDir);
 269    // }
 270   
 271  56 _compileFiles(filesToCompile, buildDir);
 272    }
 273    catch (Throwable t) {
 274  0 DJError err = new DJError(t.toString(), false);
 275  0 _distributeErrors(Arrays.asList(err));
 276  0 throw new UnexpectedException(t);
 277    }
 278    }
 279    }
 280    finally {
 281  56 Utilities.invokeLater(new Runnable() {
 282  56 public void run() { _notifier.compileEnded(_model.getWorkingDirectory(), excludedFiles); }
 283    });
 284    }
 285    }
 286   
 287   
 288    //-------------------------------- Helpers --------------------------------//
 289   
 290    /** Converts JExprParseExceptions thrown by the JExprParser in language levels to CompilerErrors. */
 291  0 private LinkedList<DJError> _parseExceptions2CompilerErrors(LinkedList<JExprParseException> pes) {
 292  0 final LinkedList<DJError> errors = new LinkedList<DJError>();
 293  0 Iterator<JExprParseException> iter = pes.iterator();
 294  0 while (iter.hasNext()) {
 295  0 JExprParseException pe = iter.next();
 296  0 errors.addLast(new DJError(pe.getFile(), pe.currentToken.beginLine-1, pe.currentToken.beginColumn-1,
 297    pe.getMessage(), false));
 298    }
 299  0 return errors;
 300    }
 301   
 302    /** Converts errors thrown by the language level visitors to CompilerErrors. */
 303  0 private LinkedList<DJError> _visitorErrors2CompilerErrors(LinkedList<Pair<String, JExpressionIF>> visitorErrors) {
 304  0 final LinkedList<DJError> errors = new LinkedList<DJError>();
 305  0 Iterator<Pair<String, JExpressionIF>> iter = visitorErrors.iterator();
 306  0 while (iter.hasNext()) {
 307  0 Pair<String, JExpressionIF> pair = iter.next();
 308  0 String message = pair.getFirst();
 309    // System.out.println("Got error message: " + message);
 310  0 JExpressionIF jexpr = pair.getSecond();
 311   
 312  0 SourceInfo si;
 313  0 if (jexpr == null) si = SourceInfo.NO_INFO;
 314  0 else si = pair.getSecond().getSourceInfo();
 315   
 316  0 errors.addLast(new DJError(si.getFile(), si.getStartLine()-1, si.getStartColumn()-1, message, false));
 317    }
 318  0 return errors;
 319    }
 320   
 321    /** Compile the given files and update the model with any errors that result. Does not notify listeners.
 322    * All public compile methods delegate to this one so this method is the only one that uses synchronization to
 323    * prevent compiling and unit testing at the same time.
 324    * @param files The files to be compiled
 325    * @param buildDir The output directory for all the .class files; @code{null} means output to the same
 326    * directory as the source file
 327    */
 328  56 private void _compileFiles(List<File> files, File buildDir) throws IOException {
 329  56 if (! files.isEmpty()) {
 330    /* Canonicalize buildDir */
 331  55 if (buildDir == FileOps.NULL_FILE) buildDir = null; // compiler interface wants null pointer if no build directory
 332  0 if (buildDir != null) buildDir = IOUtil.attemptCanonicalFile(buildDir);
 333   
 334  55 List<File> classPath = CollectUtil.makeList(_model.getClassPath());
 335   
 336    // Temporary hack to allow a boot class path to be specified
 337  55 List<File> bootClassPath = null;
 338  55 String bootProp = System.getProperty("drjava.bootclasspath");
 339  0 if (bootProp != null) { bootClassPath = CollectUtil.makeList(IOUtil.parsePath(bootProp)); }
 340   
 341  55 final LinkedList<DJError> errors = new LinkedList<DJError>();
 342   
 343  55 List<? extends File> preprocessedFiles = _compileLanguageLevelsFiles(files, errors, classPath, bootClassPath);
 344   
 345  55 if (errors.isEmpty()) {
 346  55 CompilerInterface compiler = getActiveCompiler();
 347   
 348    // Mutual exclusion with JUnit code that finds all test classes (in DefaultJUnitModel)
 349  55 synchronized(_compilerLock) {
 350  55 if (preprocessedFiles == null) {
 351  55 errors.addAll(compiler.compile(files, classPath, null, buildDir, bootClassPath, null, true));
 352    }
 353    else {
 354    /** If compiling a language level file, do not show warnings, as these are not caught by the language level
 355    * parser */
 356  0 errors.addAll(compiler.compile(preprocessedFiles, classPath, null, buildDir, bootClassPath, null, false));
 357    }
 358    }
 359    }
 360  55 _distributeErrors(errors);
 361    }
 362    else {
 363    // TODO: Is this necessary?
 364  1 _distributeErrors(Collections.<DJError>emptyList());
 365    }
 366    }
 367   
 368    /** Reorders files so that all file names containing "Test" are at the end. */
 369  0 private static List<File> _testFileSort(List<File> files) {
 370  0 LinkedList<File> testFiles = new LinkedList<File>();
 371  0 LinkedList<File> otherFiles = new LinkedList<File>();
 372  0 for (File f: files) {
 373  0 if (f.getName().contains("Test")) testFiles.add(f);
 374  0 else otherFiles.add(f);
 375    }
 376  0 otherFiles.addAll(testFiles);
 377  0 return otherFiles;
 378    }
 379   
 380    /** Compiles the language levels files in the list. Adds any errors to the given error list.
 381    * @return An updated list for compilation containing no Language Levels files, or @code{null}
 382    * if there were no Language Levels files to process.
 383    */
 384  55 private List<File> _compileLanguageLevelsFiles(List<File> files, List<DJError> errors,
 385    Iterable<File> classPath, Iterable<File> bootClassPath) {
 386    /* Construct the collection of files to be compild by javac, renaming any language levels (.dj*) files to the
 387    * corresponding java (.java) files. By using a HashSet, we avoid creating duplicates in this collection.
 388    */
 389  55 HashSet<File> javaFileSet = new HashSet<File>();
 390  55 LinkedList<File> newFiles = new LinkedList<File>(); // Used to record the LL files that must be converted
 391  55 final LinkedList<File> filesToBeClosed = new LinkedList<File>(); // Used to record .java files that are open at
 392    // the same time as their .dj? files.
 393  55 boolean containsLanguageLevels = false;
 394  55 for (File f : files) {
 395  60 File canonicalFile = IOUtil.attemptCanonicalFile(f);
 396  60 String fileName = canonicalFile.getPath();
 397  60 if (DrJavaFileUtils.isLLFile(fileName)) {
 398  0 containsLanguageLevels = true;
 399  0 File javaFile = new File(DrJavaFileUtils.getJavaForLLFile(fileName));
 400   
 401    //checks if .dj? file has a matching .java file open in project. Eventually warns user (later on in code)
 402  0 if (files.contains(javaFile)) filesToBeClosed.add(javaFile);
 403    // delete file later so closeFiles doesn't complain about missing files
 404    else
 405    // Delete the stale .java file now (if it exists), a file with this name will subsequently be generated
 406  0 javaFile.delete();
 407   
 408  0 javaFileSet.add(javaFile);
 409  0 newFiles.add(javaFile);
 410    }
 411  60 else javaFileSet.add(canonicalFile);
 412    }
 413   
 414  55 for (File f: filesToBeClosed) {
 415  0 if (files.contains(DrJavaFileUtils.getDJForJavaFile(f)) ||
 416    files.contains(DrJavaFileUtils.getDJ0ForJavaFile(f)) ||
 417    files.contains(DrJavaFileUtils.getDJ1ForJavaFile(f)) ||
 418    files.contains(DrJavaFileUtils.getDJ2ForJavaFile(f))) {
 419  0 files.remove(f);
 420    }
 421    }
 422   
 423  55 if (!filesToBeClosed.isEmpty()) {
 424  0 final JButton closeButton = new JButton(new AbstractAction("Close Files") {
 425  0 public void actionPerformed(ActionEvent e) {
 426    // no op, i.e. delete everything
 427    }
 428    });
 429  0 final JButton keepButton = new JButton(new AbstractAction("Keep Open") {
 430  0 public void actionPerformed(ActionEvent e) {
 431    // clear the set, i.e. do not delete anything
 432  0 filesToBeClosed.clear();
 433    }
 434    });
 435  0 ScrollableListDialog<File> dialog = new ScrollableListDialog.Builder<File>()
 436  0 .setTitle("Java File" + (filesToBeClosed.size() == 1?"":"s") + " Need to Be Closed")
 437  0 .setText("The following .java " + (filesToBeClosed.size() == 1?
 438    "file has a matching .dj? file":
 439    "files have matching .dj? files") + " open.\n" +
 440  0 (filesToBeClosed.size() == 1?
 441    "This .java file needs":
 442    "These .java files need") + " to be closed for proper compiling.")
 443    .setItems(filesToBeClosed)
 444    .setMessageType(JOptionPane.WARNING_MESSAGE)
 445    .setFitToScreen(true)
 446    .clearButtons()
 447    .addButton(closeButton)
 448    .addButton(keepButton)
 449    .build();
 450   
 451  0 dialog.showDialog();
 452   
 453  0 LinkedList<OpenDefinitionsDocument> docsToBeClosed = new LinkedList<OpenDefinitionsDocument>();
 454  0 for(File f: filesToBeClosed) {
 455  0 try {
 456  0 docsToBeClosed.add(_model.getDocumentForFile(f));
 457    }
 458    catch(IOException ioe) { /* ignore, just don't close this document */ }
 459    }
 460  0 _model.closeFiles(docsToBeClosed);
 461    // delete the files now because closeFiles has executed and won't complain about missing files anymore
 462  0 for(File f: filesToBeClosed) {
 463    // Delete the stale .java file now (if it exists), a file with this name will subsequently be generated
 464  0 f.delete();
 465    }
 466    }
 467   
 468  55 if (containsLanguageLevels) {
 469    /* Check if we should delete class files in directories with language level files. */
 470  0 final File buildDir = _model.getBuildDirectory();
 471  0 final File sourceDir = _model.getProjectRoot();
 472  0 if (!DrJava.getConfig().getSetting(OptionConstants.DELETE_LL_CLASS_FILES)
 473    .equals(OptionConstants.DELETE_LL_CLASS_FILES_CHOICES.get(0))) {
 474    // not "never"
 475  0 final HashSet<File> dirsWithLLFiles = new HashSet<File>();
 476  0 for(File f: newFiles) {
 477  0 try {
 478  0 File dir = f.getParentFile();
 479  0 if (buildDir != null && buildDir != FileOps.NULL_FILE &&
 480    sourceDir != null && sourceDir != FileOps.NULL_FILE) {
 481    // build directory set
 482  0 String rel = edu.rice.cs.util.FileOps.stringMakeRelativeTo(dir,sourceDir);
 483  0 dir = new File(buildDir,rel);
 484    }
 485  0 dirsWithLLFiles.add(dir);
 486    }
 487    catch(IOException ioe) { /* just don't add this directory */ }
 488    }
 489   
 490  0 if (DrJava.getConfig().getSetting(OptionConstants.DELETE_LL_CLASS_FILES)
 491    .equals(OptionConstants.DELETE_LL_CLASS_FILES_CHOICES.get(1))) {
 492    // "ask me"
 493  0 final JButton deleteButton = new JButton(new AbstractAction("Delete Class Files") {
 494  0 public void actionPerformed(ActionEvent e) {
 495    // no op
 496    }
 497    });
 498  0 final JButton keepButton = new JButton(new AbstractAction("Keep Class Files") {
 499  0 public void actionPerformed(ActionEvent e) {
 500    // clear the set, i.e. do not delete anything
 501  0 dirsWithLLFiles.clear();
 502    }
 503    });
 504  0 ScrollableListDialog<File> dialog = new ScrollableListDialog.Builder<File>()
 505    .setTitle("Delete Class Files")
 506    .setText("We suggest that you delete all class files in the directories with language\n" +
 507    "level files. Do you want to delete the class files in the following director" +
 508  0 (dirsWithLLFiles.size() == 1?"y":"ies") + "?")
 509    .setItems(new ArrayList<File>(dirsWithLLFiles))
 510    .setMessageType(JOptionPane.QUESTION_MESSAGE)
 511    .setFitToScreen(true)
 512    .clearButtons()
 513    .addButton(deleteButton)
 514    .addButton(keepButton)
 515    .build();
 516   
 517  0 dialog.showDialog();
 518    }
 519   
 520    // Delete all class files in the directories listed. If the user was asked and said "keep",
 521    // then the set will be empty
 522  0 for(File f: dirsWithLLFiles) {
 523  0 f.listFiles(new java.io.FilenameFilter() {
 524  0 public boolean accept(File dir, String name) {
 525  0 int endPos = name.lastIndexOf(".class");
 526  0 if (endPos < 0) return false; // can't be a class file
 527  0 new File(dir, name).delete();
 528    // don't need to return true, we're deleting the file here already
 529  0 return false;
 530    }
 531    });
 532    }
 533    }
 534   
 535    /* Perform language levels conversion, creating corresponding .java files. */
 536  0 LanguageLevelConverter llc = new LanguageLevelConverter();
 537  0 Options llOpts;
 538  0 if (bootClassPath == null) { llOpts = new Options(getActiveCompiler().version(), classPath); }
 539  0 else { llOpts = new Options(getActiveCompiler().version(), classPath, bootClassPath); }
 540   
 541    // NOTE: the following workaround ("_testFileSort(files)" instead of simply "files") may no longer be necessary.
 542    /* Perform the conversion incorporating the following Bug Workaround: Forward references can generate spurious
 543    * conversion errors in some cases. This problem can be mitigated by compiling JUnit test files (with names
 544    * containing the substring "Test") last.
 545    */
 546  0 Map<File,Set<String>> sourceToTopLevelClassMap = new HashMap<File,Set<String>>();
 547  0 Pair<LinkedList<JExprParseException>, LinkedList<Pair<String, JExpressionIF>>> llErrors =
 548    llc.convert(_testFileSort(files).toArray(new File[0]), llOpts, sourceToTopLevelClassMap);
 549    /* Add any errors encountered in conversion to the compilation error log. */
 550  0 errors.addAll(_parseExceptions2CompilerErrors(llErrors.getFirst()));
 551  0 errors.addAll(_visitorErrors2CompilerErrors(llErrors.getSecond()));
 552   
 553    // Since we (optionally) delete all class files in LL directories, we don't need the code
 554    // to smart-delete class files anymore.
 555    // smartDeleteClassFiles(sourceToTopLevelClassMap);
 556    }
 557   
 558  0 if (containsLanguageLevels) { return new LinkedList<File>(javaFileSet); }
 559  55 else { return null; }
 560    }
 561   
 562    /** Sorts the given array of CompilerErrors and divides it into groups based on the file, giving each group to the
 563    * appropriate OpenDefinitionsDocument, opening files if necessary. Called immediately after compilations finishes.
 564    */
 565  56 private void _distributeErrors(List<? extends DJError> errors) throws IOException {
 566    // resetCompilerErrors(); // Why is this done?
 567    // System.err.println("Preparing to construct CompilerErrorModel for errors: " + errors);
 568  56 _compilerErrorModel = new CompilerErrorModel(errors.toArray(new DJError[0]), _model);
 569  56 _model.setNumCompErrors(_compilerErrorModel.getNumCompErrors()); // cache number of compiler errors in global model
 570    }
 571   
 572    //----------------------------- Error Results -----------------------------//
 573   
 574    /** Gets the CompilerErrorModel representing the last compile. */
 575  185 public CompilerErrorModel getCompilerErrorModel() { return _compilerErrorModel; }
 576   
 577    /** Gets the total number of errors in this compiler model. */
 578  159 public int getNumErrors() { return getCompilerErrorModel().getNumErrors(); }
 579   
 580    /** Gets the total number of current compiler errors. */
 581  0 public int getNumCompErrors() { return getCompilerErrorModel().getNumCompErrors(); }
 582   
 583    /** Gets the total number of current warnings. */
 584  0 public int getNumWarnings() { return getCompilerErrorModel().getNumWarnings(); }
 585   
 586    /** Resets the compiler error state to have no errors. */
 587  5 public void resetCompilerErrors() {
 588    // TODO: see if we can get by without this function
 589  5 _compilerErrorModel = new CompilerErrorModel(new DJError[0], _model);
 590    }
 591   
 592    //-------------------------- Compiler Management --------------------------//
 593   
 594    /** Returns all registered compilers that are actually available. If there are none,
 595    * the result is {@link NoCompilerAvailable#ONLY}.
 596    */
 597  38 public Iterable<CompilerInterface> getAvailableCompilers() {
 598  0 if (_compilers.isEmpty()) { return IterUtil.singleton(NoCompilerAvailable.ONLY); }
 599  38 else { return IterUtil.snapshot(_compilers); }
 600    }
 601   
 602    /** Gets the compiler that is the "active" compiler.
 603    *
 604    * @see #setActiveCompiler
 605    */
 606  962 public CompilerInterface getActiveCompiler() { return _active; }
 607   
 608    /** Sets which compiler is the "active" compiler.
 609    *
 610    * @param compiler Compiler to set active.
 611    * @throws IllegalArgumentException If the compiler is not in the list of available compilers
 612    *
 613    * @see #getActiveCompiler
 614    */
 615  0 public void setActiveCompiler(CompilerInterface compiler) {
 616  0 if (_compilers.isEmpty() && compiler.equals(NoCompilerAvailable.ONLY)) {
 617    // _active should be set correctly already
 618    }
 619  0 else if (_compilers.contains(compiler)) {
 620  0 _active = compiler;
 621  0 _notifier.activeCompilerChanged();
 622    }
 623    else {
 624  0 throw new IllegalArgumentException("Compiler is not in the list of available compilers: " + compiler);
 625    }
 626    }
 627   
 628    /** Add a compiler to the active list */
 629  0 public void addCompiler(CompilerInterface compiler) {
 630  0 if (_compilers.isEmpty()) {
 631  0 _active = compiler;
 632    }
 633  0 _compilers.add(compiler);
 634    }
 635   
 636    /** Delete the .class files that match the following pattern:
 637    * XXX.dj? --> XXX.class
 638    * XXX$*.class
 639    * @param sourceToTopLevelClassMap a map from directories to classes in them
 640    */
 641  0 public void smartDeleteClassFiles(Map<File,Set<String>> sourceToTopLevelClassMap) {
 642  0 final File buildDir = _model.getBuildDirectory();
 643  0 final File sourceDir = _model.getProjectRoot();
 644    // Accessing the disk is the most costly part; therefore, we want to scan each directory only once.
 645    // We create a map from parent directory to class names in that directory.
 646    // Then we scan the files in each directory and delete files that match the class names listed for it.
 647    // dirToClassNameMap: key=parent directory, value=set of classes in this directory
 648  0 Map<File,Set<String>> dirToClassNameMap = new HashMap<File,Set<String>>();
 649  0 for(Map.Entry<File,Set<String>> e: sourceToTopLevelClassMap.entrySet()) {
 650  0 try {
 651  0 File dir = e.getKey().getParentFile();
 652  0 if (buildDir != null && buildDir != FileOps.NULL_FILE &&
 653    sourceDir != null && sourceDir != FileOps.NULL_FILE) {
 654    // build directory set
 655  0 String rel = edu.rice.cs.util.FileOps.stringMakeRelativeTo(dir,sourceDir);
 656  0 dir = new File(buildDir,rel);
 657    }
 658  0 Set<String> classNames = dirToClassNameMap.get(dir);
 659  0 if (classNames == null) classNames = new HashSet<String>();
 660  0 classNames.addAll(e.getValue());
 661  0 dirToClassNameMap.put(dir,classNames);
 662    // System.out.println(e.getKey() + " --> " + dir);
 663    // for(String name: e.getValue()) {
 664    // System.out.println("\t" + name);
 665    // }
 666    }
 667    catch(IOException ioe) { /* we'll fail to delete this, but that's better than deleting something we shouldn't */ }
 668    }
 669    // Now that we have a map from parent directories to the class names that should be deleted
 670    // in them, we scan the files in each directory, then check if the names match the class names.
 671  0 for(final Map.Entry<File,Set<String>> e: dirToClassNameMap.entrySet()) {
 672    // System.out.println("Processing dir: " + e.getKey());
 673    // System.out.println("\t" + java.util.Arrays.toString(e.getValue().toArray(new String[0])));
 674  0 e.getKey().listFiles(new java.io.FilenameFilter() {
 675  0 public boolean accept(File dir, String name) {
 676    // System.out.println("\t" + name);
 677  0 int endPos = name.lastIndexOf(".class");
 678  0 if (endPos < 0) return false; // can't be a class file
 679  0 int dollarPos = name.indexOf('$');
 680  0 if ((dollarPos >= 0) && (dollarPos < endPos)) endPos = dollarPos;
 681    // class name goes to the .class or the first $, whichever comes first
 682  0 Set<String> classNames = e.getValue();
 683  0 if (classNames.contains(name.substring(0,endPos))) {
 684    // this is a class file that is generated from a .dj? file
 685  0 new File(dir, name).delete();
 686    // don't need to return true, we're deleting the file here already
 687    // System.out.println("\t\tDeleted");
 688    }
 689  0 return false;
 690    }
 691    });
 692    }
 693    }
 694   
 695    /** returns the LanguageLevelStackTraceMapper
 696    * @return the LanguageLevelStackTraceMapper
 697    * */
 698  13 public LanguageLevelStackTraceMapper getLLSTM() { return _LLSTM; }
 699    }