/**************************************************************************
* Copyright (c) 2008, 2009, 2015 by KIFFER Ltd. All rights reserved. *
* *
* Redistribution and use in source and binary forms, with or without *
* modification, are permitted provided that the following conditions *
* are met: *
* 1. Redistributions of source code must retain the above copyright *
* notice, this list of conditions and the following disclaimer. *
* 2. Redistributions in binary form must reproduce the above copyright *
* notice, this list of conditions and the following disclaimer in the *
* documentation and/or other materials provided with the distribution. *
* 3. Neither the name of KIFFER Ltd nor the names of other contributors *
* may be used to endorse or promote products derived from this *
* software without specific prior written permission. *
* *
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED *
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF *
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. *
* IN NO EVENT SHALL KIFFER LTD OR OTHER CONTRIBUTORS BE LIABLE FOR ANY *
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL *
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS *
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) *
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, *
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING *
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE *
* POSSIBILITY OF SUCH DAMAGE. *
**************************************************************************/
package wonka.vm;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.Vector;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
/**
** Class Init is a utility class used to launch the garbage collection,
** finaliser, and heartbeat threads, and to run the starting class or
** jarfile.
*/
final class Init {
private static boolean verbose;
private static Method invoke_method;
private static Object[] invoke_args;
private static boolean launched;
private static URLClassLoader application_class_loader;
private static String [] start_class_args;
private static String jar_class_path;
private static void debug(String s) {
if (verbose) {
System.err.println(s);
}
}
/**
** Take one token from the argument list and process it as a system property
** (begins with -D) or a normal argument. Normal arguments are accumulated
** in temp_args.
*/
private static void argumentOrSysProp(String this_arg, Vector temp_args) {
if (this_arg.startsWith("-D")) {
Properties sysprops = System.getProperties();
String key, value;
int equals_at = this_arg.indexOf('=');
if (equals_at < 0) {
key = this_arg.substring(2);
debug("Init: Deleting system property '"+key+"'");
sysprops.remove(key);
}
else if (equals_at == 0) {
System.err.println("Init: Ignoring nameless property in "+this_arg);
}
else if (equals_at == this_arg.length()-1) {
key = this_arg.substring(2,equals_at);
debug("Init: Deleting system property '"+key+"'");
sysprops.remove(key);
}
else {
key = this_arg.substring(2, equals_at);
value = this_arg.substring(equals_at + 1);
debug("Init: Setting system property '"+key+"' to '"+value+"'");
sysprops.put(key,value);
}
}
else {
debug("Init: passing argument "+this_arg);
temp_args.addElement(this_arg);
}
}
/**
** Take all the arguments in temp_args and make them into an array of
** String which can be passed to the starting class's main() method.
*/
private static String[] marshallArgs(Vector temp_args) {
String[] args = new String[temp_args.size()];
for (int i = 0; i < temp_args.size(); ++i) {
args[i] = (String)temp_args.elementAt(i);
}
return args;
}
/**
** Get the name of the Main-Class in a jar file, with the path to the
** jar file prepended. Return null if no mainifest or no Main-Class
** attribute found.
** If a Class-Path attribute is found, its value is parsed and every
** element found is added to the URL list of the Application Class Loader
** (after prepending the path to the jar file).
*/
private static String getJarStartClassName(String[] args) {
String start_class_name = null;
if(args.length < 2){
System.err.println("Init: -jar option is used but no JarFile is mentioned");
return null;
}
try {
File file = new File(args[1]);
JarFile jf = new JarFile(file);
Manifest man = jf.getManifest();
if(man == null){
System.err.println("Init: JarFile "+args[1]+" has no Manifest");
return null;
}
start_class_name = man.getMainAttributes().getValue("Main-Class");
if(start_class_name == null){
System.err.println("Init: no Main-Class attribute in Manifest of JarFile "+args[1]);
return null;
}
String jarpath;
int jarpathend = args[1].lastIndexOf('/');
if (jarpathend < 0) {
jarpath = "";
}
else {
jarpath = args[1].substring(0, jarpathend + 1);
}
URL url = new URL("jar:"+file.toURL()+"!/");
Method m = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{url.getClass()});
m.setAccessible(true);
m.invoke(application_class_loader,new Object[]{url});
String jarclasspath = man.getMainAttributes().getValue("Class-Path");
if (jarclasspath != null) {
debug("Jarfile " + file + " has Class-Path " + jarclasspath);
StringTokenizer toks = new StringTokenizer(jarclasspath);
try {
while (true) {
file = new File(jarpath + toks.nextToken());
url = new URL("jar:"+file.toURL()+"!/");
debug(" Appending " + url + " to application class path");
m.invoke(application_class_loader,new Object[]{url});
}
}
catch (NoSuchElementException nsee) {
debug("Finished with jarfile Class-Path");
}
}
}
catch(Exception e){
System.err.println("Init: failed to load jar "+args[1]+" due to "+e);
e.printStackTrace();
return null;
}
return start_class_name;
}
/**
** Sift the array raw_args, using -D arguments to modify system properties
** and returning an array which contains the remaining arguments. Both
** raw_args and the result returned may be zero-length, but they may not be null.
*/
private static String[] processArgsArray(String[] raw_args, int offset) {
Vector temp_args = new Vector();
for (int i = offset; i < raw_args.length; ++i) {
argumentOrSysProp(raw_args[i],temp_args);
}
return marshallArgs(temp_args);
}
/**
* Process the command-line flags related to runtime assertions.
* We already checked in init.c that the flag is one of -ea, -da,
* -enable[system]assertions, -disable[system]assertions, optionally
* followed by a colon and some more stuff (not yet checked).
*/
private static void processAssertions(ClassLoader l) {
String flag = getNextAssertionFlag();
while (flag != null) {
boolean enabled = flag.startsWith("-e");
int colon = flag.indexOf(':');
boolean global = colon < 0;
boolean pkg = !global && flag.endsWith("...");
boolean system;
switch (colon >= 0 ? colon : flag.length()) {
case 3:
// -ea/-da
system = false;
break;
case 4:
// -esa/-dsa
system = true;
break;
default:
int n = flag.indexOf("system");
system = n >= 0 && (global || n < colon);
}
if (system) {
// ignore for now, we don't have any assertions is system code
}
else {
if (global) {
l.setDefaultAssertionStatus(enabled);
}
else if (pkg) {
String name = flag.substring(colon + 1, flag.length() - 3);
l.setPackageAssertionStatus(name, enabled);
}
else {
String name = flag.substring(colon + 1);
l.setClassAssertionStatus(name, enabled);
}
}
flag = getNextAssertionFlag();
}
}
/**
* Get the next command-line flag from the list created by init.c
*/
private static native String getNextAssertionFlag();
/**
** This function is invoked directly when the VM is created. First we
** parse the command line to extract the name of the starting class and
** any changes to system properties; the remaining arguments will be passed
** on to the starting class when it has been loaded. Then we try to load
** starting class and to find its main(String[]) method. If this succeeds
** then we first install the SecurityMAnager if required and launch some
** housekeeping threads and then invoke the main(String[]) method. Lastly,
** if the main(String[]) method of the starting class terminates abnormally
** we pass the exception thrown to the UncaughtExceptionHandler of the
** system thread group.
*/
private static void main(String[] args) {
String[] effective_args = args;
String start_class_name = null;
Class start_class = null;
if(args == null || args.length == 0 || args[0] == null) {
String start_command = System.getProperty("mika.default.commandline");
if (start_command != null && start_command.length() != 0){
StringTokenizer start_toks = new StringTokenizer(start_command);
ArrayList l = new ArrayList();
while (start_toks.hasMoreTokens()) {
l.add(start_toks.nextToken());
}
effective_args = (String[])l.toArray(new String[0]);
l = null;
}
else {
System.err.println("Init: no command line parameters found. Game over.");
System.exit(1);
}
}
String verboseProperty = System.getProperty("mika.verbose","");
Wonka.setWonkaVerbose(verboseProperty);
if (verboseProperty.indexOf("startup") >= 0) {
verbose = true;
}
debug("Init: loading extensions");
Wonka.loadExtensions();
String debugLineNumbers = System.getProperty("mika.debug.line.numbers", "");
if (debugLineNumbers.equalsIgnoreCase("true")) {
Wonka.setMethodDebugInfo(true);
}
else if (debugLineNumbers.equalsIgnoreCase("false")) {
Wonka.setMethodDebugInfo(false);
}
application_class_loader = (URLClassLoader)ClassLoader.getSystemClassLoader();
processAssertions(application_class_loader);
Thread.currentThread().setContextClassLoader(application_class_loader);
if (effective_args[0].equals("-jar")) {
debug("Init: '-jar' is used");
jar_class_path = effective_args[1];
start_class_name = getJarStartClassName(effective_args);
start_class_args = processArgsArray(effective_args, 2);
}
else {
start_class_name = effective_args[0];
start_class_args = processArgsArray(effective_args, 1);
}
if (start_class_name == null) {
System.err.println("Init: no start class named. Game over.");
System.exit(1);
}
invoke_args = new Object[1];
invoke_args[0] = start_class_args;
try {
start_class = Class.forName(start_class_name, false, application_class_loader);
debug("Init: launcher is " + start_class + ", loaded by " + application_class_loader);
} catch (ClassNotFoundException e) {
System.err.println("Init: no such class as "+start_class_name+" in class path!");
} catch (Throwable t) {
t.printStackTrace();
System.exit(1);
}
if (start_class == null) {
System.err.println("Init: no start class found. Game over.");
System.exit(1);
}
try {
invoke_method = start_class.getMethod("main",new Class[]{Class.forName("[Ljava.lang.String;")});
if(invoke_method == null) {
System.err.println("Init: no 'main' method start class "+start_class+" found. Game over.");
System.exit(1);
}
if (!Modifier.isStatic(invoke_method.getModifiers())) {
System.err.println("Init: " + invoke_method + " is not static. Game over.");
System.exit(1);
}
debug("Init: will invoke " + invoke_method);
invoke_method.setAccessible(true);
} catch (SecurityException e) {
System.err.println("Init: unable to invoke " + invoke_method + ": " + e);
e.printStackTrace();
} catch (ClassNotFoundException e) {
System.err.println("Init: unable to invoke " + invoke_method + ": " + e);
e.printStackTrace();
} catch (NoSuchMethodException e) {
System.err.println("Init: unable to invoke " + invoke_method + ": " + e);
e.printStackTrace();
} catch (Throwable t) {
t.printStackTrace();
System.exit(1);
}
if (invoke_method == null) {
System.err.println("Init: no 'main(String[])' method found. Game over.");
System.exit(1);
}
String theManager = System.getProperty("java.security.manager");
if("".equals(theManager) || "default".equals(theManager)) {
debug("Init: installing default SecurityManager.");
System.setSecurityManager(new SecurityManager());
}
else if (theManager != null) {
try {
debug("Init: installing custom SecurityManager: " + theManager);
System.setSecurityManager((SecurityManager)Class.forName(theManager, true, ClassLoader.getSystemClassLoader()).newInstance());
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
else {
debug("Init: not installing a SecurityManager");
}
// Start up the Garbage Collector
debug("Init: starting Garbage Collector");
GarbageCollector gc = GarbageCollector.getInstance();
// Start up the Heartbeat
debug("Init: starting Heartbeat");
Heartbeat h = Heartbeat.getInstance();
// Start JDWP (does nothing if JDWP not compiled in)
JDWP.getInstance();
// Set the default timezone to the default default
TimeZone defaultTimeZone = null;
String user_timezone = System.getProperty("user.timezone");
debug("Init: user.timezone = " + user_timezone);
if (user_timezone != null) {
defaultTimeZone = TimeZone.getTimeZone(user_timezone);
if (defaultTimeZone == null) {
System.err.println("Unable to find the default timezone '" + user_timezone + "': check the system.property 'user.timezone' and the mika.timezones file!");
}
}
TimeZone.setDefault(defaultTimeZone);
debug("Init: default TimeZone is " + TimeZone.getDefault());
String user_language = System.getProperty("user.language");
if (user_language != null) {
String defaultLocaleName = user_language;
String user_region = System.getProperty("user.region");
Locale defaultLocale = null;
Locale[] available_locales = Locale.getAvailableLocales();
for (int i = 0; i < available_locales.length; ++i) {
if (user_language.equals(available_locales[i].getLanguage())) {
if (user_region == null || user_region.equals(available_locales[i].getCountry())) {
defaultLocale = available_locales[i];
break;
}
}
}
if (defaultLocale == null) {
defaultLocale = user_region == null ? new Locale(user_language) : new Locale(user_language, user_region);
}
Locale.setDefault(defaultLocale);
}
debug("Init: default Locale is " + Locale.getDefault());
if (jar_class_path != null) {
// [CG 20060330] HACK to make java.class.path to look right when -jar is used
System.setProperty("java.class.path", jar_class_path);
}
try {
debug("Init: invoking "+invoke_method+" ...");
invoke_method.invoke(null,invoke_args);
launched = true;
} catch (IllegalAccessException e) {
System.err.println("Init: error invoking "+invoke_method+"! got "+e);
e.printStackTrace();
} catch (InvocationTargetException e) {
Thread this_thread = Thread.currentThread();
this_thread.getThreadGroup().uncaughtException(this_thread,e.getTargetException());
}
catch (Throwable t) {
t.printStackTrace();
}
finally {
if (!launched) {
System.exit(1);
}
}
}
}