1 /* 2 * Copyright (c) 2007, 2010, 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.launcher; 27 28 /* 29 * 30 * <p><b>This is NOT part of any API supported by Sun Microsystems. 31 * If you write code that depends on this, you do so at your own 32 * risk. This code and its internal interfaces are subject to change 33 * or deletion without notice.</b> 34 * 35 */ 36 37 /** 38 * A utility package for the java(1), javaw(1) launchers. 39 * The following are helper methods that the native launcher uses 40 * to perform checks etc. using JNI, see src/share/bin/java.c 41 */ 42 import java.io.File; 43 import java.io.IOException; 44 import java.io.PrintStream; 45 import java.lang.reflect.Method; 46 import java.lang.reflect.Modifier; 47 import java.math.BigDecimal; 48 import java.math.RoundingMode; 49 import java.util.ResourceBundle; 50 import java.text.MessageFormat; 51 import java.util.ArrayList; 52 import java.util.Collections; 53 import java.util.Iterator; 54 import java.util.List; 55 import java.util.Locale; 56 import java.util.Locale.Category; 57 import java.util.Properties; 58 import java.util.Set; 59 import java.util.TreeSet; 60 import java.util.jar.Attributes; 61 import java.util.jar.JarFile; 62 import java.util.jar.Manifest; 63 64 public enum LauncherHelper { 65 INSTANCE; 66 private static final String defaultBundleName = 67 "sun.launcher.resources.launcher"; 68 private static final String MAIN_CLASS = "Main-Class"; 69 70 private static StringBuilder outBuf = new StringBuilder(); 71 72 private static ResourceBundle javarb = null; 73 74 private static final String INDENT = " "; 75 private static final String VM_SETTINGS = "VM settings:"; 76 private static final String PROP_SETTINGS = "Property settings:"; 77 private static final String LOCALE_SETTINGS = "Locale settings:"; 78 79 private static synchronized ResourceBundle getLauncherResourceBundle() { 80 if (javarb == null) { 81 javarb = ResourceBundle.getBundle(defaultBundleName); 82 } 83 return javarb; 84 } 85 86 /* 87 * A method called by the launcher to print out the standard settings, 88 * by default -XshowSettings is equivalent to -XshowSettings:all, 89 * Specific information may be gotten by using suboptions with possible 90 * values vm, properties and locale. 91 * 92 * printToStderr: choose between stdout and stderr 93 * 94 * optionFlag: specifies which options to print default is all other 95 * possible values are vm, properties, locale. 96 * 97 * initialHeapSize: in bytes, as set by the launcher, a zero-value indicates 98 * this code should determine this value, using a suitable method or 99 * the line could be omitted. 100 * 101 * maxHeapSize: in bytes, as set by the launcher, a zero-value indicates 102 * this code should determine this value, using a suitable method. 103 * 104 * stackSize: in bytes, as set by the launcher, a zero-value indicates 105 * this code determine this value, using a suitable method or omit the 106 * line entirely. 107 */ 108 static void showSettings(boolean printToStderr, String optionFlag, 109 long initialHeapSize, long maxHeapSize, long stackSize, 110 boolean isServer) { 111 112 PrintStream ostream = (printToStderr) ? System.err : System.out; 113 String opts[] = optionFlag.split(":"); 114 String optStr = (opts.length > 1 && opts[1] != null) 115 ? opts[1].trim() 116 : "all"; 117 switch (optStr) { 118 case "vm": 119 printVmSettings(ostream, initialHeapSize, maxHeapSize, 120 stackSize, isServer); 121 break; 122 case "properties": 123 printProperties(ostream); 124 break; 125 case "locale": 126 printLocale(ostream); 127 break; 128 default: 129 printVmSettings(ostream, initialHeapSize, maxHeapSize, 130 stackSize, isServer); 131 printProperties(ostream); 132 printLocale(ostream); 133 break; 134 } 135 } 136 137 /* 138 * prints the main vm settings subopt/section 139 */ 140 private static void printVmSettings(PrintStream ostream, 141 long initialHeapSize, long maxHeapSize, 142 long stackSize, boolean isServer) { 143 144 ostream.println(VM_SETTINGS); 145 if (stackSize != 0L) { 146 ostream.println(INDENT + "Stack Size: " + 147 SizePrefix.scaleValue(stackSize)); 148 } 149 if (initialHeapSize != 0L) { 150 ostream.println(INDENT + "Min. Heap Size: " + 151 SizePrefix.scaleValue(initialHeapSize)); 152 } 153 if (maxHeapSize != 0L) { 154 ostream.println(INDENT + "Max. Heap Size: " + 155 SizePrefix.scaleValue(maxHeapSize)); 156 } else { 157 ostream.println(INDENT + "Max. Heap Size (Estimated): " 158 + SizePrefix.scaleValue(Runtime.getRuntime().maxMemory())); 159 } 160 ostream.println(INDENT + "Ergonomics Machine Class: " 161 + ((isServer) ? "server" : "client")); 162 ostream.println(INDENT + "Using VM: " 163 + System.getProperty("java.vm.name")); 164 ostream.println(); 165 } 166 167 /* 168 * prints the properties subopt/section 169 */ 170 private static void printProperties(PrintStream ostream) { 171 Properties p = System.getProperties(); 172 ostream.println(PROP_SETTINGS); 173 List<String> sortedPropertyKeys = new ArrayList<>(); 174 sortedPropertyKeys.addAll(p.stringPropertyNames()); 175 Collections.sort(sortedPropertyKeys); 176 for (String x : sortedPropertyKeys) { 177 printPropertyValue(ostream, x, p.getProperty(x)); 178 } 179 ostream.println(); 180 } 181 182 private static boolean isPath(String key) { 183 return key.endsWith(".dirs") || key.endsWith(".path"); 184 } 185 186 private static void printPropertyValue(PrintStream ostream, 187 String key, String value) { 188 ostream.print(INDENT + key + " = "); 189 if (key.equals("line.separator")) { 190 for (byte b : value.getBytes()) { 191 switch (b) { 192 case 0xd: 193 ostream.print("\\r "); 194 break; 195 case 0xa: 196 ostream.print("\\n "); 197 break; 198 default: 199 // print any bizzare line separators in hex, but really 200 // shouldn't happen. 201 ostream.printf("0x%02X", b & 0xff); 202 break; 203 } 204 } 205 ostream.println(); 206 return; 207 } 208 if (!isPath(key)) { 209 ostream.println(value); 210 return; 211 } 212 String[] values = value.split(System.getProperty("path.separator")); 213 boolean first = true; 214 for (String s : values) { 215 if (first) { // first line treated specially 216 ostream.println(s); 217 first = false; 218 } else { // following lines prefix with indents 219 ostream.println(INDENT + INDENT + s); 220 } 221 } 222 } 223 224 /* 225 * prints the locale subopt/section 226 */ 227 private static void printLocale(PrintStream ostream) { 228 Locale locale = Locale.getDefault(); 229 ostream.println(LOCALE_SETTINGS); 230 ostream.println(INDENT + "default locale = " + 231 locale.getDisplayLanguage()); 232 ostream.println(INDENT + "default display locale = " + 233 Locale.getDefault(Category.DISPLAY).getDisplayName()); 234 ostream.println(INDENT + "default format locale = " + 235 Locale.getDefault(Category.FORMAT).getDisplayName()); 236 printLocales(ostream); 237 ostream.println(); 238 } 239 240 private static void printLocales(PrintStream ostream) { 241 Locale[] tlocales = Locale.getAvailableLocales(); 242 final int len = tlocales == null ? 0 : tlocales.length; 243 if (len < 1 ) { 244 return; 245 } 246 // Locale does not implement Comparable so we convert it to String 247 // and sort it for pretty printing. 248 Set<String> sortedSet = new TreeSet<>(); 249 for (Locale l : tlocales) { 250 sortedSet.add(l.toString()); 251 } 252 253 ostream.print(INDENT + "available locales = "); 254 Iterator<String> iter = sortedSet.iterator(); 255 final int last = len - 1; 256 for (int i = 0 ; iter.hasNext() ; i++) { 257 String s = iter.next(); 258 ostream.print(s); 259 if (i != last) { 260 ostream.print(", "); 261 } 262 // print columns of 8 263 if ((i + 1) % 8 == 0) { 264 ostream.println(); 265 ostream.print(INDENT + INDENT); 266 } 267 } 268 } 269 270 private enum SizePrefix { 271 272 KILO(1024, "K"), 273 MEGA(1024 * 1024, "M"), 274 GIGA(1024 * 1024 * 1024, "G"), 275 TERA(1024L * 1024L * 1024L * 1024L, "T"); 276 long size; 277 String abbrev; 278 279 SizePrefix(long size, String abbrev) { 280 this.size = size; 281 this.abbrev = abbrev; 282 } 283 284 private static String scale(long v, SizePrefix prefix) { 285 return BigDecimal.valueOf(v).divide(BigDecimal.valueOf(prefix.size), 286 2, RoundingMode.HALF_EVEN).toPlainString() + prefix.abbrev; 287 } 288 /* 289 * scale the incoming values to a human readable form, represented as 290 * K, M, G and T, see java.c parse_size for the scaled values and 291 * suffixes. The lowest possible scaled value is Kilo. 292 */ 293 static String scaleValue(long v) { 294 if (v < MEGA.size) { 295 return scale(v, KILO); 296 } else if (v < GIGA.size) { 297 return scale(v, MEGA); 298 } else if (v < TERA.size) { 299 return scale(v, GIGA); 300 } else { 301 return scale(v, TERA); 302 } 303 } 304 } 305 306 /** 307 * A private helper method to get a localized message and also 308 * apply any arguments that we might pass. 309 */ 310 private static String getLocalizedMessage(String key, Object... args) { 311 String msg = getLauncherResourceBundle().getString(key); 312 return (args != null) ? MessageFormat.format(msg, args) : msg; 313 } 314 315 /** 316 * The java -help message is split into 3 parts, an invariant, followed 317 * by a set of platform dependent variant messages, finally an invariant 318 * set of lines. 319 * This method initializes the help message for the first time, and also 320 * assembles the invariant header part of the message. 321 */ 322 static void initHelpMessage(String progname) { 323 outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.header", 324 (progname == null) ? "java" : progname )); 325 outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.datamodel", 326 32)); 327 outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.datamodel", 328 64)); 329 } 330 331 /** 332 * Appends the vm selection messages to the header, already created. 333 * initHelpSystem must already be called. 334 */ 335 static void appendVmSelectMessage(String vm1, String vm2) { 336 outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.vmselect", 337 vm1, vm2)); 338 } 339 340 /** 341 * Appends the vm synoym message to the header, already created. 342 * initHelpSystem must be called before using this method. 343 */ 344 static void appendVmSynonymMessage(String vm1, String vm2) { 345 outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.hotspot", 346 vm1, vm2)); 347 } 348 349 /** 350 * Appends the vm Ergo message to the header, already created. 351 * initHelpSystem must be called before using this method. 352 */ 353 static void appendVmErgoMessage(boolean isServerClass, String vm) { 354 outBuf = outBuf.append(getLocalizedMessage("java.launcher.ergo.message1", 355 vm)); 356 outBuf = (isServerClass) 357 ? outBuf.append(",\n" + 358 getLocalizedMessage("java.launcher.ergo.message2") + "\n\n") 359 : outBuf.append(".\n\n"); 360 } 361 362 /** 363 * Appends the last invariant part to the previously created messages, 364 * and finishes up the printing to the desired output stream. 365 * initHelpSystem must be called before using this method. 366 */ 367 static void printHelpMessage(boolean printToStderr) { 368 PrintStream ostream = (printToStderr) ? System.err : System.out; 369 outBuf = outBuf.append(getLocalizedMessage("java.launcher.opt.footer", 370 File.pathSeparator)); 371 ostream.println(outBuf.toString()); 372 } 373 374 /** 375 * Prints the Xusage text to the desired output stream. 376 */ 377 static void printXUsageMessage(boolean printToStderr) { 378 PrintStream ostream = (printToStderr) ? System.err : System.out; 379 ostream.println(getLocalizedMessage("java.launcher.X.usage", 380 File.pathSeparator)); 381 } 382 383 static String getMainClassFromJar(String jarname) throws IOException { 384 JarFile jarFile = null; 385 try { 386 jarFile = new JarFile(jarname); 387 Manifest manifest = jarFile.getManifest(); 388 if (manifest == null) { 389 throw new IOException("manifest not found in " + jarname); 390 } 391 Attributes mainAttrs = manifest.getMainAttributes(); 392 if (mainAttrs == null) { 393 throw new IOException("no main mainifest attributes, in " + 394 jarname); 395 } 396 return mainAttrs.getValue(MAIN_CLASS).trim(); 397 } finally { 398 if (jarFile != null) { 399 jarFile.close(); 400 } 401 } 402 } 403 404 405 // From src/share/bin/java.c: 406 // enum LaunchMode { LM_UNKNOWN = 0, LM_CLASS, LM_JAR }; 407 408 private static final int LM_UNKNOWN = 0; 409 private static final int LM_CLASS = 1; 410 private static final int LM_JAR = 2; 411 412 /** 413 * This method does the following: 414 * 1. gets the classname from a Jar's manifest, if necessary 415 * 2. loads the class using the System ClassLoader 416 * 3. ensures the availability and accessibility of the main method, 417 * using signatureDiagnostic method. 418 * a. does the class exist 419 * b. is there a main 420 * c. is the main public 421 * d. is the main static 422 * c. does the main take a String array for args 423 * 4. and off we go...... 424 * 425 * @param printToStderr 426 * @param isJar 427 * @param name 428 * @return 429 * @throws java.io.IOException 430 */ 431 public static Class<?> checkAndLoadMain(boolean printToStderr, 432 int mode, 433 String what) throws IOException 434 { 435 436 ClassLoader ld = ClassLoader.getSystemClassLoader(); 437 438 // get the class name 439 String cn = null; 440 switch (mode) { 441 case LM_CLASS: 442 cn = what; 443 break; 444 case LM_JAR: 445 cn = getMainClassFromJar(what); 446 break; 447 default: 448 throw new InternalError("" + mode + ": Unknown launch mode"); 449 } 450 cn = cn.replace('/', '.'); 451 452 PrintStream ostream = (printToStderr) ? System.err : System.out; 453 Class<?> c = null; 454 try { 455 c = ld.loadClass(cn); 456 } catch (ClassNotFoundException cnfe) { 457 ostream.println(getLocalizedMessage("java.launcher.cls.error1", 458 cn)); 459 NoClassDefFoundError ncdfe = new NoClassDefFoundError(cn); 460 ncdfe.initCause(cnfe); 461 throw ncdfe; 462 } 463 signatureDiagnostic(ostream, c); 464 return c; 465 } 466 467 static void signatureDiagnostic(PrintStream ostream, Class<?> clazz) { 468 String classname = clazz.getName(); 469 Method method = null; 470 try { 471 method = clazz.getMethod("main", String[].class); 472 } catch (NoSuchMethodException nsme) { 473 ostream.println(getLocalizedMessage("java.launcher.cls.error4", 474 classname)); 475 throw new RuntimeException("Main method not found in " + classname); 476 } 477 /* 478 * getMethod (above) will choose the correct method, based 479 * on its name and parameter type, however, we still have to 480 * ensure that the method is static and returns a void. 481 */ 482 int mod = method.getModifiers(); 483 if (!Modifier.isStatic(mod)) { 484 ostream.println(getLocalizedMessage("java.launcher.cls.error2", 485 "static", classname)); 486 throw new RuntimeException("Main method is not static in class " + 487 classname); 488 } 489 if (method.getReturnType() != java.lang.Void.TYPE) { 490 ostream.println(getLocalizedMessage("java.launcher.cls.error3", 491 classname)); 492 throw new RuntimeException("Main method must return a value" + 493 " of type void in class " + 494 classname); 495 } 496 return; 497 } 498 }