PageRenderTime 58ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/game_platform/lib/lwjgl-2.8.5/java/org/lwjgl/util/applet/AppletLoader.java

https://bitbucket.org/dagronlund/opengl-game-platform
Java | 2269 lines | 1206 code | 404 blank | 659 comment | 263 complexity | f40c797d0634ce2f6a6a9c19101a9b11 MD5 | raw file
  1. /*
  2. * Copyright (c) 2002-2008 LWJGL Project
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are
  7. * met:
  8. *
  9. * * Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. *
  12. * * Redistributions in binary form must reproduce the above copyright
  13. * notice, this list of conditions and the following disclaimer in the
  14. * documentation and/or other materials provided with the distribution.
  15. *
  16. * * Neither the name of 'LWJGL' nor the names of
  17. * its contributors may be used to endorse or promote products derived
  18. * from this software without specific prior written permission.
  19. *
  20. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  22. * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  23. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  24. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  25. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  26. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  27. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  28. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  29. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  30. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31. */
  32. package org.lwjgl.util.applet;
  33. import java.applet.Applet;
  34. import java.applet.AppletStub;
  35. import java.awt.BorderLayout;
  36. import java.awt.Color;
  37. import java.awt.EventQueue;
  38. import java.awt.FontMetrics;
  39. import java.awt.Graphics;
  40. import java.awt.Image;
  41. import java.awt.MediaTracker;
  42. import java.awt.image.ImageObserver;
  43. import java.io.BufferedInputStream;
  44. import java.io.File;
  45. import java.io.FileInputStream;
  46. import java.io.FileOutputStream;
  47. import java.io.FilePermission;
  48. import java.io.IOException;
  49. import java.io.InputStream;
  50. import java.io.ObjectInputStream;
  51. import java.io.ObjectOutputStream;
  52. import java.io.OutputStream;
  53. import java.io.PrintWriter;
  54. import java.io.StringWriter;
  55. import java.io.Writer;
  56. import java.lang.reflect.Constructor;
  57. import java.lang.reflect.Field;
  58. import java.lang.reflect.Method;
  59. import java.net.HttpURLConnection;
  60. import java.net.JarURLConnection;
  61. import java.net.SocketPermission;
  62. import java.net.URL;
  63. import java.net.URLClassLoader;
  64. import java.net.URLConnection;
  65. import java.security.AccessControlException;
  66. import java.security.AccessController;
  67. import java.security.AllPermission;
  68. import java.security.CodeSource;
  69. import java.security.PermissionCollection;
  70. import java.security.Permissions;
  71. import java.security.PrivilegedExceptionAction;
  72. import java.security.SecureClassLoader;
  73. import java.security.cert.Certificate;
  74. import java.util.Enumeration;
  75. import java.util.HashMap;
  76. import java.util.Iterator;
  77. import java.util.LinkedList;
  78. import java.util.Queue;
  79. import java.util.StringTokenizer;
  80. import java.util.Vector;
  81. import java.util.concurrent.ExecutorService;
  82. import java.util.concurrent.Executors;
  83. import java.util.concurrent.Future;
  84. import java.util.jar.JarEntry;
  85. import java.util.jar.JarFile;
  86. import java.util.jar.JarOutputStream;
  87. import java.util.jar.Pack200;
  88. import java.util.zip.CRC32;
  89. import java.util.zip.CheckedInputStream;
  90. import java.util.zip.GZIPInputStream;
  91. import java.util.zip.ZipEntry;
  92. import java.util.zip.ZipFile;
  93. /**
  94. * <p>
  95. * The AppletLoader enables deployment of LWJGL to applets in an easy
  96. * and polished way. The loader will display a configurable logo and progressbar
  97. * while the relevant jars (generic and native) are downloaded from a specified source.
  98. * </p>
  99. * <p>
  100. * The downloaded jars are extracted to the users temporary directory - and if enabled, cached for
  101. * faster loading in future uses.
  102. * </p>
  103. * <p>
  104. * The following applet parameters are required:
  105. * <ul>
  106. * <li>al_main - [String] Full package and class the applet to instantiate and display when loaded.</li>
  107. * <li>al_jars - [String] Comma separated list of jars to download.</li>
  108. * <p>
  109. * <li>al_windows - [String] Jar containing native files for windows.</li>
  110. * <li>al_linux - [String] Jar containing native files for linux.</li>
  111. * <li>al_mac - [String] Jar containing native files for mac.</li>
  112. * <li>al_solaris - [String] Jar containing native files for solaris.</li>
  113. * <li>al_freebsd - [String] Jar containing native files for freebsd.</li>
  114. * </ul>
  115. * </p>
  116. * <p>
  117. * Additionally the following parameters can be supplied to tweak the behaviour of the AppletLoader.
  118. * <ul>
  119. * <li>al_cache - [boolean] Whether to use cache system. <i>Default: true</i>.</li>
  120. * <li>al_version - [int or float] Version of deployment. If this is specified, the jars will be cached and
  121. * reused if the version matches. If version doesn't match all of the files are reloaded.</li>
  122. *
  123. * <li>al_debug - [boolean] Whether to enable debug mode. <i>Default: false</i>.</li>
  124. * <li>al_min_jre - [String] Specify the minimum jre version that the applet requires, should be in format like 1.6.0_24 or a subset like 1.6 <i>Default: 1.5</i>.</li>
  125. * <li>al_prepend_host - [boolean] Whether to limit caching to this domain, disable if your applet is hosted on multiple domains and needs to share the cache. <i>Default: true</i>.</li>
  126. * <li>al_lookup_threads - [int] Specify the number of concurrent threads to use to get file information before downloading. <i>Default: 1</i>.</li>
  127. * <p>
  128. * <li>al_windows64 - [String] If specified it will be used instead of al_windows on 64bit windows systems.</li>
  129. * <li>al_windows32 - [String] If specified it will be used instead of al_windows on 32bit windows systems.</li>
  130. * <li>al_linux64 - [String] If specified it will be used instead of al_linux on 64bit linux systems.</li>
  131. * <li>al_linux32 - [String] If specified it will be used instead of al_linux on 32bit linux systems.</li>
  132. * <li>al_mac32 - [String] If specified it will be used instead of al_mac on 64bit mac systems.</li>
  133. * <li>al_mac64 - [String] If specified it will be used instead of al_mac on 32bit mac systems.</li>
  134. * <li>al_macppc - [String] If specified it will be used instead of al_mac on PPC mac systems.</li>
  135. * <p>
  136. * <li>boxbgcolor - [String] any String AWT color ("red", "blue", etc), RGB (0-255) or hex formated color (#RRGGBB) to use as background. <i>Default: #ffffff</i>.</li>
  137. * <li>boxfgcolor - [String] any String AWT color ("red", "blue", etc), RGB (0-255) or hex formated color (#RRGGBB) to use as foreground. <i>Default: #000000</i>.</li>
  138. * <p>
  139. * <li>al_logo - [String Path of of the logo resource to paint while loading.<i>Default: "appletlogo.gif"</i>.</li>
  140. * <li>al_progressbar - [String] Path of the progressbar resource to paint on top of the logo, width clipped by percentage.<i>Default: "appletprogress.gif"</i>.</li>
  141. * <p>
  142. * <li>lwjgl_arguments - </li> [String] used to pass LWJGL parameters to LWJGL e.g. ("-Dorg.lwjgl.input.Mouse.allowNegativeMouseCoords=true -Dorg.lwjgl.util.Debug=true").</li>
  143. * </ul>
  144. * </p>
  145. * @author kappaOne <one.kappa@gmail.com>
  146. * @author Brian Matzon <brian@matzon.dk>
  147. * @version $Revision$
  148. * $Id$
  149. *
  150. * Contributors:
  151. * <ul>
  152. * <li>Arielsan</li>
  153. * <li>Bobjob</li>
  154. * <li>Dashiva</li>
  155. * <li>Dr_evil</li>
  156. * <li>Elias Naur</li>
  157. * <li>Kevin Glass</li>
  158. * <li>Matthias Mann</li>
  159. * <li>Mickelukas</li>
  160. * <li>NateS</li>
  161. * <li>Pelle Johnsen</li>
  162. * <li>Riven</li>
  163. * <li>Ruben01</li>
  164. * <li>Shannon Smith</li>
  165. * </ul>
  166. *
  167. */
  168. public class AppletLoader extends Applet implements Runnable, AppletStub {
  169. /** initializing */
  170. public static final int STATE_INIT = 1;
  171. /** checking version of jre */
  172. public static final int STATE_CHECK_JRE_VERSION = 2;
  173. /** determining which packages that are required */
  174. public static final int STATE_DETERMINING_PACKAGES = 3;
  175. /** checking for already downloaded files */
  176. public static final int STATE_CHECKING_CACHE = 4;
  177. /** checking if any updates are available for cache files */
  178. public static final int STATE_CHECKING_FOR_UPDATES = 5;
  179. /** downloading packages */
  180. public static final int STATE_DOWNLOADING = 6;
  181. /** extracting packages */
  182. public static final int STATE_EXTRACTING_PACKAGES = 7;
  183. /** validating packages */
  184. public static final int STATE_VALIDATING_PACKAGES = 8;
  185. /** updating the classpath */
  186. public static final int STATE_UPDATING_CLASSPATH = 9;
  187. /** switching to real applet */
  188. public static final int STATE_SWITCHING_APPLET = 10;
  189. /** initializing real applet */
  190. public static final int STATE_INITIALIZE_REAL_APPLET = 11;
  191. /** stating real applet */
  192. public static final int STATE_START_REAL_APPLET = 12;
  193. /** done */
  194. public static final int STATE_DONE = 13;
  195. /** used to calculate length of progress bar */
  196. protected volatile int percentage;
  197. /** total size of download in bytes */
  198. protected int totalDownloadSize;
  199. /** current size of extracted in bytes */
  200. protected int currentSizeExtract;
  201. /** total size of extracted in bytes */
  202. protected int totalSizeExtract;
  203. /** logo to be shown while loading */
  204. protected Image logo, logoBuffer;
  205. /** progressbar to render while loading */
  206. protected Image progressbar, progressbarBuffer;
  207. /** offscreen image used */
  208. protected Image offscreen;
  209. /** set to true while painting is done */
  210. protected boolean painting;
  211. /** background color of applet */
  212. protected Color bgColor = Color.white;
  213. /** color to write foreground in */
  214. protected Color fgColor = Color.black;
  215. /** urls of the jars to download */
  216. protected URL[] urlList;
  217. /** classLoader used to add downloaded jars to the classpath */
  218. protected ClassLoader classLoader;
  219. /** actual thread that does the loading */
  220. protected Thread loaderThread;
  221. /** animation thread that renders our load screen while loading */
  222. protected Thread animationThread;
  223. /** applet to load after all downloads are complete */
  224. protected Applet lwjglApplet;
  225. /** whether we're running in debug mode */
  226. protected boolean debugMode;
  227. /** whether to prepend host to cache path */
  228. protected boolean prependHost;
  229. /** Used to store file names with lastModified time */
  230. protected HashMap<String, Long> filesLastModified;
  231. /** Sizes of files to download */
  232. protected int[] fileSizes;
  233. /** Number of native jars */
  234. protected int nativeJarCount;
  235. /** whether to use caching system, only download files that have changed */
  236. protected boolean cacheEnabled;
  237. /** String to display as a subtask */
  238. protected String subtaskMessage = "";
  239. /** state of applet loader */
  240. protected volatile int state = STATE_INIT;
  241. /** whether lzma is supported */
  242. protected boolean lzmaSupported;
  243. /** whether pack200 is supported */
  244. protected boolean pack200Supported;
  245. /** whether to run in headless mode */
  246. protected boolean headless = false;
  247. /** whether to switch applets in headless mode or wait longer */
  248. protected boolean headlessWaiting = true;
  249. /** messages to be passed via liveconnect in headless mode */
  250. protected String[] headlessMessage;
  251. /** threads to use when fetching information of files to be downloaded */
  252. protected int concurrentLookupThreads;
  253. /** whether a fatal error occurred */
  254. protected boolean fatalError;
  255. /** whether a certificate refused error occurred */
  256. protected boolean certificateRefused;
  257. /** whether the minimum required JRE version is not found */
  258. protected boolean minimumJreNotFound;
  259. /** generic error message to display on error */
  260. protected String[] genericErrorMessage = { "An error occured while loading the applet.",
  261. "Please contact support to resolve this issue.",
  262. "<placeholder for error message>"};
  263. /** error message to display if user refuses to accept certificate*/
  264. protected String[] certificateRefusedMessage = { "Permissions for Applet Refused.",
  265. "Please accept the permissions dialog to allow",
  266. "the applet to continue the loading process."};
  267. /** error message to display if minimum JRE version is not met */
  268. protected String[] minimumJREMessage = { "Your version of Java is out of date.",
  269. "Visit java.com to get the latest version.",
  270. "Java <al_min_jre> or greater is required."};
  271. /** fatal error message to display */
  272. protected String[] errorMessage;
  273. /** have natives been loaded by another instance of this applet */
  274. protected static boolean natives_loaded;
  275. /*
  276. * @see java.applet.Applet#init()
  277. */
  278. public void init() {
  279. setState(STATE_INIT);
  280. // sanity check
  281. String[] requiredArgs = {"al_main", "al_jars"};
  282. for ( String requiredArg : requiredArgs ) {
  283. if ( getParameter(requiredArg) == null ) {
  284. fatalErrorOccured("missing required applet parameter: " + requiredArg, null);
  285. return;
  286. }
  287. }
  288. // whether to use cache system
  289. cacheEnabled = getBooleanParameter("al_cache", true);
  290. // whether to run in debug mode
  291. debugMode = getBooleanParameter("al_debug", false);
  292. // whether to prepend host to cache path
  293. prependHost = getBooleanParameter("al_prepend_host", true);
  294. // whether to run in headless mode
  295. headless = getBooleanParameter("al_headless", false);
  296. // obtain the number of concurrent lookup threads to use
  297. concurrentLookupThreads = getIntParameter("al_lookup_threads", 1); // defaults to 1
  298. // get colors of applet
  299. bgColor = getColor("boxbgcolor", Color.white);
  300. setBackground(bgColor);
  301. fgColor = getColor("boxfgcolor", Color.black);
  302. if (!headless) {
  303. // load logos
  304. logo = getImage(getStringParameter("al_logo", "appletlogo.gif"));
  305. progressbar = getImage(getStringParameter("al_progressbar", "appletprogress.gif"));
  306. }
  307. // check for lzma support
  308. try {
  309. Class.forName("LZMA.LzmaInputStream");
  310. lzmaSupported = true;
  311. } catch (Throwable e) {
  312. /* no lzma support */
  313. }
  314. // check pack200 support
  315. try {
  316. java.util.jar.Pack200.class.getSimpleName();
  317. pack200Supported = true;
  318. } catch (Throwable e) {
  319. /* no pack200 support */
  320. }
  321. }
  322. /**
  323. * Generates a stacktrace in the form of a string
  324. * @param exception Exception to make stacktrace of
  325. * @return Stacktrace of exception in the form of a string
  326. */
  327. private static String generateStacktrace(Exception exception) {
  328. Writer result = new StringWriter();
  329. PrintWriter printWriter = new PrintWriter(result);
  330. exception.printStackTrace(printWriter);
  331. return result.toString();
  332. }
  333. /*
  334. * @see java.applet.Applet#start()
  335. */
  336. public void start() {
  337. if (lwjglApplet != null) {
  338. lwjglApplet.start();
  339. }
  340. else {
  341. if(loaderThread == null && !fatalError) {
  342. loaderThread = new Thread(this);
  343. loaderThread.setName("AppletLoader.loaderThread");
  344. loaderThread.start();
  345. if (!headless) {
  346. animationThread = new Thread() {
  347. public void run() {
  348. while(loaderThread != null) {
  349. repaint();
  350. AppletLoader.this.sleep(100);
  351. }
  352. animationThread = null;
  353. }
  354. };
  355. animationThread.setName("AppletLoader.animationthread");
  356. animationThread.start();
  357. }
  358. }
  359. }
  360. }
  361. /*
  362. * @see java.applet.Applet#stop()
  363. */
  364. public void stop() {
  365. if (lwjglApplet != null) {
  366. lwjglApplet.stop();
  367. }
  368. }
  369. /*
  370. * @see java.applet.Applet#destroy()
  371. */
  372. public void destroy() {
  373. if (lwjglApplet != null) {
  374. lwjglApplet.destroy();
  375. }
  376. }
  377. /**
  378. * Clean up resources
  379. */
  380. protected void cleanUp() {
  381. progressbar = null;
  382. logo = null;
  383. logoBuffer = null;
  384. progressbarBuffer = null;
  385. offscreen = null;
  386. }
  387. /**
  388. * Retrieves the applet that has been loaded. Useful for liveconnect.
  389. */
  390. public Applet getApplet() {
  391. return lwjglApplet;
  392. }
  393. /**
  394. * Retrieves the current status of the AppletLoader and is
  395. * used by liveconnect when running in headless mode.
  396. *
  397. * This method will return the current progress of the AppletLoader
  398. * as a value from 0-100. In the case of a fatal error it will
  399. * return -1. If the certificate is refused it will return -2.
  400. * If the minimum jre requirement is not met will return -3.
  401. *
  402. * When method returns 100 the AppletLoader will sleep until the
  403. * method is called again. When called again it will switch to the
  404. * LWJGL Applet. This is a useful trigger to start the LWJGL applet
  405. * when needed.
  406. */
  407. public int getStatus() {
  408. if (fatalError) {
  409. headlessMessage = errorMessage;
  410. if (certificateRefused) return -2;
  411. if (minimumJreNotFound) return -3;
  412. return -1;
  413. }
  414. if (percentage == 100 && headlessWaiting) {
  415. headlessWaiting = false;
  416. }
  417. if (percentage == 95) {
  418. percentage = 100; // ready to switch applet
  419. }
  420. String[] message = {getDescriptionForState(), subtaskMessage};
  421. headlessMessage = message;
  422. return percentage;
  423. }
  424. /**
  425. * Retrieves the current message for the current status.
  426. * Used by liveconnect when running in headless mode.
  427. */
  428. public String[] getMessages() {
  429. return headlessMessage;
  430. }
  431. /**
  432. * Transfers the call of AppletResize from the stub to the lwjglApplet.
  433. */
  434. public void appletResize(int width, int height) {
  435. resize(width, height);
  436. }
  437. /*
  438. * @see java.awt.Container#update(java.awt.Graphics)
  439. */
  440. public final void update(Graphics g) {
  441. paint(g);
  442. }
  443. /*
  444. * @see java.awt.Container#paint(java.awt.Graphics)
  445. */
  446. public void paint(Graphics g) {
  447. // don't paint loader if applet loaded
  448. if(state == STATE_DONE) {
  449. cleanUp(); // clean up resources
  450. return;
  451. }
  452. // no drawing in headless mode
  453. if (headless) return;
  454. // create offscreen if missing
  455. if (offscreen == null) {
  456. offscreen = createImage(getWidth(), getHeight());
  457. // create buffers for animated gifs
  458. if (logo != null) {
  459. logoBuffer = createImage(logo.getWidth(null), logo.getHeight(null));
  460. // add image observer, it will notify when next animated gif frame is ready
  461. offscreen.getGraphics().drawImage(logo, 0, 0, this);
  462. // in case image is not animated fill image buffer once
  463. imageUpdate(logo, ImageObserver.FRAMEBITS, 0, 0, 0, 0);
  464. }
  465. if (progressbar != null) {
  466. progressbarBuffer = createImage(progressbar.getWidth(null), progressbar.getHeight(null));
  467. // add image observer, it will notify when next animated gif frame is ready
  468. offscreen.getGraphics().drawImage(progressbar, 0, 0, this);
  469. // in case image is not animated fill image buffer once
  470. imageUpdate(progressbar, ImageObserver.FRAMEBITS, 0, 0, 0, 0);
  471. }
  472. }
  473. // draw everything onto an image before drawing to avoid flicker
  474. Graphics og = offscreen.getGraphics();
  475. FontMetrics fm = og.getFontMetrics();
  476. // clear background color
  477. og.setColor(bgColor);
  478. og.fillRect(0, 0, offscreen.getWidth(null), offscreen.getHeight(null));
  479. og.setColor(fgColor);
  480. // if we had a failure of some sort, notify the user
  481. if (fatalError) {
  482. for(int i=0; i<errorMessage.length; i++) {
  483. if(errorMessage[i] != null) {
  484. int messageX = (offscreen.getWidth(null) - fm.stringWidth(errorMessage[i])) / 2;
  485. int messageY = (offscreen.getHeight(null) - (fm.getHeight() * errorMessage.length)) / 2;
  486. og.drawString(errorMessage[i], messageX, messageY + i*fm.getHeight());
  487. }
  488. }
  489. } else {
  490. og.setColor(fgColor);
  491. painting = true;
  492. // get position at the middle of the offscreen buffer
  493. int x = offscreen.getWidth(null)/2;
  494. int y = offscreen.getHeight(null)/2;
  495. // draw logo
  496. if (logo != null) {
  497. og.drawImage(logoBuffer, x-logo.getWidth(null)/2, y-logo.getHeight(null)/2, this);
  498. }
  499. // draw message
  500. String message = getDescriptionForState();
  501. int messageX = (offscreen.getWidth(null) - fm.stringWidth(message)) / 2;
  502. int messageY = y + 20;
  503. if (logo != null) messageY += logo.getHeight(null)/2;
  504. else if (progressbar != null) messageY += progressbar.getHeight(null)/2;
  505. og.drawString(message, messageX, messageY);
  506. // draw subtaskmessage, if any
  507. if(subtaskMessage.length() > 0) {
  508. messageX = (offscreen.getWidth(null) - fm.stringWidth(subtaskMessage)) / 2;
  509. og.drawString(subtaskMessage, messageX, messageY+20);
  510. }
  511. // draw loading progress bar, clipping it depending on percentage done
  512. if (progressbar != null) {
  513. int barSize = (progressbar.getWidth(null) * percentage) / 100;
  514. og.clipRect(x-progressbar.getWidth(null)/2, 0, barSize, offscreen.getHeight(null));
  515. og.drawImage(progressbarBuffer, x-progressbar.getWidth(null)/2, y-progressbar.getHeight(null)/2, this);
  516. }
  517. painting = false;
  518. }
  519. og.dispose();
  520. // finally draw it all centred
  521. g.drawImage(offscreen, (getWidth() - offscreen.getWidth(null))/2, (getHeight() - offscreen.getHeight(null))/2, null);
  522. }
  523. /**
  524. * When an animated gif frame is ready to be drawn the ImageObserver
  525. * will call this method.
  526. *
  527. * The Image frame is copied into a buffer, which is then drawn.
  528. * This is done to prevent image tearing on gif animations.
  529. */
  530. public boolean imageUpdate(Image img, int flag, int x, int y, int width, int height) {
  531. // finish with this ImageObserver
  532. if (state == STATE_DONE) return false;
  533. // if image frame is ready to be drawn and is currently not being painted
  534. if (flag == ImageObserver.FRAMEBITS && !painting) {
  535. Image buffer;
  536. // select which buffer to fill
  537. if (img == logo) buffer = logoBuffer;
  538. else buffer = progressbarBuffer;
  539. Graphics g = buffer.getGraphics();
  540. // clear background on buffer
  541. g.setColor(bgColor);
  542. g.fillRect(0, 0, buffer.getWidth(null), buffer.getHeight(null));
  543. // buffer background is cleared, so draw logo under progressbar
  544. if (img == progressbar && logo != null) {
  545. g.drawImage(logoBuffer, progressbar.getWidth(null)/2-logo.getWidth(null)/2,
  546. progressbar.getHeight(null)/2-logo.getHeight(null)/2, null);
  547. }
  548. g.drawImage(img, 0, 0, this);
  549. g.dispose();
  550. repaint();
  551. }
  552. return true;
  553. }
  554. /**
  555. * @return string describing the state of the loader
  556. */
  557. protected String getDescriptionForState() {
  558. switch (state) {
  559. case STATE_INIT:
  560. return "Initializing loader";
  561. case STATE_CHECK_JRE_VERSION:
  562. return "Checking version";
  563. case STATE_DETERMINING_PACKAGES:
  564. return "Determining packages to load";
  565. case STATE_CHECKING_CACHE:
  566. return "Calculating download size";
  567. case STATE_CHECKING_FOR_UPDATES:
  568. return "Checking for updates";
  569. case STATE_DOWNLOADING:
  570. return "Downloading packages";
  571. case STATE_EXTRACTING_PACKAGES:
  572. return "Extracting downloaded packages";
  573. case STATE_VALIDATING_PACKAGES:
  574. return "Validating packages";
  575. case STATE_UPDATING_CLASSPATH:
  576. return "Updating classpath";
  577. case STATE_SWITCHING_APPLET:
  578. return "Switching applet";
  579. case STATE_INITIALIZE_REAL_APPLET:
  580. return "Initializing real applet";
  581. case STATE_START_REAL_APPLET:
  582. return "Starting real applet";
  583. case STATE_DONE:
  584. return "Done loading";
  585. default:
  586. return "unknown state";
  587. }
  588. }
  589. /**
  590. * Trims the passed file string based on the available capabilities
  591. * @param file string of files to be trimmed
  592. * @return trimmed string based on capabilities of client
  593. */
  594. protected String trimExtensionByCapabilities(String file) {
  595. if (!pack200Supported) {
  596. file = file.replace(".pack", "");
  597. }
  598. if (!lzmaSupported && file.endsWith(".lzma")) {
  599. file = file.replace(".lzma", "");
  600. System.out.println("LZMA decoder (lzma.jar) not found, trying " + file + " without lzma extension.");
  601. }
  602. return file;
  603. }
  604. /**
  605. * Reads list of jars to download and adds the urls to urlList
  606. * also finds out which OS you are on and adds appropriate native
  607. * jar to the urlList
  608. */
  609. protected void loadJarURLs() throws Exception {
  610. setState(STATE_DETERMINING_PACKAGES);
  611. // jars to load
  612. String jarList = getParameter("al_jars");
  613. String nativeJarList = null;
  614. String osName = System.getProperty("os.name");
  615. if (osName.startsWith("Win")) {
  616. // check if arch specific natives have been specified
  617. if (System.getProperty("os.arch").endsWith("64")) {
  618. nativeJarList = getParameter("al_windows64");
  619. } else {
  620. nativeJarList = getParameter("al_windows32");
  621. }
  622. if (nativeJarList == null) {
  623. nativeJarList = getParameter("al_windows");
  624. }
  625. } else if (osName.startsWith("Linux") || osName.startsWith("Unix")) {
  626. // check if arch specific natives have been specified
  627. if (System.getProperty("os.arch").endsWith("64")) {
  628. nativeJarList = getParameter("al_linux64");
  629. } else {
  630. nativeJarList = getParameter("al_linux32");
  631. }
  632. if (nativeJarList == null) {
  633. nativeJarList = getParameter("al_linux");
  634. }
  635. } else if (osName.startsWith("Mac") || osName.startsWith("Darwin")) {
  636. // check if arch specific natives have been specified
  637. if (System.getProperty("os.arch").endsWith("64")) {
  638. nativeJarList = getParameter("al_mac64");
  639. } else if (System.getProperty("os.arch").contains("ppc")) {
  640. nativeJarList = getParameter("al_macppc");
  641. } else {
  642. nativeJarList = getParameter("al_mac32");
  643. }
  644. if (nativeJarList == null) {
  645. nativeJarList = getParameter("al_mac");
  646. }
  647. } else if (osName.startsWith("Solaris") || osName.startsWith("SunOS")) {
  648. nativeJarList = getParameter("al_solaris");
  649. } else if (osName.startsWith("FreeBSD")) {
  650. nativeJarList = getParameter("al_freebsd");
  651. } else {
  652. fatalErrorOccured("OS (" + osName + ") not supported", null);
  653. return;
  654. }
  655. if (nativeJarList == null) {
  656. fatalErrorOccured("no lwjgl natives files found", null);
  657. return;
  658. }
  659. jarList = trimExtensionByCapabilities(jarList);
  660. StringTokenizer jars = new StringTokenizer(jarList, ", ");
  661. nativeJarList = trimExtensionByCapabilities(nativeJarList);
  662. StringTokenizer nativeJars = new StringTokenizer(nativeJarList, ", ");
  663. int jarCount = jars.countTokens();
  664. nativeJarCount = nativeJars.countTokens();
  665. urlList = new URL[jarCount+nativeJarCount];
  666. URL path = getCodeBase();
  667. // set jars urls
  668. for (int i = 0; i < jarCount; i++) {
  669. urlList[i] = new URL(path, jars.nextToken());
  670. }
  671. for (int i = jarCount; i < jarCount+nativeJarCount; i++) {
  672. urlList[i] = new URL(path, nativeJars.nextToken());
  673. }
  674. }
  675. /**
  676. * 9 steps
  677. *
  678. * 1) check jre version meets minimum requirements
  679. * 2) check applet cache and decide which jars to download
  680. * 3) download the jars
  681. * 4) extract native files
  682. * 5) validate jars for any corruption
  683. * 6) save applet cache information
  684. * 7) add jars to class path
  685. * 8) set any lwjgl properties
  686. * 9) switch to loaded applet
  687. */
  688. public void run() {
  689. percentage = 5;
  690. try {
  691. debug_sleep(2000);
  692. // check JRE version meets minimum requirements
  693. if (!isMinJREVersionAvailable()) {
  694. minimumJreNotFound = true;
  695. fatalErrorOccured("Java " + getStringParameter("al_min_jre", "1.5") + " or greater is required.", null);
  696. return;
  697. }
  698. // parse the urls for the jars into the url list
  699. loadJarURLs();
  700. // get path where applet files will be stored
  701. String path = getCacheDirectory();
  702. File dir = new File(path);
  703. // create directory
  704. if (!dir.exists()) {
  705. dir.mkdirs();
  706. }
  707. File versionFile = new File(dir, "version");
  708. // if specified applet version already available don't download anything
  709. boolean versionAvailable = false;
  710. // version string of applet
  711. String version = getParameter("al_version");
  712. // if applet version specifed, compare with version in the cache
  713. if (version != null) {
  714. versionAvailable = compareVersion(versionFile, version.toLowerCase());
  715. }
  716. // if jars not available or need updating download them
  717. if (!versionAvailable) {
  718. // get jars file sizes and check cache
  719. getJarInfo(dir); // 5-15%
  720. // downloads jars from the server
  721. downloadJars(path); // 15-55%
  722. // Extract Pack and LZMA files
  723. extractJars(path); // 55-65%
  724. // Extracts Native Files
  725. extractNatives(path); // 65-80%
  726. // Validate Jars // 80-90%
  727. validateJars(path);
  728. // save version information once jars downloaded successfully
  729. if (version != null) {
  730. percentage = 90;
  731. writeObjectFile(versionFile, version.toLowerCase());
  732. }
  733. // save file names with last modified info once downloaded successfully
  734. writeObjectFile(new File(dir, "timestamps"), filesLastModified);
  735. }
  736. // add the downloaded jars and natives to classpath
  737. updateClassPath(path);
  738. // set lwjgl properties
  739. setLWJGLProperties();
  740. // if headless mode then sleep, until told to continue
  741. if (headless) {
  742. while(headlessWaiting) {
  743. Thread.sleep(100);
  744. }
  745. }
  746. // make applet switch on the EDT as an AWT/Swing permission dialog could be called
  747. EventQueue.invokeAndWait(new Runnable() {
  748. public void run() {
  749. try {
  750. switchApplet();
  751. } catch (Exception e) {
  752. fatalErrorOccured("This occurred while '" + getDescriptionForState() + "'", e);
  753. }
  754. setState(STATE_DONE);
  755. repaint();
  756. }
  757. });
  758. } catch (Exception e) {
  759. certificateRefused = e instanceof AccessControlException;
  760. fatalErrorOccured("This occurred while '" + getDescriptionForState() + "'", e);
  761. } finally {
  762. loaderThread = null;
  763. }
  764. }
  765. /**
  766. * When this method is supplied with a JRE version it will compare it to the
  767. * current JRE version.
  768. *
  769. * minimum requried JRE version is set using al_min_jre parameter, if not
  770. * this is not set then the value will default to version 1.5
  771. *
  772. * The minimumVersion should follow a structure such as x.x.x_x
  773. * Example values would include 1.6.0_10 or a subset like 1.6.0 or 1.6
  774. *
  775. * @return returns true if the available version is greater or equal to the
  776. * minimum version required
  777. *
  778. * @throws Exception a NumberFormatException is thrown if the string is not valid
  779. */
  780. public boolean isMinJREVersionAvailable() throws Exception {
  781. setState(STATE_CHECK_JRE_VERSION);
  782. String minimumVersion = getStringParameter("al_min_jre", "1.5");
  783. String javaVersion = System.getProperty("java.version");
  784. // remove dash and anything after it (letters) from version string e.g. 1.5.0_01-ea
  785. minimumVersion = javaVersion.split("-")[0];
  786. javaVersion = minimumVersion.split("-")[0];
  787. // split version string into a string arrays
  788. String[] jvmVersionData = javaVersion.split("[_\\.]");
  789. String[] minVersionData = minimumVersion.split("[_\\.]");
  790. int maxLength = Math.max(jvmVersionData.length, minVersionData.length);
  791. // convert string arrays into int arrays
  792. int[] jvmVersion = new int[maxLength];
  793. int[] minVersion = new int[maxLength];
  794. for (int i = 0; i < jvmVersionData.length; i++) {
  795. jvmVersion[i] = Integer.parseInt(jvmVersionData[i]);
  796. }
  797. for (int i = 0; i < minVersionData.length; i++) {
  798. minVersion[i] = Integer.parseInt(minVersionData[i]);
  799. }
  800. // compare versions
  801. for (int i = 0; i < maxLength; i++) {
  802. if (jvmVersion[i] < minVersion[i]) return false; // minVersion is greater then jvmVersion
  803. }
  804. return true;
  805. }
  806. /**
  807. * This method will return true if the version stored in the file
  808. * matches the supplied String version.
  809. *
  810. * @param versionFile - location to file containing version information
  811. * @param version - String version that needs to be compared
  812. * @return returns true if the version in file matches specified version
  813. */
  814. protected boolean compareVersion(File versionFile, String version) {
  815. // if version file exists
  816. if (versionFile.exists()) {
  817. String s = readStringFile(versionFile);
  818. // compare to version with file
  819. if (s != null && s.equals(version)) {
  820. percentage = 90; // not need to download cache files again
  821. if(debugMode) {
  822. System.out.println("Loading Cached Applet Version: " + version);
  823. }
  824. debug_sleep(2000);
  825. return true; // version matches file
  826. }
  827. }
  828. return false;
  829. }
  830. /**
  831. * Parses the java_arguments list and sets lwjgl specific
  832. * properties accordingly, before the launch.
  833. */
  834. protected void setLWJGLProperties() {
  835. String lwjglArguments = getParameter("lwjgl_arguments");
  836. if(lwjglArguments != null && lwjglArguments.length() > 0) {
  837. int start = lwjglArguments.indexOf("-Dorg.lwjgl");
  838. while(start != -1) {
  839. int end = lwjglArguments.indexOf(" ", start);
  840. if(end == -1) {
  841. end = lwjglArguments.length();
  842. }
  843. String[] keyValue = lwjglArguments.substring(start+2, end).split("=");
  844. System.setProperty(keyValue[0], keyValue[1]);
  845. if(debugMode) {
  846. System.out.println("Setting property " + keyValue[0] + " to " + keyValue[1]);
  847. }
  848. start = lwjglArguments.indexOf("-Dorg.lwjgl", end);
  849. }
  850. }
  851. }
  852. /**
  853. * This method will return the location of the cache directory. All the
  854. * applet files will be downloaded and stored here. A folder will be
  855. * created inside the LWJGL cache directory from the al_title parameter.
  856. * This folder will also be prepended by the host name of the codebase
  857. * to avoid conflict with same named applets on other hosts.
  858. *
  859. * @return path to applets cache directory
  860. * @throws Exception if access is denied
  861. */
  862. protected String getCacheDirectory() throws Exception {
  863. String path = AccessController.doPrivileged(new PrivilegedExceptionAction<String>() {
  864. public String run() throws Exception {
  865. // we append the code base to avoid naming collisions with al_title
  866. String codebase = "";
  867. if(prependHost) {
  868. codebase = getCodeBase().getHost();
  869. if(codebase == null || codebase.length() == 0) {
  870. codebase = "localhost";
  871. }
  872. codebase += File.separator;
  873. }
  874. return getLWJGLCacheDir() + File.separator + codebase + getParameter("al_title") + File.separator;
  875. }
  876. });
  877. return path;
  878. }
  879. /**
  880. * Get path to the lwjgl cache directory. This location will be where
  881. * the OS keeps temporary files.
  882. *
  883. * @return path to the lwjgl cache directory
  884. */
  885. protected String getLWJGLCacheDir() {
  886. String cacheDir = System.getProperty("deployment.user.cachedir");
  887. if (cacheDir == null || System.getProperty("os.name").startsWith("Win")) {
  888. cacheDir = System.getProperty("java.io.tmpdir");
  889. }
  890. return cacheDir + File.separator + "lwjglcache";
  891. }
  892. /**
  893. * read String object from File
  894. *
  895. * @param file to be read
  896. * @return the String stored in the file or null if it fails
  897. */
  898. protected String readStringFile(File file) {
  899. try {
  900. return (String)readObjectFile(file);
  901. } catch (Exception e) {
  902. // failed to read version file
  903. e.printStackTrace();
  904. }
  905. // return null if failed to read file
  906. return null;
  907. }
  908. /**
  909. * read the HashMap from File
  910. *
  911. * @param file the file to read
  912. * @return the hashmap stored in the file or an empty hashmap if it fails
  913. */
  914. @SuppressWarnings("unchecked")
  915. protected HashMap<String, Long> readHashMapFile(File file) {
  916. try {
  917. return (HashMap<String, Long>) readObjectFile(file);
  918. } catch (Exception e) {
  919. // failed to read hashmap from file
  920. e.printStackTrace();
  921. }
  922. // return an empty map if failed to read file
  923. return new HashMap<String, Long>();
  924. }
  925. /**
  926. * read the object from the File
  927. *
  928. * @param file the file to read
  929. * @return the object contained in the file or null if it fails
  930. * @throws Exception if it fails to read object from file
  931. */
  932. protected Object readObjectFile(File file) throws Exception {
  933. FileInputStream fis = new FileInputStream(file);
  934. try {
  935. ObjectInputStream dis = new ObjectInputStream(fis);
  936. Object object = dis.readObject();
  937. dis.close();
  938. return object;
  939. } catch (Exception e) {
  940. // failed to read file
  941. throw e;
  942. } finally {
  943. fis.close();
  944. }
  945. }
  946. /**
  947. * write object to specified File
  948. *
  949. * @param file the file to write out to
  950. * @param object the contents of the file
  951. * @throws Exception if it fails to write file
  952. */
  953. protected void writeObjectFile(File file, Object object) throws Exception {
  954. FileOutputStream fos = new FileOutputStream(file);
  955. try {
  956. ObjectOutputStream dos = new ObjectOutputStream(fos);
  957. dos.writeObject(object);
  958. dos.close();
  959. } finally {
  960. fos.close();
  961. }
  962. }
  963. /**
  964. * Edits the ClassPath at runtime to include the jars
  965. * that have just been downloaded and then adds the
  966. * lwjgl natives folder property.
  967. *
  968. * @param path location where applet is stored
  969. * @throws Exception if it fails to add classpath
  970. */
  971. protected void updateClassPath(final String path) throws Exception {
  972. setState(STATE_UPDATING_CLASSPATH);
  973. percentage = 95;
  974. URL[] urls = new URL[urlList.length];
  975. for (int i = 0; i < urlList.length; i++) {
  976. String file = new File(path, getJarName(urlList[i])).toURI().toString();
  977. // fix JVM bug where ! is not escaped
  978. file = file.replace("!", "%21");
  979. urls[i] = new URL(file);
  980. }
  981. // get AppletLoader certificates
  982. final Certificate[] certs = getCurrentCertificates();
  983. // detect if we are running on a mac and save result as boolean
  984. String osName = System.getProperty("os.name");
  985. final boolean isMacOS = (osName.startsWith("Mac") || osName.startsWith("Darwin"));
  986. // add downloaded jars to the classpath with required permissions
  987. classLoader = new URLClassLoader(urls) {
  988. protected PermissionCollection getPermissions (CodeSource codesource) {
  989. PermissionCollection perms = null;
  990. try {
  991. // if mac, apply workaround for the multiple security dialog issue
  992. if (isMacOS) {
  993. // if certificates match the AppletLoader certificates then don't use SecureClassLoader to get further permissions
  994. if (certificatesMatch(certs, codesource.getCertificates())) {
  995. perms = new Permissions();
  996. perms.add(new AllPermission());
  997. return perms;
  998. }
  999. }
  1000. // getPermissions from original classloader is important as it checks for signed jars and shows any security dialogs needed
  1001. Method method = SecureClassLoader.class.getDeclaredMethod("getPermissions", new Class[] { CodeSource.class });
  1002. method.setAccessible(true);
  1003. perms = (PermissionCollection)method.invoke(getClass().getClassLoader(), new Object[] {codesource});
  1004. String host = getCodeBase().getHost();
  1005. if (host != null && (host.length() > 0)) {
  1006. // add permission for downloaded jars to access host they were from
  1007. perms.add(new SocketPermission(host, "connect,accept"));
  1008. }
  1009. else if ( "file".equals(codesource.getLocation().getProtocol()) ) {
  1010. // if running locally add file permission
  1011. String path = codesource.getLocation().getFile().replace('/', File.separatorChar);
  1012. perms.add(new FilePermission(path, "read"));
  1013. }
  1014. } catch (Exception e) {
  1015. e.printStackTrace();
  1016. }
  1017. return perms;
  1018. }
  1019. // allow non lwjgl native to be found from cache directory
  1020. protected String findLibrary (String libname) {
  1021. String libPath = path + "natives" + File.separator + System.mapLibraryName(libname);
  1022. if (new File(libPath).exists()) {
  1023. return libPath;
  1024. }
  1025. return super.findLibrary(libname);
  1026. }
  1027. };
  1028. debug_sleep(2000);
  1029. // unload natives loaded by a previous instance of this lwjgl applet
  1030. unloadNatives(path);
  1031. // add natives files path to native class path
  1032. System.setProperty("org.lwjgl.librarypath", path + "natives");
  1033. // Make sure jinput knows about the new path too
  1034. System.setProperty("net.java.games.input.librarypath", path + "natives");
  1035. // set the library path, useful for non lwjgl natives
  1036. System.setProperty("java.library.path", path + "natives");
  1037. // mark natives as loaded
  1038. natives_loaded = true;
  1039. }
  1040. /**
  1041. * Unload natives loaded by a different classloader.
  1042. *
  1043. * Due to limitations of the jvm, native files can only
  1044. * be loaded once and only be used by the classloader
  1045. * they were loaded from.
  1046. *
  1047. * Due to the way applets on plugin1 work, one jvm must
  1048. * be used for all applets. We need to use multiple
  1049. * classloaders in the same jvm due to LWJGL's static
  1050. * nature. In order to solve this we simply remove the
  1051. * natives from a previous classloader allowing a new
  1052. * classloader to use those natives in the same jvm.
  1053. *
  1054. * This method will only attempt to unload natives from a
  1055. * previous classloader if it detects that the natives have
  1056. * been loaded in the same jvm.
  1057. *
  1058. * @param nativePath directory where natives are stored
  1059. */
  1060. private void unloadNatives(String nativePath) {
  1061. // check whether natives have been loaded into this jvm
  1062. if (!natives_loaded) {
  1063. return;
  1064. }
  1065. try {
  1066. Field field = ClassLoader.class.getDeclaredField("loadedLibraryNames");
  1067. field.setAccessible(true);
  1068. Vector libs = (Vector) field.get(getClass().getClassLoader());
  1069. String path = new File(nativePath).getCanonicalPath();
  1070. for (int i = 0; i < libs.size(); i++) {
  1071. String s = (String) libs.get(i);
  1072. // if a native from the nativePath directory is loaded, unload it
  1073. if (s.startsWith(path)) {
  1074. libs.remove(i);
  1075. i--;
  1076. }
  1077. }
  1078. } catch (Exception e) {
  1079. e.printStackTrace();
  1080. }
  1081. }
  1082. /**
  1083. * replace the current applet with the lwjgl applet
  1084. * using AppletStub and initialise and start it
  1085. */
  1086. protected void switchApplet() throws Exception {
  1087. setState(STATE_SWITCHING_APPLET);
  1088. percentage = 100;
  1089. debug_sleep(2000);
  1090. // set correct context classloader for lwjgl applet
  1091. Thread.currentThread().setContextClassLoader(classLoader);
  1092. Class appletClass = classLoader.loadClass(getParameter("al_main"));
  1093. lwjglApplet = (Applet) appletClass.newInstance();
  1094. lwjglApplet.setStub(this);
  1095. lwjglApplet.setSize(getWidth(), getHeight());
  1096. setLayout(new BorderLayout());
  1097. add(lwjglApplet);
  1098. validate();
  1099. setState(STATE_INITIALIZE_REAL_APPLET);
  1100. lwjglApplet.init();
  1101. setState(STATE_START_REAL_APPLET);
  1102. lwjglApplet.start();
  1103. }
  1104. /**
  1105. * This method will get the files sizes of the files to download.
  1106. * It wil further get the lastModified time of files
  1107. * and save it in a hashmap, if cache is enabled it will mark
  1108. * those files that have not changed since last download to not
  1109. * redownloaded.
  1110. *
  1111. * @param dir - location to read cache file from
  1112. * @throws Exception - if fails to get infomation
  1113. */
  1114. protected void getJarInfo(File dir) throws Exception {
  1115. setState(STATE_CHECKING_CACHE);
  1116. filesLastModified = new HashMap<String, Long>();
  1117. // store file sizes and mark which files not to download
  1118. fileSizes = new int[urlList.length];
  1119. File timestampsFile = new File(dir, "timestamps");
  1120. // if timestamps file exists, load it
  1121. if (timestampsFile.exists()) {
  1122. setState(STATE_CHECKING_FOR_UPDATES);
  1123. filesLastModified = readHashMapFile(timestampsFile);
  1124. }
  1125. // calculate total size of jars to download
  1126. ExecutorService executorService = Executors.newFixedThreadPool(concurrentLookupThreads);
  1127. Queue<Future> requests = new LinkedList<Future>();
  1128. // create unique object to sync code in requests
  1129. final Object sync = new Integer(1);
  1130. for (int j = 0; j < urlList.length; j++) {
  1131. final int i = j;
  1132. Future request = executorService.submit(new Runnable() {
  1133. public void run() {
  1134. try {
  1135. URLConnection urlconnection = urlList[i].openConnection();
  1136. urlconnection.setDefaultUseCaches(false);
  1137. if (urlconnection instanceof HttpURLConnection) {
  1138. ((HttpURLConnection) urlconnection).setRequestMethod("HEAD");
  1139. }
  1140. fileSizes[i] = urlconnection.getContentLength();
  1141. long lastModified = urlconnection.getLastModified();
  1142. String fileName = getFileName(urlList[i]);
  1143. if (cacheEnabled && lastModified != 0 && filesLastModified.containsKey(fileName)) {
  1144. long savedLastModified = filesLastModified.get(fileName);
  1145. // if lastModifed time is the same, don't redownload
  1146. if (savedLastModified == lastModified) {
  1147. fileSizes[i] = -2; // mark it to not redownload
  1148. }
  1149. }
  1150. if (fileSizes[i] >= 0) {
  1151. synchronized (sync) {
  1152. totalDownloadSize += fileSizes[i];
  1153. }
  1154. }
  1155. // put key and value in the hashmap
  1156. filesLastModified.put(fileName, lastModified);
  1157. } catch (Exception e) {
  1158. throw new RuntimeException("Failed to fetch information for " + urlList[i], e);
  1159. }
  1160. }});
  1161. requests.add(request);
  1162. }
  1163. while (!requests.isEmpty()) {
  1164. Iterator<Future> iterator = requests.iterator();
  1165. while (iterator.hasNext()) {
  1166. Future request = iterator.next();
  1167. if (request.isDone()) {
  1168. request.get(); // will throw an exception if request thrown an exception.
  1169. iterator.remove();
  1170. // update progress bar
  1171. percentage = 5 + (int) (10 * (urlList.length - requests.size()) / (float) urlList.length);
  1172. }
  1173. }
  1174. Thread.sleep(10);
  1175. }
  1176. executorService.shutdown();
  1177. }
  1178. /**
  1179. * Will download the jars from the server using the list of urls
  1180. * in urlList, while at the same time updating progress bar
  1181. *
  1182. * @param path location of the directory to save to
  1183. * @throws Exception if download fails
  1184. */
  1185. protected void downloadJars(String path) throws Exception {
  1186. setState(STATE_DOWNLOADING);
  1187. URLConnection urlconnection;
  1188. int initialPercentage = percentage = 15;
  1189. int amountDownloaded = 0;
  1190. // download each jar
  1191. byte buffer[] = new byte[65536];
  1192. for (int i = 0; i < urlList.length; i++) {
  1193. // skip file if marked as -2 (already downloaded and not changed)
  1194. if (fileSizes[i] == -2) continue;
  1195. int unsuccessfulAttempts = 0;
  1196. int maxUnsuccessfulAttempts = 3;
  1197. boolean downloadFile = true;
  1198. String currentFile = getFileName(urlList[i]);
  1199. // download the jar a max of 3 times
  1200. while(downloadFile) {
  1201. downloadFile = false;
  1202. debug_sleep(2000);
  1203. try {
  1204. urlconnection = urlList[i].openConnection();
  1205. urlconnection.setUseCaches(false);
  1206. if (urlconnection instanceof HttpURLConnection) {
  1207. urlconnection.setRequestProperty("Cache-Control", "no-store,max-age=0,no-cache");
  1208. urlconnection.connect();
  1209. }
  1210. InputStream inputstream = getJarInputStream(currentFile, urlconnection);
  1211. FileOutputStream fos = new FileOutputStream(path + currentFile);
  1212. int bufferSize;
  1213. int currentDownload = 0;
  1214. long downloadStartTime = System.currentTimeMillis();
  1215. int downloadedAmount = 0;
  1216. String downloadSpeedMessage = "";
  1217. try {
  1218. while ((bufferSize = inputstream.read(buffer, 0, buffer.length)) != -1) {
  1219. debug_sleep(10);
  1220. fos.write(buffer, 0, bufferSize);
  1221. currentDownload += bufferSize;
  1222. int totalDownloaded = amountDownloaded + currentDownload;
  1223. percentage = initialPercentage + ((totalDownloaded * 45) / totalDownloadSize);
  1224. subtaskMessage = "Retrieving: " + currentFile + " " + ((totalDownloaded * 100) / totalDownloadSize) + "%";
  1225. downloadedAmount += bufferSize;
  1226. long timeLapse = System.currentTimeMillis() - downloadStartTime;
  1227. // update only if a second or more has passed
  1228. if (timeLapse >= 1000) {
  1229. // get kb/s, nice that bytes/millis is same as kilobytes/seconds
  1230. float downloadSpeed = (float) downloadedAmount / timeLapse;
  1231. // round to two decimal places
  1232. downloadSpeed = ((int)(downloadSpeed*100))/100f;
  1233. // set current speed message
  1234. downloadSpeedMessage = " - " + downloadSpeed + " KB/sec";
  1235. // reset downloaded amount
  1236. downloadedAmount = 0;
  1237. // reset start time
  1238. downloadStartTime = System.currentTimeMillis();
  1239. }
  1240. subtaskMessage += downloadSpeedMessage;
  1241. }
  1242. } finally {
  1243. inputstream.close();
  1244. fos.close();
  1245. }
  1246. // download complete, verify if it was successful
  1247. if (urlconnection instanceof HttpURLConnection) {
  1248. if (currentDownload == fileSizes[i]) {
  1249. // successful download
  1250. }
  1251. else if (fileSizes[i] <= 0 && currentDownload != 0) {
  1252. // If contentLength for fileSizes[i] <= 0, we don't know if the download
  1253. // is complete. We're going to guess the download is complete.
  1254. }
  1255. else {
  1256. throw new Exception("size mismatch on download of " + currentFile +
  1257. " expected " + fileSizes[i] + " got " + currentDownload);
  1258. }
  1259. }
  1260. // successful file download, update total amount downloaded
  1261. amountDownloaded += fileSizes[i];
  1262. } catch (Exception e) {
  1263. e.printStackTrace(); // output exception to console
  1264. // Failed to download the file
  1265. unsuccessfulAttempts++;
  1266. // download failed try again
  1267. if (unsuccessfulAttempts < maxUnsuccessfulAttempts) {
  1268. downloadFile = true;
  1269. Thread.sleep(100); // wait a bit before retrying
  1270. }
  1271. else {
  1272. // retry attempts exhasted, download failed
  1273. throw new Exception("failed to download " + currentFile +
  1274. " after " + maxUnsuccessfulAttempts + " attempts");
  1275. }
  1276. }
  1277. }
  1278. }
  1279. subtaskMessage = "";
  1280. }
  1281. /**
  1282. * Retrieves a jar files input stream. This method exists primarily to fix an Opera hang in getInputStream
  1283. * @param urlconnection connection to get input stream from
  1284. * @return InputStream or null if not possible
  1285. */
  1286. protected InputStream getJarInputStream(final String currentFile, final URLConnection urlconnection) throws Exception {
  1287. final InputStream[] is = new InputStream[1];
  1288. // try to get the input stream 3 times.
  1289. // Wait at most 5 seconds before interrupting the thread
  1290. for (int j = 0; j < 3 && is[0] == null; j++) {
  1291. Thread t = new Thread() {
  1292. public void run() {
  1293. try {
  1294. is[0] = urlconnection.getInputStream();
  1295. } catch (IOException e) {
  1296. /* ignored */
  1297. }
  1298. }
  1299. };
  1300. t.setName("JarInputStreamThread");
  1301. t.start();
  1302. int iterationCount = 0;
  1303. while(is[0] == null && iterationCount++ < 5) {
  1304. try {
  1305. t.join(1000);
  1306. } catch (InterruptedException inte) {
  1307. /* ignored */
  1308. }
  1309. }
  1310. if(is[0] == null) {
  1311. try {
  1312. t.interrupt();
  1313. t.join();
  1314. } catch (InterruptedException inte) {
  1315. /* ignored */
  1316. }
  1317. }
  1318. }
  1319. if(is[0] == null) {
  1320. throw new Exception("Unable to get input stream for " + currentFile);
  1321. }
  1322. return is[0];
  1323. }
  1324. /**
  1325. * Extract LZMA File
  1326. * @param in Input path to pack file
  1327. * @param out output path to resulting file
  1328. * @throws Exception if any errors occur
  1329. */
  1330. protected void extractLZMA(String in, String out) throws Exception {
  1331. File f = new File(in);
  1332. FileInputStream fileInputHandle = new FileInputStream(f);
  1333. // use reflection to avoid hard dependency
  1334. Class<?> clazz = Class.forName( "LZMA.LzmaInputStream" );
  1335. Constructor constructor = clazz.getDeclaredConstructor(InputStream.class);
  1336. InputStream inputHandle = (InputStream) constructor.newInstance(fileInputHandle);
  1337. OutputStream outputHandle = new FileOutputStream(out);
  1338. byte [] buffer = new byte [1<<14];
  1339. try {
  1340. int ret = inputHandle.read(buffer);
  1341. while (ret >= 1) {
  1342. outputHandle.write(buffer,0,ret);
  1343. ret = inputHandle.read(buffer);
  1344. }
  1345. } finally {
  1346. inputHandle.close();
  1347. outputHandle.close();
  1348. }
  1349. // delete LZMA file, as it is no longer needed
  1350. f.delete();
  1351. }
  1352. /**
  1353. * Extract GZip File
  1354. * @param in Input path to pack file
  1355. * @param out output path to resulting file
  1356. * @throws Exception if any errors occur
  1357. */
  1358. protected void extractGZip(String in, String out) throws Exception {
  1359. File f = new File(in);
  1360. FileInputStream fileInputHandle = new FileInputStream(f);
  1361. InputStream inputHandle = new GZIPInputStream(fileInputHandle);
  1362. OutputStream outputHandle = new FileOutputStream(out);
  1363. try {
  1364. byte [] buffer = new byte [1<<14];
  1365. int ret = inputHandle.read(buffer);
  1366. while (ret >= 1) {
  1367. outputHandle.write(buffer,0,ret);
  1368. ret = inputHandle.read(buffer);
  1369. }
  1370. } finally {
  1371. inputHandle.close();
  1372. outputHandle.close();
  1373. }
  1374. // delete GZip file, as it is no longer needed
  1375. f.delete();
  1376. }
  1377. /**
  1378. * Extract Pack File
  1379. * @param in Input path to pack file
  1380. * @param out output path to resulting file
  1381. * @throws Exception if any errors occur
  1382. */
  1383. protected void extractPack(String in, String out) throws Exception {
  1384. File f = new File(in);
  1385. FileOutputStream fostream = new FileOutputStream(out);
  1386. JarOutputStream jostream = new JarOutputStream(fostream);
  1387. try {
  1388. Pack200.Unpacker unpacker = Pack200.newUnpacker();
  1389. unpacker.unpack(f, jostream);
  1390. } finally {
  1391. jostream.close();
  1392. fostream.close();
  1393. }
  1394. // delete pack file as its no longer needed
  1395. f.delete();
  1396. }
  1397. /**
  1398. * Extract all jars from any lzma/gz/pack files
  1399. *
  1400. * @param path output path
  1401. * @throws Exception if any errors occur
  1402. */
  1403. protected void extractJars(String path) throws Exception {
  1404. setState(STATE_EXTRACTING_PACKAGES);
  1405. float increment = (float) 10.0 / urlList.length;
  1406. // extract all gz, lzma, pack.gz and pack.lzma files
  1407. for (int i = 0; i < urlList.length; i++) {
  1408. // if file has not changed, skip it
  1409. if (fileSizes[i] == -2) continue;
  1410. percentage = 55 + (int) (increment * (i+1));
  1411. String filename = getFileName(urlList[i]);
  1412. if (filename.endsWith(".pack.lzma")) {
  1413. subtaskMessage = "Extracting: " + filename + " to " + replaceLast(filename, ".lzma", "");
  1414. debug_sleep(1000);
  1415. extractLZMA(path + filename, path + replaceLast(filename, ".lzma", ""));
  1416. subtaskMessage = "Extracting: " + replaceLast(filename, ".lzma", "") + " to " + replaceLast(filename, ".pack.lzma", "");
  1417. debug_sleep(1000);
  1418. extractPack(path + replaceLast(filename, ".lzma", ""), path + replaceLast(filename, ".pack.lzma", ""));
  1419. }
  1420. else if (filename.endsWith(".pack.gz")) {
  1421. subtaskMessage = "Extracting: " + filename + " to " + replaceLast(filename, ".gz", "");
  1422. debug_sleep(1000);
  1423. extractGZip(path + filename, path + replaceLast(filename, ".gz", ""));
  1424. subtaskMessage = "Extracting: " + replaceLast(filename, ".gz", "") + " to " + replaceLast(filename, ".pack.gz", "");
  1425. debug_sleep(1000);
  1426. extractPack(path + replaceLast(filename, ".gz", ""), path + replaceLast(filename, ".pack.gz", ""));
  1427. }
  1428. else if (filename.endsWith(".pack")) {
  1429. subtaskMessage = "Extracting: " + filename + " to " + replaceLast(filename, ".pack", "");
  1430. debug_sleep(1000);
  1431. extractPack(path + filename, path + replaceLast(filename, ".pack", ""));
  1432. }
  1433. else if (filename.endsWith(".lzma")) {
  1434. subtaskMessage = "Extracting: " + filename + " to " + replaceLast(filename, ".lzma", "");
  1435. debug_sleep(1000);
  1436. extractLZMA(path + filename, path + replaceLast(filename, ".lzma", ""));
  1437. }
  1438. else if (filename.endsWith(".gz")) {
  1439. subtaskMessage = "Extracting: " + filename + " to " + replaceLast(filename, ".gz", "");
  1440. debug_sleep(1000);
  1441. extractGZip(path + filename, path + replaceLast(filename, ".gz", ""));
  1442. }
  1443. }
  1444. }
  1445. /**
  1446. * This method will extract all file from the native jar and extract them
  1447. * to the subdirectory called "natives" in the local path, will also check
  1448. * to see if the native jar files is signed properly
  1449. *
  1450. * @param path base folder containing all downloaded jars
  1451. * @throws Exception if it fails to extract files
  1452. */
  1453. protected void extractNatives(String path) throws Exception {
  1454. setState(STATE_EXTRACTING_PACKAGES);
  1455. float percentageParts = 15f/nativeJarCount; // parts for each native jar from 15%
  1456. // create native folder
  1457. File nativeFolder = new File(path + "natives");
  1458. if (!nativeFolder.exists()) {
  1459. nativeFolder.mkdir();
  1460. }
  1461. // get the current AppletLoader certificates to compare against certificates of the native files
  1462. Certificate[] certificate = getCurrentCertificates();
  1463. for (int i = urlList.length - nativeJarCount; i < urlList.length; i++) {
  1464. // if a new native jar was not downloaded, no extracting needed
  1465. if (fileSizes[i] == -2) {
  1466. continue;
  1467. }
  1468. // get name of jar file with natives from urlList
  1469. String nativeJar = getJarName(urlList[i]);
  1470. // open jar file
  1471. JarFile jarFile = new JarFile(path + nativeJar, true);
  1472. // get list of files in jar
  1473. Enumeration entities = jarFile.entries();
  1474. totalSizeExtract = 0;
  1475. int jarNum = i - (urlList.length - nativeJarCount); // used for progressbar
  1476. // calculate the size of the files to extract for progress bar
  1477. while (entities.hasMoreElements()) {
  1478. JarEntry entry = (JarEntry) entities.nextElement();
  1479. // skip directories and anything in directories
  1480. // conveniently ignores the manifest
  1481. if (entry.isDirectory() || entry.getName().indexOf('/') != -1) {
  1482. continue;
  1483. }
  1484. totalSizeExtract += entry.getSize();
  1485. }
  1486. currentSizeExtract = 0;
  1487. // reset point to begining by getting list of file again
  1488. entities = jarFile.entries();
  1489. // extract all files from the jar
  1490. while (entities.hasMoreElements()) {
  1491. JarEntry entry = (JarEntry) entities.nextElement();
  1492. // skip directories and anything in directories
  1493. // conveniently ignores the manifest
  1494. if (entry.isDirectory() || entry.getName().indexOf('/') != -1) {
  1495. continue;
  1496. }
  1497. // check if native file already exists if so delete it to make room for new one
  1498. // useful when using the reload button on the browser
  1499. File f = new File(path + "natives" + File.separator + entry.getName());
  1500. if (f.exists()) {
  1501. if (!f.delete()) {
  1502. continue; // unable to delete file, it is in use, skip extracting it
  1503. }
  1504. }
  1505. debug_sleep(1000);
  1506. InputStream in = jarFile.getInputStream(jarFile.getEntry(entry.getName()));
  1507. OutputStream out = new FileOutputStream(path + "natives" + File.separator + entry.getName());
  1508. try {
  1509. int bufferSize;
  1510. byte buffer[] = new byte[65536];
  1511. while ((bufferSize = in.read(buffer, 0, buffer.length)) != -1) {
  1512. debug_sleep(10);
  1513. out.write(buffer, 0, bufferSize);
  1514. currentSizeExtract += bufferSize;
  1515. // update progress bar
  1516. percentage = 65 + (int)(percentageParts * (jarNum + currentSizeExtract/(float)totalSizeExtract));
  1517. subtaskMessage = "Extracting: " + entry.getName() + " " + ((currentSizeExtract * 100) / totalSizeExtract) + "%";
  1518. }
  1519. } finally {
  1520. in.close();
  1521. out.close();
  1522. }
  1523. // validate the certificate for the native file being extracted
  1524. if (!certificatesMatch(certificate, entry.getCertificates())) {
  1525. f.delete(); // delete extracted native as its certificates doesn't match
  1526. throw new Exception("The certificate(s) in " + nativeJar + " do not match the AppletLoader!");
  1527. }
  1528. }
  1529. subtaskMessage = "";
  1530. jarFile.close();
  1531. // delete native jar as it is no longer needed
  1532. File f = new File(path + nativeJar);
  1533. f.delete();
  1534. }
  1535. }
  1536. /**
  1537. * Compare two certificate chains to see if they match
  1538. *
  1539. * @param cert1 first chain of certificates
  1540. * @param cert2 second chain of certificates
  1541. *
  1542. * @return true if the certificate chains are the same
  1543. */
  1544. protected static boolean certificatesMatch(Certificate[] certs1, Certificate[] certs2) throws Exception {
  1545. if (certs1 == null || certs2 == null) {
  1546. return false;
  1547. }
  1548. if (certs1.length != certs2.length) {
  1549. System.out.println("Certificate chain differs in length [" + certs1.length + " vs " + certs2.length + "]!");
  1550. return false;
  1551. }
  1552. for (int i = 0; i < certs1.length; i++) {
  1553. if (!certs1[i].equals(certs2[i])) {
  1554. System.out.println("Certificate mismatch found!");
  1555. return false;
  1556. }
  1557. }
  1558. return true;
  1559. }
  1560. /**
  1561. * Returns the current certificate chain of the AppletLoader
  1562. *
  1563. * @return - certificate chain of AppletLoader
  1564. */
  1565. protected static Certificate[] getCurrentCertificates() throws Exception {
  1566. // get the current certificate to compare against native files
  1567. Certificate[] certificate = AppletLoader.class.getProtectionDomain().getCodeSource().getCertificates();
  1568. // workaround for bug where cached applet loader does not have certificates!?
  1569. if (certificate == null) {
  1570. URL location = AppletLoader.class.getProtectionDomain().getCodeSource().getLocation();
  1571. // manually load the certificate
  1572. JarURLConnection jurl = (JarURLConnection) (new URL("jar:" + location.toString() + "!/org/lwjgl/util/applet/AppletLoader.class").openConnection());
  1573. jurl.setDefaultUseCaches(true);
  1574. certificate = jurl.getCertificates();
  1575. jurl.setDefaultUseCaches(false);
  1576. }
  1577. return certificate;
  1578. }
  1579. /**
  1580. * Check and validate jars which will be loaded into the classloader to make
  1581. * sure that they are not corrupt. This ensures corrupt files are never marked
  1582. * as successful downloadeds by the cache system.
  1583. *
  1584. * @param path - where the jars are stored
  1585. * @throws Exception if a corrupt jar is found
  1586. */
  1587. protected void validateJars(String path) throws Exception {
  1588. setState(STATE_VALIDATING_PACKAGES);
  1589. percentage = 80;
  1590. float percentageParts = 10f / urlList.length; // percentage for each file out of 10%
  1591. for (int i = 0; i < urlList.length - nativeJarCount; i++) {
  1592. debug_sleep(1000);
  1593. // if file not downloaded, no need to validate again
  1594. if (fileSizes[i] == -2) continue;
  1595. subtaskMessage = "Validating: " + getJarName(urlList[i]);
  1596. File file = new File(path, getJarName(urlList[i]));
  1597. if (!isZipValid(file)) {
  1598. throw new Exception("The file " + getJarName(urlList[i]) + " is corrupt!");
  1599. }
  1600. percentage = 80 + (int)(percentageParts * i);
  1601. }
  1602. subtaskMessage = "";
  1603. }
  1604. /**
  1605. * This method will check if a zip file is valid by running through it
  1606. * and checking for any corruption and CRC failures
  1607. *
  1608. * @param file - zip file to test
  1609. * @return boolean - runs false if the file is corrupt
  1610. */
  1611. protected boolean isZipValid(File file) {
  1612. try {
  1613. ZipFile zipFile = new ZipFile(file);
  1614. try {
  1615. Enumeration e = zipFile.entries();
  1616. byte[] buffer = new byte[4096];
  1617. while(e.hasMoreElements()) {
  1618. ZipEntry zipEntry = (ZipEntry) e.nextElement();
  1619. CRC32 crc = new CRC32();
  1620. BufferedInputStream bis = new BufferedInputStream(zipFile.getInputStream(zipEntry));
  1621. CheckedInputStream cis = new CheckedInputStream(bis, crc);
  1622. while(cis.read(buffer, 0, buffer.length) != -1) {
  1623. // scroll through zip entry
  1624. }
  1625. if (crc.getValue() != zipEntry.getCrc()) {
  1626. return false; // CRC match failed, corrupt zip
  1627. }
  1628. }
  1629. return true; // valid zip file
  1630. } finally {
  1631. zipFile.close();
  1632. }
  1633. } catch (IOException e) {
  1634. e.printStackTrace();
  1635. return false;
  1636. }
  1637. }
  1638. /**
  1639. * Get Image from path provided
  1640. *
  1641. * @param s location of the image
  1642. * @return the Image file
  1643. */
  1644. protected Image getImage(String s) {
  1645. // if s is "" then don't load an image
  1646. if (s.length() == 0) return null;
  1647. Image image = null;
  1648. try {
  1649. image = getImage(new URL(getCodeBase(), s));
  1650. } catch (Exception e) {
  1651. /* */
  1652. }
  1653. // if image failed to load, try another method
  1654. if (image == null) {
  1655. image = getImage(Thread.currentThread().getContextClassLoader().getResource(s));
  1656. }
  1657. // if image loaded sucessfully return it
  1658. if (image != null) {
  1659. return image;
  1660. }
  1661. // show error as image could not be loaded
  1662. fatalErrorOccured("Unable to load the logo/progressbar image: " + s, null);
  1663. return null;
  1664. }
  1665. /**
  1666. * Get Image from path provided
  1667. *
  1668. * @param url location of the image
  1669. * @return the Image file
  1670. */
  1671. public Image getImage(URL url) {
  1672. try {
  1673. MediaTracker tracker = new MediaTracker(this);
  1674. Image image = super.getImage(url);
  1675. // wait for image to load
  1676. tracker.addImage(image, 0);
  1677. tracker.waitForAll();
  1678. // if no errors return image
  1679. if (!tracker.isErrorAny()) {
  1680. return image;
  1681. }
  1682. } catch (Exception e) {
  1683. /* */
  1684. }
  1685. return null;
  1686. }
  1687. /**
  1688. * Get jar name from URL.
  1689. *
  1690. * @param url Get jar file name from this url
  1691. * @return file name as string
  1692. */
  1693. protected String getJarName(URL url) {
  1694. String fileName = url.getFile();
  1695. if (fileName.endsWith(".pack.lzma")) {
  1696. fileName = replaceLast(fileName, ".pack.lzma", "");
  1697. } else if (fileName.endsWith(".pack.gz")) {
  1698. fileName = replaceLast(fileName, ".pack.gz", "");
  1699. } else if (fileName.endsWith(".pack")) {
  1700. fileName = replaceLast(fileName, ".pack", "");
  1701. } else if (fileName.endsWith(".lzma")) {
  1702. fileName = replaceLast(fileName, ".lzma", "");
  1703. } else if (fileName.endsWith(".gz")) {
  1704. fileName = replaceLast(fileName, ".gz", "");
  1705. }
  1706. return fileName.substring(fileName.lastIndexOf('/') + 1);
  1707. }
  1708. /**
  1709. * Get file name portion of URL.
  1710. *
  1711. * @param url Get file name from this url
  1712. * @return file name as string
  1713. */
  1714. protected String getFileName(URL url) {
  1715. String fileName = url.getFile();
  1716. return fileName.substring(fileName.lastIndexOf('/') + 1);
  1717. }
  1718. /**
  1719. * Retrieves the color
  1720. *
  1721. * @param param Color to load
  1722. * @param defaultColor Default color to use if no color to load
  1723. * @return Color to use
  1724. */
  1725. protected Color getColor(String param, Color defaultColor) {
  1726. String color = getParameter(param);
  1727. if (color == null) return defaultColor;
  1728. // Check if RGB format
  1729. if (color.indexOf(",") != -1) {
  1730. StringTokenizer st = new StringTokenizer(color, ",");
  1731. // We've got three components for the color
  1732. try {
  1733. return new Color(Integer.parseInt(st.nextToken().trim()),
  1734. Integer.parseInt(st.nextToken().trim()),
  1735. Integer.parseInt(st.nextToken().trim()));
  1736. } catch (Exception e) {
  1737. // failed to parse
  1738. return defaultColor;
  1739. }
  1740. }
  1741. // Check & decode if the color is in hexadecimal color format (i.e. #808000)
  1742. try {
  1743. return Color.decode(color);
  1744. } catch (NumberFormatException e) {
  1745. // ignore exception
  1746. }
  1747. // Get the color by name if it exists
  1748. try {
  1749. return (Color)Color.class.getField(color).get(null);
  1750. } catch (Exception e) {
  1751. return defaultColor;
  1752. }
  1753. }
  1754. /**
  1755. * Replaces the last occurrence of the specified target substring with
  1756. * the specified replacement string in a string.
  1757. *
  1758. * @param original - String to search
  1759. * @param target - substring to find
  1760. * @param replacement - what to replace target substring with
  1761. * @return - return the modified string, if target substring not found return original string
  1762. */
  1763. public String replaceLast(String original, String target, String replacement) {
  1764. int index = original.lastIndexOf(target);
  1765. if(index == -1) {
  1766. return original;
  1767. }
  1768. return original.substring(0, index) + replacement + original.substring(index + target.length());
  1769. }
  1770. /**
  1771. * Retrieves the String value for the parameter
  1772. * @param name Name of parameter
  1773. * @param defaultValue default value to return if no such parameter
  1774. * @return value of parameter or defaultValue
  1775. */
  1776. protected String getStringParameter(String name, String defaultValue) {
  1777. String parameter = getParameter(name);
  1778. if (parameter != null) {
  1779. return parameter;
  1780. }
  1781. return defaultValue;
  1782. }
  1783. /**
  1784. * Retrieves the boolean value for the parameter
  1785. * @param name Name of parameter
  1786. * @param defaultValue default value to return if no such parameter
  1787. * @return value of parameter or defaultValue
  1788. */
  1789. protected boolean getBooleanParameter(String name, boolean defaultValue) {
  1790. String parameter = getParameter(name);
  1791. if (parameter != null) {
  1792. return Boolean.parseBoolean(parameter);
  1793. }
  1794. return defaultValue;
  1795. }
  1796. /**
  1797. * Retrieves the int value for the applet
  1798. * @param name Name of parameter
  1799. * @param defaultValue default value to return if no such parameter
  1800. * @return value of parameter or defaultValue
  1801. */
  1802. protected int getIntParameter(String name, int defaultValue) {
  1803. String parameter = getParameter(name);
  1804. if (parameter != null) {
  1805. return Integer.parseInt(parameter);
  1806. }
  1807. return defaultValue;
  1808. }
  1809. /**
  1810. * Sets the error message and print debug information
  1811. *
  1812. * @param error Error message to print
  1813. */
  1814. protected void fatalErrorOccured(String error, Exception e) {
  1815. fatalError = true;
  1816. if (minimumJreNotFound) {
  1817. errorMessage = minimumJREMessage;
  1818. errorMessage[errorMessage.length-1] = error;
  1819. }
  1820. else if (certificateRefused) {
  1821. errorMessage = certificateRefusedMessage;
  1822. }
  1823. else {
  1824. errorMessage = genericErrorMessage;
  1825. errorMessage[errorMessage.length-1] = error;
  1826. }
  1827. System.out.println(error);
  1828. if(e != null) {
  1829. System.out.println(e.getMessage());
  1830. System.out.println(generateStacktrace(e));
  1831. }
  1832. repaint();
  1833. }
  1834. /**
  1835. * set the state of applet loader
  1836. * @param new state of applet loader
  1837. * */
  1838. protected void setState(int state) {
  1839. this.state = state;
  1840. if(debugMode) {
  1841. System.out.println(getDescriptionForState());
  1842. }
  1843. }
  1844. /**
  1845. * Utility method for sleeping
  1846. * Will only really sleep if debug has been enabled
  1847. * @param ms milliseconds to sleep
  1848. */
  1849. protected void debug_sleep(long ms) {
  1850. if(debugMode) {
  1851. sleep(ms);
  1852. }
  1853. }
  1854. /**
  1855. * Utility method for sleeping
  1856. * @param ms milliseconds to sleep
  1857. */
  1858. protected void sleep(long ms) {
  1859. try {
  1860. Thread.sleep(ms);
  1861. } catch (Exception e) {
  1862. /* ignored */
  1863. }
  1864. }
  1865. }