001 /* 002 * Copyright 2002-2004 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.springframework.richclient.util; 017 018 import java.io.File; 019 import java.io.IOException; 020 import java.lang.reflect.Constructor; 021 import java.lang.reflect.Field; 022 import java.lang.reflect.InvocationTargetException; 023 import java.lang.reflect.Method; 024 import java.net.URL; 025 026 import javax.swing.SwingUtilities; 027 028 /** 029 * BrowserLauncher is a class that provides one static method, openURL, which 030 * opens the default web browser for the current user of the system to the given 031 * URL. It may support other protocols depending on the system -- mailto, ftp, 032 * etc. -- but that has not been rigorously tested and is not guaranteed to 033 * work. 034 * <p> 035 * Yes, this is platform-specific code, and yes, it may rely on classes on 036 * certain platforms that are not part of the standard JDK. What we're trying to 037 * do, though, is to take something that's frequently desirable but inherently 038 * platform-specific -- opening a default browser -- and allow programmers (you, 039 * for example) to do so without worrying about dropping into native code or 040 * doing anything else similarly evil. 041 * <p> 042 * Anyway, this code is completely in Java and will run on all JDK 1.1-compliant 043 * systems without modification or a need for additional libraries. All classes 044 * that are required on certain platforms to allow this to run are dynamically 045 * loaded at runtime via reflection and, if not found, will not cause this to do 046 * anything other than returning an error when opening the browser. 047 * <p> 048 * There are certain system requirements for this class, as it's running through 049 * Runtime.exec(), which is Java's way of making a native system call. 050 * Currently, this requires that a Macintosh have a Finder which supports the 051 * GURL event, which is true for Mac OS 8.0 and 8.1 systems that have the 052 * Internet Scripting AppleScript dictionary installed in the Scripting 053 * Additions folder in the Extensions folder (which is installed by default as 054 * far as I know under Mac OS 8.0 and 8.1), and for all Mac OS 8.5 and later 055 * systems. On Windows, it only runs under Win32 systems (Windows 95, 98, and NT 056 * 4.0, as well as later versions of all). On other systems, this drops back 057 * from the inherently platform-sensitive concept of a default browser and 058 * simply attempts to launch firefox via a shell command. 059 * <p> 060 * This code is Copyright 1999-2001 by Eric Albert (ejalbert@cs.stanford.edu) 061 * and may be redistributed or modified in any form without restrictions as long 062 * as the portion of this comment from this paragraph through the end of the 063 * comment is not removed. The author requests that he be notified of any 064 * application, applet, or other binary that makes use of this code, but that's 065 * more out of curiosity than anything and is not required. This software 066 * includes no warranty. The author is not repsonsible for any loss of data or 067 * functionality or any adverse or unexpected effects of using this software. 068 * <p> 069 * Credits: <br> 070 * Steven Spencer, JavaWorld magazine ( <a 071 * href="http://www.javaworld.com/javaworld/javatips/jw-javatip66.html">Java Tip 072 * 66 </a>) <br> 073 * Thanks also to Ron B. Yeh, Eric Shapiro, Ben Engber, Paul Teitlebaum, Andrea 074 * Cantatore, Larry Barowski, Trevor Bedzek, Frank Miedrich, and Ron Rabakukk 075 * 076 * @author Eric Albert ( <a 077 * href="mailto:ejalbert@cs.stanford.edu">ejalbert@cs.stanford.edu </a>) 078 * @version 1.4b1 (Released June 20, 2001) 079 * 080 * @deprecated use JDIC (https://jdic.dev.java.net/) or the Desktop class in 081 * java 1.6 instead 082 */ 083 public class BrowserLauncher { 084 085 /** 086 * The Java virtual machine that we are running on. Actually, in most cases 087 * we only care about the operating system, but some operating systems 088 * require us to switch on the VM. 089 */ 090 private static int jvm; 091 092 /** The browser for the system */ 093 private static Object browser; 094 095 /** 096 * Caches whether any classes, methods, and fields that are not part of the 097 * JDK and need to be dynamically loaded at runtime loaded successfully. 098 * <p> 099 * Note that if this is <code>false</code>,<code>openURL()</code> will 100 * always return an IOException. 101 */ 102 private static boolean loadedWithoutErrors; 103 104 /** The com.apple.mrj.MRJFileUtils class */ 105 private static Class mrjFileUtilsClass; 106 107 /** The com.apple.mrj.MRJOSType class */ 108 private static Class mrjOSTypeClass; 109 110 /** The com.apple.MacOS.AEDesc class */ 111 private static Class aeDescClass; 112 113 /** The <init>(int) method of com.apple.MacOS.AETarget */ 114 private static Constructor aeTargetConstructor; 115 116 /** The <init>(int, int, int) method of com.apple.MacOS.AppleEvent */ 117 private static Constructor appleEventConstructor; 118 119 /** The <init>(String) method of com.apple.MacOS.AEDesc */ 120 private static Constructor aeDescConstructor; 121 122 /** The findFolder method of com.apple.mrj.MRJFileUtils */ 123 private static Method findFolder; 124 125 /** The getFileCreator method of com.apple.mrj.MRJFileUtils */ 126 private static Method getFileCreator; 127 128 /** The getFileType method of com.apple.mrj.MRJFileUtils */ 129 private static Method getFileType; 130 131 /** The openURL method of com.apple.mrj.MRJFileUtils */ 132 private static Method openURL; 133 134 /** The makeOSType method of com.apple.MacOS.OSUtils */ 135 private static Method makeOSType; 136 137 /** The putParameter method of com.apple.MacOS.AppleEvent */ 138 private static Method putParameter; 139 140 /** The sendNoReply method of com.apple.MacOS.AppleEvent */ 141 private static Method sendNoReply; 142 143 /** Actually an MRJOSType pointing to the System Folder on a Macintosh */ 144 private static Object kSystemFolderType; 145 146 /** The keyDirectObject AppleEvent parameter type */ 147 private static Integer keyDirectObject; 148 149 /** The kAutoGenerateReturnID AppleEvent code */ 150 private static Integer kAutoGenerateReturnID; 151 152 /** The kAnyTransactionID AppleEvent code */ 153 private static Integer kAnyTransactionID; 154 155 /** JVM constant for MRJ 2.0 */ 156 private static final int MRJ_2_0 = 0; 157 158 /** JVM constant for MRJ 2.1 or later */ 159 private static final int MRJ_2_1 = 1; 160 161 /** JVM constant for Java on Mac OS X 10.0 (MRJ 3.0) */ 162 private static final int MRJ_3_0 = 3; 163 164 /** JVM constant for MRJ 3.1 */ 165 private static final int MRJ_3_1 = 4; 166 167 /** JVM constant for any Windows NT JVM */ 168 private static final int WINDOWS_NT = 5; 169 170 /** JVM constant for any Windows 9x JVM */ 171 private static final int WINDOWS_9x = 6; 172 173 /** JVM constant for any other platform */ 174 private static final int OTHER = -1; 175 176 /** 177 * The file type of the Finder on a Macintosh. Hardcoding "Finder" would 178 * keep non-U.S. English systems from working properly. 179 */ 180 private static final String FINDER_TYPE = "FNDR"; 181 182 /** 183 * The creator code of the Finder on a Macintosh, which is needed to send 184 * AppleEvents to the application. 185 */ 186 private static final String FINDER_CREATOR = "MACS"; 187 188 /** The name for the AppleEvent type corresponding to a GetURL event. */ 189 private static final String GURL_EVENT = "GURL"; 190 191 /** 192 * The first parameter that needs to be passed into Runtime.exec() to open 193 * the default web browser on Windows. 194 */ 195 private static final String FIRST_WINDOWS_PARAMETER = "/c"; 196 197 /** The second parameter for Runtime.exec() on Windows. */ 198 private static final String SECOND_WINDOWS_PARAMETER = "start"; 199 200 /** 201 * The third parameter for Runtime.exec() on Windows. This is a "title" 202 * parameter that the command line expects. Setting this parameter allows 203 * URLs containing spaces to work. 204 */ 205 private static final String THIRD_WINDOWS_PARAMETER = "\"\""; 206 207 /** 208 * The message from any exception thrown throughout the initialization 209 * process. 210 */ 211 private static String errorMessage; 212 213 /** 214 * An initialization block that determines the operating system and loads 215 * the necessary runtime data. 216 */ 217 static { 218 loadedWithoutErrors = true; 219 String osName = System.getProperty("os.name"); 220 if (osName.startsWith("Mac OS")) { 221 String mrjVersion = System.getProperty("mrj.version"); 222 String majorMRJVersion = mrjVersion.substring(0, 3); 223 try { 224 double version = Double.valueOf(majorMRJVersion).doubleValue(); 225 if (version == 2) { 226 jvm = MRJ_2_0; 227 } 228 else if (version >= 2.1 && version < 3) { 229 // Assume that all 2.x versions of MRJ work the same. MRJ 230 // 2.1 actually works via Runtime.exec() and 2.2 supports 231 // that but has an openURL() method as well that we 232 // currently ignore. 233 jvm = MRJ_2_1; 234 } 235 else if (version == 3.0) { 236 jvm = MRJ_3_0; 237 } 238 else if (version >= 3.1) { 239 // Assume that all 3.1 and later versions of MRJ work the 240 // same. 241 jvm = MRJ_3_1; 242 } 243 else { 244 loadedWithoutErrors = false; 245 errorMessage = "Unsupported MRJ version: " + version; 246 } 247 } 248 catch (NumberFormatException nfe) { 249 loadedWithoutErrors = false; 250 errorMessage = "Invalid MRJ version: " + mrjVersion; 251 } 252 } 253 else if (osName.startsWith("Windows")) { 254 if (osName.indexOf("9") != -1) { 255 jvm = WINDOWS_9x; 256 } 257 else { 258 jvm = WINDOWS_NT; 259 } 260 } 261 else { 262 jvm = OTHER; 263 } 264 265 if (loadedWithoutErrors) {// if we haven't hit any errors yet 266 loadedWithoutErrors = loadClasses(); 267 } 268 } 269 270 /** 271 * This class should be never be instantiated; this just ensures so. 272 */ 273 private BrowserLauncher() { 274 } 275 276 /** 277 * Called by a static initializer to load any classes, fields, and methods 278 * required at runtime to locate the user's web browser. 279 * 280 * @return <code>true</code> if all intialization succeeded 281 * <code>false</code> if any portion of the initialization failed 282 */ 283 private static boolean loadClasses() { 284 switch (jvm) { 285 case MRJ_2_0: 286 try { 287 Class aeTargetClass = Class.forName("com.apple.MacOS.AETarget"); 288 Class osUtilsClass = Class.forName("com.apple.MacOS.OSUtils"); 289 Class appleEventClass = Class.forName("com.apple.MacOS.AppleEvent"); 290 Class aeClass = Class.forName("com.apple.MacOS.ae"); 291 aeDescClass = Class.forName("com.apple.MacOS.AEDesc"); 292 293 aeTargetConstructor = aeTargetClass.getDeclaredConstructor(new Class[] { int.class }); 294 appleEventConstructor = appleEventClass.getDeclaredConstructor(new Class[] { int.class, int.class, 295 aeTargetClass, int.class, int.class }); 296 aeDescConstructor = aeDescClass.getDeclaredConstructor(new Class[] { String.class }); 297 298 makeOSType = osUtilsClass.getDeclaredMethod("makeOSType", new Class[] { String.class }); 299 putParameter = appleEventClass 300 .getDeclaredMethod("putParameter", new Class[] { int.class, aeDescClass }); 301 sendNoReply = appleEventClass.getDeclaredMethod("sendNoReply", new Class[] {}); 302 303 Field keyDirectObjectField = aeClass.getDeclaredField("keyDirectObject"); 304 keyDirectObject = (Integer) keyDirectObjectField.get(null); 305 Field autoGenerateReturnIDField = appleEventClass.getDeclaredField("kAutoGenerateReturnID"); 306 kAutoGenerateReturnID = (Integer) autoGenerateReturnIDField.get(null); 307 Field anyTransactionIDField = appleEventClass.getDeclaredField("kAnyTransactionID"); 308 kAnyTransactionID = (Integer) anyTransactionIDField.get(null); 309 } 310 catch (ClassNotFoundException cnfe) { 311 errorMessage = cnfe.getMessage(); 312 return false; 313 } 314 catch (NoSuchMethodException nsme) { 315 errorMessage = nsme.getMessage(); 316 return false; 317 } 318 catch (NoSuchFieldException nsfe) { 319 errorMessage = nsfe.getMessage(); 320 return false; 321 } 322 catch (IllegalAccessException iae) { 323 errorMessage = iae.getMessage(); 324 return false; 325 } 326 break; 327 case MRJ_2_1: 328 try { 329 mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils"); 330 mrjOSTypeClass = Class.forName("com.apple.mrj.MRJOSType"); 331 Field systemFolderField = mrjFileUtilsClass.getDeclaredField("kSystemFolderType"); 332 kSystemFolderType = systemFolderField.get(null); 333 findFolder = mrjFileUtilsClass.getDeclaredMethod("findFolder", new Class[] { mrjOSTypeClass }); 334 getFileCreator = mrjFileUtilsClass.getDeclaredMethod("getFileCreator", new Class[] { File.class }); 335 getFileType = mrjFileUtilsClass.getDeclaredMethod("getFileType", new Class[] { File.class }); 336 } 337 catch (ClassNotFoundException cnfe) { 338 errorMessage = cnfe.getMessage(); 339 return false; 340 } 341 catch (NoSuchFieldException nsfe) { 342 errorMessage = nsfe.getMessage(); 343 return false; 344 } 345 catch (NoSuchMethodException nsme) { 346 errorMessage = nsme.getMessage(); 347 return false; 348 } 349 catch (SecurityException se) { 350 errorMessage = se.getMessage(); 351 return false; 352 } 353 catch (IllegalAccessException iae) { 354 errorMessage = iae.getMessage(); 355 return false; 356 } 357 break; 358 case MRJ_3_0: 359 try { 360 Class linker = Class.forName("com.apple.mrj.jdirect.Linker"); 361 Constructor constructor = linker.getConstructor(new Class[] { Class.class }); 362 constructor.newInstance(new Object[] { BrowserLauncher.class }); 363 } 364 catch (ClassNotFoundException cnfe) { 365 errorMessage = cnfe.getMessage(); 366 return false; 367 } 368 catch (NoSuchMethodException nsme) { 369 errorMessage = nsme.getMessage(); 370 return false; 371 } 372 catch (InvocationTargetException ite) { 373 errorMessage = ite.getMessage(); 374 return false; 375 } 376 catch (InstantiationException ie) { 377 errorMessage = ie.getMessage(); 378 return false; 379 } 380 catch (IllegalAccessException iae) { 381 errorMessage = iae.getMessage(); 382 return false; 383 } 384 break; 385 case MRJ_3_1: 386 try { 387 mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils"); 388 openURL = mrjFileUtilsClass.getDeclaredMethod("openURL", new Class[] { String.class }); 389 } 390 catch (ClassNotFoundException cnfe) { 391 errorMessage = cnfe.getMessage(); 392 return false; 393 } 394 catch (NoSuchMethodException nsme) { 395 errorMessage = nsme.getMessage(); 396 return false; 397 } 398 break; 399 default: 400 break; 401 } 402 return true; 403 } 404 405 /** 406 * Attempts to locate the default web browser on the local system. Caches 407 * results so it only locates the browser once for each use of this class 408 * per JVM instance. 409 * 410 * @return The browser for the system. Note that this may not be what you 411 * would consider to be a standard web browser; instead, it's the 412 * application that gets called to open the default web browser. In some 413 * cases, this will be a non-String object that provides the means of 414 * calling the default browser. 415 */ 416 private static Object locateBrowser() { 417 if (browser != null) { 418 return browser; 419 } 420 switch (jvm) { 421 case MRJ_2_0: 422 try { 423 Integer finderCreatorCode = (Integer) makeOSType.invoke(null, new Object[] { FINDER_CREATOR }); 424 Object aeTarget = aeTargetConstructor.newInstance(new Object[] { finderCreatorCode }); 425 Integer gurlType = (Integer) makeOSType.invoke(null, new Object[] { GURL_EVENT }); 426 Object appleEvent = appleEventConstructor.newInstance(new Object[] { gurlType, gurlType, aeTarget, 427 kAutoGenerateReturnID, kAnyTransactionID }); 428 // Don't set browser = appleEvent because then the next time we 429 // call locateBrowser(), we'll get the same AppleEvent, to which 430 // we'll already have added the relevant parameter. Instead, 431 // regenerate the AppleEvent every time. 432 // There's probably a way to do this better; if any has any 433 // ideas, please let me know. 434 return appleEvent; 435 } 436 catch (IllegalAccessException iae) { 437 browser = null; 438 errorMessage = iae.getMessage(); 439 return browser; 440 } 441 catch (InstantiationException ie) { 442 browser = null; 443 errorMessage = ie.getMessage(); 444 return browser; 445 } 446 catch (InvocationTargetException ite) { 447 browser = null; 448 errorMessage = ite.getMessage(); 449 return browser; 450 } 451 case MRJ_2_1: 452 File systemFolder; 453 try { 454 systemFolder = (File) findFolder.invoke(null, new Object[] { kSystemFolderType }); 455 } 456 catch (IllegalArgumentException iare) { 457 browser = null; 458 errorMessage = iare.getMessage(); 459 return browser; 460 } 461 catch (IllegalAccessException iae) { 462 browser = null; 463 errorMessage = iae.getMessage(); 464 return browser; 465 } 466 catch (InvocationTargetException ite) { 467 browser = null; 468 errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage(); 469 return browser; 470 } 471 String[] systemFolderFiles = systemFolder.list(); 472 // Avoid a FilenameFilter because that can't be stopped mid-list 473 for (int i = 0; i < systemFolderFiles.length; i++) { 474 try { 475 File file = new File(systemFolder, systemFolderFiles[i]); 476 if (!file.isFile()) { 477 continue; 478 } 479 // We're looking for a file with a creator code of 'MACS' 480 // and a type of 'FNDR'. Only requiring the type results in 481 // non-Finder applications being picked up on certain Mac OS 482 // 9 systems, especially German ones, and sending a GURL 483 // event to those applications results in a logout under 484 // Multiple Users. 485 Object fileType = getFileType.invoke(null, new Object[] { file }); 486 if (FINDER_TYPE.equals(fileType.toString())) { 487 Object fileCreator = getFileCreator.invoke(null, new Object[] { file }); 488 if (FINDER_CREATOR.equals(fileCreator.toString())) { 489 browser = file.toString();// Actually the Finder, 490 // but that's OK 491 return browser; 492 } 493 } 494 } 495 catch (IllegalArgumentException iare) { 496 errorMessage = iare.getMessage(); 497 return null; 498 } 499 catch (IllegalAccessException iae) { 500 browser = null; 501 errorMessage = iae.getMessage(); 502 return browser; 503 } 504 catch (InvocationTargetException ite) { 505 browser = null; 506 errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage(); 507 return browser; 508 } 509 } 510 browser = null; 511 break; 512 case MRJ_3_0: 513 case MRJ_3_1: 514 browser = "";// Return something non-null 515 break; 516 case WINDOWS_NT: 517 browser = "cmd.exe"; 518 break; 519 case WINDOWS_9x: 520 browser = "command.com"; 521 break; 522 case OTHER: 523 default: 524 browser = "firefox"; 525 break; 526 } 527 return browser; 528 } 529 530 /** 531 * Attempts to open the default web browser to the given URL. 532 * 533 * @param url The URL to open 534 * @throws RuntimeException If the web browser could not be located or does 535 * not run 536 */ 537 public static void openURL(final URL url) { 538 Runnable opener = new Runnable() { 539 public void run() { 540 try { 541 String urlString = url.toExternalForm(); 542 543 if (!loadedWithoutErrors) { 544 throw new IOException("Exception in finding browser: " + errorMessage); 545 } 546 Object browser = locateBrowser(); 547 if (browser == null) { 548 throw new IOException("Unable to locate browser: " + errorMessage); 549 } 550 551 switch (jvm) { 552 case MRJ_2_0: 553 Object aeDesc = null; 554 try { 555 aeDesc = aeDescConstructor.newInstance(new Object[] { urlString }); 556 putParameter.invoke(browser, new Object[] { keyDirectObject, aeDesc }); 557 sendNoReply.invoke(browser, new Object[] {}); 558 } 559 catch (InvocationTargetException ite) { 560 throw new IOException("InvocationTargetException while creating AEDesc: " 561 + ite.getMessage()); 562 } 563 catch (IllegalAccessException iae) { 564 throw new IOException("IllegalAccessException while building AppleEvent: " 565 + iae.getMessage()); 566 } 567 catch (InstantiationException ie) { 568 throw new IOException("InstantiationException while creating AEDesc: " + ie.getMessage()); 569 } 570 finally { 571 // Encourage it to get disposed if it was created 572 aeDesc = null; 573 browser = null;// Ditto 574 } 575 break; 576 case MRJ_2_1: 577 Runtime.getRuntime().exec(new String[] { (String) browser, urlString }); 578 break; 579 case MRJ_3_0: 580 int[] instance = new int[1]; 581 int result = ICStart(instance, 0); 582 if (result == 0) { 583 int[] selectionStart = new int[] { 0 }; 584 byte[] urlBytes = urlString.getBytes(); 585 int[] selectionEnd = new int[] { urlBytes.length }; 586 result = ICLaunchURL(instance[0], new byte[] { 0 }, urlBytes, urlBytes.length, 587 selectionStart, selectionEnd); 588 if (result == 0) { 589 // Ignore the return value; the URL was launched 590 // successfully 591 // regardless of what happens here. 592 ICStop(instance); 593 } 594 else { 595 throw new IOException("Unable to launch URL: " + result); 596 } 597 } 598 else { 599 throw new IOException("Unable to create an Internet Config instance: " + result); 600 } 601 break; 602 case MRJ_3_1: 603 try { 604 openURL.invoke(null, new Object[] { urlString }); 605 } 606 catch (InvocationTargetException ite) { 607 throw new IOException("InvocationTargetException while calling openURL: " 608 + ite.getMessage()); 609 } 610 catch (IllegalAccessException iae) { 611 throw new IOException("IllegalAccessException while calling openURL: " + iae.getMessage()); 612 } 613 break; 614 case WINDOWS_NT: 615 case WINDOWS_9x: 616 // Add quotes around the URL to allow ampersands and 617 // other 618 // special 619 // characters to work. 620 Process process = Runtime.getRuntime().exec( 621 new String[] { (String) browser, FIRST_WINDOWS_PARAMETER, SECOND_WINDOWS_PARAMETER, 622 THIRD_WINDOWS_PARAMETER, '"' + urlString + '"' }); 623 // This avoids a memory leak on some versions of Java on 624 // Windows. 625 // That's hinted at in 626 // <http://developer.java.sun.com/developer/qow/archive/68/>. 627 try { 628 process.waitFor(); 629 process.exitValue(); 630 } 631 catch (InterruptedException ie) { 632 throw new IOException("InterruptedException while launching browser: " + ie.getMessage()); 633 } 634 break; 635 case OTHER: 636 // Assume that we're on Unix and that firefox is 637 // installed 638 process = Runtime.getRuntime().exec(new String[] { (String) browser, urlString }); 639 try { 640 process.waitFor(); 641 process.exitValue(); 642 } 643 catch (InterruptedException ie) { 644 throw new IOException("InterruptedException while launching browser: " + ie.getMessage()); 645 } 646 break; 647 default: 648 // This should never occur, but if it does, we'll try 649 // the 650 // simplest thing possible 651 Runtime.getRuntime().exec(new String[] { (String) browser, urlString }); 652 break; 653 } 654 } 655 catch (IOException e) { 656 throw new RuntimeException(e.toString()); 657 } 658 659 } 660 }; 661 if (SwingUtilities.isEventDispatchThread()) { 662 new Thread(opener).start(); 663 } 664 else { 665 opener.run(); 666 } 667 } 668 669 /** 670 * Methods required for Mac OS X. The presence of native methods does not 671 * cause any problems on other platforms. 672 */ 673 private native static int ICStart(int[] instance, int signature); 674 675 private native static int ICStop(int[] instance); 676 677 private native static int ICLaunchURL(int instance, byte[] hint, byte[] data, int len, int[] selectionStart, 678 int[] selectionEnd); 679 680 public static void main(String[] args) throws Exception { 681 BrowserLauncher.openURL(new URL("http://www.google.com")); 682 } 683 }