package bluej;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javafx.stage.Stage;
import threadchecker.OnThread;
import threadchecker.Tag;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
| This class is the BlueJ boot loader. bluej.Boot is the class that should be
| started to execute BlueJ. No other external classpath settings are necessary.
|
| This loader finds and loads the known BlueJ classes and sets up the classpath.
| While doing this, it displays a splash screen.
|
| @author Andrew Patterson
| @author Damiano Bolla
| @author Michael Kolling
| @author Bruce Quig
|
public class Boot
{
public static final int BLUEJ_VERSION_MAJOR = 4;
public static final int BLUEJ_VERSION_MINOR = 2;
public static final int BLUEJ_VERSION_RELEASE = 1;
public static final String BLUEJ_VERSION_SUFFIX = "";
public static final String BLUEJ_VERSION = BLUEJ_VERSION_MAJOR
+ "." + BLUEJ_VERSION_MINOR
+ "." + BLUEJ_VERSION_RELEASE
+ BLUEJ_VERSION_SUFFIX;
public static final String BLUEJ_VERSION_TITLE = "BlueJ " + BLUEJ_VERSION;
private static final int bluejBuildJars = 3;
private static final String[] bluejUserJars = { "bluejcore.jar", "junit-4.11.jar", "hamcrest-core-1.3.jar", "lang-stride.jar"
};
private static final int bluejUserBuildJars = 1;
private static final String JLAYER_MP3_JAR = "jl1.0.1.jar";
public static final String[] GREENFOOT_EXPORT_JARS = {JLAYER_MP3_JAR, "lang-stride.jar"};
private static final String[] greenfootUserJars = {"extensions" + File.separatorChar + "greenfoot.jar",
"bluejcore.jar", "bluejeditor.jar", "bluejext.jar",
"junit-4.11.jar", "hamcrest-core-1.3.jar", "bluej.jar",
"classgraph-4.2.6.jar",
"diffutils-1.2.1.jar", "commons-logging-api-1.1.2.jar",
JLAYER_MP3_JAR, "opencsv-2.3.jar", "xom-1.2.9.jar",
"lang-stride.jar",
"nsmenufx-2.1.4.jar", "richtextfx-fat-0.9.0.jar",
"guava-17.0.jar",
"httpclient-4.1.1.jar", "httpcore-4.1.jar", "httpmime-4.1.1.jar"};
private static final int greenfootUserBuildJars = 4;
public static String GREENFOOT_VERSION = "3.6.0";
public static String GREENFOOT_API_VERSION = "3.0.0";
private static Boot instance;
private static final String[] bluejJars = { "bluejcore.jar", "bluejeditor.jar", "bluejext.jar",
"antlr-runtime-3.4.jar",
"classgraph-4.2.6.jar",
"commons-logging-api-1.1.2.jar",
"diffutils-1.2.1.jar",
"eddsa-0.2.0.jar",
"guava-17.0.jar",
"hamcrest-core-1.3.jar",
"httpclient-4.1.1.jar",
"httpcore-4.1.jar",
"httpmime-4.1.1.jar",
"jbcrypt-1.0.0.jar",
"jsch-0.1.53.jar",
"junit-4.11.jar",
"lang-stride.jar",
"nsmenufx-2.1.4.jar",
"org.eclipse.jgit-4.9.0.jar",
"richtextfx-fat-0.9.0.jar",
"sequence-library-1.0.3.jar",
"slf4j-api-1.7.2.jar",
"slf4j-jdk14-1.7.2.jar",
"sqljet-1.1.10.jar",
"svnkit.jar",
"svnkit-javahl.jar",
"trilead-ssh2-build-217-jenkins-11.jar",
"xom-1.2.9.jar"
};
private static String [] runtimeJars = bluejJars;
private static String [] userJars = bluejUserJars;
private static int numBuildJars = bluejBuildJars;
private static int numUserBuildJars = bluejUserBuildJars;
private static String[] javafxJars = new String[] {
"javafx.base.jar",
"javafx.controls.jar",
"javafx.fxml.jar",
"javafx.graphics.jar",
"javafx.media.jar",
"javafx.properties.jar",
"javafx.swing.jar",
"javafx.web.jar"
};
private static boolean isGreenfoot = false;
private static File bluejLibDir;
private static final ArrayList<File> macInitialProjects = new ArrayList<>();
@OnThread(Tag.FXPlatform)
private SplashWindow splashWindow;
public static String[] cmdLineArgs;
private final Properties commandLineProps;
private File javaHomeDir;
private ClassLoader bootLoader;
private URL[] runtimeUserClassPath;
private URL[] runtimeClassPath;
| Constructor for the singleton Boot object.
|
| @param props the properties (created from the args)
|
private Boot(Properties props, final FXPlatformSupplier<Image> image)
{
CompletableFuture<Boolean> shown = new CompletableFuture<>();
Platform.runLater(() -> {
splashWindow = new SplashWindow(image.get());
shown.complete(true);
});
try
{
shown.get();
}
catch (InterruptedException | ExecutionException e)
{
}
this.commandLineProps = props;
}
| Entry point for booting BlueJ
|
| @param args The command line arguments
|
public static void main(String[] args)
{
cmdLineArgs = args;
Application.launch(App.class, args);
}
public static List getMacInitialProjects()
{
return macInitialProjects;
}
| Gets the URLs for JavaFX JARs to put on the classpath
|
public URL[] getJavaFXClassPath()
{
String javafxJarsProp = commandLineProps.getProperty("javafxjars", null);
if (javafxJarsProp != null)
{
return Arrays.stream(javafxJarsProp.split(":")).map(s -> {
try
{
return new File(s).toURI().toURL();
}
catch (MalformedURLException e)
{
throw new RuntimeException(e);
}
}).toArray(URL[]::new);
}
File javafxLibPath = getJavaFXLibDir();
URL[] urls = new URL[javafxJars.length];
for (int i = 0; i < javafxJars.length; i++)
{
try
{
urls[i] = new File(javafxLibPath, javafxJars[i]).toURI().toURL();
}
catch (MalformedURLException e)
{
throw new RuntimeException(e);
}
}
return urls;
}
public File getJavaFXLibDir()
{
String javafxPathProp = commandLineProps.getProperty("javafxpath", null);
File javafxPath;
if (javafxPathProp != null)
{
javafxPath = new File(javafxPathProp);
}
else
{
javafxPath = new File(getBluejLibDir(), "javafx");
}
return new File(javafxPath, "lib");
}
| Gets the path to the JavaFX src zip, which may or may not exist.
| @return
|
public File getJavaFXSourcePath()
{
File javafxLibPath = getJavaFXLibDir();
File javafxSrcPath = new File(javafxLibPath, "src.zip");
return javafxSrcPath;
}
@FunctionalInterface
private static interface FXPlatformSupplier<T>
{
@OnThread(Tag.FXPlatform)
public T get();
}
@OnThread(Tag.Any)
public static void subMain()
{
Properties commandLineProps = processCommandLineProperties(cmdLineArgs);
isGreenfoot = commandLineProps.getProperty("greenfoot", "false").equals("true");
FXPlatformSupplier<Image> image = new FXPlatformSupplier<Image>()
{
@Override
@OnThread(Tag.FXPlatform)
public Image get()
{
URL url = getClass().getResource(isGreenfoot ? "gen-greenfoot-splash.png" : "gen-bluej-splash.png");
if (url != null)
return new Image(url.toString());
else
{
WritableImage writableImage = new WritableImage(500, 300);
for (int y = 0; y < writableImage.getHeight(); y++)
{
for (int x = 0; x < writableImage.getWidth(); x++)
{
writableImage.getPixelWriter().setColor(x, y, javafx.scene.paint.Color.WHITE);
}
}
return writableImage;
}
}
};
if (isGreenfoot) {
runtimeJars = greenfootUserJars;
userJars = greenfootUserJars;
numBuildJars = greenfootUserBuildJars;
numUserBuildJars = greenfootUserBuildJars;
}
try {
instance = new Boot(commandLineProps, image);
instance.bootBluej();
}
catch (Throwable t) {
t.printStackTrace();
System.exit(1);
}
synchronized (instance) {
while (true){
try {
instance.wait();
}
catch (InterruptedException ie) {
}
}
}
}
| Returns the singleton Boot instance, so the rest of BlueJ can find paths, args, etc.
|
| @return the singleton Boot object instance
|
public static Boot getInstance()
{
return instance;
}
| Returns the BlueJ library directory.
|
| @return The bluejLibDir value
|
public static File getBluejLibDir()
{
if (bluejLibDir == null) {
bluejLibDir = calculateBluejLibDir();
}
return bluejLibDir;
}
| Calculate the bluejLibDir value by doing some reasoning on a resource
| we know we have: the .class file for the Boot class.
| For example:
| bootUrl=jar:file:/C:/home/bluej/bluej/lib/bluej.jar!/bluej/Boot.class
| bootFullName=file:/C:/home/bluej/bluej/lib/bluej.jar!/bluej/Boot.class
| bootName=file:/C:/home/bluej/bluej/lib/bluej.jar
| finalName=/C:/home/bluej/bluej/lib/bluej.jar
| Parent=C:\home\bluej\bluej\lib
|
| @return the path of the BlueJ lib directory
|
private static File calculateBluejLibDir()
{
File bluejDir = null;
String bootFullName = Boot.class.getResource("Boot.class").toString();
try {
if (! bootFullName.startsWith("jar:")) {
File startingDir = (new File(new URI(bootFullName)).getParentFile());
while ((startingDir != null) &&
!(new File(startingDir.getParentFile(), "lib").isDirectory())) {
startingDir = startingDir.getParentFile();
}
if (startingDir == null) {
bluejDir = null;
}
else {
bluejDir = new File(startingDir.getParentFile(), "lib");
}
}
else {
int classIndex = bootFullName.indexOf("!");
String bootName = bootFullName.substring(4, classIndex);
File finalFile = new File(new URI(bootName));
bluejDir = finalFile.getParentFile();
}
}
catch (URISyntaxException use) {
}
return bluejDir;
}
| Looks for a given JAR in the lib folder of the JRE
| @param name The name of the jar file to look for
| @return The URL to the jar
| @throws MalformedURLException
|
public static URL getJREJar(String name) throws MalformedURLException
{
String jrePathName = System.getProperty("java.home");
if (jrePathName != null)
{
File jrePath = new File(jrePathName);
if (jrePath.canRead())
{
File jarPath = new File(jrePath, "lib/" + name);
if (jarPath.canRead())
{
return jarPath.toURI().toURL();
}
else
{
System.err.println("Could not find " + name + " at: " + jarPath.getAbsolutePath());
}
}
else
{
System.err.println("Could not find JRE at: " + jrePath.getAbsolutePath());
}
}
else
{
System.err.println("Could not find Java path");
}
return null;
}
| Analyse and process command line specified properties.
| Properties can be specified with -... command line options. For example: -bluej.debug=true
|
| @param args The command line parameters
| @return The property object
|
private static Properties processCommandLineProperties(String[] args)
{
Properties props = new Properties();
for (String arg : args) {
if (!arg.startsWith("-")) {
continue;
}
String definition = arg.substring(1);
int definitionEquals = definition.indexOf('=');
if (definitionEquals < 0)
continue;
String propName = definition.substring(0, definitionEquals);
String propValue = definition.substring(definitionEquals+1);
if (!propName.equals("") && !propValue.equals(""))
props.put(propName, propValue);
}
return props;
}
| Are we a trial (experiment) recording for a Greenfoot user trial? This should
| always be hardcoded to false in mainstream Greenfoot releases, and only changed
| in special releases for running Greenfoot user trials.
|
public static boolean isTrialRecording()
{
return false;
}
| Hide (and dispose) the splash window
|
public void disposeSplashWindow()
{
Platform.runLater(() ->
{
if (splashWindow != null)
{
splashWindow.hide();
splashWindow = null;
}
});
}
| @return True is we're in Greenfoot.
|
public boolean isGreenfoot()
{
return isGreenfoot;
}
| Returns the home directory of the java we have been started with
|
| @return The javaHome value
|
public File getJavaHome()
{
return javaHomeDir;
}
| Returns the runtime classpath. This contains all the classes for BlueJ.
|
| @return The runtimeClassPath value.
|
public URL[] getRuntimeClassPath()
{
return runtimeClassPath;
}
| Returns the runtime user classpath. This is available to code within BlueJ.
|
| @return The runtimeUserClassPath value.
|
public URL[] getRuntimeUserClassPath()
{
return runtimeUserClassPath;
}
| Returns the boot class loader, the one that is used to load this class.
|
| @return The bootClassLoader value.
|
public ClassLoader getBootClassLoader()
{
return bootLoader;
}
| Calculate the various path values, create a new classloader and
| construct a bluej.Main. This needs to be outside the constructor to
| ensure that the singleton instance is valid by the time
| bluej.Main is run.
|
@OnThread(Tag.Any)
private void bootBluej()
{
initializeBoot();
try {
URLClassLoader runtimeLoader = new URLClassLoader(runtimeClassPath, bootLoader);
Class<?> mainClass = Class.forName("bluej.Main", true, runtimeLoader);
mainClass.getDeclaredConstructor().newInstance();
} catch (ClassNotFoundException | InstantiationException | NoSuchMethodException
| InvocationTargetException | IllegalAccessException exc) {
|
throw new RuntimeException(exc);
}
}
private void initializeBoot()
{
bootLoader = getClass().getClassLoader();
javaHomeDir = new File(System.getProperty("java.home"));
try {
runtimeClassPath = getKnownJars(getBluejLibDir(), runtimeJars, false, numBuildJars);
runtimeUserClassPath = getKnownJars(getBluejLibDir(), userJars, true, numUserBuildJars);
}
catch (Exception exc) {
exc.printStackTrace();
}
}
| Returns an array of URLs for all the required BlueJ jars
|
| @param libDir the BlueJ "lib" dir (where the jars are stored)
|* @param jars the names of the jar files whose urls to add in the
* returned list
* @param isForUserVM True if any jar files required for the user VM should be included.
| @param numBuildJars The number of jar files in the jars array which
| are built from the BlueJ source. If running from eclipse
| these can be replaced with a single entry - the classes
| directory.
|
| @return URLs of the required JAR files
| @exception MalformedURLException for any problems with the URLs
|
private URL[] getKnownJars(File libDir, String[] jars, boolean isForUserVM, int numBuildJars)
throws MalformedURLException
{
boolean useClassesDir = commandLineProps.getProperty("useclassesdir", "false").equals("true");
int startJar = 0;
ArrayList<URL> urlList = new ArrayList<>();
if (numBuildJars != 0 && useClassesDir) {
File classesDir = new File(libDir.getParentFile(), "classes");
if (classesDir.isDirectory()) {
urlList.add(classesDir.toURI().toURL());
urlList.add(new File(libDir.getParentFile(), "threadchecker/classes").toURI().toURL());
if (isGreenfoot) {
String gfClassesDir = commandLineProps.getProperty("greenfootclassesdir");
if (gfClassesDir != null) {
classesDir = new File(gfClassesDir);
urlList.add(classesDir.toURI().toURL());
}
}
startJar = numBuildJars;
}
}
for (int i=startJar; i < jars.length; i++) {
File toAdd = new File(libDir, jars[i]);
if (toAdd.canRead())
urlList.add(toAdd.toURI().toURL());
}
if (isForUserVM)
{
urlList.addAll(Arrays.asList(getJavaFXClassPath()));
}
return (URL[]) urlList.toArray(new URL[0]);
}
| Returns command line specified properties.
|
| <p>Properties can be specified with -... command line options. For example: -bluej.debug=true
| @return The command line specified properties
|
public Properties getCommandLineProperties()
{
return commandLineProps;
}
public static class App
extends javafx.application.Application
{
public App()
{
if (System.getProperty("os.name").contains("OS X"))
{
|
|
|com.apple.eawt.Application macApp = com.apple.eawt.Application.getApplication();
|
|macApp.setOpenFileHandler(e -> {}macInitialProjects.addAll(e.getFiles());
|
|});
com.sun.glass.ui.Application glassApp = com.sun.glass.ui.Application.GetApplication();
glassApp.setEventHandler(new com.sun.glass.ui.Application.EventHandler() {
@Override
public void handleOpenFilesAction(com.sun.glass.ui.Application app, long time, String[] files)
{
for (String f : files)
{
if (!f.contains(":") && !f.equals("bluej.Boot") && !f.startsWith("-"))
macInitialProjects.add(new File(f));
}
super.handleOpenFilesAction(app, time, files);
}
@Override
public void handleQuitAction(com.sun.glass.ui.Application app, long time)
{
getInstance().quitAction.run();
super.handleQuitAction(app, time);
}
});
}
}
@Override
@OnThread(value = Tag.FXPlatform, ignoreParent = true)
public void start(Stage s) throws Exception
{
Platform.setImplicitExit(false);
s.setTitle("BlueJ");
new Thread(() -> subMain()).start();
}
}
| We don't want this Boot class to depend on further BlueJ classes, so although
| Boot needs to know how to quit, we don't want to introduce a compile-time
| dependency on the classes needed to quit. So this lambda/Runnable is a late
| binding for the same purpose
|
private Runnable quitAction;
| Sets the code to be run (on an arbitrary thread; caller's responsibility to
| switch threads/avoid deadlocks if needed) once the user triggers the Quit
| menu command in the editors.
|
public void setQuitHandler(Runnable quitAction)
{
this.quitAction = quitAction;
}
}
top,
use,
map,
class Boot
. Boot
. main
. getMacInitialProjects
. getJavaFXClassPath
. getJavaFXLibDir
. getJavaFXSourcePath
. get
. subMain
. get
. getInstance
. getBluejLibDir
. calculateBluejLibDir
. getJREJar
. processCommandLineProperties
. isTrialRecording
. disposeSplashWindow
. isGreenfoot
. getJavaHome
. getRuntimeClassPath
. getRuntimeUserClassPath
. getBootClassLoader
. bootBluej
. initializeBoot
. getKnownJars
. getCommandLineProperties
top,
use,
map,
class App
. App
. handleOpenFilesAction
. handleQuitAction
. start
. setQuitHandler
833 neLoCode
+ 75 LoComm