/* * Copyright (c) 2002-2008 LWJGL Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'LWJGL' nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.lwjgl.util.applet; import java.applet.Applet; import java.applet.AppletStub; import java.awt.BorderLayout; import java.awt.Color; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Image; import java.awt.MediaTracker; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FilePermission; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.SocketPermission; import java.net.URL; import java.net.URLClassLoader; import java.net.URLConnection; import java.security.AccessControlException; import java.security.AccessController; import java.security.CodeSource; import java.security.PermissionCollection; import java.security.PrivilegedExceptionAction; import java.security.SecureClassLoader; import java.security.cert.Certificate; import java.util.Enumeration; import java.util.StringTokenizer; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; import java.util.jar.Pack200; import sun.security.util.SecurityConstants; /** *

* The AppletLoader enables deployment of LWJGL to applets in an easy * and polished way. The loader will display a configurable logo and progressbar * while the relevant jars (generic and native) are downloaded from a specified source. *

*

* The downloaded jars are extracted to the users temporary directory - and if enabled, cached for * faster loading in future uses. *

*

* The following applet parameters are required: *

*

*

* Additionally the following parameters can be supplied to tweak the behaviour of the AppletLoader. *

*

* @author kappaOne * @author Brian Matzon * @version $Revision$ * $Id$ */ public class AppletLoader extends Applet implements Runnable, AppletStub { /** initializing */ public static final int STATE_INIT = 1; /** determining which packages that are required */ public static final int STATE_DETERMINING_PACKAGES = 2; /** checking for already downloaded files */ public static final int STATE_CHECKING_CACHE = 3; /** downloading packages */ public static final int STATE_DOWNLOADING = 4; /** extracting packages */ public static final int STATE_EXTRACTING_PACKAGES = 5; /** updating the classpath */ public static final int STATE_UPDATING_CLASSPATH = 6; /** switching to real applet */ public static final int STATE_SWITCHING_APPLET = 7; /** initializing real applet */ public static final int STATE_INITIALIZE_REAL_APPLET = 8; /** stating real applet */ public static final int STATE_START_REAL_APPLET = 9; /** done */ public static final int STATE_DONE = 10; /** used to calculate length of progress bar */ protected int percentage; /** current size of download in bytes */ protected int currentSizeDownload; /** total size of download in bytes */ protected int totalSizeDownload; /** current size of extracted in bytes */ protected int currentSizeExtract; /** total size of extracted in bytes */ protected int totalSizeExtract; /** logo to be shown while loading */ protected Image logo; /** progressbar to render while loading */ protected Image progressbar; /** offscreen image used */ protected Image offscreen; /** background color of applet */ protected Color bgColor = Color.white; /** Color to write errors in */ protected Color errorColor = Color.red; /** color to write foreground in */ protected Color fgColor = Color.black; /** urls of the jars to download */ protected URL[] urlList; /** classLoader used to add downloaded jars to the classpath */ protected ClassLoader classLoader; /** actual thread that does the loading */ protected Thread loaderThread; /** animation thread that renders our load screen while loading */ protected Thread animationThread; /** applet to load after all downloads are complete */ protected Applet lwjglApplet; /** whether a fatal error occured */ protected boolean fatalError; /** fatal error that occured */ protected String fatalErrorDescription; /** whether we're running in debug mode */ protected boolean debugMode; /** whether to prepend host to cache path */ protected boolean prependHost; /** String to display as a subtask */ protected String subtaskMessage = ""; /** state of applet loader */ protected int state = STATE_INIT; /** whether lzma is supported */ protected boolean lzmaSupported = false; /** whether pack200 is supported */ protected boolean pack200Supported = false; /** generic error message to display on error */ protected String[] genericErrorMessage = { "An error occured while loading the applet.", "Please contact support to resolve this issue.", ""}; /** whether a certificate refused error occured */ protected boolean certificateRefused; /** error message to display if user refuses to accept certicate*/ protected String[] certificateRefusedMessage = { "Permissions for Applet Refused.", "Please accept the permissions dialog to allow", "the applet to continue the loading process."}; /* * @see java.applet.Applet#init() */ public void init() { state = STATE_INIT; // sanity check String[] requiredArgs = {"al_main", "al_logo", "al_progressbar", "al_jars"}; for(int i=0; i 0) { messageX = (getWidth() - fm.stringWidth(subtaskMessage)) / 2; og.drawString(subtaskMessage, messageX, messageY+20); } // draw loading bar, clipping it depending on percentage done int barSize = (progressbar.getWidth(this) * percentage) / 100; og.clipRect(0, 0, x + barSize, getHeight()); og.drawImage(progressbar, x, y, null); } og.dispose(); // finally draw it all g.drawImage(offscreen, 0, 0, null); } /** * @return string describing the state of the loader */ protected String getDescriptionForState() { switch (state) { case STATE_INIT: return "Initializing loader"; case STATE_DETERMINING_PACKAGES: return "Determining packages to load"; case STATE_CHECKING_CACHE: return "Checking cache for existing files"; case STATE_DOWNLOADING: return "Downloading packages"; case STATE_EXTRACTING_PACKAGES: return "Extracting downloaded packages"; case STATE_UPDATING_CLASSPATH: return "Updating classpath"; case STATE_SWITCHING_APPLET: return "Switching applet"; case STATE_INITIALIZE_REAL_APPLET: return "Initializing real applet"; case STATE_START_REAL_APPLET: return "Starting real applet"; case STATE_DONE: return "Done loading"; default: return "unknown state"; } } /** * Trims the passed file string based on the available capabilities * @param file string of files to be trimmed * @return trimmed string based on capabilities of client */ protected String trimExtensionByCapabilities(String file) { if (!pack200Supported) { file = file.replaceAll(".pack", ""); } if (!lzmaSupported) { file = file.replaceAll(".lzma", ""); } return file; } /** * Reads list of jars to download and adds the urls to urlList * also finds out which OS you are on and adds appropriate native * jar to the urlList */ protected void loadJarURLs() throws Exception { state = STATE_DETERMINING_PACKAGES; // jars to load String jarList = getParameter("al_jars"); jarList = trimExtensionByCapabilities(jarList); StringTokenizer jar = new StringTokenizer(jarList, ", "); int jarCount = jar.countTokens() + 1; urlList = new URL[jarCount]; URL path = getCodeBase(); // set jars urls for (int i = 0; i < jarCount - 1; i++) { urlList[i] = new URL(path, jar.nextToken()); } // native jar url String osName = System.getProperty("os.name"); String nativeJar = null; if (osName.startsWith("Win")) { nativeJar = getParameter("al_windows"); } else if (osName.startsWith("Linux") || osName.startsWith("FreeBSD")) { nativeJar = getParameter("al_linux"); } else if (osName.startsWith("Mac")) { nativeJar = getParameter("al_mac"); } else if (osName.startsWith("Solaris") || osName.startsWith("SunOS")) { nativeJar = getParameter("al_solaris"); } else { fatalErrorOccured("OS (" + osName + ") not supported", null); } if (nativeJar == null) { fatalErrorOccured("no lwjgl natives files found", null); } else { nativeJar = trimExtensionByCapabilities(nativeJar); urlList[jarCount - 1] = new URL(path, nativeJar); } } /** * 4 steps * * 1) check version of applet and decide whether to download jars * 2) download the jars * 3) extract natives * 4) add to jars to class path * 5) switch applets */ public void run() { state = STATE_CHECKING_CACHE; percentage = 5; try { debug_sleep(2000); // parse the urls for the jars into the url list loadJarURLs(); // get path where applet will be stored String path = (String) AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws Exception { // we append the code base to avoid naming collisions with al_title String codebase = ""; if(prependHost) { codebase = getCodeBase().getHost(); if(codebase == null || codebase.length() == 0) { codebase = "localhost"; } codebase += File.separator; } return System.getProperty("java.io.tmpdir") + File.separator + codebase + getParameter("al_title") + File.separator; } }); File dir = new File(path); // create directory if (!dir.exists()) { dir.mkdirs(); } dir = new File(dir, "version"); // if applet already available don't download anything boolean cacheAvailable = false; // version of applet String version = getParameter("al_version"); float latestVersion = 0; // if applet version specifed, check if you have latest version of applet if (version != null) { latestVersion = Float.parseFloat(version); // if version file exists if (dir.exists()) { // compare to new version if (latestVersion <= readVersionFile(dir)) { cacheAvailable = true; percentage = 90; if(debugMode) { System.out.println("Loading Cached Applet Version " + latestVersion); } debug_sleep(2000); } } } // if jars not available or need updating download them if (!cacheAvailable) { // downloads jars from the server downloadJars(path); // 10-55% // Extract Pack and LZMA files extractJars(path); // 55-65% // Extracts Native Files extractNatives(path); // 65-85% // add version information once jars downloaded successfully if (version != null) { percentage = 90; writeVersionFile(dir, latestVersion); } } // add the downloaded jars and natives to classpath updateClassPath(path); // switch to LWJGL Applet switchApplet(); state = STATE_DONE; } catch (AccessControlException ace) { fatalErrorOccured(ace.getMessage(), ace); certificateRefused = true; } catch (Exception e) { fatalErrorOccured(e.getMessage(), e); } finally { loaderThread = null; } } /** * read the current version file * * @param file the file to read * @return the version value of saved file * @throws Exception if it fails to read value */ protected float readVersionFile(File file) throws Exception { DataInputStream dis = new DataInputStream(new FileInputStream(file)); float version = dis.readFloat(); dis.close(); return version; } /** * write out version file of applet * * @param file the file to write out to * @param version the version of the applet as a float * @throws Exception if it fails to write file */ protected void writeVersionFile(File file, float version) throws Exception { DataOutputStream dos = new DataOutputStream(new FileOutputStream(file)); dos.writeFloat(version); dos.close(); } /** * Edits the ClassPath at runtime to include the jars * that have just been downloaded and then adds the * lwjgl natives folder property. * * @param path location where applet is stored * @throws Exception if it fails to add classpath */ protected void updateClassPath(String path) throws Exception { state = STATE_UPDATING_CLASSPATH; percentage = 95; URL[] urls = new URL[urlList.length]; for (int i = 0; i < urlList.length; i++) { urls[i] = new URL("file:" + path + getJarName(urlList[i])); } // add downloaded jars to the classpath with required permissions classLoader = new URLClassLoader(urls) { protected PermissionCollection getPermissions (CodeSource codesource) { PermissionCollection perms = null; try { // getPermissions from original classloader is important as it checks for signed jars and shows any security dialogs needed Method method = SecureClassLoader.class.getDeclaredMethod("getPermissions", new Class[] { CodeSource.class }); method.setAccessible(true); perms = (PermissionCollection)method.invoke(getClass().getClassLoader(), new Object[] {codesource}); String host = getCodeBase().getHost(); if (host != null && (host.length() > 0)) { // add permission for downloaded jars to access host they were from perms.add(new SocketPermission(host, SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION)); } else if (codesource.getLocation().getProtocol().equals("file")) { // if running locally add file permission String path = codesource.getLocation().getFile().replace('/', File.separatorChar); perms.add(new FilePermission(path, SecurityConstants.FILE_READ_ACTION)); } } catch (Exception e) { e.printStackTrace(); } return perms; } }; debug_sleep(2000); // add natives files path to native class path System.setProperty("org.lwjgl.librarypath", path + "natives"); // Make sure jinput knows about the new path too System.setProperty("net.java.games.input.librarypath", path + "natives"); } /** * replace the current applet with the lwjgl applet * using AppletStub and initialise and start it */ protected void switchApplet() throws Exception { state = STATE_SWITCHING_APPLET; percentage = 100; debug_sleep(2000); Class appletClass = classLoader.loadClass(getParameter("al_main")); lwjglApplet = (Applet) appletClass.newInstance(); lwjglApplet.setStub(this); lwjglApplet.setSize(getWidth(), getHeight()); setLayout(new BorderLayout()); add(lwjglApplet); validate(); state = STATE_INITIALIZE_REAL_APPLET; lwjglApplet.init(); state = STATE_START_REAL_APPLET; lwjglApplet.start(); } /** * Will download the jars from the server using the list of urls * in urlList, while at the same time updating progress bar * * @param path location of the directory to save to * @throws Exception if download fails */ protected void downloadJars(String path) throws Exception { state = STATE_DOWNLOADING; URLConnection urlconnection; // calculate total size of jars to download for (int i = 0; i < urlList.length; i++) { urlconnection = urlList[i].openConnection(); urlconnection.setDefaultUseCaches(false); totalSizeDownload += urlconnection.getContentLength(); } int initialPercentage = percentage = 10; // download each jar byte buffer[] = new byte[65536]; for (int i = 0; i < urlList.length; i++) { debug_sleep(2000); urlconnection = urlList[i].openConnection(); String currentFile = getFileName(urlList[i]); InputStream inputstream = getJarInputStream(currentFile, urlconnection); FileOutputStream fos = new FileOutputStream(path + currentFile); int bufferSize; long downloadStartTime = System.currentTimeMillis(); int downloadedAmount = 0; String downloadSpeedMessage = ""; while ((bufferSize = inputstream.read(buffer, 0, buffer.length)) != -1) { debug_sleep(10); fos.write(buffer, 0, bufferSize); currentSizeDownload += bufferSize; percentage = initialPercentage + ((currentSizeDownload * 45) / totalSizeDownload); subtaskMessage = "Retrieving: " + currentFile + " " + ((currentSizeDownload * 100) / totalSizeDownload) + "%"; downloadedAmount += bufferSize; long timeLapse = System.currentTimeMillis() - downloadStartTime; // update only if a second or more has passed if (timeLapse >= 1000) { // get kb/s, nice that bytes/millis is same as kilobytes/seconds float downloadSpeed = (float) downloadedAmount / timeLapse; // round to two decimal places downloadSpeed = ((int)(downloadSpeed*100))/100f; // set current speed message downloadSpeedMessage = " @ " + downloadSpeed + " KB/sec"; // reset downloaded amount downloadedAmount = 0; // reset start time downloadStartTime = System.currentTimeMillis(); } subtaskMessage += downloadSpeedMessage; } inputstream.close(); fos.close(); } subtaskMessage = ""; } /** * Retrieves a jar files input stream. This method exists primarily to fix an Opera hang in getInputStream * @param urlconnection connection to get input stream from * @return InputStream or null if not possible */ protected InputStream getJarInputStream(final String currentFile, final URLConnection urlconnection) throws Exception { final InputStream[] is = new InputStream[1]; // try to get the input stream 3 times. // Wait at most 5 seconds before interrupting the thread for (int j = 0; j < 3 && is[0] == null; j++) { Thread t = new Thread() { public void run() { try { is[0] = urlconnection.getInputStream(); } catch (IOException e) { /* ignored */ } } }; t.setName("JarInputStreamThread"); t.start(); int iterationCount = 0; while(is[0] == null && iterationCount++ < 5) { try { t.join(1000); } catch (InterruptedException inte) { /* ignored */ } } if(is[0] == null) { try { t.interrupt(); t.join(); } catch (InterruptedException inte) { /* ignored */ } } } if(is[0] == null) { throw new Exception("Unable to get input stream for " + currentFile); } return is[0]; } /** * Extract LZMA File * @param in Input path to pack file * @param out output path to resulting file * @throws exception if any errors occur */ protected void extractLZMA(String in, String out) throws Exception { File f = new File(in); FileInputStream fileInputHandle = new FileInputStream(f); // use reflection to avoid hard dependency Class clazz = Class.forName( "LZMA.LzmaInputStream" ); Constructor constructor = clazz.getDeclaredConstructor( new Class[] {InputStream.class} ); InputStream inputHandle = (InputStream) constructor.newInstance( new Object[] {fileInputHandle} ); OutputStream outputHandle; outputHandle = new FileOutputStream(out); byte [] buffer = new byte [1<<14]; int ret = inputHandle.read(buffer); while (ret >= 1) { outputHandle.write(buffer,0,ret); ret = inputHandle.read(buffer); } inputHandle.close(); outputHandle.close(); outputHandle = null; inputHandle = null; // delete LZMA file, as it is no longer needed f.delete(); } /** * Extract Pack File * @param in Input path to pack file * @param out output path to resulting file * @throws exception if any errors occur */ protected void extractPack(String in, String out) throws Exception { File f = new File(in); FileOutputStream fostream = new FileOutputStream(out); JarOutputStream jostream = new JarOutputStream(fostream); Pack200.Unpacker unpacker = Pack200.newUnpacker(); unpacker.unpack(f, jostream); jostream.close(); // delete pack file as its no longer needed f.delete(); } /** * Extract all jars from any lzma/pack files * * @param path output path * @throws exception if any errors occur */ protected void extractJars(String path) throws Exception { state = STATE_EXTRACTING_PACKAGES; float increment = (float) 10.0 / urlList.length; // extract all lzma and pack.lzma files for (int i = 0; i < urlList.length; i++) { percentage = 55 + (int) (increment * (i+1)); String filename = getFileName(urlList[i]); if (filename.endsWith(".pack.lzma")) { subtaskMessage = "Extracting: " + filename + " to " + filename.replaceAll(".lzma", ""); debug_sleep(1000); extractLZMA(path + filename, path + filename.replaceAll(".lzma", "")); subtaskMessage = "Extracting: " + filename.replaceAll(".lzma", "") + " to " + filename.replaceAll(".pack.lzma", ""); debug_sleep(1000); extractPack(path + filename.replaceAll(".lzma", ""), path + filename.replaceAll(".pack.lzma", "")); } else if (filename.endsWith(".pack")) { subtaskMessage = "Extracting: " + filename + " to " + filename.replace(".pack", ""); debug_sleep(1000); extractPack(path + filename, path + filename.replace(".pack", "")); } else if (filename.endsWith(".lzma")) { subtaskMessage = "Extracting: " + filename + " to " + filename.replace(".lzma", ""); debug_sleep(1000); extractLZMA(path + filename, path + filename.replace(".lzma", "")); } } } /** * This method will extract all file from the native jar and extract them * to the subdirectory called "natives" in the local path, will also check * to see if the native jar files is signed properly * * @param path base folder containing all downloaded jars * @throws Exception if it fails to extract files */ protected void extractNatives(String path) throws Exception { state = STATE_EXTRACTING_PACKAGES; int initialPercentage = percentage; // get name of jar file with natives from urlList, it will be the last url String nativeJar = getJarName(urlList[urlList.length - 1]); // get the current certificate to compare against native files Certificate[] certificate = AppletLoader.class.getProtectionDomain().getCodeSource().getCertificates(); // create native folder File nativeFolder = new File(path + "natives"); if (!nativeFolder.exists()) { nativeFolder.mkdir(); } // open jar file JarFile jarFile = new JarFile(path + nativeJar, true); // get list of files in jar Enumeration entities = jarFile.entries(); totalSizeExtract = 0; // calculate the size of the files to extract for progress bar while (entities.hasMoreElements()) { JarEntry entry = (JarEntry) entities.nextElement(); // skip directories and anything in directories // conveniently ignores the manifest if (entry.isDirectory() || entry.getName().indexOf('/') != -1) { continue; } totalSizeExtract += entry.getSize(); } currentSizeExtract = 0; // reset point to begining by getting list of file again entities = jarFile.entries(); // extract all files from the jar while (entities.hasMoreElements()) { JarEntry entry = (JarEntry) entities.nextElement(); // skip directories and anything in directories // conveniently ignores the manifest if (entry.isDirectory() || entry.getName().indexOf('/') != -1) { continue; } // check if native file already exists if so delete it to make room for new one // useful when using the reload button on the browser File f = new File(path + "natives" + File.separator + entry.getName()); if (f.exists()) { if (!f.delete()) { continue; // unable to delete file, it is in use, skip extracting it } } debug_sleep(1000); InputStream in = jarFile.getInputStream(jarFile.getEntry(entry.getName())); OutputStream out = new FileOutputStream(path + "natives" + File.separator + entry.getName()); int bufferSize; byte buffer[] = new byte[65536]; while ((bufferSize = in.read(buffer, 0, buffer.length)) != -1) { debug_sleep(10); out.write(buffer, 0, bufferSize); currentSizeExtract += bufferSize; // update progress bar percentage = initialPercentage + ((currentSizeExtract * 20) / totalSizeExtract); subtaskMessage = "Extracting: " + entry.getName() + " " + ((currentSizeExtract * 100) / totalSizeExtract) + "%"; } // validate if the certificate for native file is correct validateCertificateChain(certificate, entry.getCertificates()); in.close(); out.close(); } subtaskMessage = ""; jarFile.close(); // delete native jar as it is no longer needed File f = new File(path + nativeJar); f.delete(); } /** * Validates the certificate chain for a single file * * @param ownCerts Chain of certificates to check against * @param native_certs Chain of certificates to check */ protected static void validateCertificateChain(Certificate[] ownCerts, Certificate[] native_certs) throws Exception { if (native_certs == null) throw new Exception("Unable to validate certificate chain. Native entry did not have a certificate chain at all"); if (ownCerts.length != native_certs.length) throw new Exception("Unable to validate certificate chain. Chain differs in length [" + ownCerts.length + " vs " + native_certs.length + "]"); for (int i = 0; i < ownCerts.length; i++) { if (!ownCerts[i].equals(native_certs[i])) { throw new Exception("Certificate mismatch: " + ownCerts[i] + " != " + native_certs[i]); } } } /** * Get Image from path provided * * @param s location of the image * @return the Image file */ protected Image getImage(String s) { try { URL url = AppletLoader.class.getResource("/"+s); // if image not found in jar, look outside it if (url == null) { url = new URL(getCodeBase(), s); } Image image = super.getImage(url); // wait for image to load MediaTracker tracker = new MediaTracker(this); tracker.addImage(image, 0); tracker.waitForAll(); return image; } catch (Exception e) { /* */ } return null; } /** * Get jar name from URL. * * @param url Get jar file name from this url * @return file name as string */ protected String getJarName(URL url) { String fileName = url.getFile(); if (fileName.endsWith(".pack.lzma")) { fileName = fileName.replaceAll(".pack.lzma", ""); } else if (fileName.endsWith(".pack")) { fileName = fileName.replaceAll(".pack", ""); } else if (fileName.endsWith(".lzma")) { fileName = fileName.replaceAll(".lzma", ""); } return fileName.substring(fileName.lastIndexOf('/') + 1); } /** * Get file name portion of URL. * * @param url Get file name from this url * @return file name as string */ protected String getFileName(URL url) { String fileName = url.getFile(); return fileName.substring(fileName.lastIndexOf('/') + 1); } /** * Retrieves the color * * @param color Color to load * @param defaultColor Default color to use if no color to load * @return Color to use */ protected Color getColor(String color, Color defaultColor) { String param_color = getParameter(color); if (param_color != null) { return new Color(Integer.parseInt(param_color, 16)); } return defaultColor; } /** * Retrieves the boolean value for the applet * @param name Name of parameter * @param defaultValue default value to return if no such parameter * @return value of parameter or defaultValue */ protected boolean getBooleanParameter(String name, boolean defaultValue) { String parameter = getParameter(name); if(parameter != null) { return Boolean.parseBoolean(parameter); } return defaultValue; } /** * Sets the state of the loaded and prints some debug information * * @param error Error message to print */ protected void fatalErrorOccured(String error, Exception e) { fatalError = true; fatalErrorDescription = "Fatal error occured (" + state + "): " + error; System.out.println(fatalErrorDescription); if(e != null) { System.out.println(generateStacktrace(e)); } repaint(); } /** * Utility method for sleeping * Will only really sleep if debug has been enabled * @param ms milliseconds to sleep */ protected void debug_sleep(long ms) { if(debugMode) { sleep(ms); } } /** * Utility method for sleeping * @param ms milliseconds to sleep */ protected void sleep(long ms) { try { Thread.sleep(ms); } catch (Exception e) { /* ignored */ } } }