001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.drjava.model;
038:
039: import java.io.File;
040: import java.util.Set;
041: import java.util.LinkedHashSet;
042: import java.util.Arrays;
043: import java.util.List;
044: import java.util.Map;
045: import java.util.TreeMap;
046:
047: import edu.rice.cs.plt.iter.IterUtil;
048: import edu.rice.cs.plt.io.IOUtil;
049: import edu.rice.cs.plt.lambda.LambdaUtil;
050: import edu.rice.cs.plt.lambda.Predicate;
051: import edu.rice.cs.plt.reflect.ReflectUtil;
052: import edu.rice.cs.plt.reflect.PathClassLoader;
053: import edu.rice.cs.plt.reflect.ShadowingClassLoader;
054: import edu.rice.cs.plt.reflect.PreemptingClassLoader;
055: import edu.rice.cs.plt.reflect.ReflectException;
056: import edu.rice.cs.plt.reflect.JavaVersion;
057: import edu.rice.cs.plt.reflect.JavaVersion.FullVersion;
058:
059: import edu.rice.cs.drjava.model.compiler.CompilerInterface;
060: import edu.rice.cs.drjava.model.compiler.NoCompilerAvailable;
061: import edu.rice.cs.drjava.model.debug.Debugger;
062: import edu.rice.cs.drjava.model.debug.NoDebuggerAvailable;
063: import edu.rice.cs.drjava.model.javadoc.JavadocModel;
064: import edu.rice.cs.drjava.model.javadoc.DefaultJavadocModel;
065: import edu.rice.cs.drjava.model.javadoc.NoJavadocAvailable;
066:
067: public class JarJDKToolsLibrary extends JDKToolsLibrary {
068:
069: /** Packages to shadow when loading a new tools.jar. These should be verified whenever a new Java version
070: * is released. (We can't just shadow *everything* because some classes, at least in OS X's classes.jar,
071: * can only be loaded by the JVM.)
072: */
073: private static final String[] TOOLS_PACKAGES = new String[] {
074: // From 1.4 tools.jar:
075: "com.sun.javadoc",
076: "com.sun.jdi",
077: "com.sun.tools",
078: "sun.applet", // also bundled in rt.jar
079: "sun.rmi.rmic",
080: //"sun.security.tools", // partially bundled in rt.jar -- it's inconsistent between versions, so we need to
081: // allow these classes to be loaded. Hopefully this doesn't break anything.
082: "sun.tools", // sun.tools.jar, sun.tools.hprof, and (sometimes) sun.tools.util.CommandLine are also in rt.jar
083:
084: // Additional from 5 tools.jar:
085: "com.sun.jarsigner",
086: "com.sun.mirror",
087: "sun.jvmstat",
088:
089: // Additional from 6 tools.jar:
090: "com.sun.codemodel",
091: "com.sun.istack.internal.tools", // other istack packages are in rt.jar
092: "com.sun.istack.internal.ws",
093: "com.sun.source",
094: "com.sun.xml.internal.dtdparser", // other xml.internal packages are in rt.jar
095: "com.sun.xml.internal.rngom", "com.sun.xml.internal.xsom",
096: "org.relaxng" };
097:
098: private final File _location;
099:
100: private JarJDKToolsLibrary(File location, FullVersion version,
101: CompilerInterface compiler, Debugger debugger,
102: JavadocModel javadoc) {
103: super (version, compiler, debugger, javadoc);
104: _location = location;
105: }
106:
107: public File location() {
108: return _location;
109: }
110:
111: public String toString() {
112: return super .toString() + " at " + _location;
113: }
114:
115: public static JarJDKToolsLibrary makeFromFile(File f,
116: GlobalModel model) {
117: FullVersion version = guessVersion(f);
118: CompilerInterface compiler = NoCompilerAvailable.ONLY;
119: Debugger debugger = NoDebuggerAvailable.ONLY;
120: JavadocModel javadoc = new NoJavadocAvailable(model);
121:
122: if (JavaVersion.CURRENT.supports(version.majorVersion())) {
123: // block tools.jar classes, so that references don't point to a different version of the classes
124: ClassLoader loader = new ShadowingClassLoader(
125: JarJDKToolsLibrary.class.getClassLoader(),
126: TOOLS_PACKAGES);
127: Iterable<File> path = IterUtil.singleton(IOUtil
128: .attemptAbsoluteFile(f));
129:
130: String compilerAdapter = adapterForCompiler(version
131: .majorVersion());
132: if (compilerAdapter != null) {
133: List<File> bootClassPath = null;
134: if (f.getName().equals("classes.jar")) {
135: bootClassPath = Arrays.asList(f);
136: } else if (f.getName().equals("tools.jar")) {
137: File rtJar = new File(f.getParentFile(),
138: "../jre/lib/rt.jar");
139: if (!rtJar.exists()) {
140: rtJar = new File(f.getParentFile(), "rt.jar");
141: }
142: if (rtJar.exists()) {
143: rtJar = IOUtil.attemptCanonicalFile(rtJar);
144: bootClassPath = Arrays.asList(rtJar);
145: }
146: }
147: try {
148: Class[] sig = new Class[] { FullVersion.class,
149: String.class, List.class };
150: Object[] args = new Object[] { version,
151: f.toString(), bootClassPath };
152: CompilerInterface attempt = (CompilerInterface) ReflectUtil
153: .loadLibraryAdapter(loader, path,
154: compilerAdapter, sig, args);
155: if (attempt.isAvailable()) {
156: compiler = attempt;
157: }
158: } catch (ReflectException e) { /* can't load */
159: } catch (LinkageError e) { /* can't load */
160: }
161: }
162:
163: String debuggerAdapter = adapterForDebugger(version
164: .majorVersion());
165: String debuggerPackage = "edu.rice.cs.drjava.model.debug.jpda";
166: if (debuggerAdapter != null) {
167: try {
168: Class[] sig = new Class[] { GlobalModel.class };
169: // can't use loadLibraryAdapter because we need to preempt the whole package
170: ClassLoader debugLoader = new PreemptingClassLoader(
171: new PathClassLoader(loader, path),
172: debuggerPackage);
173: Debugger attempt = (Debugger) ReflectUtil
174: .loadObject(debugLoader, debuggerAdapter,
175: sig, model);
176: if (attempt.isAvailable()) {
177: debugger = attempt;
178: }
179: } catch (ReflectException e) { /* can't load */
180: } catch (LinkageError e) { /* can't load */
181: }
182: }
183:
184: try {
185: new PathClassLoader(loader, path)
186: .loadClass("com.sun.tools.javadoc.Main");
187: File bin = new File(f.getParentFile(), "../bin");
188: if (!IOUtil.attemptIsDirectory(bin)) {
189: bin = new File(f.getParentFile(), "../Home/bin");
190: }
191: if (!IOUtil.attemptIsDirectory(bin)) {
192: bin = new File(System.getProperty("java.home", f
193: .getParent()));
194: }
195: javadoc = new DefaultJavadocModel(model, bin, path);
196: } catch (ClassNotFoundException e) { /* can't load */
197: } catch (LinkageError e) { /* can't load (probably not necessary, but might as well catch it) */
198: }
199:
200: }
201:
202: return new JarJDKToolsLibrary(f, version, compiler, debugger,
203: javadoc);
204: }
205:
206: private static FullVersion guessVersion(File f) {
207: FullVersion result = null;
208:
209: // We could start with f.getParentFile(), but this simplifies the logic
210: File current = IOUtil.attemptCanonicalFile(f);
211: do {
212: String name = current.getName();
213: if (name.startsWith("jdk")) {
214: result = JavaVersion
215: .parseFullVersion(name.substring(3));
216: } else if (name.startsWith("j2sdk")) {
217: result = JavaVersion
218: .parseFullVersion(name.substring(5));
219: } else if (name.matches("\\d+\\.\\d+\\.\\d+")) {
220: result = JavaVersion.parseFullVersion(name);
221: }
222: current = current.getParentFile();
223: } while (current != null && result == null);
224:
225: if (result == null
226: || result.majorVersion().equals(
227: JavaVersion.UNRECOGNIZED)) {
228: // Couldn't find a good version number, so we'll just guess that it's the currently-running version
229: // Useful where the tools.jar file is in an unusual custom location
230: result = JavaVersion.CURRENT_FULL;
231: }
232: return result;
233: }
234:
235: // // Lifted from DrJava.java; may be a useful alternative to the path-based approach of guessVersion.
236: // /** @return a string with the suspected version of the tools.jar file, or null if an error occurred. */
237: // private static String _getToolsJarVersion(File toolsJarFile) {
238: // try {
239: // JarFile jf = new JarFile(toolsJarFile);
240: // Manifest mf = jf.getManifest();
241: // ByteArrayOutputStream baos = new ByteArrayOutputStream();
242: // mf.write(baos);
243: // String str = baos.toString();
244: // // the expected format of str is:
245: // // Manifest-Version: 1.0
246: // // Created-By: 1.5.0_07 (Sun Microsystems Inc.)
247: // //
248: // final String CB = "Created-By: ";
249: // int beginPos = str.indexOf(CB);
250: // if (beginPos >= 0) {
251: // beginPos += CB.length();
252: // int endPos = str.indexOf(StringOps.EOL, beginPos);
253: // if (endPos >= 0) return str.substring(beginPos, endPos);
254: // else {
255: // endPos = str.indexOf(' ', beginPos);
256: // if (endPos >= 0) return str.substring(beginPos, endPos);
257: // else {
258: // endPos = str.indexOf('\t', beginPos);
259: // if (endPos >= 0) return str.substring(beginPos, endPos);
260: // }
261: // }
262: // }
263: // }
264: // catch(Exception rte) { /* ignore, just return null */ }
265: // return null;
266: // }
267:
268: /**
269: * Produce a list of tools libraries discovered on the file system. A variety of locations are searched;
270: * only those files that can produce a valid library (see {@link #isValid} are returned. The result is
271: * sorted by version. Where one library of the same version might be preferred over another, the preferred
272: * library appears earlier in the result list.
273: */
274: public static Iterable<JarJDKToolsLibrary> search(GlobalModel model) {
275: String javaHome = System.getProperty("java.home");
276: String envJavaHome = null;
277: String programFiles = null;
278: String systemDrive = null;
279: if (JavaVersion.CURRENT.supports(JavaVersion.JAVA_5)) {
280: // System.getenv is deprecated under 1.3 and 1.4, and may throw a java.lang.Error (!),
281: // which we'd rather not have to catch
282: envJavaHome = System.getenv("JAVA_HOME");
283: programFiles = System.getenv("ProgramFiles");
284: systemDrive = System.getenv("SystemDrive");
285: }
286:
287: /* roots is a list of possible parent directories of Java installations; we want to eliminate duplicates &
288: * remember insertion order
289: */
290: LinkedHashSet<File> roots = new LinkedHashSet<File>();
291:
292: if (javaHome != null) {
293: addIfDir(new File(javaHome), roots);
294: addIfDir(new File(javaHome, ".."), roots);
295: addIfDir(new File(javaHome, "../.."), roots);
296: }
297: if (envJavaHome != null) {
298: addIfDir(new File(envJavaHome), roots);
299: addIfDir(new File(envJavaHome, ".."), roots);
300: addIfDir(new File(envJavaHome, "../.."), roots);
301: }
302:
303: if (programFiles != null) {
304: addIfDir(new File(programFiles, "Java"), roots);
305: addIfDir(new File(programFiles), roots);
306: }
307: addIfDir(new File("/C:/Program Files/Java"), roots);
308: addIfDir(new File("/C:/Program Files"), roots);
309: if (systemDrive != null) {
310: addIfDir(new File(systemDrive, "Java"), roots);
311: addIfDir(new File(systemDrive), roots);
312: }
313: addIfDir(new File("/C:/Java"), roots);
314: addIfDir(new File("/C:"), roots);
315:
316: addIfDir(
317: new File(
318: "/System/Library/Frameworks/JavaVM.framework/Versions"),
319: roots);
320:
321: addIfDir(new File("/usr/java"), roots);
322: addIfDir(new File("/usr/j2se"), roots);
323: addIfDir(new File("/usr"), roots);
324: addIfDir(new File("/usr/local/java"), roots);
325: addIfDir(new File("/usr/local/j2se"), roots);
326: addIfDir(new File("/usr/local"), roots);
327:
328: /* jars is a list of possible tools.jar (or classes.jar) files; we want to eliminate duplicates &
329: * remember insertion order
330: */
331: LinkedHashSet<File> jars = new LinkedHashSet<File>();
332: // matches: starts with "j2sdk", starts with "jdk", has form "[number].[number].[number]" (as in OS X)
333: Predicate<File> subdirFilter = LambdaUtil
334: .or(
335: IOUtil
336: .regexpCanonicalCaseFilePredicate("j2sdk.*"),
337: IOUtil
338: .regexpCanonicalCaseFilePredicate("jdk.*"),
339: IOUtil
340: .regexpCanonicalCaseFilePredicate("\\d+\\.\\d+\\.\\d+"));
341:
342: for (File root : roots) {
343: for (File subdir : IOUtil.attemptListFilesAsIterable(root,
344: subdirFilter)) {
345: addIfFile(new File(subdir, "lib/tools.jar"), jars);
346: addIfFile(new File(subdir, "Classes/classes.jar"), jars);
347: }
348: }
349:
350: // We store everything in reverse order, since that's the natural order of the versions
351: Map<FullVersion, Iterable<JarJDKToolsLibrary>> results = new TreeMap<FullVersion, Iterable<JarJDKToolsLibrary>>();
352: for (File jar : jars) {
353: JarJDKToolsLibrary lib = makeFromFile(jar, model);
354: if (lib.isValid()) {
355: FullVersion v = lib.version();
356: if (results.containsKey(v)) {
357: results.put(v, IterUtil
358: .compose(lib, results.get(v)));
359: } else {
360: results.put(v, IterUtil.singleton(lib));
361: }
362: }
363: }
364: return IterUtil.reverse(IterUtil.collapse(results.values()));
365: }
366:
367: /** Add a canonicalized {@code f} to the given set if it is an existing directory */
368: private static void addIfDir(File f, Set<? super File> set) {
369: f = IOUtil.attemptCanonicalFile(f);
370: if (IOUtil.attemptIsDirectory(f)) {
371: set.add(f);
372: }
373: }
374:
375: /** Add a canonicalized {@code f} to the given set if it is an existing file */
376: private static void addIfFile(File f, Set<? super File> set) {
377: f = IOUtil.attemptCanonicalFile(f);
378: if (IOUtil.attemptIsFile(f)) {
379: set.add(f);
380: }
381: }
382:
383: }
|