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 }