/*BEGIN_COPYRIGHT_BLOCK
*
* This file is part of DrJava. Download the current version of this project from http://www.drjava.org/
* or http://sourceforge.net/projects/drjava/
*
* DrJava Open Source License
*
* Copyright (C) 2001-2005 JavaPLT group at Rice University (javaplt@rice.edu). All rights reserved.
*
* Developed by: Java Programming Languages Team, Rice University, http://www.cs.rice.edu/~javaplt/
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal with the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* - Redistributions of source code must retain the above copyright notice, this list of conditions and the
* following disclaimers.
* - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
* following disclaimers in the documentation and/or other materials provided with the distribution.
* - Neither the names of DrJava, the JavaPLT, Rice University, nor the names of its contributors may be used to
* endorse or promote products derived from this Software without specific prior written permission.
* - Products derived from this software may not be called "DrJava" nor use the term "DrJava" as part of their
* names without prior written permission from the JavaPLT group. For permission, write to javaplt@rice.edu.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* WITH THE SOFTWARE.
*
*END_COPYRIGHT_BLOCK*/
package edu.rice.cs.drjava;
import static edu.rice.cs.drjava.config.OptionConstants.*;
import java.io.File;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import edu.rice.cs.drjava.config.FileConfiguration;
import edu.rice.cs.drjava.config.FileOption;
import edu.rice.cs.drjava.platform.PlatformFactory;
import edu.rice.cs.drjava.ui.DrJavaErrorHandler;
import edu.rice.cs.drjava.ui.ClassPathFilter;
import edu.rice.cs.drjava.ui.SplashScreen;
import edu.rice.cs.util.ArgumentTokenizer;
import edu.rice.cs.util.FileOps;
import edu.rice.cs.util.Log;
import edu.rice.cs.util.classloader.ToolsJarClassLoader;
import edu.rice.cs.util.newjvm.ExecJVM;
/** Startup class for DrJava consisting entirely of static members. The main method reads the .drjava file (creating
* one if none exists) to get the critical information required to start the main JVM for DrJava:
* (i) the location of tools.jar in the Java JDK installed on this machine (so DrJava can invoke the javac compiler
* stored in tools.jar)
* (ii) the argument string for invoking the main JVM (notably -X options used to determine maximum heap size, etc.)
* This version of DrJava no longer supports the transitional JSR-14 compilers or the GJ compiler.
* @version $Id$
*/
public class DrJava {
private static Log _log = new Log("DrJava.txt", false);
/** Class to probe to see if the debugger is available */
public static final String TEST_DEBUGGER_CLASS = "com.sun.jdi.Bootstrap";
/** Class to probe to see if the 1.4 compiler is available */
public static final String TEST_14_COMPILER_CLASS = "com.sun.tools.javac.v8.JavaCompiler";
/** Class to probe to see if the 1.5/1.6 compiler is available */
public static final String TEST_15_16_COMPILER_CLASS = "com.sun.tools.javac.main.JavaCompiler";
/** Class to probe to see if the compiler is available; either TEST_14_COMPILER_CLASS or TEST_15_16_COMPILER_CLASS. */
public static final String TEST_COMPILER_CLASS;
static {
if (System.getProperty("java.version").startsWith("1.4")) {
TEST_COMPILER_CLASS = TEST_14_COMPILER_CLASS;
}
else {
TEST_COMPILER_CLASS = TEST_15_16_COMPILER_CLASS;
}
}
/** Pause time for displaying DrJava banner on startUp (in milliseconds) */
private static final int PAUSE_TIME = 2000;
private static final String DEFAULT_MAX_HEAP_SIZE_ARG = "-Xmx128M";
private static final ArrayList<String> _filesToOpen = new ArrayList<String>();
private static final ArrayList<String> _jvmArgs = new ArrayList<String>();
static volatile boolean _showDebugConsole = false;
/* Config objects can't be public static final, since we have to delay construction until we know the
* config file's location. (Might be specified on command line.) Instead, use accessor methods to
* prevent others from assigning new values. */
/** Default properties file used by the configuration object, i.e. ".drjava" in the user's home directory. */
public static final File DEFAULT_PROPERTIES_FILE = new File(System.getProperty("user.home"), ".drjava");
/** Properties file used by the configuration object. Defaults to DEFAULT_PROPERTIES_FILE. */
private static volatile File _propertiesFile = DEFAULT_PROPERTIES_FILE;
/** Configuration object with all customized and default values. Initialized from _propertiesFile. */
private static volatile FileConfiguration _config = _initConfig();
private static ToolsJarClassLoader _toolsLoader = new ToolsJarClassLoader(getConfig().getSetting(JAVAC_LOCATION));
private static final ClassLoader _thisLoader = DrJava.class.getClassLoader();
/** Returns the properties file used by the configuration object. */
public static File getPropertiesFile() { return _propertiesFile; }
/** Returns the configuration object with all customized and default values. */
public static FileConfiguration getConfig() { return _config; }
/** @return an array of the files that were passed on the command line. */
public static String[] getFilesToOpen() { return _filesToOpen.toArray(new String[0]); }
/** @return true if the debug console should be enabled */
public static boolean getShowDebugConsole() { return _showDebugConsole; }
/** Starts running DrJava.
* @param args Command line argument array
*/
public static void main(final String[] args) {
final SplashScreen splash = new SplashScreen();
splash.setVisible(true);
splash.repaint();
// Utilities.showDebug("Calling configureAndLoadDrJavaRoot with args = " + args);
configureAndLoadDrJavaRoot(args);
// This obviously only runs in the main thread, not the UI thread, so use SwingUtilities rather than Utilities.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try { Thread.sleep(PAUSE_TIME); }
catch(InterruptedException e) { }
splash.dispose();
}});
}
public static void configureAndLoadDrJavaRoot(String[] args) {
try {
// handleCommandLineArgs will return true if the DrJava should be loaded
if (handleCommandLineArgs(args)) {
// Check that compiler and debugger are available on classpath (including tools.jar location)
checkForCompilersAndDebugger(args);
// Start the DrJava master JVM
String pathSep = System.getProperty("path.separator");
String classPath = edu.rice.cs.util.FileOps.convertToAbsolutePathEntries(System.getProperty("java.class.path"));
// Include both the javac location stored in .drjava prefences and the path proposed by ToolsJarClassLoader
File toolsFromConfig = getConfig().getSetting(JAVAC_LOCATION);
classPath += pathSep + ToolsJarClassLoader.getToolsJarClassPath(toolsFromConfig);
File workDir = new File(System.getProperty("user.home"));
LinkedList<String> classArgsList = new LinkedList<String>();
// need to make the paths absolute since the working directory might change
for(String fn: _filesToOpen) {
classArgsList.add(new File(fn).getAbsolutePath());
}
// Add the parameters "-debugConsole" to classArgsList if _showDebugConsole is true
if (_showDebugConsole) classArgsList.add(0,"-debugConsole");
String[] jvmArgs = _jvmArgs.toArray(new String[0]);
if (!_propertiesFile.equals(DEFAULT_PROPERTIES_FILE)) {
classArgsList.add(0,"-config");
// need to make the paths absolute since the working directory might change
classArgsList.add(1,_propertiesFile.getAbsolutePath());
}
String[] classArgs = classArgsList.toArray(new String[0]);
// Run a new copy of DrJava and exit
try {
// Utilities.showDebug("Starting DrJavaRoot with classArgs = " + Arrays.toString(classArgs) + "; classPath = " + classPath +
// "; jvmArgs = " + Arrays.toString(jvmArgs) + "; workDir = " + workDir);
ExecJVM.runJVM("edu.rice.cs.drjava.DrJavaRoot", classArgs, classPath, jvmArgs, workDir);
}
catch (IOException ioe) {
// Display error
final String[] text = {
"DrJava was unable to load its compiler and debugger. Would you ",
"like to start DrJava without a compiler and debugger?", "\nReason: " + ioe.toString()
};
int result = JOptionPane.showConfirmDialog(null, text, "Could Not Load Compiler and Debugger",
JOptionPane.YES_NO_OPTION);
if (result != JOptionPane.YES_OPTION) { System.exit(0); }
}
}
}
catch (Throwable t) {
// Show any errors to the System.err and in an DrJavaErrorHandler
System.out.println(t.getClass().getName() + ": " + t.getMessage());
t.printStackTrace(System.err);System.out.println("error thrown");
new DrJavaErrorHandler().handle(t);
}
}
/** Handles any command line arguments that have been specified.
* @return true if DrJava should load, false if not
*/
static boolean handleCommandLineArgs(String[] args) {
boolean heapSizeGiven = false; // indicates whether args includes an argument of the form -Xmx<number>
// Loop through arguments looking for known options
int argIndex = 0;
int len = args.length;
_filesToOpen.clear();
while(argIndex < len) {
String arg = args[argIndex++];
if (arg.equals("-config")) {
if (len == argIndex) {
// config option is missing file name; should we generate an error?
return true;
}
// arg.length > i+1 implying args list incudes config file name and perhaps files to open
setPropertiesFile(args[argIndex++]);
_config = _initConfig(); // read specified .djrava file into _config
_toolsLoader = new ToolsJarClassLoader(getConfig().getSetting(JAVAC_LOCATION));
}
else if ((arg.length() > 1) && (arg.substring(0,2).equals("-X"))) {
if (arg.substring(0,4).equals("-Xmx")) heapSizeGiven = true;
_jvmArgs.add(arg);
}
else if ((arg.length() > 1) && (arg.substring(0,2).equals("-D"))) {
_jvmArgs.add(arg);
}
else if (arg.equals("-debugConsole")) _showDebugConsole = true;
else if (arg.equals("-help") || arg.equals("-?")) {
displayUsage();
return false;
}
else {
// this is the first file to open, do not consume
--argIndex;
break;
}
}
String jvmArgString = getConfig().getSetting(MASTER_JVM_ARGS);
List<String> jvmArgs = ArgumentTokenizer.tokenize(jvmArgString);
if (jvmArgs != null && jvmArgs.size() != 0) _jvmArgs.addAll(jvmArgs);
if (PlatformFactory.ONLY.isMacPlatform()) {
_jvmArgs.add("-Dcom.apple.macos.useScreenMenuBar=true");
_jvmArgs.add("-Xdock:name=DrJava");
_jvmArgs.add("-Xdock:icon=/Applications/DrJava.app/Contents/Resources/DrJava.icns");
}
if (! heapSizeGiven && jvmArgString.indexOf("-Xmx")<0) _jvmArgs.add(DEFAULT_MAX_HEAP_SIZE_ARG);
_log.log("_jvmArgs = " + _jvmArgs);
// Open the remaining args as filenames
for (int i = argIndex; i < len; i++) { _filesToOpen.add(args[i]); }
return true;
}
/** Displays a usage message about the available options. */
static void displayUsage() {
final StringBuilder buf = new StringBuilder();
buf.append("Usage: java -jar drjava.jar [OPTIONS] [FILES]\n\n");
buf.append("where options include:\n");
buf.append(" -config [FILE] use a custom config file\n");
buf.append(" -help | -? print this help message\n");
buf.append(" -X<jvmOption> specify a JVM configuration option for the master DrJava JVM\n");
buf.append(" -D<name>[=<value>] set a Java property for the master DrJava JVM\n");
System.out.print(buf.toString());
}
/** Check to see if a compiler and the debugger are available in a tools.jar file. If it can't find them, it prompts
* the user to optionally specify the location of a propert tools.jar file.
* @param args Command line argument array, in case we need to restart
*/
static void checkForCompilersAndDebugger(String[] args) {
boolean needCompiler = ! hasAvailableCompiler();
boolean needDebugger = ! hasAvailableDebugger();
// Try to make sure both compiler and debugger are available
if (needCompiler || needDebugger) promptForToolsJar(needCompiler, needDebugger);
}
/** Returns whether the debugger will be able to load successfully. Checks for the ability to load the
* com.sun.jdi.Bootstrap class.
*/
public static boolean hasAvailableDebugger() {
return canLoad(_thisLoader, TEST_DEBUGGER_CLASS) || canLoad(_toolsLoader, TEST_DEBUGGER_CLASS);
}
public static boolean hasAvailableCompiler() {
return canLoad(_thisLoader, TEST_COMPILER_CLASS) || canLoad(_toolsLoader, TEST_COMPILER_CLASS);
}
/* Tests whether the specified class loader can load the specifed class */
public static boolean canLoad(ClassLoader cl, String className) {
try {
cl.loadClass(className);
return true;
}
catch(ClassNotFoundException e) { return false; }
catch(UnsupportedClassVersionError e) { return false; }
catch(RuntimeException e) { return false; }
}
/** Prompts the user that the location of tools.jar needs to be specified to be able to use the compiler and/or the
* debugger.
* @param needCompiler whether DrJava needs tools.jar for a compiler
* @param needDebugger whether DrJava needs tools.jar for the debugger
*/
public static void promptForToolsJar(boolean needCompiler, boolean needDebugger) {
File selectedFile = getConfig().getSetting(JAVAC_LOCATION);
String selectedVersion = _getToolsJarVersion(selectedFile);
final String[] text;
if (selectedVersion==null) {
text = new String[] {
"DrJava cannot find a 'tools.jar' file for the version of Java ",
"that is being used to run DrJava (Java version "+System.getProperty("java.version")+").",
"Would you like to specify the location of the requisite 'tools.jar' file?",
"If you say 'No', DrJava might be unable to compile or debug Java programs."
};
}
else {
text = new String[] {
"DrJava cannot find a 'tools.jar' file for the version of Java ",
"that is being used to run DrJava (Java version "+System.getProperty("java.version")+").",
"The file you have selected appears to be for version "+selectedVersion+".",
"Would you like to specify the ocation of the requisite 'tools.jar' file?",
"If you say 'No', DrJava might be unable to compile or debug Java programs.)"
};
}
int result = JOptionPane.showConfirmDialog(null, text, "Locate 'tools.jar'?", JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_OPTION) {
JFileChooser chooser = new JFileChooser();
chooser.setFileFilter(new ClassPathFilter() {
public boolean accept(File f) {
if (f.isDirectory()) return true;
String ext = getExtension(f);
return ext != null && ext.equals("jar");
}
public String getDescription() { return "Jar Files"; }
});
// Loop until we find a good tools.jar or the user gives up
do {
if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
File jar = chooser.getSelectedFile();
if (jar != null) {
// set the tools.jar property
getConfig().setSetting(JAVAC_LOCATION, jar);
// Adjust if we needed a compiler
if (needCompiler && classLoadersCanFind(TEST_COMPILER_CLASS)) needCompiler = false;
// Adjust if we need a debugger
if (needDebugger && classLoadersCanFind(TEST_DEBUGGER_CLASS)) needDebugger = false;
}
}
// Utilities.showDebug("need Compiler = " + needCompiler + "; needDebugger = " + needDebugger);
}
while ((needCompiler || needDebugger) && _userWantsToPickAgain());
// Save config with good tools.jar if available
if ((! needCompiler) && (! needDebugger)) _saveConfig();
}
}
/** Returns whether the debugger will be able to load successfully when we start the DrJava master JVM with
* tools.jar on the classpath. Uses ToolsJarClassLoader to try to load com.sun.jdi.Bootstrap.
*/
public static boolean classLoadersCanFind(String className) {
// First check the specified location
File jar = getConfig().getSetting(JAVAC_LOCATION);
if (jar != FileOption.NULL_FILE) {
try {
URL[] urls = new URL[] { FileOps.toURL(jar) };
URLClassLoader loader = new URLClassLoader(urls);
if (canLoad(loader, className)) return true;
}
catch(MalformedURLException e) { /* fall through */ }
}
return canLoad(_toolsLoader, className);
}
/** Switches the config object to use a custom config file. Ensures that Java source files aren't
* accidentally used.
*/
static void setPropertiesFile(String fileName) {
if (!fileName.endsWith(".java")) _propertiesFile = new File(fileName);
}
/** Initializes the configuration object with the current notion of the properties file.
* @throws IllegalStateException if config has already been assigned
*/
static FileConfiguration _initConfig() throws IllegalStateException {
// // Make sure someone doesn't try to change the config object.
// if (_config != null) throw new IllegalStateException("Can only call initConfig once!");
FileConfiguration config;
try { _propertiesFile.createNewFile(); } // be nice and ensure a config file if there isn't one
catch (IOException e) { /* IOException occurred, continue without a real file */ }
config = new FileConfiguration(_propertiesFile);
try { config.loadConfiguration(); }
catch (Exception e) {
// Problem parsing the config file. Use defaults and remember what happened (for the UI).
config.resetToDefaults();
config.storeStartupException(e);
}
_config = config; // required to support calls on DrJava._initConfig() in unit tests
return config;
}
/** Saves the contents of the config file. TO DO: log any IOExceptions that occur. */
protected static void _saveConfig() {
try { getConfig().saveConfiguration(); }
catch(IOException e) {
JOptionPane.showMessageDialog(null,
"Could not save the location of tools.jar in \n" +
"the '.drjava' file in your home directory. \n" +
"Another process may be using the file.\n\n" + e,
"Could Not Save Changes",
JOptionPane.ERROR_MESSAGE);
// TODO: log this error
}
}
/** Displays a prompt to the user indicating that tools.jar could not be found in the specified location, and asks
* if he would like to specify a new location.
*/
private static boolean _userWantsToPickAgain() {
File selectedFile = getConfig().getSetting(JAVAC_LOCATION);
String selectedVersion = _getToolsJarVersion(selectedFile);
final String[] text;
if (selectedVersion==null) {
text = new String[] {
"The file you chose did not appear to be the correct 'tools.jar'",
"that is compatible with the version of Java that is used to",
"run DrJava (Java version "+System.getProperty("java.version")+").",
"Your choice might be an incompatible version of the file.",
"Would you like to pick again? The 'tools.jar' file is ",
"generally located in the 'lib' subdirectory under your ",
"JDK installation directory.",
"(If you say 'No', DrJava might be unable to compile or ",
"debug programs.)"
};
}
else {
text = new String[] {
"The file you chose did not appear to be the correct 'tools.jar'",
"that is compatible with the version of Java that is used to",
"run DrJava (Java version "+System.getProperty("java.version")+").",
"The file you have selected appears to be for",
"Java version "+selectedVersion+".",
"Your choice might be an incompatible version of the file.",
"Would you like to pick again? The 'tools.jar' file is ",
"generally located in the 'lib' subdirectory under your ",
"JDK installation directory.",
"If you say 'No', DrJava might be unable to compile or ",
"debug programs."
};
}
int result = JOptionPane.showConfirmDialog(null, text, "Locate 'tools.jar'?", JOptionPane.YES_NO_OPTION);
return result == JOptionPane.YES_OPTION;
}
/** @return a string with the suspected version of the tools.jar file, or null if an error occurred. */
private static String _getToolsJarVersion(File toolsJarFile) {
try {
JarFile jf = new JarFile(toolsJarFile);
Manifest mf = jf.getManifest();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
mf.write(baos);
String str = baos.toString();
// the expected format of str is:
// Manifest-Version: 1.0
// Created-By: 1.5.0_07 (Sun Microsystems Inc.)
//
final String CB = "Created-By: ";
int beginPos = str.indexOf(CB);
if (beginPos >= 0) {
beginPos += CB.length();
int endPos = str.indexOf(System.getProperty("line.separator"), beginPos);
if (endPos >= 0) return str.substring(beginPos, endPos);
else {
endPos = str.indexOf(' ', beginPos);
if (endPos >= 0) return str.substring(beginPos, endPos);
else {
endPos = str.indexOf('\t', beginPos);
if (endPos >= 0) return str.substring(beginPos, endPos);
}
}
}
}
catch(Exception rte) { /* ignore, just return null */ }
return null;
}
}