import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Properties;
import java.util.Scanner;
import java.util.StringTokenizer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipInputStream;
import javax.swing.*;
import javax.swing.text.Keymap;
| Java installer. This is a GUI for unpacking an application (BlueJ or
| Greenfoot) bundled inside a jar file. A properties file also bundled
| in the jar file controls the installation.
|
| @author Michael Kolling
| @author based partly on code by Andrew Hunt, Toolshed Technologies Inc.
|
| This program is free software; you can redistribute it and/or modify
| it under the terms of the GNU General Public License as published by
| the Free Software Foundation; either version 2 of the License, or
| (at your option) any later version.
|
| Modified by Davin McCall, 2005-09-06, and 2010-09-24
|
public class Installer
extends JFrame
implements ActionListener
{
private static final String nl = System.getProperty("line.separator");
private static final char slash = File.separatorChar;
private static final String colon = File.pathSeparator;
static private String jdkFile = "/jmods/jdk.compiler.jmod";
static private String javaFxFile = "/lib/javafx.graphics.jar";
static final int BUFFER_SIZE=8192;
JTextField directoryField;
JTextField javaField;
JTextField javaFxField;
JLabel textLabel1;
JLabel textLabel2;
JButton browseDirButton;
JButton browseJdkButton;
JButton browseJavaFxButton;
JButton installButton;
JButton cancelButton;
JProgressBar progress;
UpdateProgress progressUpdater;
String currentDirectory;
String osname;
String architecture;
String javaVersion;
boolean isJDK12;
boolean isJDK13;
String installationDir = "";
String javaPath = "";
String javaFxPath = "";
Properties properties;
long myTotalBytes = 400000;
|
| Default behaviour for JTextFields is to generate an ActionEvent when
| "Enter" is pressed. We don't want that. Here, we remove the Enter key
|* from the keymap used by all JTextFields. Then we can use the default
* button for dialogs (Enter will then activate the default button).
static {
JTextField f = new JTextField();
KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
Keymap map = f.getKeymap();
map.removeKeyStrokeBinding(enter);
}
public static void main(String[] args)
{
try {
EventQueue.invokeAndWait(new Runnable() {
public void run()
{
new Installer();
}
});
}
catch (InterruptedException ie) { ie.printStackTrace(); }
catch (InvocationTargetException ite) { ite.printStackTrace(); }
}
public Installer()
{
super();
currentDirectory = System.getProperty("user.dir");
osname = System.getProperty("os.name");
architecture = System.getProperty("os.arch");
javaVersion = System.getProperty("java.specification.version");
javaPath = findJavaPath();
loadProperties();
String installDirName = getProperty("installDirName");
if(currentDirectory.endsWith(installDirName))
installationDir = currentDirectory;
else
installationDir = currentDirectory + File.separator + installDirName;
try {
myTotalBytes = Integer.parseInt(getProperty("pkgJarSize"));
}
catch (NumberFormatException nfe) {}
progressUpdater = new UpdateProgress();
makeWindow();
String requiredJavaVersion = getProperty("requiredJavaVersion");
if(javaVersion.compareTo(requiredJavaVersion) < 0) {
notifyError(getProperty("jdkError1") + javaVersion + " " + getProperty("jdkError2"), getProperty("jdkMsg"));
}
}
| Load installer properties - name of the archive to extract, the logo, color scheme,
| etc.
|
private void loadProperties()
{
InputStream propStream = ClassLoader.getSystemResourceAsStream("installer.props");
properties = new Properties();
try {
properties.load(propStream);
}
catch (IOException ioe) {
System.err.println("Error loading installer configuration:");
ioe.printStackTrace(System.err);
System.exit(1);
}
}
| Try and figure out the path to the currently-running JDK
| @return The path, or an empty string if could not be determined
|
private String findJavaPath()
{
String javaHome = System.getProperty("java.home");
if(isJDKPath(javaHome))
return javaHome;
if(javaHome.endsWith("jre")) {
javaHome = javaHome.substring(0, javaHome.length()-4);
if(isJDKPath(javaHome))
return javaHome;
}
String shortVersion = javaVersion.substring(0, javaVersion.length()-2);
String[] tryPaths = {
"C:\\jdk" + javaVersion,
"C:\\jdk" + shortVersion,
"D:\\jdk" + javaVersion,
"D:\\jdk" + shortVersion,
"/usr/java",
"/usr/local/java",
"/usr/jdk" + javaVersion,
"/usr/jdk" + shortVersion,
"/usr/local/jdk" + javaVersion,
"/usr/local/jdk" + shortVersion,
"/System/Library/Frameworks/JavaVM.framework",
};
for(int i = 0; i < tryPaths.length; i++)
if(isJDKPath(tryPaths[i]))
return tryPaths[i];
return "";
}
| Handle button press.
|
public void actionPerformed(ActionEvent evt)
{
Object src = evt.getSource();
if(src == browseDirButton) {
getInstallDirectory();
}
else if(src == browseJdkButton) {
getJDKDirectory();
}
else if (src == browseJavaFxButton) {
getJavaFxDirectory();
}
else if(src == installButton) {
installButton.setEnabled(false);
InstallThread p = new InstallThread();
p.setPriority(Thread.MIN_PRIORITY + 1);
p.start();
}
else if(src == cancelButton)
doCancel();
}
class InstallThread
extends Thread {
public void run()
{
doInstall();
}
}
| Cancel button action
|
private void doCancel()
{
System.exit(0);
}
| Get an installtion directory from the user via a file selection
| dialogue.
|
private void getInstallDirectory()
{
String dirName = getDirName("Select installation directory");
if(dirName != null) {
if(dirName.endsWith(getProperty("installDirName")))
installationDir = dirName;
else
installationDir = dirName + File.separator + getProperty("installDirName");
directoryField.setText(installationDir);
checkInstallDir(installationDir, false);
}
}
| Get the jdk directory from the user via a file selection
| dialogue.
|
private void getJDKDirectory()
{
String dirName = getDirName("Select JDK directory");
if(dirName != null) {
javaPath = dirName;
javaField.setText(javaPath);
if(! isJDKPath(javaPath))
jdkPathProblem();
}
}
| Get the JavaFX directory from the user via a file selection
| dialogue.
|
private void getJavaFxDirectory()
{
String dirName = getDirName("Select JavaFX directory");
if(dirName != null) {
javaFxPath = dirName;
javaFxField.setText(javaFxPath);
if (!isJavaFxPath(javaFxPath))
javaFxPathProblem();
}
}
| Install button action. This gets executed on a seperate thread.
|
public void doInstall()
{
readInputValues();
if(! isJDKPath(javaPath)) {
jdkPathProblem();
return;
}
if (!isJavaFxPath(javaFxPath))
{
javaFxPathProblem();
return;
}
try {
if(!checkInstallDir(installationDir, true))
return;
unpackTo();
if (getProperty("exeName") != null) {
if(osname == null) {
writeWindows();
writeUnix(false);
}
else if(osname.startsWith("Windows")) {
writeWindows();
}
else if(osname.startsWith("Mac")) {
writeUnix(true);
}
else
writeUnix(false);
}
} catch (Exception e) {
e.printStackTrace();
finish("Installation FAILED: ", e.getMessage());
return;
}
if (getProperty("exeName") != null) {
finish(getProperty("appName") + " has been installed to " + installationDir,
"To run it, execute \"" +
getProperty("exeName") + "\".");
} else {
finish("The package has been installed to "+installationDir, " ");
}
}
| Read the values that the user selected into the appropriate variables.
|
private void readInputValues()
{
installationDir = directoryField.getText();
javaPath = javaField.getText();
javaFxPath = javaFxField.getText();
}
| Check that the current Java version is a full JDK. Warn if not.
|
public boolean isJDKPath(String path)
{
String jdkFilePath = path + jdkFile;
if(new File(jdkFilePath).exists())
return true;
else {
return false;
}
}
| Check that the JavaFX path has JavaFX in it
|
public boolean isJavaFxPath(String path)
{
String javaFxFilePath = path + javaFxFile;
return new File(javaFxFilePath).exists();
}
| Update the status dialog. Called on the installer thread.
|
public void setStatus(final String text)
{
EventQueue.invokeLater(new Runnable() {
public void run()
{
textLabel1.setText(text);
textLabel1.repaint();
}
});
}
| Show message in main window and finish.
|
public void finish(String msg1, String msg2)
{
textLabel1.setText(msg1);
textLabel2.setText(msg2);
installButton.setEnabled(false);
cancelButton.setText("Done");
getRootPane().setDefaultButton(cancelButton);
}
| Pop up a dialog box with the error message. After an error,
| installation cannot proceed.
|
public void notifyError(String error, String msg)
{
JOptionPane.showMessageDialog(this, error);
finish(msg, "Installation aborted.");
}
| Inform user of invalid jdk.
|
private void jdkPathProblem()
{
notifyProblem(
"The Java directory you have specified is not a valid \n" +
"JDK directory. It must contain the file \n" +
jdkFile);
}
| Inform user of invalid jdk.
|
private void javaFxPathProblem()
{
notifyProblem(
"JavaFX must be installed, via package manager or downloaded\n" +
"from openjfx.io The JavaFX directory you have specified\n" +
"is not a valid JavaFX directory. It must contain the file\n" +
javaFxFile);
}
| Inform user of invalid install dir. Return true if everything is fine.
|
private boolean checkInstallDir(String dirName, boolean make)
{
File installDir = new File(dirName);
if(installDir.exists()) {
if(installDir.isDirectory())
return true;
else {
notifyProblem("The name you specified exists\n" +
"and is not a directory. Cannot\n" +
"install there.");
return false;
}
}
else {
File parent = installDir.getParentFile();
if(parent.exists()) {
if(parent.isDirectory()) {
if(make)
installDir.mkdir();
return true;
}
else {
notifyProblem(parent.getAbsolutePath() + " is not\n" +
"a directory. Cannot install there.");
return false;
}
}
else {
notifyProblem(
"The directory " + parent.getAbsolutePath() +
"\ndoes not exist.\n" +
"Please check the path and enter again.");
return false;
}
}
}
| Pop up a dialog box with the message. After a problem,
| installation can proceed.
|
private void notifyProblem(final String problem)
{
EventQueue.invokeLater(new Runnable() {
public void run()
{
JOptionPane.showMessageDialog(Installer.this, problem);
installButton.setEnabled(true);
}
});
}
| Create and show the main window
|
public void makeWindow()
{
Color backgroundColour = colorFromString(getProperty("color.background"));
setBackground(backgroundColour);
String title = getProperty("title");
if(title != null)
setTitle(title);
JPanel mainPanel = (JPanel)getContentPane();
mainPanel.setLayout(new BorderLayout(15, 15));
mainPanel.setBorder(BorderFactory.createEmptyBorder(20,20,20,20));
mainPanel.setBackground(backgroundColour);
URL logoUrl = ClassLoader.getSystemResource(getProperty("gif.logo"));
Image img = getToolkit().createImage(logoUrl);
JLabel logoLabel = new JLabel(new ImageIcon(img));
mainPanel.add(logoLabel, BorderLayout.NORTH);
JPanel buttonPanel = new JPanel();
buttonPanel.setBackground(backgroundColour);
installButton = new JButton("Install");
buttonPanel.add(installButton);
installButton.addActionListener(this);
cancelButton = new JButton("Cancel");
buttonPanel.add(cancelButton);
cancelButton.addActionListener(this);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
Box centrePanel = new Box(BoxLayout.Y_AXIS);
Box dirPanel = new Box(BoxLayout.X_AXIS);
dirPanel.add(Box.createHorizontalGlue());
dirPanel.add(new JLabel("Directory to install to:"));
directoryField = new JTextField(installationDir, 16);
dirPanel.add(directoryField);
browseDirButton = new JButton("Browse");
browseDirButton.addActionListener(this);
dirPanel.add(browseDirButton);
centrePanel.add(dirPanel);
centrePanel.add(Box.createVerticalStrut(5));
Box jdkDirPanel = new Box(BoxLayout.X_AXIS);
jdkDirPanel.add(Box.createHorizontalGlue());
jdkDirPanel.add(new JLabel("Java (JDK) directory:"));
javaField = new JTextField(javaPath, 16);
jdkDirPanel.add(javaField);
browseJdkButton = new JButton("Browse");
browseJdkButton.addActionListener(this);
jdkDirPanel.add(browseJdkButton);
centrePanel.add(jdkDirPanel);
centrePanel.add(Box.createVerticalStrut(5));
Box javaFxDirPanel = new Box(BoxLayout.X_AXIS);
javaFxDirPanel.add(Box.createHorizontalGlue());
javaFxDirPanel.add(new JLabel("JavaFX directory:"));
javaFxField = new JTextField("", 16);
javaFxDirPanel.add(javaFxField);
browseJavaFxButton = new JButton("Browse");
browseJavaFxButton.addActionListener(this);
javaFxDirPanel.add(browseJavaFxButton);
centrePanel.add(javaFxDirPanel);
centrePanel.add(Box.createVerticalStrut(24));
progress = new JProgressBar();
centrePanel.add(progress);
centrePanel.add(Box.createVerticalStrut(5));
JPanel labelPanel = new JPanel(new GridLayout(0,1));
labelPanel.setBackground(backgroundColour);
textLabel1 = new JLabel(" ", JLabel.LEFT);
labelPanel.add(textLabel1);
textLabel2 = new JLabel(" ", JLabel.LEFT);
labelPanel.add(textLabel2);
centrePanel.add(labelPanel);
String tagline = getProperty("tagline");
if(tagline != null)
textLabel2.setText(tagline);
mainPanel.add(centrePanel, BorderLayout.CENTER);
getRootPane().setDefaultButton(installButton);
pack();
setLocation(100,100);
setVisible(true);
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
public void windowActivated(WindowEvent e)
{
installButton.requestFocus();
}
});
}
| Write out a Unix, Bourne shell script to start the application
| For JDK 1.4 and later
|
public void writeUnix(boolean isMacOS)
throws IOException
{
File outputFile = new File(installationDir, getProperty("exeName"));
FileWriter out = new FileWriter(outputFile.toString());
out.write("#!/bin/sh\n");
out.write("APPBASE=\"" + installationDir + "\"\n");
out.write("JAVAPATH=\"" + javaPath + "\"\n");
out.write("JAVAFXPATH=\"" + javaFxPath + "\"\n");
String commands;
String javaName = "$JAVAPATH/bin/java";
if(isMacOS) {
commands = getProperty("commands.mac").toString();
}
else {
commands = getProperty("commands.unix").toString();
}
commands += "\n" + getProperty("javafx.classpath.unix").toString();
if(commands != null) {
out.write(commands);
out.write("\n");
}
out.write("\"" + javaName + "\" " + getProperty("javaOpts.unix") + " " +
getProperty("mainClass") + " " +
getProperty("arguments.unix") + " \"$@\"\n");
out.close();
try {
String cmdArray[] = { "chmod", "755", outputFile.toString() };
Runtime.getRuntime().exec(cmdArray);
} catch(Exception e) {
}
}
| Write out an MSDOS style batch file to start the application.
| (JDK 1.4 and later)
|
public void writeWindows() throws IOException
{
File outputFile = new File(installationDir,
getProperty("exeName") + ".bat");
FileWriter out = new FileWriter(outputFile.toString());
out.write("@echo off\r\n");
out.write("set APPBASE=\"" + installationDir + "\"\r\n");
out.write("set JAVAFXPATH=\"" + javaFxPath + "\"\r\n");
String commands = getProperty("commands.win").toString();
if(commands != null) {
commands = replace(commands, '~', "%APPBASE%");
commands = replace(commands, '!', "\"" + javaPath + "\"");
commands = replace(commands, '@', architecture);
out.write(commands);
out.write("\r\n");
}
out.write(getProperty("javafx.classpath.win").toString() + "\r\n");
out.write("\"" + javaPath + "\\bin\\java\" " +
getProperty("javaOpts.win") + " " +
getProperty("mainClass") + " " +
getProperty("arguments.win") +
" %1 %2 %3 %4 %5 %6 %7 %8 %9\r\n");
out.close();
}
| Grab the jar data from the class file and unjar it into the
| install directory.
|
public void unpackTo()
{
try {
InputStream in = ClassLoader.getSystemResourceAsStream(getProperty("pkgJar"));
dumpJar(installationDir, new ProgressTrackerStream(in));
}
catch (Exception e) {
notifyError("Installer failed to open: " + e,
"Could not open install file.");
}
}
| Recursively make directories needed for a file.
|
public void makeDirsFor(String start, String path)
{
String sofar = null;
StringTokenizer tok = new StringTokenizer(path,"/",false);
sofar = start;
while (tok.hasMoreTokens()) {
String part = tok.nextToken();
if (tok.hasMoreTokens()) {
File d = new File(sofar + File.separatorChar + part);
sofar = d.toString();
d.mkdirs();
}
}
return;
}
| Extract a JAR from a file stream to the given directory on disk.
|
public void dumpJar(String dir, InputStream in)
throws IOException, ZipException
{
makeDirsFor(dir,"");
long bytesRead = 0;
ZipInputStream zip = new ZipInputStream(in);
byte[] buffer = new byte[BUFFER_SIZE];
while (true) {
ZipEntry z = zip.getNextEntry();
if (z == null)
break;
String name = dir + "/" + z.getName();
if (z.isDirectory()) {
File d = new File(name);
d.mkdirs();
continue;
}
if (z.getName().indexOf('/') != -1) {
makeDirsFor(dir,z.getName());
}
FileOutputStream out;
try {
out = new FileOutputStream(name);
} catch (FileNotFoundException e) {
throw new IOException("Couldn't write to specified file/directory");
}
int len;
setStatus("extracting: " + name);
while ((len = zip.read(buffer,0, BUFFER_SIZE)) != -1) {
bytesRead += len;
out.write(buffer,0,len);
}
out.close();
if (z.getTime() != -1) {
File f = new File(name);
f.setLastModified(z.getTime());
}
zip.closeEntry();
}
zip.close();
progress.setValue(100);
}
| Constructs a new string by replacing the character in
| pattern found in src by the string subst
|
String replace(String src, char pattern, String subst)
{
char[] patterns = { pattern };
String patString = new String(patterns);
StringTokenizer tokenizer = new StringTokenizer(src, patString, true);
StringBuffer ret = new StringBuffer();
while(tokenizer.hasMoreElements()) {
String tok = tokenizer.nextToken();
if(tok.length() == 1 && tok.equals(patString))
ret.append(subst);
else
ret.append(tok);
}
return ret.toString();
}
| Get a directory name via a file selection dialog.
|
public String getDirName(String title)
{
JFileChooser newChooser = new JFileChooser();
newChooser.setDialogTitle(title);
newChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
int result = newChooser.showDialog(this, "Choose");
if (result == JFileChooser.APPROVE_OPTION)
return newChooser.getSelectedFile().getPath();
else
return null;
}
| Property access
|
public String getProperty(String key)
{
return (String) properties.get("install." + key);
}
private Color colorFromString(String value)
{
StringTokenizer tok = new StringTokenizer(value," \t,",false);
int r = (Integer.valueOf(tok.nextToken())).intValue();
int g = (Integer.valueOf(tok.nextToken())).intValue();
int b = (Integer.valueOf(tok.nextToken())).intValue();
return new Color(r,g,b);
}
|
| Progress tracking
|
| Update the progress bar with a percentage value. This is called from
| the installation thread.
|
private void updateProgress(int value)
{
progressUpdater.value = value;
try {
EventQueue.invokeAndWait(progressUpdater);
}
catch (Exception e) { e.printStackTrace(); }
}
| A stream which keeps track of progress through the underlying stream,
| updating the progress bar at appropriate occassions.
|
| @author Davin McCall
|
private class ProgressTrackerStream
extends InputStream
{
InputStream underlying;
long readCount = 0;
long markerIncrement = (myTotalBytes / 100);
long nextMarker = (myTotalBytes / 100);
public ProgressTrackerStream(InputStream over)
{
underlying = over;
}
public int read() throws IOException
{
int r = underlying.read();
if (r != -1)
readCount++;
if (readCount > nextMarker) {
updateProgress((int) (readCount / markerIncrement));
nextMarker = nextMarker + markerIncrement;
}
return r;
}
public int read(byte[] b, int off, int len) throws IOException
{
int r = underlying.read(b, off, len);
readCount += r;
if (readCount > nextMarker) {
updateProgress((int) (readCount / markerIncrement));
nextMarker = nextMarker + markerIncrement;
}
return r;
}
}
| A runnable used to update the progress bar.
|
| @author Davin McCall
|
private class UpdateProgress
implements Runnable
{
int value;
public void run()
{
progress.setValue(value);
}
}
}
top,
use,
map,
class Installer
. main
. run
. Installer
. loadProperties
. findJavaPath
. actionPerformed
top,
use,
map,
class Installer . InstallThread
. run
. doCancel
. getInstallDirectory
. getJDKDirectory
. getJavaFxDirectory
. doInstall
. readInputValues
. isJDKPath
. isJavaFxPath
. setStatus
. run
. finish
. notifyError
. jdkPathProblem
. javaFxPathProblem
. checkInstallDir
. notifyProblem
. run
. makeWindow
. windowClosing
. windowActivated
. writeUnix
. writeWindows
. unpackTo
. makeDirsFor
. dumpJar
. getDirName
. getProperty
. colorFromString
. updateProgress
top,
use,
map,
class Installer . InstallThread . ProgressTrackerStream
. ProgressTrackerStream
. read
. read
top,
use,
map,
class Installer . InstallThread . ProgressTrackerStream . UpdateProgress
. run
743 neLoCode
+ 60 LoComm