/*******************************************************************************
* Copyright (c) 2001, 2010 Mathew A. Nelson and Robocode contributors
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://robocode.sourceforge.net/license/epl-v10.html
*
* Contributors:
* Mathew A. Nelson
* - Initial API and implementation
* Flemming N. Larsen
* - Replaced deprecated methods
* - Added check for the Java version that the user has installed. If the
* Java version is not 5.0, an error dialog will be display and the
* installation will terminate
* - Changed the information message for how to run robocode.sh, where the
* user does not have to change the directory before calling robocode.sh
* - Added creating file associations in the Windows Registry
*******************************************************************************/
package net.sf.robocode.installer;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.net.URL;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
/**
* Installer for Robocode.
*
* @author Mathew A. Nelsen (original)
* @author Flemming N. Larsen (contributor)
*/
public class AutoExtract implements ActionListener {
private JDialog licenseDialog;
private boolean accepted;
private final String[] spinner = { "-", "\\", "|", "/"};
private String message = "";
private static File installDir;
private static final String osName = System.getProperty("os.name");
private static final double osVersion = doubleValue(System.getProperty("os.version"));
private static final String javaVersion = System.getProperty("java.version");
private static double doubleValue(String s) {
int p = s.indexOf(".");
if (p >= 0) {
p = s.indexOf(".", p + 1);
}
if (p >= 0) {
s = s.substring(0, p);
}
double d = 0.0;
try {
d = Double.parseDouble(s);
} catch (NumberFormatException e) {
e.printStackTrace(System.err);
}
return d;
}
private boolean acceptLicense() {
StringBuffer licenseText = new StringBuffer();
InputStream is;
try {
JarFile extractJar = new JarFile("extract.jar");
is = extractJar.getInputStream(extractJar.getJarEntry("license/cpl-v10.html"));
} catch (IOException e) {
return true;
}
if (is == null) {
return true;
}
InputStreamReader isr = null;
BufferedReader br = null;
try {
isr = new InputStreamReader(is);
br = new BufferedReader(isr);
String line = br.readLine();
while (line != null) {
licenseText.append(line);
line = br.readLine();
}
return acceptReject(licenseText.toString());
} catch (IOException e) {
System.err.println("Could not read line from license file: " + e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (isr != null) {
try {
isr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return true;
}
private boolean acceptReject(String text) {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
licenseDialog = new JDialog();
licenseDialog.setTitle("License Agreement");
licenseDialog.setModal(true);
licenseDialog.setLocation((screenSize.width - 500) / 2, (screenSize.height - 400) / 2);
licenseDialog.setSize(500, 400);
JTextPane t = new JTextPane();
t.setContentType("text/html");
t.setText(text);
t.setFont(new Font("Dialog", Font.PLAIN, 12));
t.setEditable(false);
JScrollPane s = new JScrollPane();
s.setViewportView(t);
licenseDialog.getContentPane().setLayout(new BorderLayout());
licenseDialog.getContentPane().add(s, BorderLayout.CENTER);
JPanel p = new JPanel();
p.setLayout(new BorderLayout());
JButton b1 = new JButton("Accept");
JButton b2 = new JButton("Cancel");
p.add(b1, BorderLayout.WEST);
p.add(b2, BorderLayout.EAST);
b1.addActionListener(this);
b2.addActionListener(this);
licenseDialog.getContentPane().add(p, BorderLayout.SOUTH);
licenseDialog.setVisible(true);
return accepted;
}
public void actionPerformed(ActionEvent e) {
accepted = e.getActionCommand().equals("Accept");
licenseDialog.dispose();
licenseDialog = null;
}
private boolean extract(File dest) {
JDialog statusDialog = new JDialog();
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
int height = 50;
if (File.separatorChar == '/') {
height = 100;
}
statusDialog.setTitle("Installing");
statusDialog.setLocation((screenSize.width - 500) / 2, (screenSize.height - height) / 2);
statusDialog.setSize(500, height);
JLabel status = new JLabel();
statusDialog.getContentPane().setLayout(new BorderLayout());
statusDialog.getContentPane().add(status, BorderLayout.CENTER);
statusDialog.setVisible(true);
FileOutputStream fos;
String entryName;
byte buf[] = new byte[2048];
final String name = AutoExtract.class.getName().replaceAll("\\.", "/") + ".class";
String urlJar = AutoExtract.class.getClassLoader().getResource(name).toString();
final String src = urlJar.substring("jar:file:/".length(), urlJar.indexOf("!/"));
if (src.indexOf('!') > -1) {
message = src + "\nContains an exclamation point. Please move the file to a different directory.";
JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE);
return false;
}
JarInputStream jarIS = null;
try {
final URL url = new URL("file:/" + src);
InputStream is = url.openStream();
jarIS = new JarInputStream(is);
JarEntry entry;
while ((entry = jarIS.getNextJarEntry()) != null) {
int spin = 0;
entryName = entry.getName();
if (entry.isDirectory()) {
if (!entryName.startsWith("net")) {
File dir = new File(dest, entry.getName());
if (!dir.exists() && !dir.mkdirs()) {
System.err.println("Can't create dir: " + dir);
}
}
} else if (!entryName.equals(name)) {
// Skip .bat, .sh, and .command files depending on which OS Robocode is installed
final String entryNameLC = entryName.toLowerCase();
boolean skipEntry = false;
final boolean isBatFile = entryNameLC.length() > ".bat".length() && entryNameLC.endsWith(".bat");
final boolean isShFile = entryNameLC.length() > ".sh".length() && entryNameLC.endsWith(".sh");
final boolean isCommandFile = entryNameLC.length() > ".command".length()
&& entryNameLC.endsWith(".command");
// Unix systems and Mac OS X
if (File.separatorChar == '/') {
// Skip .bat files under Unix and Mac OS X
// Skip .command files under Unix
skipEntry = isBatFile || (isCommandFile && !isMacOSX());
} else {
// Under Windows the .sh and .command files are skipped
skipEntry = isShFile || isCommandFile;
}
// If we are not skipping the entry, then copy from our .jar into the installation dir
if (!skipEntry) {
status.setText(entryName + " " + spinner[spin++]);
File out = new File(dest, entry.getName());
File parentDirectory = new File(out.getParent());
if (!parentDirectory.exists() && !parentDirectory.mkdirs()) {
System.err.println("Can't create dir: " + parentDirectory);
}
fos = new FileOutputStream(out);
int index = 0;
int num;
int count = 0;
while ((num = jarIS.read(buf, 0, 2048)) != -1) {
fos.write(buf, 0, num);
index += num;
count++;
if (count > 80) {
status.setText(entryName + " " + spinner[spin++] + " (" + index + " bytes)");
if (spin > 3) {
spin = 0;
}
count = 0;
}
}
fos.close();
// Set file permissions for .sh and .command files under Unix and Mac OS X
if (File.separatorChar == '/') {
if (isShFile || isCommandFile) {
// Grant read and execute access for everyone and also write access for the owner of the file
Runtime.getRuntime().exec("chmod 755 " + out.toString());
}
}
status.setText(entryName + " " + spinner[spin] + " (" + index + " bytes)");
}
}
}
statusDialog.dispose();
return true;
} catch (IOException e) {
message = "Installation failed" + e;
JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE);
return false;
} finally {
if (jarIS != null) {
try {
jarIS.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String argv[]) {
String suggestedDirName;
if (argv.length == 1) {
suggestedDirName = argv[0];
} else if (isWindowsOS()) {
suggestedDirName = "C:\\robocode\\";
} else {
suggestedDirName = System.getProperty("user.home") + File.separator + "robocode" + File.separator;
}
String message;
if (install(new File(suggestedDirName))) {
message = "Installation successful";
} else {
message = "Installation cancelled";
}
// Delete the class file with the installer and it's parent folders in the robocode home dir
if (installDir != null) {
String installerPath = AutoExtract.class.getName().replaceAll("\\.", "/") + "$1.class";
deleteFileAndParentDirsIfEmpty(new File(installDir, installerPath));
}
JOptionPane.showMessageDialog(null, message);
}
private static boolean install(File suggestedDir) {
// Verify that the Java version is version 5 (1.5.0) or newer
if (javaVersion.startsWith("1.") && javaVersion.charAt(2) < '5') {
final String message = "Robocode requires Java 5.0 (1.5.0) or newer.\n"
+ "Your system is currently running Java " + javaVersion + ".\n"
+ "If you have not installed (or activated) at least\n" + "JRE 5.0 or JDK 5.0, please do so.";
JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE);
System.err.println(message);
return false;
}
// Set native look and feel
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Throwable t) {// For some reason Ubuntu 7 can cause a NullPointerException when trying to getting the LAF
}
AutoExtract extractor = new AutoExtract();
if (extractor.acceptLicense()) {
boolean done = false;
while (!done) {
int rc = JOptionPane.showConfirmDialog(null,
"Robocode will be installed in:\n" + suggestedDir + "\nIs this ok?", "Installing Robocode",
JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
if (rc == JOptionPane.YES_OPTION) {
installDir = suggestedDir;
done = true;
} else if (rc == JOptionPane.NO_OPTION) {
Object r = JOptionPane.showInputDialog(null, "Please type in the installation directory",
"Installation Directory", JOptionPane.PLAIN_MESSAGE, null, null, suggestedDir);
if (r != null) {
suggestedDir = new File(((String) r).trim());
} else {
return false;
}
} else if (rc == JOptionPane.CANCEL_OPTION) {
return false;
}
}
if (!installDir.exists()) {
int rc = JOptionPane.showConfirmDialog(null,
installDir.getPath() + "\ndoes not exist. Would you like to create it?", "Installing Robocode",
JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
if (rc == JOptionPane.YES_OPTION) {
if (!installDir.exists() && !installDir.mkdirs()) {
System.err.println("Can't create dir: " + installDir);
}
} else {
return false;
}
}
deleteOldLibs(installDir);
// The .robotcache has been renamed to .data
File robotsCacheDir = new File(installDir, "robots/.robotcache");
File robotsDataDir = new File(installDir, "robots/.data");
if (robotsCacheDir.exists()) {
// Rename ".robotcache" into ".data"
robotsCacheDir.renameTo(robotsDataDir);
}
// Fix problem with .data starting with a underscore dir by
// renaming files containing ".data/_" into ".data"
if (robotsDataDir.exists()) {
File underScoreDir = new File(robotsDataDir, "_");
String[] list = underScoreDir.list();
if (list != null) {
for (String fileName : list) {
File file = new File(underScoreDir, fileName);
file.renameTo(new File(robotsDataDir, fileName));
}
underScoreDir.delete();
}
}
// Create shortcuts and file associations
if (extractor.extract(installDir)) {
extractor.createShortcuts(installDir, "robocode.bat", "Robocode", "Robocode");
extractor.createFileAssociations(installDir);
return true;
}
}
return false;
}
private static void deleteOldLibs(File installDir) {
File libs = new File(installDir, "libs");
if (libs.exists()) {
final File[] del = libs.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
String test = name.toLowerCase();
return test.endsWith(".jar") || test.endsWith(".dll");
}
});
for (File d : del) {
if (!d.delete()) {
System.err.println("Can't delete: " + d);
}
}
}
}
private static boolean deleteDir(File dir) {
if (dir.isDirectory()) {
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
// Skip directories ending with ".data"
if (file.getName().endsWith(".data")) {
continue;
}
try {
// Test for symlink and ignore.
// Robocode won't create one, but just in case a user does...
if (file.getCanonicalFile().getParentFile().equals(dir.getCanonicalFile())) {
deleteDir(file);
if (file.exists() && !file.delete()) {
System.err.println("Can't delete: " + file);
}
} else {
System.out.println("Warning: " + file + " may be a symlink. It has been ignored");
}
} catch (IOException e) {
System.out.println(
"Warning: Cannot determine canonical file for " + file + ". It has been ignored");
}
} else {
if (file.exists() && !file.delete()) {
System.err.println("Can't delete: " + file);
}
}
}
return dir.delete();
}
return false;
}
private void createShortcuts(File installDir, String runnable, String folder, String name) {
if (osName.toLowerCase().indexOf("win") == 0) {
if (createWindowsShortcuts(installDir, runnable, folder, name) == false) {
JOptionPane.showMessageDialog(null,
message + "\n" + "To start Robocode, enter the following at a command prompt:\n" + "cd "
+ installDir.getAbsolutePath() + "\n" + "robocode.bat");
}
} else if (osName.toLowerCase().indexOf("mac") == 0) {
if (osVersion >= 10.1) {
JOptionPane.showMessageDialog(null,
message + "\n" + "To start Robocode, browse to " + installDir + " then double-click robocode.sh\n");
} else {
JOptionPane.showMessageDialog(null,
message + "\n" + "To start Robocode, enter the following at a command prompt:\n"
+ installDir.getAbsolutePath() + "/robocode.sh");
}
} else {
JOptionPane.showMessageDialog(null,
message + "\n" + "To start Robocode, enter the following at a command prompt:\n"
+ installDir.getAbsolutePath() + "/robocode.sh");
}
}
private boolean createWindowsShortcuts(File installDir, String runnable, String folder, String name) {
int rc = JOptionPane.showConfirmDialog(null,
"Would you like to install a shortcut to Robocode in the Start menu? (Recommended)", "Create Shortcuts",
JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
if (rc != JOptionPane.YES_OPTION) {
return false;
}
final String command = getWindowsCmd() + " cscript.exe ";
try {
File shortcutMaker = new File(installDir, "makeshortcut.vbs");
PrintStream out = new PrintStream(new FileOutputStream(shortcutMaker));
out.println("WScript.Echo(\"Creating shortcuts...\")");
out.println("Set Shell = CreateObject (\"WScript.Shell\")");
out.println("Set fso = CreateObject(\"Scripting.FileSystemObject\")");
out.println("ProgramsPath = Shell.SpecialFolders(\"Programs\")");
out.println("if (not(fso.folderExists(ProgramsPath + \"\\\\" + folder + "\"))) Then");
out.println(" fso.CreateFolder(ProgramsPath + \"\\\\" + folder + "\")");
out.println("End If");
out.println("Set link = Shell.CreateShortcut(ProgramsPath + \"\\\\" + folder + "\\\\" + name + ".lnk\")");
out.println("link.Arguments = \"\"");
out.println("link.Description = \"" + name + "\"");
out.println("link.HotKey = \"\"");
out.println("link.IconLocation = \"" + escaped(installDir.getAbsolutePath()) + "\\\\" + "robocode.ico,0\"");
out.println("link.TargetPath = \"" + escaped(installDir.getAbsolutePath()) + "\\\\" + runnable + "\"");
out.println("link.WindowStyle = 1");
out.println("link.WorkingDirectory = \"" + escaped(installDir.getAbsolutePath()) + "\"");
out.println("link.Save()");
out.println("DesktopPath = Shell.SpecialFolders(\"Desktop\")");
out.println("Set link = Shell.CreateShortcut(DesktopPath + \"\\\\" + name + ".lnk\")");
out.println("link.Arguments = \"\"");
out.println("link.Description = \"" + name + "\"");
out.println("link.HotKey = \"\"");
out.println("link.IconLocation = \"" + escaped(installDir.getAbsolutePath()) + "\\\\" + "robocode.ico,0\"");
out.println("link.TargetPath = \"" + escaped(installDir.getAbsolutePath()) + "\\\\" + runnable + "\"");
out.println("link.WindowStyle = 1");
out.println("link.WorkingDirectory = \"" + escaped(installDir.getAbsolutePath()) + "\"");
out.println("link.Save()");
out.println("WScript.Echo(\"Shortcuts created.\")");
out.close();
Process p = Runtime.getRuntime().exec(command + " makeshortcut.vbs", null, installDir);
int rv = p.waitFor();
if (rv != 0) {
System.err.println("Can't create shortcut: " + shortcutMaker);
return false;
}
JOptionPane.showMessageDialog(null,
message + "\n" + "A Robocode program group has been added to your Start menu\n"
+ "A Robocode icon has been added to your desktop.");
if (!shortcutMaker.delete()) {
System.err.println("Can't delete: " + shortcutMaker);
}
return true;
} catch (IOException e) {
e.printStackTrace(System.err);
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
return false;
}
private void createFileAssociations(File installDir) {
if (isWindowsOS()) {
createWindowsFileAssociations(installDir);
}
}
private void createWindowsFileAssociations(File installDir) {
int rc = JOptionPane.showConfirmDialog(null,
"Would you like to create file associations for Robocode in\n"
+ "the Windows Registry for the file extensions .battle and .br?\n"
+ "Please notice that you might need to grant permission to add\n"
+ "the file associations in the Registry, and you must be an\n"
+ "administrator or granted permission to change the registry.",
"Create File Associations",
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE);
if (rc != JOptionPane.YES_OPTION) {
return;
}
File file = null;
PrintStream out = null;
try {
file = new File(installDir + "\\FileAssoc.reg");
out = new PrintStream(new FileOutputStream(file));
String installPath = installDir.getAbsolutePath();
out.print(
createWindowsRegistryFileAssociation(installPath, ".battle", "Robocode.BattleSpecification",
"Robocode Battle Specification", "-battle"));
out.print(
createWindowsRegistryFileAssociation(installPath, ".br", "Robocode.BattleRecord", "Robocode Battle Record",
"-replay"));
out.close();
out = null;
Process p = Runtime.getRuntime().exec(getWindowsCmd() + file.getAbsolutePath(), null, null);
int rv;
try {
rv = p.waitFor();
if (rv != 0) {
System.err.println("Could not create association(s)");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
out.close();
}
if (file != null) {
if (!file.delete()) {
System.err.println("Could not delete the file: " + file);
}
}
}
}
private static String createWindowsRegistryFileAssociation(String installDir, String fileExt, String progId, String description, String robocodeCmdParam) {
StringBuffer sb = new StringBuffer();
final String HKCR = "[HKEY_CLASSES_ROOT\\";
sb.append("REGEDIT4\n");
sb.append(HKCR).append(fileExt).append("]\n");
sb.append("@=\"").append(progId).append("\"\n");
sb.append(HKCR).append(progId).append("]\n");
sb.append("@=\"").append(description).append("\"\n");
sb.append(HKCR).append(progId).append("\\shell]\n");
sb.append(HKCR).append(progId).append("\\shell\\open]\n");
sb.append(HKCR).append(progId).append("\\shell\\open\\command]\n");
sb.append("@=\"").append(getWindowsCmd()).append("\\\"cd \\\"").append(installDir.replaceAll("[\\\\]", "\\\\\\\\")).append("\\\" && robocode.bat ").append(robocodeCmdParam).append(
" \\\"%1\\\"\\\"\"\n");
return sb.toString();
}
private static String escaped(String s) {
StringBuffer eascaped = new StringBuffer();
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '\\') {
eascaped.append('\\');
}
eascaped.append(s.charAt(i));
}
return eascaped.toString();
}
private static boolean isWindowsOS() {
return osName.startsWith("Windows ");
}
private static boolean isMacOSX() {
return osName.startsWith("Mac OS X");
}
private static String getWindowsCmd() {
String os = System.getProperty("os.name");
return ((os.equals("Windows 95") || os.equals("Windows 95") || os.equals("Windows ME"))
? "command.com"
: "cmd.exe")
+ " /C ";
}
/**
* Deletes a file and afterwards deletes the parent directories that are empty.
*
* @param file the file or directory to delete
* @return true if success
*/
private static boolean deleteFileAndParentDirsIfEmpty(final File file) {
boolean wasDeleted = false;
if (file != null && file.exists()) {
if (file.isDirectory()) {
wasDeleted = deleteDir(file);
} else {
wasDeleted = file.delete();
File parent = file;
while (wasDeleted && (parent = parent.getParentFile()) != null) {
// Delete parent directory, but only if it is empty
File[] files = parent.listFiles();
if (files != null && files.length == 0) {
wasDeleted = deleteDir(parent);
} else {
wasDeleted = false;
}
}
}
}
return wasDeleted;
}
}