1 /* 2 * Copyright (c) 1995, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.applet; 27 28 import java.io.BufferedInputStream; 29 import java.io.EOFException; 30 import java.io.File; 31 import java.io.FilePermission; 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.net.MalformedURLException; 35 import java.net.URL; 36 import java.net.URLClassLoader; 37 import java.net.URLConnection; 38 import java.security.AccessControlContext; 39 import java.security.AccessController; 40 import java.security.CodeSource; 41 import java.security.Permission; 42 import java.security.PermissionCollection; 43 import java.security.PrivilegedAction; 44 import java.security.PrivilegedActionException; 45 import java.security.PrivilegedExceptionAction; 46 import java.util.Enumeration; 47 import java.util.HashMap; 48 import java.util.NoSuchElementException; 49 50 import sun.awt.AppContext; 51 import sun.awt.SunToolkit; 52 import sun.net.www.ParseUtil; 53 import sun.security.util.SecurityConstants; 54 55 /** 56 * This class defines the class loader for loading applet classes and 57 * resources. It extends URLClassLoader to search the applet code base 58 * for the class or resource after checking any loaded JAR files. 59 */ 60 public class AppletClassLoader extends URLClassLoader { 61 private URL base; /* applet code base URL */ 62 private CodeSource codesource; /* codesource for the base URL */ 63 private AccessControlContext acc; 64 private boolean exceptionStatus = false; 65 66 private final Object threadGroupSynchronizer = new Object(); 67 private final Object grabReleaseSynchronizer = new Object(); 68 69 private boolean codebaseLookup = true; 70 private volatile boolean allowRecursiveDirectoryRead = true; 71 72 /* 73 * Creates a new AppletClassLoader for the specified base URL. 74 */ 75 protected AppletClassLoader(URL base) { 76 super(new URL[0]); 77 this.base = base; 78 this.codesource = 79 new CodeSource(base, (java.security.cert.Certificate[]) null); 80 acc = AccessController.getContext(); 81 } 82 83 public void disableRecursiveDirectoryRead() { 84 allowRecursiveDirectoryRead = false; 85 } 86 87 88 /** 89 * Set the codebase lookup flag. 90 */ 91 void setCodebaseLookup(boolean codebaseLookup) { 92 this.codebaseLookup = codebaseLookup; 93 } 94 95 /* 96 * Returns the applet code base URL. 97 */ 98 URL getBaseURL() { 99 return base; 100 } 101 102 /* 103 * Returns the URLs used for loading classes and resources. 104 */ 105 public URL[] getURLs() { 106 URL[] jars = super.getURLs(); 107 URL[] urls = new URL[jars.length + 1]; 108 System.arraycopy(jars, 0, urls, 0, jars.length); 109 urls[urls.length - 1] = base; 110 return urls; 111 } 112 113 /* 114 * Adds the specified JAR file to the search path of loaded JAR files. 115 * Changed modifier to protected in order to be able to overwrite addJar() 116 * in PluginClassLoader.java 117 */ 118 protected void addJar(String name) throws IOException { 119 URL url; 120 try { 121 url = new URL(base, name); 122 } catch (MalformedURLException e) { 123 throw new IllegalArgumentException("name"); 124 } 125 addURL(url); 126 // DEBUG 127 //URL[] urls = getURLs(); 128 //for (int i = 0; i < urls.length; i++) { 129 // System.out.println("url[" + i + "] = " + urls[i]); 130 //} 131 } 132 133 /* 134 * Override loadClass so that class loading errors can be caught in 135 * order to print better error messages. 136 */ 137 public synchronized Class<?> loadClass(String name, boolean resolve) 138 throws ClassNotFoundException 139 { 140 // First check if we have permission to access the package. This 141 // should go away once we've added support for exported packages. 142 int i = name.lastIndexOf('.'); 143 if (i != -1) { 144 SecurityManager sm = System.getSecurityManager(); 145 if (sm != null) 146 sm.checkPackageAccess(name.substring(0, i)); 147 } 148 try { 149 return super.loadClass(name, resolve); 150 } catch (ClassNotFoundException e) { 151 throw e; 152 } catch (RuntimeException e) { 153 throw e; 154 } catch (Error e) { 155 throw e; 156 } 157 } 158 159 /* 160 * Finds the applet class with the specified name. First searches 161 * loaded JAR files then the applet code base for the class. 162 */ 163 protected Class<?> findClass(String name) throws ClassNotFoundException { 164 165 int index = name.indexOf(';'); 166 String cookie = ""; 167 if(index != -1) { 168 cookie = name.substring(index, name.length()); 169 name = name.substring(0, index); 170 } 171 172 // check loaded JAR files 173 try { 174 return super.findClass(name); 175 } catch (ClassNotFoundException e) { 176 } 177 178 // Otherwise, try loading the class from the code base URL 179 180 // 4668479: Option to turn off codebase lookup in AppletClassLoader 181 // during resource requests. [stanley.ho] 182 if (codebaseLookup == false) 183 throw new ClassNotFoundException(name); 184 185 // final String path = name.replace('.', '/').concat(".class").concat(cookie); 186 String encodedName = ParseUtil.encodePath(name.replace('.', '/'), false); 187 final String path = (new StringBuffer(encodedName)).append(".class").append(cookie).toString(); 188 try { 189 byte[] b = AccessController.doPrivileged( 190 new PrivilegedExceptionAction<byte[]>() { 191 public byte[] run() throws IOException { 192 try { 193 URL finalURL = new URL(base, path); 194 195 // Make sure the codebase won't be modified 196 if (base.getProtocol().equals(finalURL.getProtocol()) && 197 base.getHost().equals(finalURL.getHost()) && 198 base.getPort() == finalURL.getPort()) { 199 return getBytes(finalURL); 200 } 201 else { 202 return null; 203 } 204 } catch (Exception e) { 205 return null; 206 } 207 } 208 }, acc); 209 210 if (b != null) { 211 return defineClass(name, b, 0, b.length, codesource); 212 } else { 213 throw new ClassNotFoundException(name); 214 } 215 } catch (PrivilegedActionException e) { 216 throw new ClassNotFoundException(name, e.getException()); 217 } 218 } 219 220 /** 221 * Returns the permissions for the given codesource object. 222 * The implementation of this method first calls super.getPermissions, 223 * to get the permissions 224 * granted by the super class, and then adds additional permissions 225 * based on the URL of the codesource. 226 * <p> 227 * If the protocol is "file" 228 * and the path specifies a file, permission is granted to read all files 229 * and (recursively) all files and subdirectories contained in 230 * that directory. This is so applets with a codebase of 231 * file:/blah/some.jar can read in file:/blah/, which is needed to 232 * be backward compatible. We also add permission to connect back to 233 * the "localhost". 234 * 235 * @param codesource the codesource 236 * @throws NullPointerException if {@code codesource} is {@code null}. 237 * @return the permissions granted to the codesource 238 */ 239 protected PermissionCollection getPermissions(CodeSource codesource) 240 { 241 final PermissionCollection perms = super.getPermissions(codesource); 242 243 URL url = codesource.getLocation(); 244 245 String path = null; 246 Permission p; 247 248 try { 249 p = url.openConnection().getPermission(); 250 } catch (java.io.IOException ioe) { 251 p = null; 252 } 253 254 if (p instanceof FilePermission) { 255 path = p.getName(); 256 } else if ((p == null) && (url.getProtocol().equals("file"))) { 257 path = url.getFile().replace('/', File.separatorChar); 258 path = ParseUtil.decode(path); 259 } 260 261 if (path != null) { 262 final String rawPath = path; 263 if (!path.endsWith(File.separator)) { 264 int endIndex = path.lastIndexOf(File.separatorChar); 265 if (endIndex != -1) { 266 path = path.substring(0, endIndex + 1) + "-"; 267 perms.add(new FilePermission(path, 268 SecurityConstants.FILE_READ_ACTION)); 269 } 270 } 271 final File f = new File(rawPath); 272 final boolean isDirectory = f.isDirectory(); 273 // grant codebase recursive read permission 274 // this should only be granted to non-UNC file URL codebase and 275 // the codesource path must either be a directory, or a file 276 // that ends with .jar or .zip 277 if (allowRecursiveDirectoryRead && (isDirectory || 278 rawPath.toLowerCase().endsWith(".jar") || 279 rawPath.toLowerCase().endsWith(".zip"))) { 280 281 Permission bperm; 282 try { 283 bperm = base.openConnection().getPermission(); 284 } catch (java.io.IOException ioe) { 285 bperm = null; 286 } 287 if (bperm instanceof FilePermission) { 288 String bpath = bperm.getName(); 289 if (bpath.endsWith(File.separator)) { 290 bpath += "-"; 291 } 292 perms.add(new FilePermission(bpath, 293 SecurityConstants.FILE_READ_ACTION)); 294 } else if ((bperm == null) && (base.getProtocol().equals("file"))) { 295 String bpath = base.getFile().replace('/', File.separatorChar); 296 bpath = ParseUtil.decode(bpath); 297 if (bpath.endsWith(File.separator)) { 298 bpath += "-"; 299 } 300 perms.add(new FilePermission(bpath, SecurityConstants.FILE_READ_ACTION)); 301 } 302 303 } 304 } 305 return perms; 306 } 307 308 /* 309 * Returns the contents of the specified URL as an array of bytes. 310 */ 311 private static byte[] getBytes(URL url) throws IOException { 312 URLConnection uc = url.openConnection(); 313 if (uc instanceof java.net.HttpURLConnection) { 314 java.net.HttpURLConnection huc = (java.net.HttpURLConnection) uc; 315 int code = huc.getResponseCode(); 316 if (code >= java.net.HttpURLConnection.HTTP_BAD_REQUEST) { 317 throw new IOException("open HTTP connection failed."); 318 } 319 } 320 int len = uc.getContentLength(); 321 322 // Fixed #4507227: Slow performance to load 323 // class and resources. [stanleyh] 324 // 325 // Use buffered input stream [stanleyh] 326 InputStream in = new BufferedInputStream(uc.getInputStream()); 327 328 byte[] b; 329 try { 330 b = in.readAllBytes(); 331 if (len != -1 && b.length != len) 332 throw new EOFException("Expected:" + len + ", read:" + b.length); 333 } finally { 334 in.close(); 335 } 336 return b; 337 } 338 339 // Object for synchronization around getResourceAsStream() 340 private Object syncResourceAsStream = new Object(); 341 private Object syncResourceAsStreamFromJar = new Object(); 342 343 // Flag to indicate getResourceAsStream() is in call 344 private boolean resourceAsStreamInCall = false; 345 private boolean resourceAsStreamFromJarInCall = false; 346 347 /** 348 * Returns an input stream for reading the specified resource. 349 * 350 * The search order is described in the documentation for {@link 351 * #getResource(String)}.<p> 352 * 353 * @param name the resource name 354 * @return an input stream for reading the resource, or {@code null} 355 * if the resource could not be found 356 * @since 1.1 357 */ 358 public InputStream getResourceAsStream(String name) 359 { 360 361 if (name == null) { 362 throw new NullPointerException("name"); 363 } 364 365 try 366 { 367 InputStream is = null; 368 369 // Fixed #4507227: Slow performance to load 370 // class and resources. [stanleyh] 371 // 372 // The following is used to avoid calling 373 // AppletClassLoader.findResource() in 374 // super.getResourceAsStream(). Otherwise, 375 // unnecessary connection will be made. 376 // 377 synchronized(syncResourceAsStream) 378 { 379 resourceAsStreamInCall = true; 380 381 // Call super class 382 is = super.getResourceAsStream(name); 383 384 resourceAsStreamInCall = false; 385 } 386 387 // 4668479: Option to turn off codebase lookup in AppletClassLoader 388 // during resource requests. [stanley.ho] 389 if (codebaseLookup == true && is == null) 390 { 391 // If resource cannot be obtained, 392 // try to download it from codebase 393 URL url = new URL(base, ParseUtil.encodePath(name, false)); 394 is = url.openStream(); 395 } 396 397 return is; 398 } 399 catch (Exception e) 400 { 401 return null; 402 } 403 } 404 405 406 /** 407 * Returns an input stream for reading the specified resource from the 408 * the loaded jar files. 409 * 410 * The search order is described in the documentation for {@link 411 * #getResource(String)}.<p> 412 * 413 * @param name the resource name 414 * @return an input stream for reading the resource, or {@code null} 415 * if the resource could not be found 416 * @since 1.1 417 */ 418 public InputStream getResourceAsStreamFromJar(String name) { 419 420 if (name == null) { 421 throw new NullPointerException("name"); 422 } 423 424 try { 425 InputStream is = null; 426 synchronized(syncResourceAsStreamFromJar) { 427 resourceAsStreamFromJarInCall = true; 428 // Call super class 429 is = super.getResourceAsStream(name); 430 resourceAsStreamFromJarInCall = false; 431 } 432 433 return is; 434 } catch (Exception e) { 435 return null; 436 } 437 } 438 439 440 /* 441 * Finds the applet resource with the specified name. First checks 442 * loaded JAR files then the applet code base for the resource. 443 */ 444 public URL findResource(String name) { 445 // check loaded JAR files 446 URL url = super.findResource(name); 447 448 // 6215746: Disable META-INF/* lookup from codebase in 449 // applet/plugin classloader. [stanley.ho] 450 if (name.startsWith("META-INF/")) 451 return url; 452 453 // 4668479: Option to turn off codebase lookup in AppletClassLoader 454 // during resource requests. [stanley.ho] 455 if (codebaseLookup == false) 456 return url; 457 458 if (url == null) 459 { 460 //#4805170, if it is a call from Applet.getImage() 461 //we should check for the image only in the archives 462 boolean insideGetResourceAsStreamFromJar = false; 463 synchronized(syncResourceAsStreamFromJar) { 464 insideGetResourceAsStreamFromJar = resourceAsStreamFromJarInCall; 465 } 466 467 if (insideGetResourceAsStreamFromJar) { 468 return null; 469 } 470 471 // Fixed #4507227: Slow performance to load 472 // class and resources. [stanleyh] 473 // 474 // Check if getResourceAsStream is called. 475 // 476 boolean insideGetResourceAsStream = false; 477 478 synchronized(syncResourceAsStream) 479 { 480 insideGetResourceAsStream = resourceAsStreamInCall; 481 } 482 483 // If getResourceAsStream is called, don't 484 // trigger the following code. Otherwise, 485 // unnecessary connection will be made. 486 // 487 if (insideGetResourceAsStream == false) 488 { 489 // otherwise, try the code base 490 try { 491 url = new URL(base, ParseUtil.encodePath(name, false)); 492 // check if resource exists 493 if(!resourceExists(url)) 494 url = null; 495 } catch (Exception e) { 496 // all exceptions, including security exceptions, are caught 497 url = null; 498 } 499 } 500 } 501 return url; 502 } 503 504 505 private boolean resourceExists(URL url) { 506 // Check if the resource exists. 507 // It almost works to just try to do an openConnection() but 508 // HttpURLConnection will return true on HTTP_BAD_REQUEST 509 // when the requested name ends in ".html", ".htm", and ".txt" 510 // and we want to be able to handle these 511 // 512 // Also, cannot just open a connection for things like FileURLConnection, 513 // because they succeed when connecting to a nonexistent file. 514 // So, in those cases we open and close an input stream. 515 boolean ok = true; 516 try { 517 URLConnection conn = url.openConnection(); 518 if (conn instanceof java.net.HttpURLConnection) { 519 java.net.HttpURLConnection hconn = 520 (java.net.HttpURLConnection) conn; 521 522 // To reduce overhead, using http HEAD method instead of GET method 523 hconn.setRequestMethod("HEAD"); 524 525 int code = hconn.getResponseCode(); 526 if (code == java.net.HttpURLConnection.HTTP_OK) { 527 return true; 528 } 529 if (code >= java.net.HttpURLConnection.HTTP_BAD_REQUEST) { 530 return false; 531 } 532 } else { 533 /** 534 * Fix for #4182052 - stanleyh 535 * 536 * The same connection should be reused to avoid multiple 537 * HTTP connections 538 */ 539 540 // our best guess for the other cases 541 InputStream is = conn.getInputStream(); 542 is.close(); 543 } 544 } catch (Exception ex) { 545 ok = false; 546 } 547 return ok; 548 } 549 550 /* 551 * Returns an enumeration of all the applet resources with the specified 552 * name. First checks loaded JAR files then the applet code base for all 553 * available resources. 554 */ 555 @Override 556 public Enumeration<URL> findResources(String name) throws IOException { 557 558 final Enumeration<URL> e = super.findResources(name); 559 560 // 6215746: Disable META-INF/* lookup from codebase in 561 // applet/plugin classloader. [stanley.ho] 562 if (name.startsWith("META-INF/")) 563 return e; 564 565 // 4668479: Option to turn off codebase lookup in AppletClassLoader 566 // during resource requests. [stanley.ho] 567 if (codebaseLookup == false) 568 return e; 569 570 URL u = new URL(base, ParseUtil.encodePath(name, false)); 571 if (!resourceExists(u)) { 572 u = null; 573 } 574 575 final URL url = u; 576 return new Enumeration<URL>() { 577 private boolean done; 578 public URL nextElement() { 579 if (!done) { 580 if (e.hasMoreElements()) { 581 return e.nextElement(); 582 } 583 done = true; 584 if (url != null) { 585 return url; 586 } 587 } 588 throw new NoSuchElementException(); 589 } 590 public boolean hasMoreElements() { 591 return !done && (e.hasMoreElements() || url != null); 592 } 593 }; 594 } 595 596 /* 597 * Load and resolve the file specified by the applet tag CODE 598 * attribute. The argument can either be the relative path 599 * of the class file itself or just the name of the class. 600 */ 601 Class<?> loadCode(String name) throws ClassNotFoundException { 602 // first convert any '/' or native file separator to . 603 name = name.replace('/', '.'); 604 name = name.replace(File.separatorChar, '.'); 605 606 // deal with URL rewriting 607 String cookie = null; 608 int index = name.indexOf(';'); 609 if(index != -1) { 610 cookie = name.substring(index, name.length()); 611 name = name.substring(0, index); 612 } 613 614 // save that name for later 615 String fullName = name; 616 // then strip off any suffixes 617 if (name.endsWith(".class") || name.endsWith(".java")) { 618 name = name.substring(0, name.lastIndexOf('.')); 619 } 620 try { 621 if(cookie != null) 622 name = (new StringBuffer(name)).append(cookie).toString(); 623 return loadClass(name); 624 } catch (ClassNotFoundException e) { 625 } 626 // then if it didn't end with .java or .class, or in the 627 // really pathological case of a class named class or java 628 if(cookie != null) 629 fullName = (new StringBuffer(fullName)).append(cookie).toString(); 630 631 return loadClass(fullName); 632 } 633 634 /* 635 * The threadgroup that the applets loaded by this classloader live 636 * in. In the sun.* implementation of applets, the security manager's 637 * (AppletSecurity) getThreadGroup returns the thread group of the 638 * first applet on the stack, which is the applet's thread group. 639 */ 640 private AppletThreadGroup threadGroup; 641 private AppContext appContext; 642 643 public ThreadGroup getThreadGroup() { 644 synchronized (threadGroupSynchronizer) { 645 if (threadGroup == null || threadGroup.isDestroyed()) { 646 AccessController.doPrivileged(new PrivilegedAction<Object>() { 647 public Object run() { 648 threadGroup = new AppletThreadGroup(base + "-threadGroup"); 649 // threadGroup.setDaemon(true); 650 // threadGroup is now destroyed by AppContext.dispose() 651 652 // Create the new AppContext from within a Thread belonging 653 // to the newly created ThreadGroup, and wait for the 654 // creation to complete before returning from this method. 655 AppContextCreator creatorThread = new AppContextCreator(threadGroup); 656 657 // Since this thread will later be used to launch the 658 // applet's AWT-event dispatch thread and we want the applet 659 // code executing the AWT callbacks to use their own class 660 // loader rather than the system class loader, explicitly 661 // set the context class loader to the AppletClassLoader. 662 creatorThread.setContextClassLoader(AppletClassLoader.this); 663 664 creatorThread.start(); 665 try { 666 synchronized(creatorThread.syncObject) { 667 while (!creatorThread.created) { 668 creatorThread.syncObject.wait(); 669 } 670 } 671 } catch (InterruptedException e) { } 672 appContext = creatorThread.appContext; 673 return null; 674 } 675 }); 676 } 677 return threadGroup; 678 } 679 } 680 681 /* 682 * Get the AppContext, if any, corresponding to this AppletClassLoader. 683 */ 684 public AppContext getAppContext() { 685 return appContext; 686 } 687 688 int usageCount = 0; 689 690 /** 691 * Grab this AppletClassLoader and its ThreadGroup/AppContext, so they 692 * won't be destroyed. 693 */ 694 public void grab() { 695 synchronized(grabReleaseSynchronizer) { 696 usageCount++; 697 } 698 getThreadGroup(); // Make sure ThreadGroup/AppContext exist 699 } 700 701 protected void setExceptionStatus() 702 { 703 exceptionStatus = true; 704 } 705 706 public boolean getExceptionStatus() 707 { 708 return exceptionStatus; 709 } 710 711 /** 712 * Release this AppletClassLoader and its ThreadGroup/AppContext. 713 * If nothing else has grabbed this AppletClassLoader, its ThreadGroup 714 * and AppContext will be destroyed. 715 * 716 * Because this method may destroy the AppletClassLoader's ThreadGroup, 717 * this method should NOT be called from within the AppletClassLoader's 718 * ThreadGroup. 719 * 720 * Changed modifier to protected in order to be able to overwrite this 721 * function in PluginClassLoader.java 722 */ 723 protected void release() { 724 725 AppContext tempAppContext = null; 726 727 synchronized(grabReleaseSynchronizer) { 728 if (usageCount > 1) { 729 --usageCount; 730 } else { 731 synchronized(threadGroupSynchronizer) { 732 tempAppContext = resetAppContext(); 733 } 734 } 735 } 736 737 // Dispose appContext outside any sync block to 738 // prevent potential deadlock. 739 if (tempAppContext != null) { 740 try { 741 tempAppContext.dispose(); // nuke the world! 742 } catch (IllegalThreadStateException e) { } 743 } 744 } 745 746 /* 747 * reset classloader's AppContext and ThreadGroup 748 * This method is for subclass PluginClassLoader to 749 * reset superclass's AppContext and ThreadGroup but do 750 * not dispose the AppContext. PluginClassLoader does not 751 * use UsageCount to decide whether to dispose AppContext 752 * 753 * @return previous AppContext 754 */ 755 protected AppContext resetAppContext() { 756 AppContext tempAppContext = null; 757 758 synchronized(threadGroupSynchronizer) { 759 // Store app context in temp variable 760 tempAppContext = appContext; 761 usageCount = 0; 762 appContext = null; 763 threadGroup = null; 764 } 765 return tempAppContext; 766 } 767 768 769 // Hash map to store applet compatibility info 770 private HashMap<String, Boolean> jdk11AppletInfo = new HashMap<>(); 771 private HashMap<String, Boolean> jdk12AppletInfo = new HashMap<>(); 772 773 /** 774 * Set applet target level as JDK 1.1. 775 * 776 * @param clazz Applet class. 777 * @param bool true if JDK is targeted for JDK 1.1; 778 * false otherwise. 779 */ 780 void setJDK11Target(Class<?> clazz, boolean bool) 781 { 782 jdk11AppletInfo.put(clazz.toString(), Boolean.valueOf(bool)); 783 } 784 785 /** 786 * Set applet target level as JDK 1.2. 787 * 788 * @param clazz Applet class. 789 * @param bool true if JDK is targeted for JDK 1.2; 790 * false otherwise. 791 */ 792 void setJDK12Target(Class<?> clazz, boolean bool) 793 { 794 jdk12AppletInfo.put(clazz.toString(), Boolean.valueOf(bool)); 795 } 796 797 /** 798 * Determine if applet is targeted for JDK 1.1. 799 * 800 * @param clazz Applet class. 801 * @return TRUE if applet is targeted for JDK 1.1; 802 * FALSE if applet is not; 803 * null if applet is unknown. 804 */ 805 Boolean isJDK11Target(Class<?> clazz) 806 { 807 return jdk11AppletInfo.get(clazz.toString()); 808 } 809 810 /** 811 * Determine if applet is targeted for JDK 1.2. 812 * 813 * @param clazz Applet class. 814 * @return TRUE if applet is targeted for JDK 1.2; 815 * FALSE if applet is not; 816 * null if applet is unknown. 817 */ 818 Boolean isJDK12Target(Class<?> clazz) 819 { 820 return jdk12AppletInfo.get(clazz.toString()); 821 } 822 } 823 824 /* 825 * The AppContextCreator class is used to create an AppContext from within 826 * a Thread belonging to the new AppContext's ThreadGroup. To wait for 827 * this operation to complete before continuing, wait for the notifyAll() 828 * operation on the syncObject to occur. 829 */ 830 class AppContextCreator extends Thread { 831 Object syncObject = new Object(); 832 AppContext appContext = null; 833 volatile boolean created = false; 834 835 /** 836 * Must call the 5-args super-class constructor to erase locals. 837 */ 838 private AppContextCreator() { 839 throw new UnsupportedOperationException("Must erase locals"); 840 } 841 842 AppContextCreator(ThreadGroup group) { 843 super(group, null, "AppContextCreator", 0, false); 844 } 845 846 public void run() { 847 appContext = SunToolkit.createNewAppContext(); 848 created = true; 849 synchronized(syncObject) { 850 syncObject.notifyAll(); 851 } 852 } // run() 853 854 } // class AppContextCreator