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