Clover coverage report - DrJava Test Coverage (drjava-20120304-r5456)
Coverage timestamp: Sun Mar 4 2012 03:13:23 CST
file stats: LOC: 732   Methods: 21
NCLOC: 544   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
JarJDKToolsLibrary.java 46.4% 66.6% 81% 61.5%
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;
 38   
 39    import java.io.File;
 40    import java.util.Set;
 41    import java.util.LinkedHashSet;
 42    import java.util.LinkedHashMap;
 43    import java.util.Arrays;
 44    import java.util.List;
 45    import java.util.ArrayList;
 46    import java.util.Map;
 47    import java.util.TreeMap;
 48    import java.util.Set;
 49    import java.util.HashSet;
 50    import java.util.Collections;
 51    import java.util.jar.JarFile;
 52    import java.util.jar.JarEntry;
 53    import java.util.jar.Manifest;
 54    import java.util.Enumeration;
 55    import java.io.IOException;
 56    import java.io.FileNotFoundException;
 57   
 58    import edu.rice.cs.plt.io.IOUtil;
 59    import edu.rice.cs.plt.iter.IterUtil;
 60    import edu.rice.cs.plt.lambda.Lambda;
 61    import edu.rice.cs.plt.lambda.LambdaUtil;
 62    import edu.rice.cs.plt.lambda.Predicate;
 63    import edu.rice.cs.plt.reflect.ReflectUtil;
 64    import edu.rice.cs.plt.reflect.PathClassLoader;
 65    import edu.rice.cs.plt.reflect.ShadowingClassLoader;
 66    import edu.rice.cs.plt.reflect.PreemptingClassLoader;
 67    import edu.rice.cs.plt.reflect.ReflectException;
 68    import edu.rice.cs.plt.reflect.JavaVersion;
 69    import edu.rice.cs.plt.reflect.JavaVersion.FullVersion;
 70   
 71    import edu.rice.cs.drjava.model.compiler.CompilerInterface;
 72    import edu.rice.cs.drjava.model.compiler.NoCompilerAvailable;
 73    import edu.rice.cs.drjava.model.debug.Debugger;
 74    import edu.rice.cs.drjava.model.debug.NoDebuggerAvailable;
 75    import edu.rice.cs.drjava.model.javadoc.JavadocModel;
 76    import edu.rice.cs.drjava.model.javadoc.DefaultJavadocModel;
 77    import edu.rice.cs.drjava.model.javadoc.NoJavadocAvailable;
 78    import edu.rice.cs.drjava.model.JDKDescriptor;
 79   
 80    /** A JDKToolsLibrary that was loaded from a specific jar file. */
 81    public class JarJDKToolsLibrary extends JDKToolsLibrary {
 82   
 83    /** Packages to shadow when loading a new tools.jar. If we don't shadow these classes, we won't
 84    * be able to load distinct versions for each tools.jar library. These should be verified whenever
 85    * a new Java version is released. (We can't just shadow *everything* because some classes, at
 86    * least in OS X's classes.jar, can only be loaded by the JVM.)
 87    */
 88    private static final Set<String> TOOLS_PACKAGES = new HashSet<String>();
 89    static {
 90  21 Collections.addAll(TOOLS_PACKAGES, new String[] {
 91    // From 1.4 tools.jar:
 92    "com.sun.javadoc",
 93    "com.sun.jdi",
 94    "com.sun.tools",
 95    "sun.applet", // also bundled in rt.jar
 96    "sun.rmi.rmic",
 97    //"sun.security.tools", // partially bundled in rt.jar -- it's inconsistent between versions, so we need to
 98    // allow these classes to be loaded. Hopefully this doesn't break anything.
 99    "sun.tools", // sun.tools.jar, sun.tools.hprof, and (sometimes) sun.tools.util.CommandLine are also in rt.jar
 100   
 101    // Additional from 5 tools.jar:
 102    "com.sun.jarsigner",
 103    "com.sun.mirror",
 104    "sun.jvmstat",
 105   
 106    // Additional from 6 tools.jar:
 107    "com.sun.codemodel",
 108    "com.sun.istack.internal.tools", // other istack packages are in rt.jar
 109    "com.sun.istack.internal.ws",
 110    "com.sun.source",
 111    "com.sun.xml.internal.dtdparser", // other xml.internal packages are in rt.jar
 112    "com.sun.xml.internal.rngom",
 113    "com.sun.xml.internal.xsom",
 114    "org.relaxng",
 115    });
 116    }
 117   
 118   
 119    private final File _location;
 120    private final List<File> _bootClassPath; // may be null (i.e. compiler's internal behavior)
 121   
 122  2826 private JarJDKToolsLibrary(File location, FullVersion version, JDKDescriptor jdkDescriptor,
 123    CompilerInterface compiler, Debugger debugger,
 124    JavadocModel javadoc, List<File> bootClassPath) {
 125  2826 super(version, jdkDescriptor, compiler, debugger, javadoc);
 126  2826 _location = location;
 127  2826 _bootClassPath = bootClassPath;
 128    }
 129   
 130  0 public File location() { return _location; }
 131  5024 public List<File> bootClassPath() { // may be null
 132  4082 if (_bootClassPath!=null) return new ArrayList<File>(_bootClassPath);
 133  942 else return null;
 134    }
 135   
 136  5024 public String toString() {
 137  5024 return super.toString() + " at " + _location + ", boot classpath: " + bootClassPath();
 138    }
 139   
 140    /** Create a JarJDKToolsLibrary from a specific {@code "tools.jar"} or {@code "classes.jar"} file. */
 141  2826 public static JarJDKToolsLibrary makeFromFile(File f, GlobalModel model, JDKDescriptor desc) {
 142  2826 return makeFromFile(f, model, desc, new ArrayList<File>());
 143    }
 144   
 145    /** Create a JarJDKToolsLibrary from a specific {@code "tools.jar"} or {@code "classes.jar"} file. */
 146  2826 public static JarJDKToolsLibrary makeFromFile(File f, GlobalModel model, JDKDescriptor desc,
 147    List<File> additionalBootClassPath) {
 148  2826 assert desc != null;
 149   
 150  2826 CompilerInterface compiler = NoCompilerAvailable.ONLY;
 151  2826 Debugger debugger = NoDebuggerAvailable.ONLY;
 152  2826 JavadocModel javadoc = new NoJavadocAvailable(model);
 153   
 154  2826 FullVersion version = desc.guessVersion(f);
 155  2826 JDKToolsLibrary.msg("makeFromFile: "+f+" --> "+version+", vendor: "+version.vendor());
 156  2826 JDKToolsLibrary.msg(" desc = "+desc);
 157   
 158  2826 boolean isSupported = JavaVersion.CURRENT.supports(version.majorVersion());
 159  2826 Iterable<File> additionalCompilerFiles = IterUtil.empty();
 160   
 161    // JDKDescriptor.NONE will require JavaVersion.CURRENT to be at least JavaVersion.JAVA_1_1,
 162    // i.e. it will always be supported
 163  2826 isSupported |= JavaVersion.CURRENT.supports(desc.getMinimumMajorVersion());
 164  2826 try {
 165  2826 additionalCompilerFiles = desc.getAdditionalCompilerFiles(f);
 166    }
 167    catch(FileNotFoundException fnfe) {
 168    // not all additional compiler files were found
 169  0 isSupported = false;
 170    }
 171   
 172    // We can't execute code that was possibly compiled for a later Java API version.
 173  2826 List<File> bootClassPath = null;
 174  2826 if (isSupported) {
 175    // block tools.jar classes, so that references don't point to a different version of the classes
 176  2826 ClassLoader loader =
 177    new ShadowingClassLoader(JarJDKToolsLibrary.class.getClassLoader(), true, TOOLS_PACKAGES, true);
 178  2826 Iterable<File> path = IterUtil.map(IterUtil.compose(additionalCompilerFiles, f), new Lambda<File,File>() {
 179  108487 public File value(File arg) { return IOUtil.attemptAbsoluteFile(arg); }
 180    });
 181   
 182  2826 String compilerAdapter = desc.getAdapterForCompiler(version);
 183   
 184  2826 if (compilerAdapter != null) {
 185    // determine boot class path
 186  2355 File libDir = null;
 187  0 if (f.getName().equals("classes.jar")) { libDir = f.getParentFile(); }
 188  2355 else if (f.getName().equals("tools.jar")) {
 189  2355 File jdkLibDir = f.getParentFile();
 190  2355 if (jdkLibDir != null) {
 191  2355 File jdkRoot = jdkLibDir.getParentFile();
 192  2355 if (jdkRoot != null) {
 193  2355 File jreLibDir = new File(jdkRoot, "jre/lib");
 194  2355 if (IOUtil.attemptExists(new File(jreLibDir, "rt.jar"))) { libDir = jreLibDir; }
 195    }
 196  2355 if (libDir == null) {
 197  0 if (IOUtil.attemptExists(new File(jdkLibDir, "rt.jar"))) { libDir = jdkLibDir; }
 198    }
 199    }
 200    }
 201  2355 bootClassPath = new ArrayList<File>();
 202  2355 if (libDir != null) {
 203  2355 File[] jars = IOUtil.attemptListFiles(libDir, IOUtil.extensionFilePredicate("jar"));
 204  2355 if (jars != null) { bootClassPath.addAll(Arrays.asList(jars)); }
 205    }
 206    else {
 207    // could not determine boot classpath because the file was not named classes.jar or tools.jar
 208    // at least put the compiler file itself and the additional compiler files on the boot classpath
 209  0 bootClassPath.add(f);
 210  0 for(File acf: additionalCompilerFiles) { bootClassPath.add(acf); };
 211    }
 212  2355 if (additionalBootClassPath!=null) { bootClassPath.addAll(additionalBootClassPath); }
 213  0 if (bootClassPath.isEmpty()) { bootClassPath = null; } // null defers to the compiler's default behavior
 214   
 215  2355 try {
 216  2355 Class<?>[] sig = { FullVersion.class, String.class, List.class };
 217  2355 Object[] args = { version, f.toString(), bootClassPath };
 218    // JDKToolsLibrary.msg("classpath for compiler: "+IterUtil.multilineToString(path));
 219    // JDKToolsLibrary.msg("boot classpath for compiler: "+IterUtil.multilineToString(bootClassPath));
 220  2355 CompilerInterface attempt = (CompilerInterface) ReflectUtil.loadLibraryAdapter(loader, path, compilerAdapter,
 221    sig, args);
 222  2041 if (attempt.isAvailable()) { compiler = attempt; }
 223    }
 224    catch (ReflectException e) { /* can't load */ }
 225    catch (LinkageError e) { /* can't load */ }
 226    }
 227   
 228  2826 String debuggerAdapter = desc.getAdapterForDebugger(version);
 229  2826 String debuggerPackage = "edu.rice.cs.drjava.model.debug.jpda";
 230  2826 if (debuggerAdapter != null) {
 231  2355 try {
 232  2355 JDKToolsLibrary.msg(" loading debugger: "+debuggerAdapter);
 233  2355 Class<?>[] sig = { GlobalModel.class };
 234    // can't use loadLibraryAdapter because we need to preempt the whole package
 235  2355 ClassLoader debugLoader = new PreemptingClassLoader(new PathClassLoader(loader, path), debuggerPackage);
 236  2355 Debugger attempt = (Debugger) ReflectUtil.loadObject(debugLoader, debuggerAdapter, sig, model);
 237  2041 JDKToolsLibrary.msg(" debugger="+attempt.getClass().getName());
 238  2041 if (attempt.isAvailable()) { debugger = attempt; }
 239    }
 240    catch (ReflectException e) {
 241  0 JDKToolsLibrary.msg(" no debugger, ReflectException "+e); /* can't load */
 242    }
 243    catch (LinkageError e) {
 244  314 JDKToolsLibrary.msg(" no debugger, LinkageError "+e); /* can't load */
 245    }
 246    }
 247   
 248  2826 try {
 249  2826 new PathClassLoader(loader, path).loadClass("com.sun.tools.javadoc.Main");
 250  2512 File bin = new File(f.getParentFile(), "../bin");
 251  0 if (!IOUtil.attemptIsDirectory(bin)) { bin = new File(f.getParentFile(), "../Home/bin"); }
 252  0 if (!IOUtil.attemptIsDirectory(bin)) { bin = new File(System.getProperty("java.home", f.getParent())); }
 253  2512 javadoc = new DefaultJavadocModel(model, bin, path);
 254    }
 255    catch (ClassNotFoundException e) { /* can't load */ }
 256    catch (LinkageError e) { /* can't load (probably not necessary, but might as well catch it) */ }
 257   
 258    }
 259   
 260  2826 return new JarJDKToolsLibrary(f, version, desc, compiler, debugger, javadoc, bootClassPath);
 261    }
 262   
 263  2826 public static FullVersion guessVersion(File f, JDKDescriptor desc) {
 264  2826 assert desc != null;
 265   
 266  2826 FullVersion result = null;
 267  2826 boolean forceUnknown = desc.isCompound();
 268   
 269    // We could start with f.getParentFile(), but this simplifies the logic
 270  2826 File current = IOUtil.attemptCanonicalFile(f);
 271  2826 String parsedVersion = "";
 272  2826 String vendor = "";
 273  2826 do {
 274  8478 String name = current.getName();
 275  8478 String path = current.getAbsolutePath();
 276  8478 if (!forceUnknown) {
 277  0 if (path.startsWith("/System/Library/Frameworks/JavaVM.framework")) vendor = "apple";
 278  0 else if (path.toLowerCase().contains("openjdk")) vendor = "openjdk";
 279  0 else if (path.toLowerCase().contains("sun")) vendor = "sun";
 280    }
 281  8478 if (name.startsWith("jdk-")) {
 282  157 result = JavaVersion.parseFullVersion(parsedVersion = name.substring(4),vendor,vendor,f);
 283    }
 284  8321 else if (name.startsWith("jdk")) {
 285  2355 result = JavaVersion.parseFullVersion(parsedVersion = name.substring(3),vendor,vendor,f);
 286    }
 287  5966 else if (name.startsWith("j2sdk")) {
 288  314 result = JavaVersion.parseFullVersion(parsedVersion = name.substring(5),vendor,vendor,f);
 289    }
 290  5652 else if (name.matches("\\d+\\.\\d+\\.\\d+")) {
 291  0 result = JavaVersion.parseFullVersion(parsedVersion = name,vendor,vendor,f);
 292    }
 293  8478 current = current.getParentFile();
 294  8478 } while (current != null && result == null);
 295  2826 if (result == null || result.majorVersion().equals(JavaVersion.UNRECOGNIZED) ||
 296    result.majorVersion().equals(JavaVersion.FUTURE)) {
 297  0 JarFile jf = null;
 298  0 try {
 299  0 jf = new JarFile(f);
 300  0 Manifest mf = jf.getManifest();
 301  0 if (mf != null) {
 302  0 String v = mf.getMainAttributes().getValue("Created-By");
 303  0 if (v != null) {
 304  0 int space = v.indexOf(' ');
 305  0 if (space >= 0) v = v.substring(0,space);
 306  0 result = JavaVersion.parseFullVersion(parsedVersion = v,vendor,vendor,f);
 307    }
 308    }
 309   
 310    // still unknown or future
 311  0 if (result == null || result.majorVersion().equals(JavaVersion.UNRECOGNIZED) ||
 312    result.majorVersion().equals(JavaVersion.FUTURE)) {
 313    // look for the first class file
 314  0 Enumeration<JarEntry> jes = jf.entries();
 315  0 while(jes.hasMoreElements()) {
 316  0 JarEntry je = jes.nextElement();
 317  0 if (je.getName().endsWith(".class")) {
 318  0 result = JavaVersion.parseClassVersion(jf.getInputStream(je)).fullVersion();
 319  0 break;
 320    }
 321    }
 322    }
 323    }
 324  0 catch(IOException ioe) { result = null; }
 325    finally {
 326  0 try {
 327  0 if (jf != null) jf.close();
 328    }
 329    catch(IOException ioe) { /* ignore, just trying to close the file */ }
 330    }
 331   
 332  0 if (result == null || result.majorVersion().equals(JavaVersion.UNRECOGNIZED)) {
 333    // Couldn't find a good version number, so we'll just guess that it's the currently-running version
 334    // Useful where the tools.jar file is in an unusual custom location
 335  0 result = JavaVersion.CURRENT_FULL;
 336    }
 337   
 338  0 parsedVersion = result.versionString();
 339    }
 340   
 341  2826 if ((result == null) || (result.vendor()==JavaVersion.VendorType.UNKNOWN)) {
 342  2826 if (!forceUnknown) {
 343  2826 if (result.majorVersion().compareTo(JavaVersion.JAVA_6)<0) {
 344    // Java 5 or earlier, assume Sun
 345  1256 vendor = "sun";
 346    }
 347    else {
 348    // distinguish Sun Java 6 and OpenJDK 6 if it is still unknown
 349  1570 JarFile jf = null;
 350  1570 try {
 351  1570 jf = new JarFile(f);
 352    /* if (jf.getJarEntry("com/sun/tools/javac/file/JavacFileManager.class")!=null) {
 353    // NOTE: this may cause OpenJDK 7 to also be recognized as sun
 354    vendor = "sun";
 355    }
 356  1570 else */ if (jf.getJarEntry("com/sun/tools/javac/util/JavacFileManager.class")!=null) {
 357  0 vendor = "openjdk";
 358    }
 359  1570 else if (jf.getJarEntry("com/sun/tools/javac/util/DefaultFileManager.class")!=null) {
 360  1256 vendor = "sun";
 361    }
 362    }
 363    catch(IOException ioe) { /* keep existing version */ }
 364    finally {
 365  1570 try {
 366  1570 if (jf != null) jf.close();
 367    }
 368    catch(IOException ioe) { /* ignore, just trying to close the file */ }
 369    }
 370    }
 371    }
 372  2826 result = JavaVersion.parseFullVersion(parsedVersion,vendor,vendor,f);
 373    }
 374  2826 return result;
 375    }
 376   
 377    /** return a collection with the default roots. */
 378  157 protected static LinkedHashMap<File,Set<JDKDescriptor>> getDefaultSearchRoots() {
 379  157 JDKToolsLibrary.msg("---- Getting Default Search Roots ----");
 380   
 381    /* roots is a list of possible parent directories of Java installations; we want to eliminate duplicates &
 382    * remember insertion order
 383    */
 384  157 LinkedHashMap<File,Set<JDKDescriptor>> roots = new LinkedHashMap<File,Set<JDKDescriptor>>();
 385   
 386  157 String javaHome = System.getProperty("java.home");
 387  157 String envJavaHome = null;
 388  157 String programFiles = null;
 389  157 String systemDrive = null;
 390  157 if (JavaVersion.CURRENT.supports(JavaVersion.JAVA_5)) {
 391    // System.getenv is deprecated under 1.3 and 1.4, and may throw a java.lang.Error (!),
 392    // which we'd rather not have to catch
 393  157 envJavaHome = System.getenv("JAVA_HOME");
 394  157 programFiles = System.getenv("ProgramFiles");
 395  157 systemDrive = System.getenv("SystemDrive");
 396    }
 397   
 398  157 if (javaHome != null) {
 399  157 addIfDir(new File(javaHome), roots);
 400  157 addIfDir(new File(javaHome, ".."), roots);
 401  157 addIfDir(new File(javaHome, "../.."), roots);
 402    }
 403  157 if (envJavaHome != null) {
 404  157 addIfDir(new File(envJavaHome), roots);
 405  157 addIfDir(new File(envJavaHome, ".."), roots);
 406  157 addIfDir(new File(envJavaHome, "../.."), roots);
 407    }
 408   
 409  157 if (programFiles != null) {
 410  0 addIfDir(new File(programFiles, "Java"), roots);
 411  0 addIfDir(new File(programFiles), roots);
 412    }
 413  157 addIfDir(new File("/C:/Program Files/Java"), roots);
 414  157 addIfDir(new File("/C:/Program Files"), roots);
 415  157 if (systemDrive != null) {
 416  0 addIfDir(new File(systemDrive, "Java"), roots);
 417  0 addIfDir(new File(systemDrive), roots);
 418    }
 419  157 addIfDir(new File("/C:/Java"), roots);
 420  157 addIfDir(new File("/C:"), roots);
 421   
 422  157 addIfDir(new File("/System/Library/Frameworks/JavaVM.framework/Versions"), roots);
 423   
 424  157 addIfDir(new File("/usr/java"), roots);
 425  157 addIfDir(new File("/usr/j2se"), roots);
 426  157 addIfDir(new File("/usr"), roots);
 427  157 addIfDir(new File("/usr/local/java"), roots);
 428  157 addIfDir(new File("/usr/local/j2se"), roots);
 429  157 addIfDir(new File("/usr/local"), roots);
 430   
 431    /* Entries for Linux java packages */
 432  157 addIfDir(new File("/usr/lib/jvm"), roots);
 433  157 addIfDir(new File("/usr/lib/jvm/java-6-sun"), roots);
 434  157 addIfDir(new File("/usr/lib/jvm/java-1.5.0-sun"), roots);
 435  157 addIfDir(new File("/usr/lib/jvm/java-6-openjdk"), roots);
 436   
 437  157 addIfDir(new File("/home/javaplt/java/Linux-i686"), roots);
 438   
 439  157 return roots;
 440    }
 441   
 442    /* Search for jar files in roots and, if found, transfer them to the jars collection. */
 443  157 protected static void searchRootsForJars(LinkedHashMap<File,Set<JDKDescriptor>> roots,
 444    LinkedHashMap<File,Set<JDKDescriptor>> jars) {
 445    // matches: starts with "j2sdk", starts with "jdk", has form "[number].[number].[number]" (OS X), or
 446    // starts with "java-" (Linux)
 447  157 Predicate<File> subdirFilter = LambdaUtil.or(IOUtil.regexCanonicalCaseFilePredicate("j2sdk.*"),
 448    IOUtil.regexCanonicalCaseFilePredicate("jdk.*"),
 449    LambdaUtil.or(IOUtil.regexCanonicalCaseFilePredicate("\\d+\\.\\d+\\.\\d+"),
 450    IOUtil.regexCanonicalCaseFilePredicate("java.*")));
 451  157 for (Map.Entry<File,Set<JDKDescriptor>> root : roots.entrySet()) {
 452  1099 for (File subdir : IOUtil.attemptListFilesAsIterable(root.getKey(), subdirFilter)) {
 453  4239 addIfFile(new File(subdir, "lib/tools.jar"), root.getValue(), jars);
 454  4239 addIfFile(new File(subdir, "Classes/classes.jar"), root.getValue(), jars);
 455    }
 456    }
 457    }
 458   
 459    /** Check which jars are valid JDKs, and determine if they are compound or full (non-compound) JDKs. */
 460  157 protected static void collectValidResults(GlobalModel model,
 461    LinkedHashMap<File,Set<JDKDescriptor>> jars,
 462    Map<FullVersion, Iterable<JarJDKToolsLibrary>> results,
 463    Map<FullVersion, Iterable<JarJDKToolsLibrary>> compoundResults) {
 464  157 JDKToolsLibrary.msg("---- Collecting Valid Results ----");
 465  157 for (Map.Entry<File,Set<JDKDescriptor>> jar : jars.entrySet()) {
 466  2826 for (JDKDescriptor desc : jar.getValue()) {
 467  2826 assert desc != null;
 468   
 469  2826 boolean containsCompiler = desc.containsCompiler(jar.getKey());
 470  2826 JDKToolsLibrary.msg("Checking file "+jar.getKey()+" for "+desc);
 471  2826 JDKToolsLibrary.msg(" "+containsCompiler);
 472  0 if (!containsCompiler) continue;
 473   
 474  2826 JarJDKToolsLibrary lib = makeFromFile(jar.getKey(), model, desc);
 475  2826 if (lib.isValid()) {
 476  2512 FullVersion v = lib.version();
 477  2512 Map<FullVersion, Iterable<JarJDKToolsLibrary>> mapToAddTo = results;
 478  0 if (desc.isCompound()) { mapToAddTo = compoundResults; }
 479   
 480  0 if (mapToAddTo.containsKey(v)) { mapToAddTo.put(v, IterUtil.compose(lib, mapToAddTo.get(v))); }
 481  2512 else { mapToAddTo.put(v, IterUtil.singleton(lib)); }
 482    }
 483    else {
 484  314 JDKToolsLibrary.msg(" library is not valid: compiler="+lib.compiler().isAvailable()+
 485    " debugger="+lib.debugger().isAvailable()+" javadoc="+lib.javadoc().isAvailable());
 486    }
 487    }
 488    }
 489    }
 490   
 491    /** Get completed compound JDKs by going through the list of compound JDKs and finding full JDKs that
 492    * complete them. */
 493  157 protected static Map<FullVersion, Iterable<JarJDKToolsLibrary>>
 494    getCompletedCompoundResults(GlobalModel model,
 495    Iterable<JarJDKToolsLibrary> collapsed,
 496    Iterable<JarJDKToolsLibrary> compoundCollapsed) {
 497  157 JDKToolsLibrary.msg("---- Getting Completed Compound Results ----");
 498   
 499  157 Map<FullVersion, Iterable<JarJDKToolsLibrary>> completedResults =
 500    new TreeMap<FullVersion, Iterable<JarJDKToolsLibrary>>();
 501   
 502    // now we have the JDK libraries in collapsed and the compound libraries in compoundCollapsed
 503  157 for(JarJDKToolsLibrary compoundLib: compoundCollapsed) {
 504  0 JDKToolsLibrary.msg("compoundLib: "+compoundLib);
 505  0 JDKToolsLibrary.msg(" "+compoundLib.location());
 506  0 FullVersion compoundVersion = compoundLib.version();
 507  0 JarJDKToolsLibrary found = null;
 508    // try to find a JDK in results that matches compoundVersion exactly, except for vendor
 509  0 for(JarJDKToolsLibrary javaLib: collapsed) {
 510  0 if (!javaLib.jdkDescriptor().isBaseForCompound()) continue; // javaLib not suitable as base
 511  0 JDKToolsLibrary.msg(" exact? "+javaLib);
 512  0 FullVersion javaVersion = javaLib.version();
 513  0 if ((javaVersion.majorVersion().equals(compoundVersion.majorVersion())) &&
 514    (javaVersion.maintenance()==compoundVersion.maintenance()) &&
 515    (javaVersion.update()==compoundVersion.update()) &&
 516    (javaVersion.release()==compoundVersion.release()) &&
 517    (javaVersion.supports(compoundLib.jdkDescriptor().getMinimumMajorVersion()))) {
 518  0 JDKToolsLibrary.msg(" found");
 519  0 found = javaLib;
 520  0 break;
 521    }
 522    }
 523    // if we didn't find one, take the best JDK that matches the major version
 524  0 if (found==null) {
 525  0 for(JarJDKToolsLibrary javaLib: collapsed) {
 526  0 if (!javaLib.jdkDescriptor().isBaseForCompound()) continue; // javaLib not suitable as base
 527  0 JDKToolsLibrary.msg(" major? "+javaLib);
 528  0 FullVersion javaVersion = javaLib.version();
 529  0 if (javaVersion.majorVersion().equals(compoundVersion.majorVersion()) &&
 530    javaVersion.supports(compoundLib.jdkDescriptor().getMinimumMajorVersion())) {
 531  0 JDKToolsLibrary.msg(" found");
 532  0 found = javaLib;
 533  0 break;
 534    }
 535    }
 536    }
 537    // if we found a JDK, then create a new compound library
 538  0 if (found!=null) {
 539  0 JarJDKToolsLibrary lib = makeFromFile(compoundLib.location(), model, compoundLib.jdkDescriptor(),
 540    found.bootClassPath());
 541  0 if (lib.isValid()) {
 542  0 JDKToolsLibrary.msg(" ==> "+lib.version());
 543  0 FullVersion v = lib.version();
 544  0 if (completedResults.containsKey(v)) {
 545  0 completedResults.put(v, IterUtil.compose(lib, completedResults.get(v)));
 546    }
 547    else {
 548  0 completedResults.put(v, IterUtil.singleton(lib));
 549    }
 550    }
 551    }
 552    }
 553  157 return completedResults;
 554    }
 555   
 556    /** Produce a list of tools libraries discovered on the file system. A variety of locations are searched;
 557    * only those files that can produce a valid library (see {@link #isValid} are returned. The result is
 558    * sorted by version. Where one library of the same version might be preferred over another, the preferred
 559    * library appears earlier in the result list.
 560    */
 561  157 public static Iterable<JarJDKToolsLibrary> search(GlobalModel model) {
 562  157 JDKToolsLibrary.msg("---- Searching for Libraries ----");
 563   
 564    /* roots is a list of possible parent directories of Java installations; we want to eliminate duplicates &
 565    * remember insertion order
 566    */
 567  157 LinkedHashMap<File,Set<JDKDescriptor>> roots = getDefaultSearchRoots();
 568   
 569    /* jars is a list of possible tools.jar (or classes.jar) files; we want to eliminate duplicates &
 570    * remember insertion order
 571    */
 572  157 LinkedHashMap<File,Set<JDKDescriptor>> jars = new LinkedHashMap<File,Set<JDKDescriptor>>();
 573   
 574    // Search for all compound JDK descriptors in the drjava.jar file
 575  157 Iterable<JDKDescriptor> descriptors = searchForJDKDescriptors();
 576  157 for(JDKDescriptor desc: descriptors) {
 577    // add the specific search directories and files
 578  0 for(File f: desc.getSearchDirectories()) { addIfDir(f, desc, roots); }
 579  0 for(File f: desc.getSearchFiles()) { addIfFile(f, desc, jars); }
 580    // add to the set of packages that need to be shadowed
 581  0 TOOLS_PACKAGES.addAll(desc.getToolsPackages());
 582    }
 583   
 584    // search for jar files in roots and, if found, transfer them to the jars collection
 585  157 searchRootsForJars(roots, jars);
 586   
 587    // check which jars are valid JDKs, and determine if they are compound or full (non-compound) JDKs
 588  157 Map<FullVersion, Iterable<JarJDKToolsLibrary>> results =
 589    new TreeMap<FullVersion, Iterable<JarJDKToolsLibrary>>();
 590  157 Map<FullVersion, Iterable<JarJDKToolsLibrary>> compoundResults =
 591    new TreeMap<FullVersion, Iterable<JarJDKToolsLibrary>>();
 592   
 593  157 collectValidResults(model, jars, results, compoundResults);
 594   
 595    // We store everything in reverse order, since that's the natural order of the versions
 596  157 Iterable<JarJDKToolsLibrary> collapsed = IterUtil.reverse(IterUtil.collapse(results.values()));
 597  157 Iterable<JarJDKToolsLibrary> compoundCollapsed = IterUtil.reverse(IterUtil.collapse(compoundResults.values()));
 598   
 599    // Get completed compound JDKs by going through the list of compound JDKs and finding full JDKs that
 600    // complete them
 601  157 Map<FullVersion, Iterable<JarJDKToolsLibrary>> completedResults =
 602    getCompletedCompoundResults(model, collapsed, compoundCollapsed);
 603   
 604  157 JDKToolsLibrary.msg("Result:");
 605  157 Iterable<JarJDKToolsLibrary> result = IterUtil.
 606    compose(collapsed,IterUtil.reverse(IterUtil.collapse(completedResults.values())));
 607  157 for(JarJDKToolsLibrary lib: result) {
 608  2512 JDKToolsLibrary.msg("Found library: "+lib);
 609    }
 610   
 611  157 return result;
 612    }
 613   
 614    /** Add a canonicalized {@code f} to the given set if it is an existing directory or link */
 615  3454 private static void addIfDir(File f, Map<? super File, Set<JDKDescriptor>> map) {
 616  3454 addIfDir(f, JDKDescriptor.NONE, map);
 617    }
 618   
 619    /** Add a canonicalized {@code f} to the given set if it is an existing directory or link */
 620  3454 private static void addIfDir(File f, JDKDescriptor c, Map<? super File, Set<JDKDescriptor>> map) {
 621  3454 f = IOUtil.attemptCanonicalFile(f);
 622  3454 if (IOUtil.attemptIsDirectory(f)) {
 623  1570 Set<JDKDescriptor> set = map.get(f);
 624  1570 if (set==null) {
 625  1099 set = new LinkedHashSet<JDKDescriptor>();
 626  1099 map.put(f, set);
 627    }
 628  1570 set.add(c);
 629  1570 JDKToolsLibrary.msg("Dir added: "+f);
 630    }
 631  1884 else { JDKToolsLibrary.msg("Dir not added: "+f); }
 632    }
 633   
 634    /** Add a canonicalized {@code f} to the given set if it is an existing file */
 635  0 private static void addIfFile(File f, Map<? super File,Set<JDKDescriptor>> map) {
 636  0 addIfFile(f, JDKDescriptor.NONE, map);
 637    }
 638   
 639    /** Add a canonicalized {@code f} to the given set if it is an existing file */
 640  0 private static void addIfFile(File f, JDKDescriptor c, Map<? super File,Set<JDKDescriptor>> map) {
 641  0 addIfFile(f, Collections.singleton(c), map);
 642    }
 643   
 644    /** Add a canonicalized {@code f} to the given set if it is an existing file */
 645  8478 private static void addIfFile(File f, Set<JDKDescriptor> cs,
 646    Map<? super File,Set<JDKDescriptor>> map) {
 647  8478 f = IOUtil.attemptCanonicalFile(f);
 648  8478 if (IOUtil.attemptIsFile(f)) {
 649  3454 Set<JDKDescriptor> set = map.get(f);
 650  3454 if (set==null) {
 651  2826 set = new LinkedHashSet<JDKDescriptor>();
 652  2826 map.put(f, set);
 653    }
 654  3454 set.addAll(cs);
 655  3454 JDKToolsLibrary.msg("File added: "+f);
 656    }
 657  5024 else { JDKToolsLibrary.msg("File not added: "+f); }
 658    }
 659   
 660    /** Search for JDK descriptors.
 661    * Note: This does not work properly if not all classes are in a jar or in the same directory.
 662    * For example, when doing an "ant run", the classes are spread across classes/base and classes/lib,
 663    * with the edu.rice.cs.drjava.DrJava class in classes/base but the descriptors in classes/lib. */
 664  157 private static Iterable<JDKDescriptor> searchForJDKDescriptors() {
 665  157 JDKToolsLibrary.msg("---- Searching for descriptors ----");
 666  157 long t0 = System.currentTimeMillis();
 667  157 JDKToolsLibrary.msg("ms: "+t0);
 668  157 Iterable<JDKDescriptor> descriptors = IterUtil.empty();
 669  157 try {
 670  157 File f = edu.rice.cs.util.FileOps.getDrJavaFile();
 671  157 JDKToolsLibrary.msg("drjava.jar: "+f);
 672  157 if (f.isFile()) {
 673  0 JarFile jf = new JarFile(f);
 674  0 JDKToolsLibrary.msg("jar file: "+jf);
 675  0 Enumeration<JarEntry> entries = jf.entries();
 676  0 while(entries.hasMoreElements()) {
 677  0 JarEntry je = entries.nextElement();
 678  0 String name = je.getName();
 679  0 if (name.startsWith("edu/rice/cs/drjava/model/compiler/descriptors/") &&
 680    name.endsWith(".class") &&
 681    (name.indexOf('$')<0)) {
 682  0 descriptors = attemptToLoadDescriptor(descriptors, name);
 683    }
 684    }
 685    }
 686    else {
 687  157 final String DESC_PATH = "edu/rice/cs/drjava/model/compiler/descriptors";
 688  157 File dir = new File(f, DESC_PATH);
 689  157 JDKToolsLibrary.msg("directory, enumerating files in "+dir);
 690  157 Iterable<File> files = IOUtil.listFilesRecursively(dir, new Predicate<File>() {
 691  157 public boolean contains(File arg) {
 692  157 return (arg.isFile()) && arg.getName().endsWith(".class") && (arg.getName().indexOf('$')<0);
 693    }
 694    });
 695  157 for(File je: files) {
 696  0 String name = DESC_PATH+"/"+je.getName();
 697  0 descriptors = attemptToLoadDescriptor(descriptors, name);
 698    }
 699    }
 700    }
 701    catch(IOException ioe) {
 702    /* ignore, just return the descriptors we have (which may be none) */
 703    }
 704  157 long t1 = System.currentTimeMillis();
 705  157 JDKToolsLibrary.msg("ms: "+t1);
 706  157 JDKToolsLibrary.msg("duration ms: "+(t1-t0));
 707  157 JDKToolsLibrary.msg("---- Done searching for descriptors ----");
 708  157 return descriptors;
 709    }
 710   
 711    /** Attempt to load a JDK descriptor, append it to the list if succesful. */
 712  0 protected static Iterable<JDKDescriptor> attemptToLoadDescriptor(Iterable<JDKDescriptor> descriptors,
 713    String name) {
 714  0 int dotPos = name.indexOf(".class");
 715  0 String className = name.substring(0, dotPos).replace('/','.');
 716  0 try {
 717  0 JDKToolsLibrary.msg(" class name: "+className);
 718  0 Class<?> clazz = Class.forName(className);
 719  0 Class<? extends JDKDescriptor> descClass = clazz.asSubclass(JDKDescriptor.class);
 720  0 JDKDescriptor desc = descClass.newInstance();
 721  0 JDKToolsLibrary.msg(" loaded!");
 722  0 descriptors = IterUtil.compose(descriptors, desc);
 723    }
 724  0 catch(LinkageError le) { JDKToolsLibrary.msg("LinkageError: "+le); /* ignore */ }
 725  0 catch(ClassNotFoundException cnfe) { JDKToolsLibrary.msg("ClassNotFoundException: "+cnfe); /* ignore */ }
 726  0 catch(ClassCastException cce) { JDKToolsLibrary.msg("ClassCastException: "+cce); /* ignore */ }
 727  0 catch(IllegalAccessException iae) { JDKToolsLibrary.msg("IllegalAccessException: "+iae); /* ignore */ }
 728  0 catch(InstantiationException ie) { JDKToolsLibrary.msg("InstantiationException: "+ie); /* ignore */ }
 729   
 730  0 return descriptors;
 731    }
 732    }