Package org.tanukisoftware.wrapper

Source Code of org.tanukisoftware.wrapper.WrapperManager$ShutdownLock

package org.tanukisoftware.wrapper;

/*
* Copyright (c) 1999, 2010 Tanuki Software, Ltd.
* http://www.tanukisoftware.com
* All rights reserved.
*
* This software is the proprietary information of Tanuki Software.
* You shall use it only in accordance with the terms of the
* license agreement you entered into with Tanuki Software.
* http://wrapper.tanukisoftware.com/doc/english/licenseOverview.html
*
*
* Portions of the Software have been derived from source code
* developed by Silver Egg Technology under the following license:
*
* Copyright (c) 2001 Silver Egg Technology
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sub-license, and/or
* sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*/

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.BindException;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.net.URL;
import java.security.CodeSource;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.ResourceBundle;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.tanukisoftware.wrapper.event.WrapperControlEvent;
import org.tanukisoftware.wrapper.event.WrapperEvent;
import org.tanukisoftware.wrapper.event.WrapperEventListener;
import org.tanukisoftware.wrapper.event.WrapperLogFileChangedEvent;
import org.tanukisoftware.wrapper.event.WrapperPingEvent;
import org.tanukisoftware.wrapper.event.WrapperServiceControlEvent;
import org.tanukisoftware.wrapper.event.WrapperServicePauseEvent;
import org.tanukisoftware.wrapper.event.WrapperServiceResumeEvent;
import org.tanukisoftware.wrapper.event.WrapperTickEvent;
import org.tanukisoftware.wrapper.security.WrapperEventPermission;
import org.tanukisoftware.wrapper.security.WrapperPermission;
import org.tanukisoftware.wrapper.security.WrapperServicePermission;
import org.tanukisoftware.wrapper.security.WrapperUserEventPermission;

/**
* Handles all communication with the native portion of the Wrapper code.
*  Communication takes place either over a Pipe, or Socket.  In either
*  case, the Wrapper code will initializate its end of the communication
*  and then launch Java in a separate process.
*
* In the case of a socket, the Wrapper will set up a server socket which
*  the Java code is expected to open a socket to on startup.  When the
*  server socket is created, a port will be chosen depending on what is
*  available to the system.  This port will then be passed to the Java
*  process as property named "wrapper.port". 
*
* In the case of a pipe, the Wrapper will set up a pair of named ports
*  and then pass in a property named "wrapper.backend=pipe".  The Wrapper
*  and JVM will communicate via the pipes.
*
* For security reasons, the native code will only allow connections from
*  localhost and will expect to receive the key specified in a property
*  named "wrapper.key".
*
* This class is implemented as a singleton class.
*
* Generate JNI Headers with the following command in the build/classes
*  directory:
*    javah -jni -classpath ./ org.tanukisoftware.wrapper.WrapperManager
*
* @author Leif Mortenson <leif@tanukisoftware.com>
*/
public final class WrapperManager
    implements Runnable
{
    private static final String WRAPPER_CONNECTION_THREAD_NAME = "Wrapper-Connection";
   
    private static final int DEFAULT_PORT                = 15003;
    private static final int DEFAULT_SO_TIMEOUT          = 10000;
    private static final int DEFAULT_CPU_TIMEOUT         = 10000;
   
    /** The number of milliseconds in one tick.  Used for internal system
     *   time independent time keeping. */
    private static final int TICK_MS                     = 100;
    private static final int TIMER_FAST_THRESHOLD     = 2 * 24 * 3600 * 1000 / TICK_MS; // 2 days.
    private static final int TIMER_SLOW_THRESHOLD     = 2 * 24 * 3600 * 1000 / TICK_MS; // 2 days.
   
    private static final int BACKEND_TYPE_UNKNOWN        = 0;
    private static final int BACKEND_TYPE_SOCKET         = 1;
    private static final int BACKEND_TYPE_PIPE           = 2;
   
    private static final byte WRAPPER_MSG_START          = (byte)100;
    private static final byte WRAPPER_MSG_STOP           = (byte)101;
    private static final byte WRAPPER_MSG_RESTART        = (byte)102;
    private static final byte WRAPPER_MSG_PING           = (byte)103;
    private static final byte WRAPPER_MSG_STOP_PENDING   = (byte)104;
    private static final byte WRAPPER_MSG_START_PENDING  = (byte)105;
    private static final byte WRAPPER_MSG_STARTED        = (byte)106;
    private static final byte WRAPPER_MSG_STOPPED        = (byte)107;
    private static final byte WRAPPER_MSG_KEY            = (byte)110;
    private static final byte WRAPPER_MSG_BADKEY         = (byte)111;
    private static final byte WRAPPER_MSG_LOW_LOG_LEVEL  = (byte)112;
    private static final byte WRAPPER_MSG_PING_TIMEOUT   = (byte)113; /* No longer used. */
    private static final byte WRAPPER_MSG_SERVICE_CONTROL_CODE = (byte)114;
    private static final byte WRAPPER_MSG_PROPERTIES     = (byte)115;
    /** Log commands are actually 116 + the LOG LEVEL. */
    private static final byte WRAPPER_MSG_LOG            = (byte)116;
    private static final byte WRAPPER_MSG_CHILD_LAUNCH   = (byte)132;
    private static final byte WRAPPER_MSG_CHILD_TERM     = (byte)133;
    private static final byte WRAPPER_MSG_LOGFILE        = (byte)134;
    private static final byte WRAPPER_MSG_CHECK_DEADLOCK = (byte)135;
    private static final byte WRAPPER_MSG_DEADLOCK       = (byte)136;
    private static final byte WRAPPER_MSG_APPEAR_ORPHAN  = (byte)137; /* No longer used. */
    private static final byte WRAPPER_MSG_PAUSE          = (byte)138;
    private static final byte WRAPPER_MSG_RESUME         = (byte)139;
    private static final byte WRAPPER_MSG_GC             = (byte)140;
    private static final byte WRAPPER_MSG_FIRE_USER_EVENT= (byte)141;
   
    /** Received when the user presses CTRL-C in the console on Windows or UNIX platforms. */
    public static final int WRAPPER_CTRL_C_EVENT         = 200;
   
    /** Received when the user clicks on the close button of a Console on Windows. */
    public static final int WRAPPER_CTRL_CLOSE_EVENT     = 201;
   
    /** Received when the user logs off of a Windows system. */
    public static final int WRAPPER_CTRL_LOGOFF_EVENT    = 202;
   
    /** Received when a Windows system is shutting down. */
    public static final int WRAPPER_CTRL_SHUTDOWN_EVENT  = 203;
   
    /** Received when a SIG TERM is received on a UNIX system. */
    public static final int WRAPPER_CTRL_TERM_EVENT      = 204;
   
    /** Received when a SIG HUP is received on a UNIX system. */
    public static final int WRAPPER_CTRL_HUP_EVENT       = 205;
   
    /** Received when a SIG USR1 is received on a UNIX system. */
    public static final int WRAPPER_CTRL_USR1_EVENT      = 206;
   
    /** Received when a SIG USR2 is received on a UNIX system. */
    public static final int WRAPPER_CTRL_USR2_EVENT      = 207;
   
    /** Log message at debug log level. */
    public static final int WRAPPER_LOG_LEVEL_DEBUG      = 1;
    /** Log message at info log level. */
    public static final int WRAPPER_LOG_LEVEL_INFO       = 2;
    /** Log message at status log level. */
    public static final int WRAPPER_LOG_LEVEL_STATUS     = 3;
    /** Log message at warn log level. */
    public static final int WRAPPER_LOG_LEVEL_WARN       = 4;
    /** Log message at error log level. */
    public static final int WRAPPER_LOG_LEVEL_ERROR      = 5;
    /** Log message at fatal log level. */
    public static final int WRAPPER_LOG_LEVEL_FATAL      = 6;
    /** Log message at advice log level. */
    public static final int WRAPPER_LOG_LEVEL_ADVICE     = 7;
    /** Log message at notice log level. */
    public static final int WRAPPER_LOG_LEVEL_NOTICE     = 8;
   
    /** Service Control code which can be sent to start a service. */
    public static final int SERVICE_CONTROL_CODE_START       = 0x10000;
   
    /** Service Control code which can be sent or received to stop a service. */
    public static final int SERVICE_CONTROL_CODE_STOP        = 1;
   
    /** Service Control code which can be sent to pause a service. */
    public static final int SERVICE_CONTROL_CODE_PAUSE       = 2;
   
    /** Service Control code which can be sent to resume a paused service. */
    public static final int SERVICE_CONTROL_CODE_CONTINUE    = 3;
   
    /** Service Control code which can be sent to or received interrogate the status of a service. */
    public static final int SERVICE_CONTROL_CODE_INTERROGATE = 4;
   
    /** Service Control code which can be received when the system is shutting down. */
    public static final int SERVICE_CONTROL_CODE_SHUTDOWN    = 5;
   
    /** Service Control code which is received when the system being suspended. */
    public static final int SERVICE_CONTROL_CODE_POWEREVENT_QUERYSUSPEND       = 0x0D00;
   
    /** Service Control code which is received when permission to suspend the
     *   computer was denied by a process.  Support for this event was removed
     *   from the Windows OS starting with Vista.*/
    public static final int SERVICE_CONTROL_CODE_POWEREVENT_QUERYSUSPENDFAILED = 0x0D02;
   
    /** Service Control code which is received when the computer is about to
     *   enter a suspended state. */
    public static final int SERVICE_CONTROL_CODE_POWEREVENT_SUSPEND            = 0x0D04;
   
    /** Service Control code which is received when the system has resumed
     *   operation. This event can indicate that some or all applications did
     *   not receive a SERVICE_CONTROL_CODE_POWEREVENT_SUSPEND event.
     *   Support for this event was removed from the Windows OS starting with
     *   Vista.  See SERVICE_CONTROL_CODE_POWEREVENT_RESUMEAUTOMATIC. */
    public static final int SERVICE_CONTROL_CODE_POWEREVENT_RESUMECRITICAL     = 0x0D06;
   
    /** Service Control code which is received when the system has resumed
     *   operation after being suspended. */
    public static final int SERVICE_CONTROL_CODE_POWEREVENT_RESUMESUSPEND      = 0x0D07;
   
    /** Service Control code which is received when the battery power is low.
     *   Support for this event was removed from the Windows OS starting with
     *   Vista.  See SERVICE_CONTROL_CODE_POWEREVENT_POWERSTATUSCHANGE. */
    public static final int SERVICE_CONTROL_CODE_POWEREVENT_BATTERYLOW         = 0x0D09;
   
    /** Service Control code which is received when there is a change in the
     *   power status of the computer, such as a switch from battery power to
     *   A/C. The system also broadcasts this event when remaining battery
     *   power slips below the threshold specified by the user or if the
     *   battery power changes by a specified percentage. */
    public static final int SERVICE_CONTROL_CODE_POWEREVENT_POWERSTATUSCHANGE  = 0x0D0A;
   
    /** Service Control code which is received when the APM BIOS has signaled
     *   an APM OEM event.  Support for this event was removed from the Windows
     *   OS starting with Vista. */
    public static final int SERVICE_CONTROL_CODE_POWEREVENT_OEMEVENT           = 0x0D0B;
   
    /** Service Control code which is received when the computer has woken up
     *   automatically to handle an event. */
    public static final int SERVICE_CONTROL_CODE_POWEREVENT_RESUMEAUTOMATIC    = 0x0D12;
   
    /** Reference to the original value of System.out. */
    private static PrintStream m_out;
   
    /** Reference to the original value of System.err. */
    private static PrintStream m_err;
   
    /** Info level log channel */
    private static WrapperPrintStream m_outInfo;
   
    /** Error level log channel */
    private static WrapperPrintStream m_outError;
   
    /** Debug level log channel */
    private static WrapperPrintStream m_outDebug;
   
    /** Flag to remember whether or not this is Windows. */
    private static boolean m_windows = false;
   
    /** Flag to remember whether or not this is MacOSX. */
    private static boolean m_macosx = false;
   
    /** Flag that will be set to true once a SecurityManager has been detected and tested. */
    private static boolean m_securityManagerChecked = false;
   
    private static boolean m_disposed = false;
   
    /** The starting flag is set when the Application has been asked to start. */
    private static boolean m_starting = false;
   
    /** The started flag is set when the Application has completed its startup. */
    private static boolean m_started = false;
    private static WrapperManager m_instance = null;
    private static Thread m_hook = null;
    private static boolean m_hookTriggered = false;
    private static boolean m_hookRemoveFailed = false;
   
    /* Flag which records when the shutdownJVM method has completed. */
    private static boolean m_shutdownJVMComplete = false;
   
    /** Map which stores shutdown locks for each thread. */
    private static Map m_shutdownLockMap = new HashMap();
   
    /** Tracks the total number of outstanding shutdown locks. */
    private static int m_shutdownLocks = 0;
   
    private static String[] m_args;
    private static int m_backendType = BACKEND_TYPE_UNKNOWN;
    private static boolean m_backendConnected = false;
    private static OutputStream m_backendOS = null;
    private static InputStream m_backendIS = null;
    private static int m_port    = DEFAULT_PORT;
    private static int m_jvmPort;
    private static int m_jvmPortMin;
    private static int m_jvmPortMax;
    private static String m_key;
    private static long m_cpuTimeout = DEFAULT_CPU_TIMEOUT;
   
    /** Tick count when the start method completed. */
    private static int m_startedTicks;
   
    /** The lowest configured log level in the Wrapper's configuration.  This
     *   is set to a high value by default to disable all logging if the
     *   Wrapper does not register its low level or is not present. */
    private static int m_lowLogLevel = WRAPPER_LOG_LEVEL_NOTICE + 1;
   
    /** Flag, set when the JVM is launched that is used to remember whether
     *   or not system signals are supposed to be ignored. */
    private static boolean m_ignoreSignals = false;
   
    /** Flag which controls whether the Wrapper process is expected to close the
     *   connection after the STARTED packet is sent. */
    private static boolean m_detachStarted = false;
   
    /** Thread which processes all communications with the native code. */
    private static Thread m_commRunner;
    private static boolean m_commRunnerStarted = false;
    private static Thread m_eventRunner;
    private static int m_eventRunnerTicks;
    private static Thread m_startupRunner;
   
    /** True if the system time should be used for internal timeouts. */
    private static boolean m_useSystemTime;
   
    /** The threashold of how many ticks the timer can be fast before a
     *   warning is displayed. */
    private static int m_timerFastThreshold;
   
    /** The threashold of how many ticks the timer can be slow before a
     *   warning is displayed. */
    private static int m_timerSlowThreshold;
   
    /** Flag which controls whether or not the WrapperListener.stop method will
     *   be called on shutdown when the WrapperListener.start method has not
     *   returned or returned an exit code. */
    private static boolean m_listenerForceStop;
   
    /**
     * Bit depth of the currently running JVM.  Will be 32 or 64.
     *  A 64-bit JVM means that the system is also 64-bit, but a 32-bit JVM
     *  can be run either on a 32 or 64-bit system.
     */
    private static int m_jvmBits;
   
    /** An integer which stores the number of ticks since the
     *   JVM was launched.  Using an int rather than a long allows the value
     *   to be used without requiring any synchronization.  This is only
     *   used if the m_useSystemTime flag is false. */
    private static volatile int m_ticks;
   
    private static WrapperListener m_listener;
   
    private static int m_lastPingTicks;
    private static Socket m_backendSocket;
    private static boolean m_appearHung = false;
   
    private static boolean m_ignoreUserLogoffs = false;
   
    private static boolean m_service = false;
    private static boolean m_debug = false;
    private static int m_jvmId = 0;
    /** Flag set when any thread initiates a stop or restart. */
    private static boolean m_stoppingInit = false;
    /** Flag set when the thread that will be in charge of actually stopping has been fixed. */
    private static boolean m_stopping = false;
    /** Thread that is in charge of stopping. */
    private static Thread m_stoppingThread;
    /** If set then this message will be sent as a STOP message as soon as we connect to the Wrapper. */
    private static String m_pendingStopMessage = null;
    private static int m_exitCode;
    private static boolean m_libraryOK = false;
    private static byte[] m_commandBuffer = new byte[512];
    private static File m_logFile = null;
   
    /** The contents of the wrapper configuration. */
    private static WrapperProperties m_properties;
   
    /** List of registered WrapperEventListeners and their registered masks. */
    private static List m_wrapperEventListenerMaskList = new ArrayList();
   
    /** Array of registered WrapperEventListeners and their registered masks.
     *   Should not be referenced directly.  Access by calling
     *   getWrapperEventListenerMasks(). */
    private static WrapperEventListenerMask[] m_wrapperEventListenerMasks = null;
   
    /** Flag used to tell whether or not WrapperCoreEvents should be produced. */
    private static boolean m_produceCoreEvents = false;
   
    // message resources: eventually these will be split up
    private static WrapperResources m_res;
   
    /**
     * Returns the WrapperResources object which is used to manage all resources for
     *  the Java Service Wrapper.
     *
     * @return the Wrapper's resouces.
     */
    public static WrapperResources getRes()
    {
        return m_res;
    }
   
    /*---------------------------------------------------------------
     * Class Initializer
     *-------------------------------------------------------------*/
    /**
     * When the WrapperManager class is first loaded, it attempts to load the
     *  configuration file specified using the 'wrapper.config' system property.
     *  When the JVM is launched from the Wrapper native code, the
     *  'wrapper.config' and 'wrapper.key' parameters are specified.
     *  The 'wrapper.key' parameter is a password which is used to verify that
     *  connections are only coming from the native Wrapper which launched the
     *  current JVM.
     */
    static
    {
        // The wraper.jar must be given AllPermissions if a security manager
        //  has been configured.  This is not a problem if one of the standard
        //  Wrapper helper classes is used to launch the JVM.
        // If however a custom WrapperListener is being implemented then this
        //  class will most likely be loaded by code that is neither part of
        //  the system, nor part of the Wrapper code base.  To avoid having
        //  to also give those classes AllPermissions as well, we do all of
        //  initialization in a Privileged block.  This means that the code
        //  only requires that the wrapper.jar has been given the required
        //  permissions.
        AccessController.doPrivileged(
            new PrivilegedAction() {
                public Object run() {
                    privilegedClassInit();
                    return null;
                }
            }
        );
    }
   
   
    /**
     * Logs information about the package of the specified class.
     *
     * @param clazz Class to log.
     */
    private static void logPackageInfo( Class clazz )
    {
        if ( m_debug )
        {
            Package pkg = WrapperManager.class.getPackage();
            if ( pkg == null )
            {
                m_outDebug.println( getRes().getString( "{0} package not found.", clazz.getName() ) );
            }
            else
            {
                m_outDebug.println( getRes().getString( "{0} package information:", clazz.getName() ) );
                m_outDebug.println( getRes().getString( "  Implementation Title: {0}", pkg.getImplementationTitle() ) );
                m_outDebug.println( getRes().getString( "  Implementation Vendor: {0}", pkg.getImplementationVendor() ) );
                m_outDebug.println( getRes().getString( "  Implementation Version: {0}", pkg.getImplementationVersion() ) );
                m_outDebug.println( getRes().getString( "  Is Sealed?: {0}", pkg.isSealed() ? getRes().getString( "True" ) : getRes().getString( "False" ) ) );
            }
           
            ProtectionDomain proDom = clazz.getProtectionDomain();
            m_outDebug.println( getRes().getString( "{0} protection domain:", clazz.getName() ) );
            CodeSource codeSource = proDom.getCodeSource();
            URL jarLocation = codeSource.getLocation();
            m_outDebug.println( getRes().getString( "  Location: {0}", jarLocation ) );
           
            // Try reading in the full jar so we can calculate its size and MD5 hash.
            try
            {
                InputStream is = jarLocation.openStream();
                try
                {
                    int jarSize = 0;
                    MessageDigest md = MessageDigest.getInstance( "MD5" );
                    int data;
                    while ( ( data = is.read() ) >= 0 )
                    {
                        jarSize++;
                        md.update( (byte)( data & 0xff ) );
                    }
                    m_outDebug.println( getRes().getString( "    Size: {0}", new Integer( jarSize ) ) );
                   
                    byte[] bytes = md.digest();
                    StringBuffer sb = new StringBuffer();
                    for ( int i = 0; i < bytes.length; i++ )
                    {
                        String val = Integer.toString( bytes[i] & 0xff, 16 ).toLowerCase();
                        if ( val.length() == 1 )
                        {
                            sb.append( "0" );
                        }
                        sb.append( val );
                    }
                    m_outDebug.println( getRes().getString( "    MD5: {0}" , sb ) );
                }
                finally
                {
                    is.close();
                }
            }
            catch ( NoSuchAlgorithmException e )
            {
                m_outDebug.println( getRes().getString( "    Unable to calculate MD5: {0}", e ) );
            }
            catch ( IOException e )
            {
                m_outDebug.println( getRes().getString( "    Unable to access location: {0}", e ) );
            }
        }
    }
   
    /**
     * The body of the static initializer is moved into a seperate method so
     *  it can be run as a PrivilegedAction.
     */
    private static void privilegedClassInit()
    {
        // Store references to the original System.out and System.err
        //  PrintStreams.  The WrapperManager will always output to the
        //  original streams so its output will always end up in the
        //  wrapper.log file even if the end user code redirects the
        //  output to another log file.
        // This is also important to be protect the Wrapper's functionality
        //  from the case where the user PrintStream enters a deadlock state.
        m_out = System.out;
        m_err = System.err;

        // Set up some log channels
        m_outInfo = new WrapperPrintStream( m_out, "WrapperManager: " );
        m_outError = new WrapperPrintStream( m_out, "WrapperManager Error: " );
        m_outDebug = new WrapperPrintStream( m_out, "WrapperManager Debug: " );
       
        // Always create an empty properties object in case we are not running
        //  in the Wrapper or the properties are never sent.
        m_properties = new WrapperProperties();
        m_properties.lock();
       
        // Create a dummy resources file so initial localization will work until the native library is loaded.
        m_res = new WrapperResources();
       
        // This must be done before attempting to access any System Properties
        //  as that could cause a SecurityException if it is too strict.
        checkSecurityManager();
       
        // Check for the debug flag
        m_debug = WrapperSystemPropertyUtil.getBooleanProperty( "wrapper.debug", false );
       
        if ( m_debug )
        {
            m_outDebug.println( getRes().getString( "WrapperManager class initialized by thread: {0}   Using classloader: {1}", Thread.currentThread().getName(), WrapperManager.class.getClassLoader().toString() ) );
        }
       
        // The copyright banner was moved into the wrapper binary.  In order to
        //  aid in the debugging of user integrations, some kind of a known message
        //  needs to be displayed on startup so it is obvious whether or not the
        //  WrapperManager class is being initialized.
        m_outInfo.println( getRes().getString( "Initializing..." ) );
       
        // Check for the jvmID
        m_jvmId = WrapperSystemPropertyUtil.getIntProperty( "wrapper.jvmid", 1 );
        if ( m_debug )
        {
            m_outDebug.println( "JVM #" + m_jvmId );
        }
       
        // Decide whether this is a 32 or 64 bit version of Java.
        m_jvmBits = WrapperSystemPropertyUtil.getIntProperty( "sun.arch.data.model", -1 );
        if ( m_jvmBits == -1 )
        {
            m_jvmBits = WrapperSystemPropertyUtil.getIntProperty( "com.ibm.vm.bitmode", -1 );
        }
        if ( m_debug )
        {
            if ( m_jvmBits > 0 )
            {
                m_outDebug.println( getRes().getString( "Running a {0}-bit JVM.", new Integer( m_jvmBits ) ) );
            }
            else
            {
                m_outDebug.println( getRes().getString( "The bit depth of this JVM could not be determined." ) );
            }
        }
       
        // Log information about the Wrapper's package.
        logPackageInfo( WrapperManager.class );
       
        // Get the detachStarted flag.
        m_detachStarted = WrapperSystemPropertyUtil.getBooleanProperty( "wrapper.detachStarted", false );
       
        // Initialize the timerTicks to a very high value.  This means that we will
        // always encounter the first rollover (200 * WRAPPER_MS / 1000) seconds
        // after the Wrapper the starts, which means the rollover will be well
        // tested.
        m_ticks = Integer.MAX_VALUE - 200;
       
        m_useSystemTime = WrapperSystemPropertyUtil.getBooleanProperty(
            "wrapper.use_system_time", false );
        m_timerFastThreshold = WrapperSystemPropertyUtil.getIntProperty(
            "wrapper.timer_fast_threshold", TIMER_FAST_THRESHOLD ) * 1000 / TICK_MS;
        m_timerSlowThreshold = WrapperSystemPropertyUtil.getIntProperty(
            "wrapper.timer_slow_threshold", TIMER_SLOW_THRESHOLD ) * 1000 / TICK_MS;
       
        // Check to see if we should register a shutdown hook
        boolean disableShutdownHook = WrapperSystemPropertyUtil.getBooleanProperty(
            "wrapper.disable_shutdown_hook", false );
                // Check to see if the listener stop method should always be called.
        m_listenerForceStop = WrapperSystemPropertyUtil.getBooleanProperty(
            "wrapper.listener.force_stop", false );
       
        // If the shutdown hook is not disabled, then register it.
        if ( !disableShutdownHook )
        {
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString( "Registering shutdown hook" ) );
            }
            m_hook = new Thread( "Wrapper-Shutdown-Hook" )
            {
                /**
                 * Run the shutdown hook. (Triggered by the JVM when it is about to shutdown)
                 */
                public void run()
                {
                    // Stop the Wrapper cleanly.
                    m_hookTriggered = true;
                   
                    if ( m_debug )
                    {
                        m_outDebug.println( getRes().getString( "ShutdownHook started" ) );
                    }
                   
                    // Let the startup thread die since the shutdown hook is running.
                    m_startupRunner = null;
                   
                    // If we are not already stopping, then do so.
                    WrapperManager.stop( 0 );
                   
                    if ( m_debug )
                    {
                        m_outDebug.println( getRes().getString( "ShutdownHook complete" ) );
                    }
                }
            };
           
            // Register the shutdown hook.
            Runtime.getRuntime().addShutdownHook( m_hook );
        }
       
        // Initialize connection values.
        m_backendType = BACKEND_TYPE_UNKNOWN;
        m_port = 0;
        m_jvmPort = 0;
        m_jvmPortMin = 0;
        m_jvmPortMax = 0;
       
        // A key is required for the wrapper to work correctly.  If it is not
        //  present, then assume that we are not being controlled by the native
        //  wrapper.
        if ( ( m_key = System.getProperty( "wrapper.key" ) ) == null )
        {
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString( "Not using wrapper.  (key not specified)" ) );
            }
           
            // The wrapper will not be used, so other values will not be used.
            m_service = false;
            m_cpuTimeout = 31557600000L; // One Year.  Effectively never.
        }
        else
        {
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString( "Using wrapper" ) );
            }
           
            if ( WrapperSystemPropertyUtil.getBooleanProperty( "wrapper.disable_console_input", false ) )
            {
                // Replace the System.in stream with one of our own to disable it.
                System.setIn( new WrapperInputStream() );
            }
          
            // Decide what the backend connection type is
            if ( WrapperSystemPropertyUtil.getStringProperty( "wrapper.backend", "SOCKET" ).equalsIgnoreCase( "PIPE" ) )
            {
                // Pipe based communication
                m_backendType = BACKEND_TYPE_PIPE;
            }
            else
            {
                // Socket based communication
                m_backendType = BACKEND_TYPE_SOCKET;
               
                // A port must have been specified.
                String sPort;
                if ( ( sPort = System.getProperty( "wrapper.port" ) ) == null )
                {
                    String msg = getRes().getString( "The 'wrapper.port' system property was not set." );
                    m_outError.println( msg );
                    throw new ExceptionInInitializerError( msg );
                }
                try
                {
                    m_port = Integer.parseInt( sPort );
                }
                catch ( NumberFormatException e )
                {
                    String msg = getRes().getString( "''{0}'' is not a valid value for ''wrapper.port''.", sPort );
                    m_outError.println( msg );
                    throw new ExceptionInInitializerError( msg );
                }
               
                m_jvmPort =
                    WrapperSystemPropertyUtil.getIntProperty( "wrapper.jvm.port", 0 );
                m_jvmPortMin =
                    WrapperSystemPropertyUtil.getIntProperty( "wrapper.jvm.port.min", 31000 );
                m_jvmPortMax =
                    WrapperSystemPropertyUtil.getIntProperty( "wrapper.jvm.port.max", 31999 );
            }
           
            // Check for the ignore signals flag
            m_ignoreSignals = WrapperSystemPropertyUtil.getBooleanProperty(
                "wrapper.ignore_signals", false );
           
            // If this is being run as a headless server, then a flag would have been set
            m_service = WrapperSystemPropertyUtil.getBooleanProperty( "wrapper.service", false );
           
            // Get the cpuTimeout
            String sCPUTimeout = System.getProperty( "wrapper.cpu.timeout" );
            if ( sCPUTimeout == null )
            {
                m_cpuTimeout = DEFAULT_CPU_TIMEOUT;
            }
            else
            {
                try
                {
                    m_cpuTimeout = Integer.parseInt( sCPUTimeout ) * 1000L;
                }
                catch ( NumberFormatException e )
                {
                    String msg = getRes().getString( "''{0}'' is not a valid value for ''wrapper.cpu.timeout''.", sCPUTimeout );
                    m_outError.println( msg );
                    throw new ExceptionInInitializerError( msg );
                }
            }
        }
       
        // Make sure that the version of the Wrapper is correct.
        verifyWrapperVersion();

        // Register the MBeans if configured to do so.
        if ( WrapperSystemPropertyUtil.getBooleanProperty(
            WrapperManager.class.getName() + ".mbean", true ) )
        {
            registerMBean( new org.tanukisoftware.wrapper.jmx.WrapperManager(),
                "org.tanukisoftware.wrapper:type=WrapperManager" );
        }
        if ( WrapperSystemPropertyUtil.getBooleanProperty(
            WrapperManager.class.getName() + ".mbean.testing", false ) )
        {
            registerMBean( new org.tanukisoftware.wrapper.jmx.WrapperManagerTesting(),
                "org.tanukisoftware.wrapper:type=WrapperManagerTesting" );
        }
       
        // Initialize the native code to trap system signals
        initializeNativeLibrary();
       
        if ( m_libraryOK )
        {
            // Make sure that the native library's version is correct.
            verifyNativeLibraryVersion();
           
            // Get the PID of the current JVM from the native library.  Be careful as the method
            //  will not exist if the library is old.
            try
            {
                System.setProperty( "wrapper.java.pid", Integer.toString( nativeGetJavaPID() ) );
            }
            catch ( Throwable e )
            {
                if ( m_debug )
                {
                    m_outDebug.println( getRes().getString( "Call to nativeGetJavaPID() failed: {0}", e ) );
                }
            }
        }
       
        // Start a thread which looks for control events sent to the
        //  process.  The thread is also used to keep track of whether
        //  the VM has been getting CPU to avoid invalid timeouts and
        //  to maintain the number of ticks since the JVM was launched.
        m_eventRunnerTicks = getTicks();
        m_eventRunner = new Thread( "Wrapper-Control-Event-Monitor" )
        {
            public void run()
            {
                if ( m_debug )
                {
                    m_outDebug.println( getRes().getString( "Control event monitor thread started." ) );
                }
               
                try
                {
                    WrapperTickEventImpl tickEvent = new WrapperTickEventImpl();
                    int lastTickOffset = 0;
                    boolean first = true;
                    boolean stoppingLogged = false;
                   
                    // This loop should never exit because the tick counting is required
                    //  for the life of the JVM.
                    while ( true )
                    {
                        int offsetDiff;
                        if ( !m_useSystemTime )
                        {
                            // Get the tick count based on the system time.
                            int sysTicks = getSystemTicks();
                           
                            // Increment the tick counter by 1. This loop takes just slightly
                            //  more than the length of a "tick" but it is a good enough
                            //  approximation for our purposes.  The accuracy of the tick length
                            //  falls sharply when the system is under heavly load, but this
                            //  has the desired effect as the Wrapper is also much less likely
                            //  to encounter false timeouts due to the heavy load.
                            // The ticks field is volatile and a single integer, so it is not
                            //  necessary to synchronize this.
                            // When the ticks count reaches the upper limit of the int range,
                            //  it is ok to just let it overflow and wrap.
                            m_ticks++;
                           
                            // Calculate the offset between the two tick counts.
                            //  This will always work due to overflow.
                            int tickOffset = sysTicks - m_ticks;
                           
                            // The number we really want is the difference between this tickOffset
                            //  and the previous one.
                            offsetDiff = tickOffset - lastTickOffset;
                           
                            if ( first )
                            {
                                first = false;
                            }
                            else
                            {
                                if ( offsetDiff > m_timerSlowThreshold )
                                {
                                    m_outInfo.println( getRes().getString( "The timer fell behind the system clock by {0} ms." ,
                                        new Integer( offsetDiff * TICK_MS ) ) );
                                }
                                else if ( offsetDiff < - m_timerFastThreshold )
                                {
                                    m_outInfo.println( getRes().getString( "The system clock fell behind the timer by {0} ms." ,
                                            new Integer( -1 * offsetDiff * TICK_MS ) ) );
                                }
                            }
                           
                            // Store this tick offset for the net time through the loop.
                            lastTickOffset = tickOffset;
                        }
                        else
                        {
                            offsetDiff = 0;
                        }
                       
                        //m_outInfo.println( "  UNIX Time: "
                        //  + Long.toHexString( System.currentTimeMillis() )
                        //  + ", ticks=" + Integer.toHexString( getTicks() ) + ", sysTicks="
                        //  + Integer.toHexString( getSystemTicks() ) );
                           
                        // Attempt to detect whether or not we are being starved of CPU.
                        //  This will only have any effect if the m_useSystemTime flag is
                        //  set.
                        // The tick timer will always result in an age of exactly one
                        //  because it is incremented each time through this loop.
                        int nowTicks = getTicks();
                        long age = getTickAge( m_eventRunnerTicks, nowTicks );
                        if ( ( m_cpuTimeout > 0 ) && ( age > m_cpuTimeout ) )
                        {
                            m_outInfo.println( getRes().getString( "JVM Process has not received any CPU time for {0} seconds.  Extending timeouts." ,
                                new Long( age / 1000 ) ) );
                           
                            // Make sure that we don't get any ping timeouts in this event
                            m_lastPingTicks = nowTicks;
                        }
                        m_eventRunnerTicks = nowTicks;
                       
                        // If there are any listeners interrested in core events then fire
                        //  off a tick event.
                        if ( m_produceCoreEvents )
                        {
                            tickEvent.m_ticks = nowTicks;
                            tickEvent.m_tickOffset = offsetDiff;
                            fireWrapperEvent( tickEvent );
                        }
                       
                        if ( m_libraryOK )
                        {
                            // To avoid the JVM shutting down while we are in the middle of a JNI call,
                            if ( !isShuttingDown() )
                            {
                                // Look for control events in the wrapper library.
                                //  There may be more than one.
                                int event = 0;
                                do
                                {
                                    event = WrapperManager.nativeGetControlEvent();
                                    if ( event != 0 )
                                    {
                                        WrapperManager.controlEvent( event );
                                    }
                                }
                                while ( event != 0 );
                            }
                            else if ( !stoppingLogged )
                            {
                                stoppingLogged = true;
                                if ( m_debug )
                                {
                                    m_outDebug.println( getRes().getString( "Stopped checking for control events." ) );
                                }
                            }
                        }
                       
                        // Wait before checking for another control event.
                        try
                        {
                            Thread.sleep( TICK_MS );
                        }
                        catch ( InterruptedException e )
                        {
                        }
                    }
                }
                finally
                {
                    if ( m_debug )
                    {
                        m_outDebug.println( getRes().getString( "Control event monitor thread stopped." ) );
                    }
                }
            }
        };
        m_eventRunner.setDaemon( true );
        m_eventRunner.start();
       
        // Resolve the system thread count based on the Java Version
        String fullVersion = System.getProperty( "java.fullversion" );
        String vendor = System.getProperty( "java.vm.vendor", "" );
        String os = System.getProperty( "os.name", "" ).toLowerCase();
        if ( fullVersion == null )
        {
            fullVersion = System.getProperty( "java.runtime.version" ) + " "
                + System.getProperty( "java.vm.name" );
        }

        if ( m_debug )
        {
            // Display more JVM info right after the call initialization of the
            // library.
            m_outDebug.println( getRes().getString( "Java Version   : {0}", fullVersion ) );
            m_outDebug.println( getRes().getString( "Java VM Vendor : {0}", vendor ) );
            m_outDebug.println( getRes().getString( "OS Name        : {0}", System.getProperty( "os.name", "" ) ) );
            m_outDebug.println( getRes().getString( "OS Arch        : {0}", System.getProperty( "os.arch", "" ) ) );
            m_outDebug.println();
        }
       
        // This thread will most likely be thread which launches the JVM.
        //  Once this method returns however, the main thread will likely
        //  quit.  There will be a slight delay before the Wrapper binary
        //  has a change to send a command to start the application.
        //  During this lag, the JVM may not have any non-daemon threads
        //  running and would exit.   To keep it from doing so, start a
        //  simple non-daemon thread which will run until the
        //  WrapperListener.start() method returns or the Wrapper's
        //  shutdown thread has started.
        m_startupRunner = new Thread( "Wrapper-Startup-Runner" )
        {
            public void run()
            {
                // Make sure the rest of this thread does not fall behind the application.
                Thread.currentThread().setPriority( Thread.MAX_PRIORITY );
               
                if ( m_debug )
                {
                    m_outDebug.println( getRes().getString( "Startup runner thread started." ) );
                }
               
                try
                {
                    while ( m_startupRunner != null )
                    {
                        try
                        {
                            Thread.sleep( 100 );
                        }
                        catch ( InterruptedException e )
                        {
                            // Ignore.
                        }
                    }
                }
                finally
                {
                    if ( m_debug )
                    {
                        m_outDebug.println( getRes().getString( "Startup runner thread stopped." ) );
                    }
                }
            }
        };
        // This thread must not be a daemon thread.
        m_startupRunner.setDaemon( false );
        m_startupRunner.start();
       
        // Create the singleton
        m_instance = new WrapperManager();
    }

    /*---------------------------------------------------------------
     * Native Methods
     *-------------------------------------------------------------*/
    private static native void nativeInit( boolean debug );
    private static native String nativeGetLibraryVersion();
    private static native int nativeGetJavaPID();
    private static native boolean nativeIsProfessionalEdition();
    private static native boolean nativeIsStandardEdition();
    private static native int nativeGetControlEvent();
    private static native int nativeRedirectPipes();
    private static native void nativeRequestThreadDump();
    private static native void accessViolationInner();
    private static native void nativeSetConsoleTitle( String titleBytes );
    private static native WrapperUser nativeGetUser( boolean groups );
    private static native WrapperUser nativeGetInteractiveUser( boolean groups );
    private static native WrapperWin32Service[] nativeListServices();
    private static native WrapperWin32Service nativeSendServiceControlCode( String serviceName, int controlCode );
    private static native WrapperProcess nativeExec( String[] cmdArray, String cmdLine, WrapperProcessConfig config, boolean allowCWDOnSpawn );
    private static native String nativeWrapperGetEnv( String val ) throws NullPointerException;
    private static native WrapperResources nativeLoadWrapperResources(String domain, String folder, boolean makeActive);
    private static native boolean nativeCheckDeadLocks();
   
    /*---------------------------------------------------------------
     * Methods
     *-------------------------------------------------------------*/
    /**
     * Returns a tick count calculated from the system clock.
     */
    private static int getSystemTicks()
    {
        // Calculate a tick count using the current system time.  The
        //  conversion from a long in ms, to an int in TICK_MS increments
        //  will result in data loss, but the loss of bits and resulting
        //  overflow is expected and Ok.
        return (int)( System.currentTimeMillis() / TICK_MS );
    }
   
    /**
     * Returns the number of ticks since the JVM was launched.  This
     *  count is not good enough to be used where accuracy is required but
     *  it allows us to implement timeouts in environments where the system
     *  time is modified while the JVM is running.
     * <p>
     * An int is used rather than a long so the counter can be implemented
     *  without requiring any synchronization.  At the tick resolution, the
     *  tick counter will overflow and wrap (every 6.8 years for 100ms ticks).
     *  This behavior is expected.  The getTickAge method should be used
     *  in cases where the difference between two ticks is required.
     *
     * Returns the tick count.
     */
    private static int getTicks()
    {
        if ( m_useSystemTime )
        {
            return getSystemTicks();
        }
        else
        {
            return m_ticks;
        }
    }
   
    /**
     * Returns the number of milliseconds that have elapsed between the
     *  start and end counters.  This method assumes that both tick counts
     *  were obtained by calling getTicks().  This method will correctly
     *  handle cases where the tick counter has overflowed and reset.
     *
     * @param start A base tick count.
     * @param end An end tick count.
     *
     * @return The number of milliseconds that are represented by the
     *         difference between the two specified tick counts.
     */
    private static long getTickAge( int start, int end )
    {
        // Important to cast the first value so that negative values are correctly
        //  cast to negative long values.
        return (long)( end - start ) * TICK_MS;
    }
   
    /**
     * Attempts to load the a native library file.
     *
     * @param name Name of the library to load.
     * @param file Name of the actual library file.
     *
     * @return null if the library was successfully loaded, an error message
     *         otherwise.
     */
    private static String loadNativeLibrary( String name, String file )
    {
        try
        {
            System.loadLibrary( name );
           
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString( "  Loaded native library: " , file ) );
            }
           
            return null;
        }
        catch ( UnsatisfiedLinkError e )
        {
            if ( m_debug )
            {
                m_outDebug.println(getRes().getString( "  Unable to load native library: {0}  Cause: {1}",  file , e.getMessage() ) );
            }
            String error = e.getMessage();
            if ( error == null )
            {
                error = e.toString();
            }
            return error;
        }
        catch ( Throwable e )
        {
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString( "  Loading native library failed: {0}  Cause: {1}", file , e  ) );
            }
            String error = e.toString();
            return error;
        }
    }
   
    /**
     * Java 1.5 and above supports the ability to register the WrapperManager
     *  MBean internally.
     */
    private static void registerMBean( Object mbean, String name )
    {
        Class classManagementFactory;
        Class classMBeanServer;
        Class classObjectName;
        try
        {
            classManagementFactory = Class.forName( "java.lang.management.ManagementFactory" );
            classMBeanServer = Class.forName( "javax.management.MBeanServer" );
            classObjectName = Class.forName( "javax.management.ObjectName" );
        }
        catch ( ClassNotFoundException e )
        {
            if ( m_debug )
            {
                m_outDebug.println(getRes().getString( "Registering MBeans not supported by current JVM: {0}" ,  name  ) );
            }
            return;
        }
       
        try
        {
            // This code uses reflection so it combiles on older JVMs.
            // The original code is as follows:
            // javax.management.MBeanServer mbs =
            //     java.lang.management.ManagementFactory.getPlatformMBeanServer();
            // javax.management.ObjectName oName = new javax.management.ObjectName( name );
            // mbs.registerMBean( mbean, oName );
           
            // The version of the above code using reflection follows.
            Method methodGetPlatformMBeanServer =
                classManagementFactory.getMethod( "getPlatformMBeanServer", (Class[])null );
            Constructor constructorObjectName =
                classObjectName.getConstructor( new Class[] {String.class} );
            Method methodRegisterMBean = classMBeanServer.getMethod(
                "registerMBean", new Class[] {Object.class, classObjectName} );
            Object mbs = methodGetPlatformMBeanServer.invoke( (Object)null, (Object[])null );
            Object oName = constructorObjectName.newInstance( new Object[] {name} );
            methodRegisterMBean.invoke( mbs, new Object[] {mbean, oName} );
           
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString( "Registered MBean with Platform MBean Server: {0}",  name ) );
            }
        }
        catch ( Throwable t )
        {
            if ( t instanceof ClassNotFoundException ) {
                m_outError.println( "Using MBean requires at least a JVM version 1.5." );
            }
            m_outError.println( "Unable to register the " + name + " MBean." );
            t.printStackTrace( m_outError );
        }
    }
   
    /**
     * Searches for a file on a path.
     *
     * @param file File to look for.
     * @param path Path to be searched.
     *
     * @return Reference to thr file object if found, otherwise null.
     */
    private static File locateFileOnPath( String file, String path )
    {
        // A library path exists but the library was not found on it.
        String pathSep = System.getProperty( "path.separator" );
       
        // Search for the file on the library path to verify that it does not
        //  exist, it could be some other problem
        StringTokenizer st = new StringTokenizer( path, pathSep );
        while( st.hasMoreTokens() )
        {
            File libFile = new File( new File( st.nextToken() ), file );
            if ( libFile.exists() )
            {
                return libFile;
            }
        }
       
        return null;
    }
   
    /**
     * Generates a detailed native library base name which is made up of the
     *  base name, the os name, architecture, and the bits of the current JVM,
     *  not the platform.
     *
     * @return A detailed native library base name.
     */
    private static String generateDetailedNativeLibraryBaseName( String baseName,
                                                                 int jvmBits )
    {
        // Generate an os name.  Most names are used as is, but some are modified.
        String os = System.getProperty( "os.name", "" ).toLowerCase();
        if ( os.startsWith( "windows" ) )
        {
            os = "windows";
            m_windows = true;
        }
        else if ( os.equals( "sunos" ) )
        {
            os = "solaris";
        }
        else if ( os.equals( "hp-ux" ) || os.equals( "hp-ux64" ) )
        {
            os = "hpux";
        }
        else if ( os.equals( "mac os x" ) )
        {
            os = "macosx";
            m_macosx = true;
        }
        else if ( os.equals( "unix_sv" ) )
        {
            os = "unixware";
        }
        else if ( os.equals( "os/400" ) )
        {
            os = "os400";
        }
        else if ( os.equals( "z/os" ) )
        {
            os = "zos";
        }
       
        // Generate an architecture name.
        String arch;
        if ( m_macosx )
        {
            arch = "universal";
        }
        else
        {
            arch = System.getProperty( "os.arch", "" ).toLowerCase();
            if ( arch.equals( "amd64" ) || arch.equals( "athlon" ) || arch.equals( "x86_64" ) ||
                arch.equals( "i686" ) || arch.equals( "i586" ) || arch.equals( "i486" ) ||
                arch.equals( "i386" ) )
            {
                arch = "x86";
            }
            else if ( arch.startsWith( "ia32" ) || arch.startsWith( "ia64" ) )
            {
                arch = "ia";
            }
            else if ( arch.startsWith( "sparc" ) )
            {
                arch = "sparc";
            }
            else if ( arch.equals( "power" ) || arch.equals( "powerpc" ) || arch.equals( "ppc64" ) )
            {
                arch = "ppc";
            }
            else if ( arch.startsWith( "pa_risc" ) || arch.startsWith( "pa-risc" ) )
            {
                arch = "parisc";
            }
            else if ( arch.equals( "s390" ) || arch.equals( "s390x" ) )
            {
                arch = "390";
            }
        }
       
        return baseName + "-" + os + "-" + arch + "-" + jvmBits;
    }
   
    /**
     * Searches for and then loads the native library.  This method will attempt
     *  locate the wrapper library using one of the following 3 naming
     */
    private static void initializeNativeLibrary()
    {
        // Look for the base name of the library.
        String baseName = System.getProperty( "wrapper.native_library" );
        if ( baseName == null )
        {
            // This should only happen if an old version of the Wrapper binary is being used.
            m_outInfo.println( getRes().getString( "WARNING - The wrapper.native_library system property was not" ) );
            m_outInfo.println( getRes().getString( "          set. Using the default value, 'wrapper'." ) );
            baseName = "wrapper";
        }
        String[] detailedNames = new String[4];
        if ( m_jvmBits > 0 )
        {
            detailedNames[0] = generateDetailedNativeLibraryBaseName( baseName, m_jvmBits );
        }
        else
        {
            detailedNames[0] = generateDetailedNativeLibraryBaseName( baseName, 32 );
            detailedNames[1] = generateDetailedNativeLibraryBaseName( baseName, 64 );
        }
       
        // Construct brief and detailed native library file names.
        String file = System.mapLibraryName( baseName );
        String[] detailedFiles = new String[detailedNames.length];
        for ( int i = 0; i < detailedNames.length; i++ )
        {
            if ( detailedNames[i] != null )
            {
                detailedFiles[i] = System.mapLibraryName( detailedNames[i] );
            }
        }
       
        String[] detailedErrors = new String[detailedNames.length];
        String baseError = null;
        // Try loading the native library using the detailed name first.  If that fails, use
        //  the brief name.
        if ( m_debug )
        {
            m_outDebug.println( getRes().getString(
                "Load native library. One or more attempts may fail if platform specific libraries do not exist.  This is NORMAL and is only a problem if they all fail." ) );
        }
        m_libraryOK = false;
        for ( int i = 0; i < detailedNames.length; i++ )
        {
            if ( detailedNames[i] != null )
            {
                detailedErrors[i] = loadNativeLibrary( detailedNames[i], detailedFiles[i] );
                if ( detailedErrors[i] == null )
                {
                    m_libraryOK = true;
                    break;
                }
            }
        }
        if ( ( !m_libraryOK ) && ( ( baseError = loadNativeLibrary( baseName, file ) ) == null ) )
        {
            m_libraryOK = true;
        }
        if ( m_libraryOK )
        {
            // Try reloading the resources once the library is initialized so we get actual localized content.
            m_res = loadWrapperResourcesInner( System.getProperty( "wrapper.lang.domain") + "jni",
                WrapperSystemPropertyUtil.getStringProperty( "wrapper.lang.folder", "../lang" ), true );
           
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString( "Loaded native localization method." ) );
            }
            // The library was loaded correctly, so initialize it.
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString( "Calling native initialization method." ) );
            }
            nativeInit( m_debug );
           
            if ( m_stoppingInit )
            {
                // Certain checks in the nativeInit call can result in the JVM starting to shutdown.
                //  Avoid further JNI related messages that would be confusing.
                m_libraryOK = false;
            }
        }
        else
        {
            // The library could not be loaded, so we want to give the user a useful
            //  clue as to why not.
            String libPath = System.getProperty( "java.library.path" );
            m_outInfo.println();
            if ( libPath.equals( "" ) )
            {
                // No library path
                m_outInfo.println( getRes().getString(
                    "WARNING - Unable to load the Wrapper's native library because the" ) );
                m_outInfo.println( getRes().getString(
                    "          java.library.path was set to ''.  Please see the" ) );
                m_outInfo.println( getRes().getString(
                    "          documentation for the wrapper.java.library.path " ) );
                m_outInfo.println( getRes().getString(
                    "          configuration property." ) );
            }
            else
            {
                // Attempt to locate the actual files on the path.
                String error = null;
                File libFile = null;
                for ( int i = 0; i < detailedNames.length; i++ )
                {
                    if ( detailedFiles[i] != null )
                    {
                        libFile = locateFileOnPath( detailedFiles[i], libPath );
                        if ( libFile != null )
                        {
                            error = detailedErrors[i];
                            break;
                        }
                    }
                }
                if ( libFile == null )
                {
                    libFile = locateFileOnPath( file, libPath );
                    if ( libFile != null )
                    {
                        error = baseError;
                    }
                }
                if ( libFile == null )
                {
                    // The library could not be located on the library path.
                    m_outInfo.println( getRes().getString(
                        "WARNING - Unable to load the Wrapper's native library because none of the" ) );
                    m_outInfo.println( getRes().getString(
                        "          following files:" ) );
                    for ( int i = 0; i < detailedNames.length; i++ )
                    {
                        if ( detailedFiles[i] != null )
                        {
                            m_outInfo.println(
                                "            " + detailedFiles[i] );
                        }
                    }
                    m_outInfo.println(
                        "            " + file );
                    m_outInfo.println( getRes().getString(
                        "          could be located on the following java.library.path:" ) );
                   
                    String pathSep = System.getProperty( "path.separator" );
                    StringTokenizer st = new StringTokenizer( libPath, pathSep );
                    while ( st.hasMoreTokens() )
                    {
                        File pathElement = new File( st.nextToken() );
                        m_outInfo.println( "            " + pathElement.getAbsolutePath() );
                    }
                    m_outInfo.println( getRes().getString(
                        "          Please see the documentation for the wrapper.java.library.path" ) );
                    m_outInfo.println(getRes().getString(
                        "          configuration property." ) );
                }
                else
                {
                    // The library file was found but could not be loaded for some reason.
                    m_outInfo.println( getRes().getString(
                        "WARNING - Unable to load the Wrapper''s native library ''{0}''.", libFile.getName() ) );
                    m_outInfo.println( getRes().getString(
                        "          The file is located on the path at the following location but" ) );
                    m_outInfo.println( getRes().getString(
                        "          could not be loaded:" ) );
                    m_outInfo.println(
                        "            " + libFile.getAbsolutePath() );
                    m_outInfo.println(getRes().getString(
                        "          Please verify that the file is both readable and executable by the" ) );
                    m_outInfo.println(getRes().getString(
                        "          current user and that the file has not been corrupted in any way." ) );
                    m_outInfo.println(getRes().getString(
                        "          One common cause of this problem is running a 32-bit version" ) );
                    m_outInfo.println(getRes().getString(
                        "          of the Wrapper with a 64-bit version of Java, or vica versa." ) );
                    if ( m_jvmBits > 0 )
                    {
                        m_outInfo.println( getRes().getString(
                            "          This is a {0}-bit JVM."new Integer( m_jvmBits ) ) );
                    }
                    else
                    {
                        m_outInfo.println( getRes().getString(
                            "          The bit depth of this JVM could not be determined." ) );
                    }
                    m_outInfo.println( getRes().getString(
                        "          Reported cause:" ) );
                    m_outInfo.println(
                        "            " + error );
                }
            }
            m_outInfo.println( getRes().getString(
                    "          System signals will not be handled correctly." ) );
            m_outInfo.println();
        }
    }
   
    /**
     * Compares the version of the wrapper which launched this JVM with that of
     *  the jar.  If they differ then a Warning message will be displayed.  The
     *  Wrapper application will still be allowed to start.
     */
    private static void verifyWrapperVersion()
    {
        // If we are not being controlled by the wrapper then return.
        if ( !WrapperManager.isControlledByNativeWrapper() )
        {
            return;
        }
       
        // Lookup the version from the wrapper.  It should have been set as a property
        //  when the JVM was launched.
        String wrapperVersion = System.getProperty( "wrapper.version" );
        if ( wrapperVersion == null )
        {
            wrapperVersion = getRes().getString( "unknown" );
        }
        if ( wrapperVersion.endsWith( "-pro" ) )
        {
            wrapperVersion = wrapperVersion.substring( 0, wrapperVersion.length() - 4 );
        }
        else if ( wrapperVersion.endsWith( "-st" ) )
        {
            wrapperVersion = wrapperVersion.substring( 0, wrapperVersion.length() - 3 );
        }
       
        if ( !WrapperInfo.getVersion().equals( wrapperVersion ) )
        {
            m_outInfo.println(getRes().getString(
                "WARNING - The Wrapper jar file currently in use is version \"{0}\"" ,
                WrapperInfo.getVersion() ) );
            m_outInfo.println(getRes().getString(
                "          while the version of the Wrapper which launched this JVM is " ) );
            m_outInfo.println(
                "          \"" + wrapperVersion + "\"." );
            m_outInfo.println( getRes().getString(
                "          The Wrapper may appear to work correctly but some features may" ) );
            m_outInfo.println( getRes().getString(
                "          not function correctly.  This configuration has not been tested" ) );
            m_outInfo.println( getRes().getString(
                "          and is not supported." ) );
            m_outInfo.println();
        }
    }
   
    /**
     * Compares the version of the native library with that of this jar.  If
     *  they differ then a Warning message will be displayed.  The Wrapper
     *  application will still be allowed to start.
     */
    private static void verifyNativeLibraryVersion()
    {
        // Request the version from the native library.  Be careful as the method
        //  will not exist if the library is old.
        String jniVersion;
        try
        {
            jniVersion = nativeGetLibraryVersion();
        }
        catch ( Throwable e )
        {
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString(
                        "Call to nativeGetLibraryVersion() failed: {0}",  e ) );
            }
            jniVersion = getRes().getString( "unknown" );
        }
       
        String wrapperVersion = System.getProperty( "wrapper.version" );
        if ( wrapperVersion == null )
        {
            wrapperVersion = getRes().getString( "unknown" );
        }
       
        if ( !wrapperVersion.equals( jniVersion ) )
        {
            m_outInfo.println( getRes().getString(
                "WARNING - The version of the Wrapper which launched this JVM is " ) );
            m_outInfo.println( getRes().getString(
                "          \"{0}\" while the version of the native library ", wrapperVersion ) );
            m_outInfo.println(getRes().getString(                 "          is \"{0}\"." ,jniVersion ) );
            m_outInfo.println(getRes().getString(
                "          The Wrapper may appear to work correctly but some features may" ) );
            m_outInfo.println( getRes().getString(
                "          not function correctly.  This configuration has not been tested" ) );
            m_outInfo.println( getRes().getString(
                "          and is not supported." ) );
            m_outInfo.println();
        }
    }
   
    /**
     * Checks to make sure that the configured temp directory is writable.  Failures are only logged
     *  to debug output.
     */
    private static void checkTmpDir()
    {
        File tmpDir = new File( System.getProperty( "java.io.tmpdir" ) );
        if ( m_debug )
        {
            m_outDebug.println( getRes().getString( "Java temporary directory: {0}", tmpDir ) );
        }
       
        boolean tmpDirCheck = getProperties().getProperty( "wrapper.java.tmpdir.check", "TRUE").equalsIgnoreCase( "TRUE" );
        if ( !tmpDirCheck )
        {
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString( "Validation of temporary directory disabled." ) );
            }
            return;
        }
       
        boolean tmpDirRequired = getProperties().getProperty( "wrapper.java.tmpdir.required", "FALSE" ).equalsIgnoreCase( "TRUE" );
        boolean tmpDirWarnSilently = getProperties().getProperty( "wrapper.java.tmpdir.warn_silently", "TRUE" ).equalsIgnoreCase( "TRUE" );
        Exception ex = null;
        try
        {
            tmpDir = tmpDir.getCanonicalFile();
            File tempFile = new File( tmpDir, "wrapper-" + System.currentTimeMillis() + "-" + getJavaPID() );
            if ( tempFile.createNewFile() )
            {
                if ( !tempFile.delete() )
                {
                    m_outError.println( "Unable to delete temporary file: " + tempFile );
                }
            }
            else
            {
                if ( m_debug )
                {
                    m_outDebug.println( "Unable to create temporary file: " + tempFile );
                }
            }
        }
        catch ( IOException e )
        {
            ex = e;
        }
        catch ( SecurityException e )
        {
            ex = e;
        }
       
        if ( ex != null )
        {
            if ( tmpDirRequired )
            {
                m_outError.println( getRes().getString( "Unable to write to the configured Java temporary directory: {0} : {1}", tmpDir, ex.toString() ) );
                m_outError.println( getRes().getString( "Shutting down." ) );
                System.exit( 1 );
            }
            else
            {
                if ( tmpDirWarnSilently )
                {
                    if ( m_debug )
                    {
                        m_outDebug.println( getRes().getString( "Unable to write to the configured Java temporary directory: {0} : {1}", tmpDir, ex.toString() ) );
                    }
                }
                else
                {
                    m_outInfo.println( getRes().getString( "Unable to write to the configured Java temporary directory: {0} : {1}", tmpDir,ex.toString() ) );
                }
                if ( m_debug )
                {
                    m_outDebug.println( getRes().getString( "  The lack of a temp directory could lead to problems with features that store temporary data, including remote jar class loading." ) );
                    m_outDebug.println( getRes().getString( "  The Java temporary directory can be redefined with the java.io.tmpdir system property." ) );
                }
            }
        }
    }
   
    /**
     * Loads a WrapperResources based on the current locale of the JVM.
     *
     * @param domain Domain of the resource.
     * @param folder Location of the resource.
     *
     * @return The requested resource.
     */
    private static WrapperResources loadWrapperResourcesInner( String domain, String folder, boolean makeActive )
    {
        try
        {
            return nativeLoadWrapperResources( domain, folder, makeActive );
        }
        catch ( UnsatisfiedLinkError e )
        {
            return new WrapperResources();
        }
    }
   
    /**
     * Loads a WrapperResources based on the current locale of the JVM.
     *
     * @param domain Domain of the resource.
     * @param folder Location of the resource.
     *
     * @return The requested resource.
     */
    public static WrapperResources loadWrapperResources( String domain, String folder )
    {
        return loadWrapperResourcesInner( domain, folder, false );
    }
   
    /**
     * Obtain the current version of Wrapper.
     *
     * @return The version of the Wrapper.
     */
    public static String getVersion()
    {
        return WrapperInfo.getVersion();
    }
   
    /**
     * Obtain the build time of Wrapper.
     *
     * @return The time that the Wrapper was built.
     */
    public static String getBuildTime()
    {
        return WrapperInfo.getBuildTime();
    }
   
    /**
     * Returns the Id of the current JVM.  JVM Ids increment from 1 each time
     *  the wrapper restarts a new one.
     *
     * @return The Id of the current JVM.
     */
    public static int getJVMId()
    {
        return m_jvmId;
    }
   

    private static String[] parseCommandLine( String cmdLine )
    {
        ArrayList argList = new ArrayList();
        StringBuffer arg = new StringBuffer();
        boolean quoteMode = false;
        boolean escapeNextCharIfQuote = false;
        char c[] = cmdLine.toCharArray();
        for ( int i = 0; i < cmdLine.length(); i++ )
        {
            if ( ( c[i] == '\\' ) && !escapeNextCharIfQuote )
            {

                escapeNextCharIfQuote = true;
            }
            else
            {
                if ( Character.isWhitespace( c[i] ) && ( quoteMode == false ) )
                {
                    if ( arg.length() > 0 )
                    {
                        argList.add( arg.toString() );
                        arg.setLength( 0 );
                    }
                }
                else
                {
                    if ( c[i] == '\"' )
                    {
                        if ( escapeNextCharIfQuote == false )
                        {
                            quoteMode = ( ( quoteMode == true ) ? false : true );
                            escapeNextCharIfQuote = false;
                            continue;
                        }
                        else
                        {
                            // arg.append('\\');
                            escapeNextCharIfQuote = false;
                        }
                        arg.append( c[i] );
                    }
                    else if ( c[i] == '\\' )
                    {
                        if ( escapeNextCharIfQuote == true )
                        {
                            escapeNextCharIfQuote = false;
                        }
                        arg.append( '\\' );
                    }
                    else
                    {
                        if ( escapeNextCharIfQuote == true )
                        {
                            arg.append( '\\' );
                            escapeNextCharIfQuote = false;
                        }
                        arg.append( c[i] );
                    }
                } // else
            } // else
        } // for
        if ( arg.length() > 0 )
        {
            argList.add( arg.toString() );
        }
        String[] args = new String[ argList.size() ];
        argList.toArray( args );
        return args;
    }

    /**
     * A more powerful replacement to the java.lang.Runtime.exec method.
     * <p>
     * When the JVM exits or is terminated for any reason, the Wrapper will
     *  clean up any child processes launched with this method automatically
     *  before shutting down or launching a new JVM.
     * <p>
     * This method is the same as calling <code><pre>WrapperManger.exec(command, new WrapperProcessConfig());</pre></code>
     * <p>
     * The returned WrapperProcess object can be used to control the child
     *  process, supply input, or process output.
     * <p>
     * Professional Edition feature.
     *
     * @param command A specified system command in one String.
     *
     * @return A new WrapperProcess object for managing the subprocess.
     *
     * @throws IOException Will be thrown if an I/O error occurs
     * @throws NullPointerException If command is null.
     * @throws IllegalArgumentException If command is empty
     * @throws SecurityException If a SecurityManager is present and its
     *                           checkExec method doesn't allow creation of a
     *                           subprocess.   
     * @throws WrapperJNIError If the native library has not been loaded.
     * @throws WrapperLicenseError If the function is called other than in
     *                             the Professional Edition or from a Standalone JVM.
     * @throws UnsatisfiedLinkError If the posix_spawn function couldn't be found
     *
     * @see #isProfessionalEdition()
     * @see #exec(String command, WrapperProcessConfig config)
     * @see WrapperProcessConfig
     * @since Wrapper 3.4.0
     */
    public static WrapperProcess exec( String command )
        throws SecurityException, IOException, NullPointerException, IllegalArgumentException, WrapperJNIError, WrapperLicenseError, UnsatisfiedLinkError
    {
        WrapperProcess proc = exec( command, new WrapperProcessConfig() );
        return proc;
    }

    /**
     * A more powerful replacement to the java.lang.Runtime.exec method.
     * <p>
     * By configuring the WrapperProcessConfig object, it is possible to
     *  control whether or not the child process will be automatically
     *  cleaned up when the JVM exits or is terminated.  It is also possible
     *  to control how the child process is launched to work around memory
     *  issues on some platforms.
     * <p>
     * For example, on Solaris when the JVM is very large, doing a fork will
     *  duplicate the entire JVM's memory space and cause an out of memory
     *  error or JVM crash, to avoid such memory problems the child process
     *  can be launched using posix spawn as follows:<p>
     * <code><pre>WrapperManger.exec( command, new WrapperProcessConfig().setStartType( WrapperProcessConfig.POSIX_SPAWN ) );</pre></code>
     * <p>
     * Please review the WrapperProcessConfig class for a full list of
     *  options.
     * <p>
     * The returned WrapperProcess object can be used to control the child
     *  process, supply input, or process output.
     * <p>
     * Professional Edition feature.
     *
     * @param command A specified system command in one String.
     * @param config A WrapperProcessConfig object representing the Start/Run
     *               Configurations of the subprocess
     *
     * @return A new WrapperProcess object for managing the subprocess.
     *
     * @throws IOException Will be thrown if an I/O error occurs
     * @throws NullPointerException If command is null.
     * @throws IllegalArgumentException If command is empty or the configuration is invalid

     * @throws SecurityException If a SecurityManager is present and its
     *                           checkExec method doesn't allow creation of a
     *                           subprocess.
     * @throws WrapperJNIError If the native library has not been loaded.
     * @throws WrapperLicenseError If the function is called other than in
     *                             the Professional Edition or from a Standalone JVM.
     * @throws UnsatisfiedLinkError If the posix_spawn function couldn't be found
     *
     * @see #isProfessionalEdition()
     * @see WrapperProcessConfig
     * @since Wrapper 3.4.0
     */
    public static WrapperProcess exec( String command, WrapperProcessConfig config )
        throws SecurityException, IOException, NullPointerException, IllegalArgumentException, WrapperJNIError, WrapperLicenseError, UnsatisfiedLinkError
   
        if ( ( command == null ) || ( command.length() == 0 ) )
        {
            throw new IllegalArgumentException( getRes().getString( "No command specified." ) );
        }

        return exec( null, command, config );
    }

    /**
     * A more powerful replacement to the java.lang.Runtime.exec method.
     * <p>
     * When the JVM exits or is terminated for any reason, the Wrapper will
     *  clean up any child processes launched with this method automatically
     *  before shutting down or launching a new JVM.
     * <p>
     * This method is the same as calling <code><pre>WrapperManger.exec(cmdArray, new WrapperProcessConfig());</pre></code>
     * <p>
     * The returned WrapperProcess object can be used to control the child
     *  process, supply input, or process output.
     * <p>
     * Professional Edition feature.
     *
     * @param cmdArray A specified system command in array format for each
     *               parameter a single element.
     *
     * @return A new WrapperProcess object for managing the subprocess
     *
     * @throws IOException Will be thrown at any error realated with Memory
     *                     allocation, or if the command does not exist.
     * @throws NullPointerException If cmdarray is null, or one of the elements
     *                              of cmdarray is null.
     * @throws IndexOutOfBoundsException If cmdarray is an empty array (has
     *                                   length 0)

     * @throws SecurityException If a SecurityManager is present and its
     *                           checkExec method doesn't allow creation of a
     *                           subprocess.   
     * @throws IllegalArgumentException If there are any problems with the
     *                                  WrapperProcessConfig object.
     * @throws WrapperJNIError If the native library has not been loaded.
     * @throws WrapperLicenseError If the function is called other than in
     *                             the Professional Edition or from a Standalone JVM.
     * @throws UnsatisfiedLinkError If the posix_spawn function couldn't be found
     *
     * @see #isProfessionalEdition()
     * @since Wrapper 3.4.0
     */
    public static WrapperProcess exec( String[] cmdArray )
        throws SecurityException, IOException, NullPointerException, IndexOutOfBoundsException, IllegalArgumentException, WrapperJNIError, UnsatisfiedLinkError, WrapperLicenseError
    {
        WrapperProcess proc = exec( cmdArray, new WrapperProcessConfig() );
        return proc;
    }
   
   

    /**
     * A more powerful replacement to the java.lang.Runtime.exec method.
     * <p>
     * By configuring the WrapperProcessConfig object, it is possible to
     *  control whether or not the child process will be automatically
     *  cleaned up when the JVM exits or is terminated.  It is also possible
     *  to control how the child process is launched to work around memory
     *  issues on some platforms.
     * <p>
     * For example, on Solaris when the JVM is very large, doing a fork will
     *  duplicate the entire JVM's memory space and cause an out of memory
     *  error or JVM crash, to avoid such memory problems the child process
     *  can be launched using posix spawn as follows:<p>
     * <code><pre>WrapperManger.exec( cmdArray, new WrapperProcessConfig().setStartType( WrapperProcessConfig.POSIX_SPAWN ) );</pre></code>
     * <p>
     * Please review the WrapperProcessConfig class for a full list of
     *  options.
     * <p>
     * The returned WrapperProcess object can be used to control the child
     *  process, supply input, or process output.
     * <p>
     * Professional Edition feature.
     *
     * @param cmdArray A specified system command in array format, for each
     *               parameter a single element.
     * @param config A WrapperProcessConfig object representing the Start/Run
     *               Configurations of the subprocess
     *
     * @return A new WrapperProcess object for managing the subprocess.
     *
     * @throws IOException Will be thrown if an I/O error occurs 
     * @throws NullPointerException If cmdarray is null, or one of the elements
     *                              of cmdarray is null.
     * @throws IndexOutOfBoundsException If cmdarray is an empty array (has
     *                                   length 0)
     * @throws SecurityException If a SecurityManager is present and its
     *                           checkExec method doesn't allow creation of a
     *                           subprocess.
     * @throws IllegalArgumentException If there are any problems with the
     *                                  WrapperProcessConfig object.
     * @throws WrapperJNIError If the native library has not been loaded.
     * @throws WrapperLicenseError If the function is called other than in
     *                             the Professional Edition or from a Standalone JVM.
     * @throws UnsatisfiedLinkError If the posix_spawn function couldn't be found
     *
     * @see #isProfessionalEdition()
     * @since Wrapper 3.4.0
     */
    public static WrapperProcess exec( String[] cmdArray, WrapperProcessConfig config )
        throws SecurityException, IOException, NullPointerException, IndexOutOfBoundsException, IllegalArgumentException, WrapperJNIError, WrapperLicenseError, UnsatisfiedLinkError
    {
            return exec( cmdArray, null, config );
    }

    /**
     * Executes an external command.
     */
    private static WrapperProcess exec( String[] cmdArray, String cmdLine, WrapperProcessConfig config )
        throws SecurityException, IOException, NullPointerException, IndexOutOfBoundsException, IllegalArgumentException, WrapperJNIError, WrapperLicenseError, UnsatisfiedLinkError {
       
        // If the cmdArray parameter is null then the cmdLine will be parsed into an a cmdArray.
        //  Then both the cmdArray and cmdLine will be passed off to the native code.
        //  The cmdLine may be null.
       
        if ( !isProfessionalEdition() )
        {
            throw new WrapperLicenseError( getRes().getString( "Requires the Professional Edition." ) );
        }
       
        if ( ( cmdArray == null ) && ( cmdLine == null ) )
        {
            throw new NullPointerException( getRes().getString( "No command specified" ) );

        }
        else if ( ( cmdArray != null) && ( cmdArray.length == 0 ) )
        {
            throw new IndexOutOfBoundsException( getRes().getString( "cmdArray is empty" ) );
        }
       
        if ( ( cmdArray == null ) && ( cmdLine != null ) ) {
            cmdArray = parseCommandLine( cmdLine );
        }
       
        if ( config == null )
        {
            throw new NullPointerException( getRes().getString( "config is null" ) );
        }
       
        // Make sure the call stack has permission to execute this command.
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkExec( cmdArray[0] );
        }
       
        if ( m_libraryOK )
        {
            for ( int i = 0; i < cmdArray.length; i++ )
            {
                if ( cmdArray[i] == null )
                {
                    throw new NullPointerException( getRes().getString( "cmdarray[{0}]: Invalid element (isNull).",
                            new Integer( i)  ) );
                }
            }
           
            // On UNIX platforms, we want to try and make sure the command is
            //  valid before we run it to avoid problems later.  Not necessary
            //  on Windows.
            if ( !m_windows )
            {
                if ( !new File( cmdArray[0] ).exists() )
                {
                    boolean found = false;
                    String path = nativeWrapperGetEnv( "PATH" );
                    if ( path != null )
                    {
                        String[] paths = path.split( File.pathSeparator );
                       
                        for ( int i = 0; i < paths.length; i++ )
                        {
                            File file = new File( paths[i] + File.separator + cmdArray[0] );
                            // m_outInfo.println( blu.getPath() );
                            if ( file.exists() )
                            {
                                cmdArray[0] = file.getPath();
                                found = true;
                                break;
                            }
                        }
                    }
                    if ( !found )
                    {
                        throw new IOException(getRes().getString( "''{0}'' not found." , cmdArray[0]  ) );
                    }
                }
            }
            if ( m_debug )
            {
                for ( int j = 0; j < cmdArray.length; j++ )
                {
                    m_outDebug.println( "args[" + j+ "] = " + cmdArray[j] );
                }
            }
            return nativeExec( cmdArray, cmdLine, config.setEnvironment( config.getEnvironment() ), WrapperSystemPropertyUtil.getBooleanProperty( "wrapper.child.allowCWDOnSpawn", false ) );
        } else {
            throw new WrapperJNIError( getRes().getString( "Wrapper native library not loaded." ) );
        }
       
    }
   
    /**
     * Returns true if the native library has been loaded successfully, false
     *  otherwise.
     *
     * @return True if the native library is loaded.
     */
    public static boolean isNativeLibraryOk()
    {
        return m_libraryOK;
    }
   
    /**
     * Returns true if the current JVM is Windows.
     *
     * @return True if this is Windows.
     *
     * @since Wrapper 3.5.1
     */
    public static boolean isWindows()
    {
        return m_windows;
    }
   
    /**
     * Returns true if the current JVM is Windows.
     *
     * @return True if this is Mac OSX.
     *
     * @since Wrapper 3.5.1
     */
    public static boolean isMacOSX()
    {
        return m_macosx;
    }
   
    /**
     * Returns true if the current Wrapper edition has support for Professional
     *  Edition features.
     *
     * @return True if professional features are supported.
     */
    public static boolean isProfessionalEdition()
    {
        // Be careful as this will not exist in older versions
        if ( m_libraryOK )
        {
            try
            {
                return nativeIsProfessionalEdition();
            }
            catch ( Throwable e )
            {
                if ( m_debug )
                {
                    m_outDebug.println( getRes().getString(
                            "Call to nativeIsProfessionalEdition() failed: {0}" , e  ) );
                }
                return false;
            }
        }
        else
        {
            return false;
        }
    }
   
    /**
     * Returns true if the current Wrapper edition has support for Standard
     *  Edition features.
     *
     * @return True if standard features are supported.
     */
    public static boolean isStandardEdition()
    {
        // Be careful as this will not exist in older versions
        if ( m_libraryOK )
        {
            try
            {
                return nativeIsStandardEdition();
            }
            catch ( Throwable e )
            {
                if ( m_debug )
                {
                    m_outDebug.println( getRes().getString(
                    "Call to nativeIsStandardEdition() failed: " , e ) );
                }
                return false;
            }
        }
        else
        {
            return false;
        }
    }
   
    /**
     *  Fires a user event user_n specified in the conf file
     *
     *  @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("fireUserEvent")
     *                           permission.
     *  @throws WrapperLicenseError If the function is called other than in
     *                             the Professional Edition or from a Standalone JVM.
     */
    public static void fireUserEvent( int eventNr )
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperUserEventPermission( "fireUserEvent", String.valueOf( eventNr ) ) );
        }
        if ( eventNr <= 0 || eventNr > 32767 ) {
            throw new java.lang.IllegalArgumentException( getRes().getString( "The user-event number must be in the range of 1-32767." ) );
        }
        if ( !isProfessionalEdition() )
        {
            throw new WrapperLicenseError( getRes().getString( "Requires the Professional Edition." ) );
        }
        sendCommand( WRAPPER_MSG_FIRE_USER_EVENT, String.valueOf( eventNr ) );
    }


    /**
     * Sets the title of the console in which the Wrapper is running.  This
     *  is currently only supported on Windows platforms.
     * <p>
     * As an alternative, it is also possible to set the console title from
     *  within the wrapper.conf file using the wrapper.console.title property.
     *
     * @param title The new title.  The specified string will be encoded
     *              to a byte array using the default encoding for the
     *              current platform.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("setConsoleTitle")
     *                           permission.
     *
     * @see WrapperPermission
     */
    public static void setConsoleTitle( String title )
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "setConsoleTitle" ) );
        }
       
        if ( m_libraryOK )
        {
            nativeSetConsoleTitle( title );
        }
    }
   
    /**
     * Returns a WrapperUser object which describes the user under which the
     *  Wrapper is currently running.  Additional platform specific information
     *  can be obtained by casting the object to a platform specific subclass.
     *  WrapperWin32User, for example.
     *
     * @param groups True if the user's groups should be returned as well.
     *               Requesting the groups that a user belongs to increases
     *               the CPU load required to complete the call.
     *
     * @return An object describing the current user.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("getUser") permission.
     *
     * @see WrapperPermission
     */
    public static WrapperUser getUser( boolean groups )
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "getUser" ) );
        }
       
        WrapperUser user = null;
        if ( m_libraryOK )
        {
            user = nativeGetUser( groups );
        }
        return user;
    }
   
    /**
     * Returns a WrapperUser object which describes the interactive user whose
     *  desktop is being interacted with.  When a service running on a Windows
     *  platform has its interactive flag set, this method will return the user
     *  who is currently logged in.  Additional platform specific information
     *  can be obtained by casting the object to a platform specific subclass.
     *  WrapperWin32User, for example.
     * <p>
     * If a user is not currently logged on then this method will return null.
     *  User code can repeatedly call this method to detect when a user has
     *  logged in.  To detect when a user has logged out, there are two options.
     *  1) The user code can continue to call this method until it returns null.
     *  2) Or if the WrapperListener method is being implemented, the
     *     WrapperListener.controlEvent method will receive a WRAPPER_CTRL_LOGOFF_EVENT
     *     event when the user logs out.
     * <p>
     * On XP systems, it is possible to switch to another account rather than
     *  actually logging out.  In such a case, the interactive user will be
     *  the first user that logged in.  This will also be the only user with
     *  which the service will interact.  If other users are logged in when the
     *  interactive user logs out, the service will not automatically switch to
     *  another logged in user.  Rather, the next user to log in will become
     *  the new user which the service will interact with.
     * <p>
     * This method will always return NULL on versions of NT prior to Windows
     *  2000.  This can not be helped as some required functions were not added
     *  to the windows API until NT version 5.0, also known as Windows 2000.
     *
     * @param groups True if the user's groups should be returned as well.
     *               Requesting the groups that a user belongs to increases
     *               the CPU load required to complete the call.
     *
     * @return The current interactive user, or null.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("getInteractiveUser")
     *                           permission.
     *
     * @see WrapperPermission
     */
    public static WrapperUser getInteractiveUser( boolean groups )
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "getInteractiveUser" ) );
        }
       
        WrapperUser user = null;
        if ( m_libraryOK )
        {
            user = nativeGetInteractiveUser( groups );
        }
        return user;
    }
   
    /**
     * Returns a Properties object containing expanded the contents of the
     *  configuration file used to launch the Wrapper.
     *
     * All properties are included so it is possible to define properties
     *  not used by the Wrapper in the configuration file and have then
     *  be available in this Properties object.
     *
     * @return The contents of the Wrapper configuration file.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("getProperties")
     *                           permission.
     *
     * @see WrapperPermission
     */
    public static Properties getProperties()
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "getProperties" ) );
        }
       
        return m_properties;
    }
   
    /**
     * Returns the PID of the Wrapper process.
     *
     * A PID of 0 will be returned if the JVM was launched standalone.
     *
     * This value can also be obtained using the 'wrapper.pid' system property.
     *
     * @return The PID of the Wrpper process.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("getWrapperPID") permission.
     *
     * @see WrapperPermission
     */
    public static int getWrapperPID()
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "getWrapperPID" ) );
        }
       
        return WrapperSystemPropertyUtil.getIntProperty( "wrapper.pid", 0 );
    }
   
    /**
     * Returns the PID of the Java process.
     *
     * A PID of 0 will be returned if the native library has not been initialized.
     *
     * This value can also be obtained using the 'wrapper.java.pid' system property.
     *
     * @return The PID of the Java process.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("getJavaPID") permission.
     *
     * @see WrapperPermission
     */
    public static int getJavaPID()
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "getJavaPID" ) );
        }
       
        return WrapperSystemPropertyUtil.getIntProperty( "wrapper.java.pid", 0 );
    }
   
    /**
     * Requests that the current JVM process request a thread dump.  This is
     *  the same as pressing CTRL-BREAK (under Windows) or CTRL-\ (under Unix)
     *  in the the console in which Java is running.  This method does nothing
     *  if the native library is not loaded.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("requestThreadDump")
     *                           permission.
     *
     * @see WrapperPermission
     */
    public static void requestThreadDump()
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "requestThreadDump" ) );
        }
       
        if ( m_libraryOK )
        {
            nativeRequestThreadDump();
        }
        else
        {
            m_outInfo.println( getRes().getString( "  wrapper library not loaded." ) );
        }
    }
   
    /**
     * (Testing Method) Causes the WrapperManager to go into a state which makes the JVM appear
     *  to be hung when viewed from the native Wrapper code.  Does not have any effect when the
     *  JVM is not being controlled from the native Wrapper. Useful for testing the Wrapper
     *  functions.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("test.appearHung") permission.
     *
     * @see WrapperPermission
     */
    public static void appearHung()
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "test.appearHung" ) );
        }
       
        m_outInfo.println( getRes().getString( "WARNING: Making JVM appear to be hung..." ) );
        m_appearHung = true;
    }
   
    /**
     * @deprecated Removed as of 3.5.8
     */
    public static void appearOrphan()
    {
    }
   
    /**
     * (Testing Method) Cause an access violation within the Java code.  Useful
     *  for testing the Wrapper functions.  This currently only crashes Sun
     *  JVMs and takes advantage of Bug #4369043 which does not exist in newer
     *  JVMs.  Use of the accessViolationNative() method is preferred.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("test.accessViolation")
     *                           permission.
     *
     * @see WrapperPermission
     */
    public static void accessViolation()
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "test.accessViolation" ) );
        }
       
        m_outInfo.println( getRes().getString(
                "WARNING: Attempting to cause an access violation..." ) );
       
        try
        {
            Class c = Class.forName( "java.lang.String" );
            java.lang.reflect.Method m = c.getDeclaredMethod( (String)null, (Class[])null );
        }
        catch( NoSuchMethodException ex )
        {
            // Correctly did not find method.  access_violation attempt failed.  Not Sun JVM?
        }
        catch( Exception ex )
        {
            if ( ex instanceof NoSuchFieldException )
            {
                // Can't catch this in a catch because the compiler doesn't think it is being
                //  thrown.  But it is thrown on IBM jvms at least
                // Correctly did not find method.  access_violation attempt failed.  Not Sun JVM?
            }
            else
            {
                // Shouldn't get here.
                ex.printStackTrace( m_outError );
            }
        }
       
        m_outInfo.println( getRes().getString(
                "  Attempt to cause access violation failed.  JVM is still alive." ) );
    }

    /**
     * (Testing Method) Cause an access violation within native JNI code.
     *  Useful for testing the Wrapper functions. This currently causes the
     *  access violation by attempting to write to a null pointer.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("test.accessViolationNative")
     *                           permission.
     *
     * @see WrapperPermission
     */
    public static void accessViolationNative()
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "test.accessViolationNative" ) );
        }
       
        m_outInfo.println( getRes().getString(
                "WARNING: Attempting to cause an access violation..." ) );
        if ( m_libraryOK )
        {
            accessViolationInner();
       
            m_outInfo.println( getRes().getString(
                    "  Attempt to cause access violation failed.  JVM is still alive." ) );
        }
        else
        {
            m_outInfo.println( getRes().getString( "  wrapper library not loaded." ) );
        }
    }
       
    /**
     * Returns true if the JVM was launched by the Wrapper application.  False
     *  if the JVM was launched manually without the Wrapper controlling it.
     *
     * @return True if the current JVM was launched by the Wrapper.
     */
    public static boolean isControlledByNativeWrapper()
    {
        return m_key != null;
    }
   
    /**
     * Returns true if the Wrapper was launched as an NT service on Windows or
     *  as a daemon process on UNIX platforms.  False if launched as a console.
     *  This can be useful if you wish to display a user interface when in
     *  Console mode.  On UNIX platforms, this is not as useful because an
     *  X display may not be visible even if launched in a console.
     *
     * @return True if the Wrapper is running as an NT service or daemon
     *         process.
     */
    public static boolean isLaunchedAsService()
    {
        return m_service;
    }
   
    /**
     * Returns true if the JVM should ignore user logoff events.  Mainly used
     *  within WrapperListener.controlEvent() method implemenations.
    
     * @return True if user logoff events should be ignroed.
     */
    public static boolean isIgnoreUserLogoffs()
    {
        return m_ignoreUserLogoffs;
    }
   
    /**
     * Returns true if the wrapper.debug property, or any of the logging
     *  channels are set to DEBUG in the wrapper configuration file.  Useful
     *  for deciding whether or not to output certain information to the
     *  console.
     *
     * @return True if the Wrapper is logging any Debug level output.
     */
    public static boolean isDebugEnabled()
    {
        return m_debug;
    }
   
    /**
     * Start the Java side of the Wrapper code running.  This will make it
     *  possible for the native side of the Wrapper to detect that the Java
     *  Wrapper is up and running.
     * <p>
     * This method must be called on startup and then can only be called once
     *  so there is no reason for any security permission checks on this call.
     *
     * @param listener The WrapperListener instance which represents the
     *                 application being started.
     * @param args The argument list passed to the JVM when it was launched.
     */
    public static void start( final WrapperListener listener, final String[] args )
    {
        // As was done in the static initializer, we need to execute the following
        //  code in a privileged action so it is not necessary for the calling code
        //  to have the same privileges as the wrapper jar.
        // This is safe because this method can only be called once and that one call
        //  will presumably be made on JVM startup.
        AccessController.doPrivileged(
            new PrivilegedAction() {
                public Object run() {
                    privilegedStart( listener, args );
                    return null;
                }
            }
        );
    }
   
    /**
     * Called by the start method within a PrivilegedAction.
     *
     * @param WrapperListener The WrapperListener instance which represents
     *                        the application being started.
     * @param args The argument list passed to the JVM when it was launched.
     */
    private static void privilegedStart( WrapperListener listener, String[] args )
    {
        // Check the SecurityManager here as it is possible that it was set before this call.
        checkSecurityManager();
       
        // Just in case the user failed to provide an argument list, recover by creating one
        //  here.  This will avoid possible problems down stream.
        if ( args == null )
        {
            args = new String[0];
        }
       
        if ( m_debug )
        {
            StringBuffer sb = new StringBuffer();
            sb.append( "args[" );
            for ( int i = 0; i < args.length; i++ )
            {
                if ( i > 0 )
                {
                    sb.append( ", " );
                }
                sb.append( "\"" );
                sb.append( args[i] );
                sb.append( "\"" );
            }
            sb.append( "]" );
           
            m_outDebug.println( getRes().getString(
                    "WrapperManager.start({0}, {1}) called by thread: {2}" ,
                     listener , sb.toString(), Thread.currentThread().getName()  ) );
        }
       
        synchronized( WrapperManager.class )
        { 
            // Make sure that the class has not already been disposed.
            if ( m_disposed)
            {
                throw new IllegalStateException( getRes().getString( "WrapperManager has already been disposed." ) );
            }
           
            if ( m_listener != null )
            {
                throw new IllegalStateException( getRes().getString(
                    "WrapperManager has already been started with a WrapperListener." ) );
            }
            if ( listener == null )
            {
                throw new IllegalStateException( getRes().getString( "A WrapperListener must be specified." ) );
            }
            m_listener = listener;
           
            m_args = args;
           
            startRunner();
           
            // If this JVM is being controlled by a native wrapper, then we want to
            //  wait for the command to start.  However, if this is a standalone
            //  JVM, then we want to start now.
            if ( !isControlledByNativeWrapper() )
            {
                startInner( true );
            }
        }
    }
   
    /**
     * Returns true if the JVM is in the process of shutting down.  This can be
     *  useful to avoid starting long running processes when it is known that the
     *  JVM will be shutting down shortly.
     *
     * @return true if the JVM is shutting down.
     */
    public static boolean isShuttingDown()
    {
        return m_stopping;
    }
   
    private static class ShutdownLock
        extends Object
    {
        private final Thread m_thread;
        private int m_count;
       
        private ShutdownLock( Thread thread )
        {
            m_thread = thread;
        }
    }
   
    /**
     * Increase the number of locks which will prevent the Wrapper from letting
     *  the JVM process exit on shutdown.   This is primarily useful around
     *  calls to native JNI functions in daemon threads where it has been shown
     *  that premature JVM exits can cause the JVM process to crash on shutdown.
     * <p>
     * Normal non-daemon threads should not require these locks as the very
     *  fact that the non-daemon thread is still running will prevent the JVM
     *  from shutting down.
     * <p>
     * It is possible to make multiple calls within a single thread.  Each call
     *  should always be paired with a call to releaseShutdownLock().
     *
     * @throws WrapperShuttingDownException If called after the Wrapper has
     *                                      already begun the shutdown of the
     *                                      JVM.
     */
    public static void requestShutdownLock()
        throws WrapperShuttingDownException
    {
        synchronized( WrapperManager.class )
        {
            if ( m_stopping )
            {
                throw new WrapperShuttingDownException();
            }
           
            Thread thisThread = Thread.currentThread();
            ShutdownLock lock = (ShutdownLock)m_shutdownLockMap.get( thisThread );
            if ( lock == null )
            {
                lock = new ShutdownLock( thisThread );
                m_shutdownLockMap.put( thisThread, lock );
            }
            lock.m_count++;
            m_shutdownLocks++;
           
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString(
                        "WrapperManager.requestShutdownLock() called by thread: {0}. New thread lock count: {1}, total lock count: {2}",
                        thisThread.getName(), new Integer( lock.m_count ), new Integer( m_shutdownLocks ) ) );
            }
        }
    }
   
    /**
     * Called by a thread which has previously called requestShutdownLock().
     *
     * @throws IllgalStateException If called without first calling requestShutdownLock() from
     *                              the same thread.
     */
    public static void releaseShutdownLock()
        throws IllegalStateException
    {
        synchronized( WrapperManager.class )
        {
            Thread thisThread = Thread.currentThread();
            ShutdownLock lock = (ShutdownLock)m_shutdownLockMap.get( thisThread );
            if ( lock == null )
            {
                throw new IllegalStateException( getRes().getString( "requestShutdownLock was not called from this thread." ) );
            }
           
            lock.m_count--;
            m_shutdownLocks--;
           
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString(
                "WrapperManager.releaseShutdownLock() called by thread: {0}. New thread lock count: {1}, total lock count: {2}" ,
                thisThread.getName(), new Integer( lock.m_count ), new Integer( m_shutdownLocks ) ) );
            }
           
            if ( lock.m_count <= 0 )
            {
                m_shutdownLockMap.remove( thisThread );
            }
           
            WrapperManager.class.notify();
        }
    }
   
    /**
     * Waits for any outstanding locks to be released before shutting down.
     */
    private static void waitForShutdownLocks()
    {
        synchronized( WrapperManager.class )
        {
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString(
                     "wait for {0} shutdown locks to be released.", new Integer(m_shutdownLocks ) ) );
            }
           
            while ( m_shutdownLocks > 0 )
            {
                try
                {
                    WrapperManager.class.wait( 5000 );
                }
                catch ( InterruptedException e )
                {
                    // Ignore and continue.
                }
               
                if ( m_shutdownLocks > 0 )
                {
                    m_outInfo.println( getRes().getString(
                        "Waiting for {0} shutdown locks to be released..." ,                         new Integer(m_shutdownLocks ) ) );
                }
            }
        }
    }
   
    /**
     * Tells the native wrapper that the JVM wants to restart, then informs
     *  all listeners that the JVM is about to shutdown before killing the JVM.
     * <p>
     * This method will not return.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("restart") permission.
     *
     * @see WrapperPermission
     */
    public static void restart()
        throws SecurityException
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "restart" ) );
        }
       
        m_stoppingInit = true;
       
        if ( m_debug )
        {
            m_outDebug.println(getRes().getString(
                    "WrapperManager.restart() called by thread: {0}" , Thread.currentThread().getName() ) );
        }
       
        restartInner();
    }
   
    /**
     * Tells the native wrapper that the JVM wants to restart, then informs
     *  all listeners that the JVM is about to shutdown before killing the JVM.
     * <p>
     * This method requests that the JVM be restarted but then returns.  This
     *  allows components to initiate a JVM exit and then continue, allowing
     *  a normal shutdown initiated by the JVM via shutdown hooks.  In
     *  applications which are designed to be shutdown when the user presses
     *  CTRL-C, this may result in a cleaner shutdown.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("restart") permission.
     *
     * @see WrapperPermission
     */
    public static void restartAndReturn()
        throws SecurityException
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "restart" ) );
        }
       
        m_stoppingInit = true;
       
        synchronized( WrapperManager.class )
        {
            if ( m_stopping )
            {
                if ( m_debug )
                {
                    m_outDebug.println(getRes().getString(
                            "WrapperManager.restartAndReturn() called by thread: {0} already stopping." ,Thread.currentThread().getName()  ) );
                }
                return;
            }
            else
            {
                if ( m_debug )
                {
                    m_outDebug.println( getRes().getString(
                            "WrapperManager.restartAndReturn() called by thread: {0}" ,Thread.currentThread().getName() ) );
                }
            }
        }
       
       
        // To make this possible, we have to create a new thread to actually do the shutdown.
        Thread restarter = new Thread( "Wrapper-Restarter" )
        {
            public void run()
            {
                restartInner();
            }
        };
        restarter.setDaemon( false );
        restarter.start();
    }
   
    /**
     * Common code used to restart the JVM.  It is assumed that the calling
     *  thread has has passed security checks before this is called.
     */
    private static void restartInner()
    {
        boolean stopping;
        synchronized( WrapperManager.class )
        {
            stopping = m_stopping;
            if ( !stopping )
            {
                m_stopping = true;
            }
        }
       
        if ( !stopping )
        {
            // I used to check to make sure the commRunner was started, but that could fail
            //  for a number of reasons.  If it is down then leave it alone.
            //if ( !m_commRunnerStarted )
            //{
            //    startRunner();
            //}
           
            // Always send the stop command
            sendCommand( WRAPPER_MSG_RESTART, "restart" );
        }
       
        // Give the Wrapper a chance to register the stop command before stopping.
        // This avoids any errors thrown by the Wrapper because the JVM died before
        //  it was expected to.
        try
        {
            Thread.sleep( 1000 );
        }
        catch ( InterruptedException e )
        {
        }
       
        // This is safe because we are already checking for the privilege to restart the JVM
        //  above.  If we get this far then we want the Wrapper to be able to do everything
        //  necessary to stop the JVM.
        AccessController.doPrivileged(
            new PrivilegedAction() {
                public Object run() {
                    privilegedStopInner( 0 );
                    return null;
                }
            }
        );
    }
   
    /**
     * Tells the native wrapper that the JVM wants to shut down, then informs
     *  all listeners that the JVM is about to shutdown before killing the JVM.
     * <p>
     * This method will not return.
     *
     * @param exitCode The exit code that the Wrapper will return when it exits.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("stop") permission.
     *
     * @see WrapperPermission
     */
    public static void stop( final int exitCode )
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "stop" ) );
        }
       
        m_stoppingInit = true;
       
        if ( m_debug )
        {
            m_outDebug.println( getRes().getString(
                    "WrapperManager.stop({0}) called by thread: {1}" ,
                    new Integer( exitCode ), Thread.currentThread().getName() ) );
        }
       
        stopCommon( exitCode, 1000 );
       
        // This is safe because we are already checking for the privilege to stop the JVM
        //  above.  If we get this far then we want the Wrapper to be able to do everything
        //  necessary to stop the JVM.
        AccessController.doPrivileged(
            new PrivilegedAction() {
                public Object run() {
                    privilegedStopInner( exitCode );
                    return null;
                }
            }
        );
    }
   
    /**
     * Tells the native wrapper that the JVM wants to shut down, then informs
     *  all listeners that the JVM is about to shutdown before killing the JVM.
     * <p>
     * This method requests that the JVM be shutdown but then returns.  This
     *  allows components to initiate a JVM exit and then continue, allowing
     *  a normal shutdown initiated by the JVM via shutdown hooks.  In
     *  applications which are designed to be shutdown when the user presses
     *  CTRL-C, this may result in a cleaner shutdown.
     *
     * @param exitCode The exit code that the Wrapper will return when it exits.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("stop" ) permission.
     *
     * @see WrapperPermission
     */
    public static void stopAndReturn( final int exitCode )
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "stop" ) );
        }
       
        m_stoppingInit = true;
       
        synchronized( WrapperManager.class )
        {
            if ( m_stopping )
            {
                if ( m_debug )
                {
                    m_outDebug.println( getRes().getString(
                        "WrapperManager.stopAndReturn({0}) called by thread: {1} already stopping.",
                         new Integer( exitCode ), Thread.currentThread().getName() ) );
                }
                return;
            }
            else
            {
                if ( m_debug )
                {
                    m_outDebug.println( getRes().getString(
                        "WrapperManager.stopAndReturn({0}) called by thread: {1}" ,
                         new Integer( exitCode ), Thread.currentThread().getName()  ) );
                }
            }
        }
       
        // To make this possible, we have to create a new thread to actually do the shutdown.
        Thread stopper = new Thread( "Wrapper-Stopper" )
        {
            public void run()
            {
                stopCommon( exitCode, 1000 );
               
                // This is safe because we are already checking for the privilege to stop the JVM
                //  above.  If we get this far then we want the Wrapper to be able to do everything
                //  necessary to stop the JVM.
                AccessController.doPrivileged(
                    new PrivilegedAction() {
                        public Object run() {
                            privilegedStopInner( exitCode );
                            return null;
                        }
                    }
                );
            }
        };
        stopper.setDaemon( false );
        stopper.start();
    }

    /**
     * Tells the native wrapper that the JVM wants to shut down and then
     *  promptly halts.  Be careful when using this method as an application
     *  will not be given a chance to shutdown cleanly.
     *
     * @param exitCode The exit code that the Wrapper will return when it exits.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("stopImmediate") permission.
     *
     * @see WrapperPermission
     */
    public static void stopImmediate( final int exitCode )
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "stopImmediate" ) );
        }
       
        if ( m_debug )
        {
            m_outDebug.println( getRes().getString(
                    "WrapperManager.stopImmediate({0}) called by thread: {1}" ,
                    new Integer( exitCode ), Thread.currentThread().getName()  ) );
        }
       
        stopCommon( exitCode, 250 );
       
        signalStopped( exitCode );
       
        // Execute runtime.halt(0) using reflection so this class will
        //  compile on 1.2.x versions of Java.
        Method haltMethod;
        try
        {
            haltMethod =
                Runtime.class.getMethod( "halt", new Class[] { Integer.TYPE } );
        }
        catch ( NoSuchMethodException e )
        {
            m_outError.println( getRes().getString( "halt not supported by current JVM." ) );
            haltMethod = null;
        }
       
        if ( haltMethod != null )
        {
            Runtime runtime = Runtime.getRuntime();
            try
            {
                haltMethod.invoke( runtime, new Object[] { new Integer( exitCode ) } );
            }
            catch ( IllegalAccessException e )
            {
                m_outError.println( getRes().getString( "Unable to call runtime.halt: {0}" , e ) );
            }
            catch ( InvocationTargetException e )
            {
                Throwable t = e.getTargetException();
                if ( t == null )
                {
                    t = e;
                }
               
                m_outError.println(getRes().getString( "Unable to call runtime.halt: {0}" , t ) );
            }
        }
        else
        {
            // Shutdown normally
           
            // This is safe because we are already checking for the privilege to stop the JVM
            //  above.  If we get this far then we want the Wrapper to be able to do everything
            //  necessary to stop the JVM.
            AccessController.doPrivileged(
                new PrivilegedAction() {
                    public Object run() {
                        privilegedStopInner( exitCode );
                        return null;
                    }
                }
            );
        }
    }
   
    /**
     * Signal the native wrapper that the startup is progressing but that more
     *  time is needed.  The current startup timeout will be extended if
     *  necessary so it will be at least 'waitHint' milliseconds in the future.
     * <p>
     * This call will have no effect if the current startup timeout is already
     *  more than 'waitHint' milliseconds in the future.
     *
     * @param waitHint Time in milliseconds to allow for the startup to
     *                 complete.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("signalStarting") permission.
     *
     * @see WrapperPermission
     */
    public static void signalStarting( int waitHint )
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "signalStarting" ) );
        }
       
        sendCommand( WRAPPER_MSG_START_PENDING, Integer.toString( waitHint ) );
    }

    /**
     * Signal the native wrapper that the shutdown is progressing but that more
     *  time is needed.  The current shutdown timeout will be extended if
     *  necessary so it will be at least 'waitHint' milliseconds in the future.
     * <p>
     * This call will have no effect if the current shutdown timeout is already
     *  more than 'waitHint' milliseconds in the future.
     *
     * @param waitHint Time in milliseconds to allow for the shutdown to
     *                 complete.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("signalStopping") permission.
     *
     * @see WrapperPermission
     */
    public static void signalStopping( int waitHint )
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "signalStopping" ) );
        }
       
        m_stopping = true;
        sendCommand( WRAPPER_MSG_STOP_PENDING, Integer.toString( waitHint ) );
    }
   
    /**
     * This method should not normally be called by user code as it is called
     *  from within the stop and restart methods.  However certain applications
     *  which stop the JVM may need to call this method to let the wrapper code
     *  know that the shutdown was intentional.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("signalStopped") permission.
     *
     * @see WrapperPermission
     */
    public static void signalStopped( int exitCode )
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "signalStopped" ) );
        }
       
        m_stopping = true;
        sendCommand( WRAPPER_MSG_STOPPED, Integer.toString( exitCode ) );
       
        // Give the socket time to actuall send the packet to the Wrapper
        //  as this call is often immediately followed by a halt command.
        try
        {
            Thread.sleep( 250 );
        }
        catch ( InterruptedException e )
        {
            // Ignore.
        }
    }
   
    /**
     * Returns true if the ShutdownHook for the JVM has already been triggered.
     *  Some code needs to know whether or not the system is shutting down.
     *
     * @return True if the ShutdownHook for the JVM has already been triggered.
     */
    public static boolean hasShutdownHookBeenTriggered()
    {
        return m_hookTriggered;
    }
   
    /**
     * Requests that the Wrapper log a message at the specified log level.
     *  If the JVM is not being managed by the Wrapper then calls to this
     *  method will be ignored.  This method has been optimized to ignore
     *  messages at a log level which will not be logged given the current
     *  log levels of the Wrapper.
     * <p>
     * Log messages will currently by trimmed by the Wrapper at 4k (4096 bytes).
     * <p>
     * Because of differences in the way console output is collected and
     *  messages logged via this method, it is expected that interspersed
     *  console and log messages will not be in the correct order in the
     *  resulting log file.
     * <p>
     * This method was added to allow simple logging to the wrapper.log
     *  file.  This is not meant to be a full featured log file and should
     *  not be used as such.  Please look into a logging package for most
     *  application logging.
     *
     * @param logLevel The level to log the message at can be one of
     *                 WRAPPER_LOG_LEVEL_DEBUG, WRAPPER_LOG_LEVEL_INFO,
     *                 WRAPPER_LOG_LEVEL_STATUS, WRAPPER_LOG_LEVEL_WARN,
     *                 WRAPPER_LOG_LEVEL_ERROR, or WRAPPER_LOG_LEVEL_FATAL.
     * @param message The message to be logged.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("log") permission.
     *
     * @see WrapperPermission
     */
    public static void log( int logLevel, String message )
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "log" ) );
        }
       
        // Make sure that the logLevel is valid to avoid problems with the
        //  command sent to the server.
       
        if ( ( logLevel < WRAPPER_LOG_LEVEL_DEBUG ) || ( logLevel > WRAPPER_LOG_LEVEL_NOTICE ) )
        {
            throw new IllegalArgumentException( getRes().getString( "The specified logLevel is not valid." ) );
        }
        if ( message == null )
        {
            throw new IllegalArgumentException( getRes().getString( "The message parameter can not be null." ) );
        }
       
        if ( m_lowLogLevel <= logLevel )
        {
            sendCommand( (byte)( WRAPPER_MSG_LOG + logLevel ), message );
        }
    }
   
    /**
     * Returns an array of all registered services.  This method is only
     *  supported on Windows platforms which support services.  Calling this
     *  method on other platforms will result in null being returned.
     *
     * @return An array of services.
     *
     * @throws SecurityException If a SecurityManager has not been set in the
     *                           JVM or if the calling code has not been
     *                           granted the WrapperPermission "listServices"
     *                           permission.  A SecurityManager is required
     *                           for this operation because this method makes
     *                           it possible to learn a great deal about the
     *                           state of the system.
     *
     * @see WrapperPermission
     */
    public static WrapperWin32Service[] listServices()
        throws SecurityException
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm == null )
        {
            throw new SecurityException( getRes().getString( "A SecurityManager has not yet been set." ) );
        }
        else
        {
            sm.checkPermission( new WrapperPermission( "listServices" ) );
        }
       
        if ( m_libraryOK )
        {
            return nativeListServices();
        }
        else
        {
            return null;
        }
    }
   
    /**
     * Sends a service control code to the specified service.  The state of the
     *  service should be tested on return.  If the service was not currently
     *  running then the control code will not be sent.
     * <p>
     * The control code sent can be one of the system control codes:
     *  WrapperManager.SERVICE_CONTROL_CODE_START,
     *  WrapperManager.SERVICE_CONTROL_CODE_STOP,
     *  WrapperManager.SERVICE_CONTROL_CODE_PAUSE,
     *  WrapperManager.SERVICE_CONTROL_CODE_CONTINUE, or
     *  WrapperManager.SERVICE_CONTROL_CODE_INTERROGATE.  In addition, user
     *  defined codes in the range 128-255 can also be sent.
     *
     * @param serviceName Name of the Windows service which will receive the
     *                    control code.
     * @param controlCode The actual control code to be sent.  User defined
     *                    control codes should be in the range 128-255.
     *
     * @return A WrapperWin32Service containing the last known status of the
     *         service after sending the control code.  This will be null if
     *         the currently platform is not a version of Windows which
     *         supports services.
     *
     * @throws WrapperServiceException If there are any problems accessing the
     *                                 specified service.
     * @throws SecurityException If a SecurityManager has not been set in the
     *                           JVM or if the calling code has not been
     *                           granted the WrapperServicePermission
     *                           permission for the specified service and
     *                           control code.  A SecurityManager is required
     *                           for this operation because this method makes
     *                           it possible to control any service on the
     *                           system, which is of course rather dangerous.
     *
     * @see WrapperServicePermission
     */
    public static WrapperWin32Service sendServiceControlCode( String serviceName, int controlCode )
        throws WrapperServiceException, SecurityException
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm == null )
        {
            throw new SecurityException( getRes().getString( "A SecurityManager has not yet been set." ) );
        }
        else
        {
            String action;
            switch( controlCode )
            {
            case SERVICE_CONTROL_CODE_START:
                action = WrapperServicePermission.ACTION_START;
                break;
               
            case SERVICE_CONTROL_CODE_STOP:
                action = WrapperServicePermission.ACTION_STOP;
                break;
               
            case SERVICE_CONTROL_CODE_PAUSE:
                action = WrapperServicePermission.ACTION_PAUSE;
                break;
               
            case SERVICE_CONTROL_CODE_CONTINUE:
                action = WrapperServicePermission.ACTION_CONTINUE;
                break;
               
            case SERVICE_CONTROL_CODE_INTERROGATE:
                action = WrapperServicePermission.ACTION_INTERROGATE;
                break;
               
            default:
                if ( ( controlCode >= 128 ) && ( controlCode <= 255 ) ) {
                    action = WrapperServicePermission.ACTION_USER_CODE;
                } else {
                    throw new IllegalArgumentException( getRes().getString( "The specified controlCode is invalid." ) );
                }
                break;
            }
           
            sm.checkPermission( new WrapperServicePermission( serviceName, action ) );
        }
       
        WrapperWin32Service service = null;
        if ( m_libraryOK )
        {
            service = nativeSendServiceControlCode( serviceName, controlCode );
        }
        return service;
    }
   
    /**
     * Adds a WrapperEventListener which will receive WrapperEvents.  The
     *  specific events can be controlled using the mask parameter.  This API
     *  was chosen to allow for additional events in the future.
     *
     * To avoid future compatibility problems, WrapperEventListeners should
     *  always test the class of an event before making use of it.  This will
     *  avoid problems caused by new event classes added in future versions
     *  of the Wrapper.
     *
     * This method should only be called once for a given WrapperEventListener.
     *  Build up a single mask to receive events of multiple types.
     *
     * @param listener WrapperEventListener to be start receiving events.
     * @param mask A mask specifying the event types that the listener is
     *             interrested in receiving.  See the WrapperEventListener
     *             class for a full list of flags.  A mask is created by
     *             combining multiple flags using the binary '|' OR operator.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the appropriate
     *                           WrapperEventPermission(...) permission.
     *
     * @see WrapperEventPermission
     */
    public static void addWrapperEventListener( WrapperEventListener listener, long mask )
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            StringBuffer sb = new StringBuffer();
            boolean first = true;
            if ( ( mask & WrapperEventListener.EVENT_FLAG_SERVICE ) != 0 )
            {
                first = false;
                sb.append( WrapperEventPermission.EVENT_TYPE_SERVICE );
            }
            if ( ( mask & WrapperEventListener.EVENT_FLAG_CONTROL ) != 0 )
            {
                if ( first )
                {
                    first = false;
                }
                else
                {
                    sb.append( "," );
                }
                sb.append( WrapperEventPermission.EVENT_TYPE_CONTROL );
            }
            if ( ( mask & WrapperEventListener.EVENT_FLAG_CORE ) != 0 )
            {
                if ( first )
                {
                    first = false;
                }
                else
                {
                    sb.append( "," );
                }
                sb.append( WrapperEventPermission.EVENT_TYPE_CORE );
            }
            sm.checkPermission( new WrapperEventPermission( sb.toString() ) );
        }
       
        synchronized( WrapperManager.class )
        {
            WrapperEventListenerMask listenerMask = new WrapperEventListenerMask();
            listenerMask.m_listener = listener;
            listenerMask.m_mask = mask;
           
            m_wrapperEventListenerMaskList.add( listenerMask );
            m_wrapperEventListenerMasks = null;
        }
       
        updateWrapperEventListenerFlags();
    }
   
    /**
     * Removes a WrapperEventListener so it will not longer receive WrapperEvents.
     *
     * @param listener WrapperEventListener to be stop receiving events.
     *
     * @throws SecurityException If a SecurityManager is present and the
     *                           calling thread does not have the
     *                           WrapperPermission("removeWrapperEventListener")
     *                           permission.
     *
     * @see WrapperPermission
     */
    public static void removeWrapperEventListener( WrapperEventListener listener )
    {
        SecurityManager sm = System.getSecurityManager();
        if ( sm != null )
        {
            sm.checkPermission( new WrapperPermission( "removeWrapperEventListener" ) );
        }
       
        synchronized( WrapperManager.class )
        {
            // Look for the first instance of a given listener in the list.
            for ( Iterator iter = m_wrapperEventListenerMaskList.iterator(); iter.hasNext(); )
            {
                WrapperEventListenerMask listenerMask = (WrapperEventListenerMask)iter.next();
                if ( listenerMask.m_listener == listener )
                {
                    iter.remove();
                    m_wrapperEventListenerMasks = null;
                    break;
                }
            }
        }
       
        updateWrapperEventListenerFlags();
    }
   
    /**
     * Returns the Log file currently being used by the Wrapper.  If log file
     *  rolling is enabled in the Wrapper then this file may change over time.
     *
     * @throws IllegalStateException If this method is called before the Wrapper
     *                               instructs this class to start the user
     *                               application.
     */
    public static File getWrapperLogFile()
    {
        File logFile = m_logFile;
        if ( logFile == null )
        {
            throw new IllegalStateException( getRes().getString( "Not yet initialized." ) );
        }
        return logFile;
    }
   
    /*---------------------------------------------------------------
     * Constructors
     *-------------------------------------------------------------*/
    /**
     * This class can not be instantiated.
     */
    private WrapperManager()
    {
    }
   
    /*---------------------------------------------------------------
     * Private methods
     *-------------------------------------------------------------*/
    /**
     * Checks for the existence of a SecurityManager and then makes sure that
     *  the Wrapper jar has been granted AllPermissions.  If not then a warning
     *  will be displayed as this will most likely result in the Wrapper
     *  failing to function correctly.
     *
     * This method is called at various points in the startup as it is possible
     *  and in fact likely that any SecurityManager will be set by user code
     *  during or shortly after initialization.  Once a SecurityManager has
     *  been located and tested then this method will become a noop.
     */
    private static void checkSecurityManager()
    {
        if ( m_securityManagerChecked )
        {
            return;
        }
       
        SecurityManager securityManager = System.getSecurityManager();
        if ( securityManager != null )
        {
            if ( m_debug )
            {
                m_outDebug.println(getRes().getString( "Detected a SecurityManager: {0} " ,
                    securityManager.getClass().getName() ) );
            }
           
            try
            {
                securityManager.checkPermission( new java.security.AllPermission() );
            }
            catch ( SecurityException e )
            {
                m_outDebug.println();
                m_outDebug.println( getRes().getString(
                    "WARNING - Detected that a SecurityManager has been installed but the " ) );
                m_outDebug.println( getRes().getString(
                    "          wrapper.jar has not been granted the java.security.AllPermission" ) );
                m_outDebug.println( getRes().getString(
                    "          permission.  This will most likely result in SecurityExceptions" ) );
                m_outDebug.println( getRes().getString(
                    "          being thrown by the Wrapper." ) );
                m_outDebug.println();
            }
           
            // Always set the flag.
            m_securityManagerChecked = true;
        }
    }
   
    /**
     * Returns an array of WrapperEventListenerMask instances which can
     *  be safely used outside of synchronization.
     *
     * @return An array of WrapperEventListenerMask instances.
     */
    private static WrapperEventListenerMask[] getWrapperEventListenerMasks()
    {
        WrapperEventListenerMask[] listenerMasks = m_wrapperEventListenerMasks;
        if ( listenerMasks == null )
        {
            synchronized( WrapperManager.class )
            {
                if ( listenerMasks == null )
                {
                    listenerMasks =
                        new WrapperEventListenerMask[m_wrapperEventListenerMaskList.size()];
                    m_wrapperEventListenerMaskList.toArray( listenerMasks );
                    m_wrapperEventListenerMasks = listenerMasks;
                }
            }
        }
       
        return listenerMasks;
    }
   
    /**
     * Updates the internal flags based on the WrapperEventListeners currently
     *  registered.
     */
    private static void updateWrapperEventListenerFlags()
    {
        boolean core = false;
       
        WrapperEventListenerMask[] listenerMasks = getWrapperEventListenerMasks();
        for ( int i = 0; i < listenerMasks.length; i++ )
        {
            long mask = listenerMasks[i].m_mask;
           
            // See whether particular event types are required.
            core = core | ( ( mask & WrapperEventListener.EVENT_FLAG_CORE ) != 0 );
        }
       
        m_produceCoreEvents = core;
    }
   
    /**
     * Notifies registered listeners that an event has been fired.
     *
     * @param event Event to notify the listeners of.
     */
    private static void fireWrapperEvent( WrapperEvent event )
    {
        long eventMask = event.getFlags();
       
        WrapperEventListenerMask[] listenerMasks = getWrapperEventListenerMasks();
        for ( int i = 0; i < listenerMasks.length; i++ )
        {
            long listenerMask = listenerMasks[i].m_mask;
           
            // See if the event should be passed to this listner.
            if ( ( listenerMask & eventMask ) != 0 )
            {
                // The listener wants the event.
                WrapperEventListener listener = listenerMasks[i].m_listener;
                try
                {
                    listener.fired( event );
                }
                catch ( Throwable t )
                {
                    m_outError.println( getRes().getString( "Encountered an uncaught exception while notifying WrapperEventListener of an event:" ) );
                    t.printStackTrace( m_outError );
                }
            }
        }
    }
   
    /**
     * Executed code common to the stop and stopImmediate methods.
     */
    private static void stopCommon( int exitCode, int delay )
    {
        boolean stopping;
        synchronized( WrapperManager.class )
        {
            stopping = m_stopping;
            if ( !stopping )
            {
                m_stopping = true;
            }
        }
       
        if ( !stopping )
        {
            // I used to check to make sure the commRunner was started, but that could fail
            //  for a number of reasons.  If it is down then leave it alone.
            //if ( !m_commRunnerStarted )
            //{
            //    startRunner();
            //}
           
            // Always send the stop command
            sendCommand( WRAPPER_MSG_STOP, Integer.toString( exitCode ) );
           
            // Give the Wrapper a chance to register the stop command before stopping.
            // This avoids any errors thrown by the Wrapper because the JVM died before
            //  it was expected to.
            try
            {
                Thread.sleep( delay );
            }
            catch ( InterruptedException e )
            {
            }
        }
    }
   
    /**
     * Dispose of all resources used by the WrapperManager.  Closes the server
     *  socket which is used to listen for events from the
     */
    private static void dispose()
    {
        synchronized( WrapperManager.class )
        {
            m_disposed = true;
           
            // Close the open backend if it exists.
            closeBackend();
           
            // Give the Connection Thread a chance to stop itself.
            try
            {
                Thread.sleep( 500 );
            }
            catch ( InterruptedException e )
            {
            }
        }
    }
   
    /**
     * Called by startInner when the WrapperListner.start method has completed.
     *
     * Only called when WrapperManager.class is synchronized.
     */
    private static void startCompleted()
    {
        m_startedTicks = getTicks();
       
        // Let the startup thread die since the application has been started.
        m_startupRunner = null;
       
        // Check the SecurityManager here as it is possible that it was set in the
        //  listener's start method.
        checkSecurityManager();
       
        // Signal that the application has started.
        signalStarted();
       
        // Wake up any threads waiting for this.
        WrapperManager.class.notifyAll();
    }
   
    /**
     * Informs the listener that it should start.
     *
     * WrapperManager.class will be synchronized when called.
     *
     * @param block True if this call should block for the WrapperListener.start
     *              method to complete.  This is true when java is being run in
     *              standalone mode without the Wrapper.
     */
    private static void startInner( boolean block )
    {
        // Set the thread priority back to normal so that any spawned threads
        //  will use the normal priority
        int oldPriority = Thread.currentThread().getPriority();
        Thread.currentThread().setPriority( Thread.NORM_PRIORITY );

        m_starting = true;
       
        // Do any setup which shoul happen just before we actually start the application.
        checkTmpDir();
       
        // This method can be called from the connection thread which must be a
        //  daemon thread by design.  We need to call the WrapperListener.start method
        //  from a non-daemon thread.  This means that if the current thread is a
        //  daemon we need to launch a new thread while we wait for the start method
        //  to return.
        if ( m_listener == null )
        {
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString( "No WrapperListener has been set.  Nothing to start." ) );
            }
           
            startCompleted();
        }
        else
        {
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString( "calling WrapperListener.start()" ) );
            }
           
            // These arrays aren't pretty, but we need final variables for the inline
            //  class and this makes it possible to get the values back.
            final Integer[] resultF = new Integer[1];
            final Throwable[] tF = new Throwable[1];
           
            // Start in a dedicated thread.
            Thread startRunner = new Thread( "WrapperListener_start_runner" )
            {
                public void run()
                {
                    if ( m_debug )
                    {
                       m_outDebug.println( getRes().getString( "WrapperListener.start runner thread started." ) );
                    }
                   
                    try
                    {
                        // This is user code, so don't trust it.
                        try
                        {
                            resultF[0] = m_listener.start( m_args );
                        }
                        catch ( Throwable t )
                        {
                            tF[0] = t;
                        }
                    }
                    finally
                    {
                        // Make sure the rest of this thread does not fall behind the application.
                        Thread.currentThread().setPriority( Thread.MAX_PRIORITY );
                       
                        // Now that we are back, handle the results.
                        if ( tF[0] != null )
                        {
                            m_outError.println( getRes().getString(
                                    "Error in WrapperListener.start callback.  {0}", tF[0] ) );
                            tF[0].printStackTrace( m_outError );
                            // Kill the JVM, but don't tell the wrapper that we want to stop.
                            //  This may be a problem with this instantiation only.
                            privilegedStopInner( 1 );
                            // Won't make it here.
                            return;
                        }
                       
                        if ( m_debug )
                        {
                            m_outDebug.println( getRes().getString( "returned from WrapperListener.start()" ) );
                        }
                        if ( resultF[0] != null )
                        {
                            int exitCode = resultF[0].intValue();
                            if ( m_debug )
                            {
                                m_outDebug.println( getRes().getString(
                                    "WrapperListener.start() returned an exit code of {0}.",
                                    new Integer( exitCode )  ) );
                            }
                           
                            // Signal the native code.
                            WrapperManager.stop( exitCode );
                            // Won't make it here.
                            return;
                        }
                       
                        synchronized( WrapperManager.class )
                        {
                            startCompleted();
                        }
                       
                        if ( m_debug )
                        {
                           m_outDebug.println( getRes().getString( "WrapperListener.start runner thread stopped." ) );
                        }
                    }
                }
            };
            startRunner.setDaemon( false );
            startRunner.start();
           
            // Crank the priority back up.
            Thread.currentThread().setPriority( oldPriority );
           
            if ( block )
            {
                // Wait for the start runner to complete.
                if ( m_debug )
                {
                   m_outDebug.println( getRes().getString(
                        "Waiting for WrapperListener.start runner thread to complete." ) );
                }
                while ( ( startRunner != null ) && ( startRunner.isAlive() ) )
                {
                    try
                    {
                        WrapperManager.class.wait();
                    }
                    catch ( InterruptedException e )
                    {
                        // Ignore and keep waiting.
                    }
                }
            }
        }
    }
   
    private static void shutdownJVM( int exitCode )
    {
        if ( m_debug )
        {
            m_outDebug.println(getRes().getString( "shutdownJVM({0}) Thread: {1}",
                new Integer( exitCode ), Thread.currentThread().getName() ) );
        }
       
        // Make sure that any shutdown locks are released.
        waitForShutdownLocks();
       
        // Signal that the application has stopped and the JVM is about to shutdown.
        signalStopped( exitCode );
       
        // Dispose the wrapper.
        dispose();
       
        m_shutdownJVMComplete = true;
       
        // Do not call System.exit if this is the ShutdownHook
        if ( Thread.currentThread() == m_hook )
        {
            // This is the shutdown hook, so fall through because things are
            //  already shutting down.
        }
        else
        {
            if ( m_debug )
            {
                m_outDebug.println(getRes().getString( "calling System.exit({0})",
                         new Integer( exitCode )  ) );
            }
            safeSystemExit( exitCode );
        }
    }
   
    /**
     * A user ran into a JVM bug where a call to System exit was causing an
     *  IllegalThreadStateException to be thrown.  Not sure how widespread
     *  this problem is.  But it is easy to avoid it causing serious problems
     *  for the wrapper.
     */
    private static void safeSystemExit( int exitCode )
    {
        try
        {
            System.exit( exitCode );
        }
        catch ( IllegalThreadStateException e )
        {
            m_outError.println( getRes().getString(
                    "Attempted System.exit({0}) call failed: {1}" ,
                    new Integer( exitCode ), e.toString() ) );
           
            m_outError.println( getRes().getString( "   Trying Runtime.halt({0})",
                    new Integer( exitCode ) ) );
            Runtime.getRuntime().halt( exitCode );
        }
    }
   
    /**
     * Informs the listener that the JVM will be shut down.
     *
     * This should only be called from within a PrivilegedAction or in a
     *  context that came from a PrivilegedAction.
     */
    private static void privilegedStopInner( int exitCode )
    {
        boolean block;
        synchronized( WrapperManager.class )
        {
            // Always set the stopping flag.
            m_stopping = true;
           
            // Only one thread can be allowed to continue.
            if ( m_stoppingThread == null )
            {
                m_stoppingThread = Thread.currentThread();
                block = false;
            }
            else
            {
                if ( Thread.currentThread() == m_stoppingThread )
                {
                    throw new IllegalStateException( getRes().getString(
                        "WrapperManager.stop() can not be called recursively." ) );
                }
               
                block = true;
            }
        }
       
        if ( block )
        {
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString(
                        "Thread, {0}, waiting for the JVM to exit.",
                    Thread.currentThread().getName() ) );
               
                if ( Thread.currentThread() == m_hook )
                {
                    if ( !m_hookRemoveFailed )
                    {
                        m_outDebug.println( getRes().getString( "System.exit appears to have been called from within the\n  WrapperListener.stop() method.  If possible the application\n  should be modified to avoid this behavior.\n  To avoid a deadlock, this thread will only wait 5 seconds\n  for the application to shutdown.  This may result in the\n  application failing to shutdown completely before the JVM\n  exits.  Removing the offending System.exit call will\n  resolve this." ) );
                    }
                }
            }
           
            // This thread needs to be put into an infinite loop until the JVM exits.
            //  This thread can not be allowed to return to the caller, but another
            //  thread is already responsible for shutting down the JVM, so this
            //  one can do nothing but wait.
            int loops = 0;
            int wait = 50;
            while( true )
            {
                try
                {
                    Thread.sleep( wait );
                }
                catch ( InterruptedException e )
                {
                }
               
                // If this is the wrapper's shutdown hook then we only want to loop until
                //  the shutdownJVM method has completed.  We will only get into this state
                //  if user code calls System.exit from within the WrapperListener.stop
                //  method.  Failing to return here will cause the shutdown process to hang.
                // If the user code calls System.exit directly in the stop method then the
                //  m_shutdownJVMComplete flag will never be set.   Always time out after
                //  5 seconds so the JVM will not hang in such cases.
                if ( Thread.currentThread() == m_hook )
                {
                    if ( m_shutdownJVMComplete || ( loops > 5000 / wait ) )
                    {
                        if ( !m_shutdownJVMComplete )
                        {
                            if ( m_debug )
                            {
                                m_outDebug.println( getRes().getString(
                                        "Thread, {0}, continuing after 5 seconds.",
                                    Thread.currentThread().getName() ) );
                            }
                        }
                       
                        // To keep the wrapper from showing a JVM exited unexpectedly message
                        //  on shutdown, tell the wrapper that we are ready to stop.
                        // If the WrapperListener.stop method is taking a long time, we will
                        //  also get here.  In that case, the Wrapper will still wait for
                        //  the configured exit timeout before killing the JVM process.
                        // In theory, the shutdown process of an application will only call
                        //  System.exit after the shutdown is complete so this should be Ok.
                        // Use the exit code from the thread which initiated the call rather
                        //  than this call as that one is the one we really want.
                        signalStopped( m_exitCode );
                       
                        return;
                    }
                }
               
                loops++;
            }
        }
       
        if ( m_debug )
        {
            m_outDebug.println( getRes().getString(
                    "Thread, {0}, handling the shutdown process.",
                     Thread.currentThread().getName() ) );
        }
        m_exitCode = exitCode;
       
        // If appropriate, unregister the shutdown hook.  This must be done before the
        //  user stop code is called to avoid nested shutdowns.
        if ( Thread.currentThread() != m_hook )
        {
            // We do not want the ShutdownHook to execute, so unregister it before calling exit.
            //  It can't be unregistered if it has already fired however.  The only way that this
            //  could happen is if user code calls System.exit from within the listener stop
            //  method.
            if ( ( !m_hookTriggered ) && ( m_hook != null ) )
            {
                // Remove the shutdown hook.
                try
                {
                    Runtime.getRuntime().removeShutdownHook( m_hook );
                }
                catch ( AccessControlException e )
                {
                    // This can happen if the security policy is not setup correctly.
                    m_outError.println( getRes().getString( "Unable to remove the Wrapper's shudownhook: {0}", e ) );
                    m_hookRemoveFailed = true;
                }
            }
        }
       
        // Only stop the listener if the app has been asked to start.  Does not need to have actually started.
        int code = exitCode;
       
        if ( ( m_listenerForceStop && m_starting ) || m_started )
        {
            // Set the thread priority back to normal so that any spawned threads
            //  will use the normal priority
            int oldPriority = Thread.currentThread().getPriority();
            Thread.currentThread().setPriority( Thread.NORM_PRIORITY );
           
            // This method can be called from the connection thread which must be a
            //  daemon thread by design.  We need to call the WrapperListener.stop method
            //  from a non-daemon thread.  This means that if the current thread is a
            //  daemon we need to launch a new thread while we wait for the stop method
            //  to return.
            if ( m_listener == null )
            {
                if ( m_debug )
                {
                    m_outDebug.println( getRes().getString( "No WrapperListener has been set.  Nothing to stop." ) );
                }
            }
            else
            {
                if ( m_debug )
                {
                    m_outDebug.println( getRes().getString( "calling listener.stop()" ) );
                }
               
                if ( Thread.currentThread().isDaemon() )
                {
                    // This array isn't pretty, but we need final variables for the inline
                    //  class and this makes it possible to get the values back.
                    final Integer[] codeF = new Integer[] {new Integer(code)};
                   
                    // Start in a dedicated thread.
                    Thread stopRunner = new Thread( "WrapperListener_stop_runner" )
                    {
                        public void run()
                        {
                            if ( m_debug )
                            {
                               m_outDebug.println( getRes().getString( "WrapperListener.stop runner thread started." ) );
                            }
                           
                            try
                            {
                                // This is user code, so don't trust it.
                                try
                                {
                                    codeF[0] = new Integer( m_listener.stop( codeF[0].intValue() ) );
                                }
                                catch ( Throwable t )
                                {
                                    m_outError.println( getRes().getString(
                                        "Error in WrapperListener.stop callback." ) );
                                    t.printStackTrace( m_outError );
                                }
                            }
                            finally
                            {
                                if ( m_debug )
                                {
                                   m_outDebug.println( getRes().getString(
                                        "WrapperListener.stop runner thread stopped." ) );
                                }
                            }
                        }
                    };
                    stopRunner.setDaemon( false );
                    stopRunner.start();
                   
                    // Wait for the start runner to complete.
                    if ( m_debug )
                    {
                       m_outDebug.println( getRes().getString(
                            "Waiting for WrapperListener.stop runner thread to complete." ) );
                    }
                    while ( ( stopRunner != null ) && ( stopRunner.isAlive() ) )
                    {
                        try
                        {
                            stopRunner.join();
                            stopRunner = null;
                        }
                        catch ( InterruptedException e )
                        {
                            // Ignore and keep waiting.
                        }
                    }
                   
                    // Get the exit code back from the array.
                    code = codeF[0].intValue();
                }
                else
                {
                    // This is user code, so don't trust it.
                    try
                    {
                        code = m_listener.stop( code );
                    }
                    catch ( Throwable t )
                    {
                        m_outError.println( getRes().getString( "Error in WrapperListener.stop callback." ) );
                        t.printStackTrace( m_outError );
                    }
                }
                if ( m_debug )
                {
                    m_outDebug.println( getRes().getString( "returned from listener.stop() -> {0}",
                             new Integer( code ) ) );
                }
            }
           
            // Crank the priority back up.
            Thread.currentThread().setPriority( oldPriority );
        }

        shutdownJVM( code );
    }
   
    private static void signalStarted()
    {
        sendCommand( WRAPPER_MSG_STARTED, "" );
        m_started = true;
    }
   
    /**
     * Called by the native code when a control event is trapped by native code.
     * Can have the values: WRAPPER_CTRL_C_EVENT, WRAPPER_CTRL_CLOSE_EVENT,
     *    WRAPPER_CTRL_LOGOFF_EVENT, WRAPPER_CTRL_SHUTDOWN_EVENT,
     *    WRAPPER_CTRL_TERM_EVENT, or WRAPPER_CTRL_HUP_EVENT.
     */
    private static void controlEvent( int event )
    {
        String eventName;
        boolean ignore;
        switch( event )
        {
        case WRAPPER_CTRL_C_EVENT:
            eventName = "WRAPPER_CTRL_C_EVENT";
            ignore = m_ignoreSignals;
            break;
        case WRAPPER_CTRL_CLOSE_EVENT:
            eventName = "WRAPPER_CTRL_CLOSE_EVENT";
            ignore = m_ignoreSignals;
            break;
        case WRAPPER_CTRL_LOGOFF_EVENT:
            eventName = "WRAPPER_CTRL_LOGOFF_EVENT";
            ignore = false;
            break;
        case WRAPPER_CTRL_SHUTDOWN_EVENT:
            eventName = "WRAPPER_CTRL_SHUTDOWN_EVENT";
            ignore = false;
            break;
        case WRAPPER_CTRL_TERM_EVENT:
            eventName = "WRAPPER_CTRL_TERM_EVENT";
            ignore = m_ignoreSignals;
            break;
        case WRAPPER_CTRL_HUP_EVENT:
            eventName = "WRAPPER_CTRL_HUP_EVENT";
            ignore = m_ignoreSignals;
            break;
        case WRAPPER_CTRL_USR1_EVENT:
            eventName = "WRAPPER_CTRL_USR1_EVENT";
            ignore = m_ignoreSignals;
            break;
        case WRAPPER_CTRL_USR2_EVENT:
            eventName = "WRAPPER_CTRL_USR2_EVENT";
            ignore = m_ignoreSignals;
            break;
        default:
            eventName =  getRes().getString( "Unexpected event: {0}", new Integer( event ) );
            ignore = false;
            break;
        }
       
        WrapperControlEvent controlEvent = new WrapperControlEvent( event, eventName );
        if ( ignore )
        {
            // Preconsume the event if it is set to be ignored, but go ahead and fire it so
            //  user can can still have the oportunity to recognize it.
            controlEvent.consume();
        }
        fireWrapperEvent( controlEvent );
       
        if ( !controlEvent.isConsumed() )
        {
            if ( ignore )
            {
                if ( m_debug )
                {
                    m_outDebug.println( getRes().getString( "Ignoring control event({0})", eventName ) );
                }
            }
            else
            {
                if ( m_debug )
                {
                    m_outDebug.println(getRes().getString( "Processing control event({0})", eventName ) );
                }
               
                // This is user code, so don't trust it.
                if ( m_listener != null )
                {
                    try
                    {
                        m_listener.controlEvent( event );
                    }
                    catch ( Throwable t )
                    {
                        m_outError.println( getRes().getString( "Error in WrapperListener.controlEvent callback." ) );
                        t.printStackTrace( m_outError );
                    }
                }
                else
                {
                    // A listener was never registered.  Always respond by exiting.
                    //  This can happen if the user does not initialize things correctly.
                    stop( 0 );
                }
            }
        }
    }
   
    /**
     * Parses a long tab separated string of properties into an internal
     *  properties object.  Actual tabs are escaped by real tabs.
     */
    private static char PROPERTY_SEPARATOR = '\t';
    private static void readProperties( String rawProps )
    {
        WrapperProperties properties = new WrapperProperties();
       
        int len = rawProps.length();
        int first = 0;
        while ( first < len )
        {
            StringBuffer sb = new StringBuffer();
            boolean foundEnd = false;
            do
            {
                int pos = rawProps.indexOf( PROPERTY_SEPARATOR, first );
                if ( pos >= 0 )
                {
                    if ( pos > 0 )
                    {
                        sb.append( rawProps.substring( first, pos ) );
                    }
                    if ( pos < len - 1 )
                    {
                        if ( rawProps.charAt( pos + 1 ) == PROPERTY_SEPARATOR )
                        {
                            // Two separators in a row, it was escaped.
                            sb.append( PROPERTY_SEPARATOR );
                            first = pos + 2;
                        }
                        else
                        {
                            foundEnd = true;
                            first = pos + 1;
                        }
                    }
                    else
                    {
                        foundEnd = true;
                        first = pos + 1;
                    }
                }
                else
                {
                    // No more separators.  The rest is the last property.
                    sb.append( rawProps.substring( first ) );
                    foundEnd = true;
                    first = len;
                }
            }
            while ( !foundEnd );
           
            String property = sb.toString();
           
            // Parse the property.
            int pos = property.indexOf( '=' );
            if ( pos > 0 )
            {
                String key = property.substring( 0, pos );
                String value;
                if ( pos < property.length() - 1 )
                {
                    value = property.substring( pos + 1 );
                }
                else
                {
                    value = "";
                }
               
                properties.setProperty( key, value );
               
                // Process special properties
                if ( key.equals( "wrapper.ignore_user_logoffs" ) )
                {
                    m_ignoreUserLogoffs = value.equalsIgnoreCase( "true" );
                }
            }
        }
       
        // Lock the properties object and store it.
        properties.lock();
       
        m_properties = properties;
    }

    /**
     * Opens a socket to the Wrapper process.
     */
    private static synchronized void openBackendSocket()
    {
        if ( m_debug )
        {
            m_outDebug.println( getRes().getString( "Open socket to wrapper...{0}",
                    Thread.currentThread().getName() ) );
        }

        InetAddress iNetAddress;
        try
        {
            iNetAddress = InetAddress.getByName( "127.0.0.1" );
        }
        catch ( UnknownHostException e )
        {
            // This is pretty fatal.
            m_outError.println( getRes().getString( "Unable to resolve localhost name: {0}", e ) );
            m_outError.println( getRes().getString( "Exiting JVM..." ) );
            stop( 1 );
            return; // please the compiler
        }
       
        // If the user has specified a specific port to use then we want to try that first.
        boolean connected = false;
        int tryPort;
        boolean fixedPort;
        if ( m_jvmPort > 0 )
        {
            tryPort = m_jvmPort;
            fixedPort = true;
        }
        else
        {
            tryPort = m_jvmPortMin;
            fixedPort = false;
        }
       
        // Loop until we find a port we can connect using.
        SocketException causeException = null;
        do
        {
            try
            {
                m_backendSocket = new Socket( iNetAddress, m_port, iNetAddress, tryPort );
                if ( m_debug )
                {
                    m_outDebug.println(getRes().getString( "Opened Socket from {0} to {1}",
                            new Integer( tryPort ), new Integer( m_port ) ) );
                }
                connected = true;
                break;
            }
            catch ( SocketException e )
            {
                String eMessage = e.getMessage();
               
                if ( e instanceof ConnectException )
                {
                    m_outError.println(getRes().getString(
                            "Failed to connect to the Wrapper at port {0}. Cause: {1}",
                            new Integer( m_port ), e ) );
                    // This is fatal because there is nobody listening.
                    m_outError.println( "Exiting JVM..." );
                    stopImmediate( 1 );
                }
                else if ( ( e instanceof BindException ) ||
                    ( ( eMessage != null ) &&
                    ( ( eMessage.indexOf( "errno: 48" ) >= 0 ) ||
                        ( eMessage.indexOf( "Address already in use" ) >= 0 ) ) ||
                        ( eMessage.indexOf( "Unrecognized Windows Sockets error: 0: JVM_Bind" ) >= 0 ) ) ) /* This message is caused by a JVM Bug: http://bugs.sun.com/view_bug.do?bug_id=6965962 */
                {
                    // Most Java implementations throw a BindException when the port is in use,
                    //  but FreeBSD throws a SocketException with a specific message.
                   
                    // This happens if the local port is already in use.  In this case, we want
                    //  to loop and try again.
                    if ( m_debug )
                    {
                        m_outDebug.println( getRes().getString(
                                "Unable to open socket to Wrapper from port {0}, already in use.",
                                new Integer( tryPort ) ) );
                    }
                   
                    if ( fixedPort )
                    {
                        // The last port checked was the fixed port, switch to the dynamic range.
                        tryPort = m_jvmPortMin;
                        fixedPort = false;
                    }
                    else
                    {
                        tryPort++;
                    }
                   
                    // Keep this exception around in case we need to log it.
                    if ( causeException == null )
                    {
                        causeException = e;
                    }
                }
                else
                {
                    // Unexpected exception.
                    m_outError.println( getRes().getString( "Unexpected exception opening backend socket: {0}", e ) );
                    m_backendSocket = null;
                    return;
                }
            }
            catch ( IOException e )
            {
                m_outError.println( getRes().getString( "Unable to open backend socket: {0}", e ) );
                m_backendSocket = null;
                return;
            }
        }
        while ( tryPort <= m_jvmPortMax );
       
        if ( connected )
        {
            if ( ( m_jvmPort > 0 ) && ( m_jvmPort != tryPort ) )
            {
                m_outInfo.println(getRes().getString(
                    "Port {0} already in use, using port {1} instead.",
                    new Integer( m_jvmPort ), new Integer( tryPort ) ) );
            }
        }
        else
        {
            if ( m_jvmPortMax > m_jvmPortMin )
            {
                m_outError.println( getRes().getString(
                        "Failed to connect to the Wrapper at port {0} by binding to any ports in the range {1} to {2}.  Cause: {3}",
                        new Integer( m_port ), new Integer( m_jvmPortMin ), new Integer( m_jvmPortMax ), causeException ) );
            }
            else
            {
                m_outError.println(getRes().getString(
                        "Failed to connect to the Wrapper at port {0} by binding to port {1}.  Cause: {2}",
                        new Integer( m_port ), new Integer( m_jvmPortMin ), causeException ) );
            }
            // This is fatal because there is nobody listening.
            m_outError.println( getRes().getString( "Exiting JVM..." ) );
            stopImmediate( 1 );
        }
       
        // Now that we have a connected socket, continue on to configure it.
        try
        {
            // Turn on the TCP_NODELAY flag.  This is very important for speed!!
            m_backendSocket.setTcpNoDelay( true );
           
            m_backendOS = m_backendSocket.getOutputStream();
            m_backendIS = m_backendSocket.getInputStream();
        }
        catch ( IOException e )
        {
            m_outError.println( e );
           
            closeBackend();
            return;
        }
       
        m_backendConnected = true;
    }
   
    private static synchronized void openBackendPipe()
    {
        String s;

        if (WrapperManager.isWindows())
        {
            s = "\\\\.\\pipe\\wrapper-" + WrapperManager.getWrapperPID() + "-" + WrapperManager.getJVMId();
        } else {
            s = "/tmp/wrapper-" + WrapperManager.getWrapperPID() + "-" + WrapperManager.getJVMId();
        }
        try
        {
            m_backendIS = new FileInputStream( new File( s + "-out") );
            m_backendOS = new FileOutputStream( new File( s + "-in" ) );
        } catch ( IOException e ) {
            m_outInfo.println( "write error " + e );
            e.printStackTrace();
        } catch (Exception ex) {
           ex.printStackTrace();
        }
        m_backendConnected = true;
    }
   
    private static synchronized void openBackend()
    {
        m_backendConnected = false;
       
        if ( m_backendType == BACKEND_TYPE_PIPE )
        {
            openBackendPipe();
        }
        else
        {
            openBackendSocket();
        }
        if ( !m_backendConnected )
        {
            return;
        }
       
        // The backend is open.
       
        // Send the key back to the wrapper so that the wrapper can feel safe
        //  that it is talking to the correct JVM
        sendCommand( WRAPPER_MSG_KEY, m_key );
       
        // If there is a stop pending then send it immediately now.
        if ( m_pendingStopMessage != null )
        {
            m_outDebug.println( getRes().getString( "Resend pending packet {0} : {1}", getPacketCodeName( WRAPPER_MSG_STOP ), m_pendingStopMessage ) );
            sendCommand( WRAPPER_MSG_STOP, m_pendingStopMessage );
            m_pendingStopMessage = null;
        }
    }
   
    private static synchronized void closeBackend()
    {
        if ( m_backendConnected )
        {
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString( "Closing backend connection." ) );
            }
            // Clear the connected flag first so other threads will recognize that we
            //  are closing correctly.
            m_backendConnected = false;
        }
       
        if ( m_backendOS != null )
        {
            try
            {
                m_backendOS.close();
            }
            catch ( IOException e )
            {
                if ( m_debug )
                {
                    m_outDebug.println( getRes().getString( "Unable to close backend output stream: {0}", e.toString() ) );
                }
            }
            m_backendOS = null;
        }
       
        if ( m_backendIS != null )
        {
            // Closing m_backendIS here will block on some platforms.  Let the Wrapper end it cleanup.
            m_backendIS = null;
        }
       
        if ( m_backendSocket != null )
        {
            try
            {
                m_backendSocket.close();
            }
            catch ( IOException e )
            {
                if ( m_debug )
                {
                    m_outDebug.println( getRes().getString( "Unable to close backend socket: {0}", e.toString() ) );
                }
            }
            m_backendSocket = null;
        }
    }
   
    private static String getPacketCodeName( byte code )
    {
        String name;
   
        switch ( code )
        {
        case WRAPPER_MSG_START:
            name ="START";
            break;
   
        case WRAPPER_MSG_STOP:
            name ="STOP";
            break;
   
        case WRAPPER_MSG_RESTART:
            name ="RESTART";
            break;
   
        case WRAPPER_MSG_PING:
            name ="PING";
            break;
   
        case WRAPPER_MSG_STOP_PENDING:
            name ="STOP_PENDING";
            break;
   
        case WRAPPER_MSG_START_PENDING:
            name ="START_PENDING";
            break;
   
        case WRAPPER_MSG_STARTED:
            name ="STARTED";
            break;
   
        case WRAPPER_MSG_STOPPED:
            name ="STOPPED";
            break;
   
        case WRAPPER_MSG_KEY:
            name ="KEY";
            break;
   
        case WRAPPER_MSG_BADKEY:
            name ="BADKEY";
            break;
   
        case WRAPPER_MSG_LOW_LOG_LEVEL:
            name ="LOW_LOG_LEVEL";
            break;
   
        case WRAPPER_MSG_PING_TIMEOUT:
            name ="PING_TIMEOUT";
            break;
   
        case WRAPPER_MSG_SERVICE_CONTROL_CODE:
            name ="SERVICE_CONTROL_CODE";
            break;
   
        case WRAPPER_MSG_PROPERTIES:
            name ="PROPERTIES";
            break;
   
        case WRAPPER_MSG_LOG + WRAPPER_LOG_LEVEL_DEBUG:
            name ="LOG(DEBUG)";
            break;
   
        case WRAPPER_MSG_LOG + WRAPPER_LOG_LEVEL_INFO:
            name ="LOG(INFO)";
            break;
   
        case WRAPPER_MSG_LOG + WRAPPER_LOG_LEVEL_STATUS:
            name ="LOG(STATUS)";
            break;
   
        case WRAPPER_MSG_LOG + WRAPPER_LOG_LEVEL_WARN:
            name ="LOG(WARN)";
            break;
   
        case WRAPPER_MSG_LOG + WRAPPER_LOG_LEVEL_ERROR:
            name ="LOG(ERROR)";
            break;
   
        case WRAPPER_MSG_LOG + WRAPPER_LOG_LEVEL_FATAL:
            name ="LOG(FATAL)";
            break;
   
        case WRAPPER_MSG_LOG + WRAPPER_LOG_LEVEL_ADVICE:
            name ="LOG(ADVICE)";
            break;
   
        case WRAPPER_MSG_LOG + WRAPPER_LOG_LEVEL_NOTICE:
            name ="LOG(NOTICE)";
            break;

        case WRAPPER_MSG_LOGFILE:
            name ="LOGFILE";
            break;
          
        case WRAPPER_MSG_CHILD_LAUNCH:
            name ="CHILD_LAUNCH";
            break;
   
        case WRAPPER_MSG_CHILD_TERM:
            name ="CHILD_TERM";
            break;
          
        case WRAPPER_MSG_CHECK_DEADLOCK:
            name ="CHECK_DEADLOCK";
            break;
   
        case WRAPPER_MSG_DEADLOCK:
            name ="DEADLOCK";
            break;
   
        case WRAPPER_MSG_PAUSE:
            name ="PAUSE";
            break;
   
        case WRAPPER_MSG_RESUME:
            name ="RESUME";
            break;
   
        case WRAPPER_MSG_GC:
            name ="GC";
            break;
   
        default:
            name = "UNKNOWN(" + code + ")";
            break;
        }
        return name;
    }
   
    private static synchronized void sendCommand( byte code, String message )
    {
        if ( m_debug )
        {
            if ( ( code == WRAPPER_MSG_PING ) && ( message.equals( "silent" ) ) )
            {
                // m_outDebug.println( "Send silent ping packet." );
            }
            else if ( !m_backendConnected )
            {
                m_outDebug.println( getRes().getString(
                        "Backend not connected, not sending packet {0} : {1}",
                        getPacketCodeName( code ), message ) );

                if ( code == WRAPPER_MSG_STOP )
                {
                    // Store this message so we can send it later if and when we connect.
                    m_pendingStopMessage = message;
                }
            }
            else
            {
                m_outDebug.println( getRes().getString( "Send a packet {0} : {1}",
                    getPacketCodeName( code ) , message ) );
            }
        }
        if ( m_appearHung )
        {
            // The WrapperManager is attempting to make the JVM appear hung, so do nothing
        }
        else
        {
            // Make a copy of the reference to make this more thread safe.
            if ( ( !m_backendConnected ) && isControlledByNativeWrapper() && ( !m_stopping ) )
            {
                // The socket is not currently open, try opening it.
                openBackend();
            }
           
            if ( ( code == WRAPPER_MSG_START_PENDING ) || ( code == WRAPPER_MSG_STARTED ) )
            {
                // Set the last ping time so that the startup process does not time out
                //  thinking that the JVM has not received a Ping for too long.
                m_lastPingTicks = getTicks();
            }
           
            // If the backend is open, then send the command, otherwise just throw it away.
            if ( m_backendConnected )
            {
                try
                {
                    // It is possible that a logged message is quite large.  Expand the size
                    // of the command buffer if necessary so that it can be included.  This
                    //  means that the command buffer will be the size of the largest message.
                    byte[] messageBytes = message.getBytes();
                    if ( m_commandBuffer.length < messageBytes.length + 2 )
                    {
                        m_commandBuffer = new byte[messageBytes.length + 2];
                    }
                   
                    // Writing the bytes one by one was sometimes causing the first byte to be lost.
                    // Try to work around this problem by creating a buffer and sending the whole lot
                    // at once.
                    m_commandBuffer[0] = code;
                    System.arraycopy( messageBytes, 0, m_commandBuffer, 1, messageBytes.length );
                    int len = messageBytes.length + 2;
                    m_commandBuffer[len - 1] = 0;

                    m_backendOS.write( m_commandBuffer, 0, len );
                    m_backendOS.flush();
                }
                catch ( IOException e )
                {
                    m_outError.println( e );
                    e.printStackTrace( m_outError );
                    closeBackend();
                }
            }
        }
    }
   
    /**
     * Loop reading packets from the native side of the Wrapper until the
     *  connection is closed or the WrapperManager class is disposed.
     *  Each packet consists of a packet code followed by a null terminated
     *  string up to 256 characters in length.  If the entire packet has not
     *  yet been received, then it must not be read until the complete packet
     *  has arived.
     */
    private static byte[] m_backendReadBuffer = new byte[256];
    private static void handleBackend()
    {
        WrapperPingEvent pingEvent = new WrapperPingEvent();
       
        try
        {
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString( "handleBackend()" ) );
            }

            DataInputStream is = new DataInputStream( m_backendIS );
            while ( !m_disposed )
            {
                // A Packet code must exist.
                byte code = is.readByte();
               
                // Always read from the buffer until a null '\0' is encountered.
                byte b;
                int i = 0;
                do
                {
                    b = is.readByte();
                    if ( b != 0 )
                    {
                        if ( i >= m_backendReadBuffer.length )
                        {
                            byte[] tmp = m_backendReadBuffer;
                            m_backendReadBuffer = new byte[tmp.length + 256];
                            System.arraycopy( tmp, 0, m_backendReadBuffer, 0, tmp.length );
                        }
                        m_backendReadBuffer[i] = b;
                        i++;
                    }
                }
                while ( b != 0 );
               
                String msg = new String( m_backendReadBuffer, 0, i );
               
                if ( m_appearHung )
                {
                    // The WrapperManager is attempting to make the JVM appear hung,
                    //   so ignore all incoming requests
                }
                else
                {
                    if ( m_debug )
                    {
                        String logMsg;
                        if ( code == WRAPPER_MSG_PROPERTIES )
                        {
                            // The property values are very large and distracting in the log.
                            //  Plus if any triggers are defined, then logging them will fire
                            //  the trigger.
                            logMsg = getRes().getString( "(Property Values)" );
                        }
                        else
                        {
                            logMsg = msg;
                        }
                       
                        // Don't log silent pings.
                        if ( ( code == WRAPPER_MSG_PING ) && ( msg.equals( "silent" ) ) )
                        {
                            //m_outDebug.println( "Received silent ping packet." );
                        }
                        else
                        {
                            m_outDebug.println( getRes().getString( "Received a packet {0} : {1}",
                                    getPacketCodeName( code ) , logMsg ) );
                        }
                    }
                   
                    // Ok, we got a packet.  Do something with it.
                    switch( code )
                    {
                    case WRAPPER_MSG_START:
                        // Don't start if we are already starting to stop.
                        if ( m_stoppingInit) {
                            if ( m_debug )
                            {
                                m_outDebug.println( getRes().getString( "Java stop initiated.  Skipping application startup." ) );
                            }
                        } else {
                            startInner( false );
                        }
                        break;
                       
                    case WRAPPER_MSG_STOP:
                        // Don't do anything if we are already stopping
                        if ( !m_stopping )
                        {
                            privilegedStopInner( 0 );
                            // Should never get back here.
                        }
                        break;
                       
                    case WRAPPER_MSG_PING:
                        m_lastPingTicks = getTicks();
                       
                        sendCommand( WRAPPER_MSG_PING, msg );
                       
                        if ( m_produceCoreEvents )
                        {
                            fireWrapperEvent( pingEvent );
                        }
                       
                        break;
                       
                    case WRAPPER_MSG_CHECK_DEADLOCK:
                        boolean deadLocked = checkDeadlocks();
                        if ( deadLocked )
                        {
                            sendCommand( WRAPPER_MSG_DEADLOCK, "deadLock" );
                        }
                        break;
                       
                    case WRAPPER_MSG_BADKEY:
                        // The key sent to the wrapper was incorrect.  We need to shutdown.
                        m_outError.println( getRes().getString("Authorization key rejected by Wrapper." ) );
                        m_outError.println( getRes().getString( "Exiting JVM..." ) );
                        closeBackend();
                        privilegedStopInner( 1 );
                        break;
                       
                    case WRAPPER_MSG_LOW_LOG_LEVEL:
                        try
                        {
                            m_lowLogLevel = Integer.parseInt( msg );
                            m_debug = ( m_lowLogLevel <= WRAPPER_LOG_LEVEL_DEBUG );
                            if ( m_debug )
                            {
                                m_outDebug.println( getRes().getString( "LowLogLevel from Wrapper is {0}",
                                    new Integer( m_lowLogLevel ) ) );
                            }
                        }
                        catch ( NumberFormatException e )
                        {
                            m_outError.println( getRes().getString(
                                    "Encountered an Illegal LowLogLevel from the Wrapper: {0}", msg ) );
                        }
                        break;
                       
                    case WRAPPER_MSG_PING_TIMEOUT:
                        /* No longer used.  This is still here in case a mix of versions are used. */
                        break;
                       
                    case WRAPPER_MSG_SERVICE_CONTROL_CODE:
                        try
                        {
                            int serviceControlCode = Integer.parseInt( msg );
                            if ( m_debug )
                            {
                                m_outDebug.println( getRes().getString(
                                        "ServiceControlCode from Wrapper with code {0}",
                                        new Integer( serviceControlCode ) ) );
                            }
                            WrapperServiceControlEvent event =
                                new WrapperServiceControlEvent( serviceControlCode );
                            fireWrapperEvent( event );
                        }
                        catch ( NumberFormatException e )
                        {
                            m_outError.println( getRes().getString(
                                    "Encountered an Illegal ServiceControlCode from the Wrapper: {0}", msg ) );
                        }
                        break;
                       
                    case WRAPPER_MSG_PAUSE:
                        try
                        {
                            int actionCode = Integer.parseInt( msg );
                            if ( m_debug )
                            {
                                m_outDebug.println( getRes().getString( "Pause from Wrapper with code {0}", new Integer( actionCode ) ) );
                            }
                            WrapperServicePauseEvent event =
                                new WrapperServicePauseEvent( actionCode );
                            fireWrapperEvent( event );
                        }
                        catch ( NumberFormatException e )
                        {
                            m_outError.println( getRes().getString(
                                    "Encountered an Illegal ActionCode from the Wrapper: {0}", msg ) );
                        }
                        break;
                       
                    case WRAPPER_MSG_RESUME:
                        try
                        {
                            int actionCode = Integer.parseInt( msg );
                            if ( m_debug )
                            {
                                m_outDebug.println( getRes().getString("Resume from Wrapper with code {0}", new Integer( actionCode ) ) );
                            }
                            WrapperServiceResumeEvent event =
                                new WrapperServiceResumeEvent( actionCode );
                            fireWrapperEvent( event );
                        }
                        catch ( NumberFormatException e )
                        {
                            m_outError.println( getRes().getString(
                                    "Encountered an Illegal ActionCode from the Wrapper: {0}", msg ) );
                        }
                        break;
                       
                    case WRAPPER_MSG_GC:
                        System.gc();
                        break;
                       
                    case WRAPPER_MSG_PROPERTIES:
                        readProperties( msg );
                        break;
                       
                    case WRAPPER_MSG_LOGFILE:
                        m_logFile = new File( msg );
                        WrapperLogFileChangedEvent event = new WrapperLogFileChangedEvent( m_logFile );
                        fireWrapperEvent( event );
                        break;
                       
                    default:
                        // Ignore unknown messages
                        m_outInfo.println( getRes().getString(
                                "Wrapper code received an unknown packet type: {0}", new Integer( code ) ) );
                        break;
                    }
                }
            }
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString( "Backend handler loop completed.  Disposed: {0}", ( m_disposed ? "True" : "False" ) ) );
            }
            return;

        }
        catch ( SocketException e )
        {
            if ( m_debug )
            {
                if ( m_backendSocket == null )
                {
                    // This error happens if the socket is closed while reading:
                    // java.net.SocketException: Descriptor not a socket: JVM_recv in socket
                    //                           input stream read
                    m_outDebug.println( getRes().getString( "Closed backend socket (Normal): {0}", e ) );
                }
                else
                {
                    m_outDebug.println( getRes().getString( "Closed backend socket: {0}", e ) );
                }
            }
            return;
        }
        catch ( IOException e )
        {
            // This means that the connection was closed.  Allow this to return.
            if ( m_debug )
            {
                m_outDebug.println( getRes().getString( "Closed backend (Normal): {0}", e ) );
            }
            return;
        }
    }
   
    private static void startRunner()
    {
        if ( isControlledByNativeWrapper() )
        {
            if ( m_commRunner == null )
            {
                // Create and launch a new thread to manage this connection
                m_commRunner = new Thread( m_instance, WRAPPER_CONNECTION_THREAD_NAME );
                m_commRunner.setDaemon( true );
                m_commRunner.start();
            }
           
            // Wait to give the runner a chance to connect.
            synchronized( WrapperManager.class )
            {
                while ( !m_commRunnerStarted )
                {
                    try
                    {
                        WrapperManager.class.wait( 100 );
                    }
                    catch ( InterruptedException e )
                    {
                    }
                }
            }
        }
        else
        {
            // Immediately mark the runner as started as it will never be used.
            synchronized( WrapperManager.class )
            {
                m_commRunnerStarted = true;
                WrapperManager.class.notifyAll();
            }
        }
    }
   
    /*---------------------------------------------------------------
     * Runnable Methods
     *-------------------------------------------------------------*/
    public void run()
    {
        // Make sure that no other threads call this method.
        if ( Thread.currentThread() != m_commRunner )
        {
            throw new IllegalStateException( getRes().getString(
                "Only the communications runner thread is allowed to call this method." ) );
        }
       
        if ( m_debug )
        {
            m_outDebug.println( getRes().getString( "Communications runner thread started." ) );
        }
       
        // This thread needs to have a very high priority so that it never
        //  gets put behind other threads.
        Thread.currentThread().setPriority( Thread.MAX_PRIORITY );
       
        // Initialize the last ping tick count.
        m_lastPingTicks = getTicks();
       
        boolean gotPortOnce = false;
        while ( !m_disposed )
        {
            try
            {
                openBackend();
               
                // After the socket has been opened the first time, mark the thread as
                //  started.  This must be done here to make sure that exits work correctly
                //  when called on startup.
                if ( !m_commRunnerStarted )
                {
                    synchronized( WrapperManager.class )
                    {
                        m_commRunnerStarted = true;
                        WrapperManager.class.notifyAll();
                    }
                }
               
                if ( m_backendSocket != null || m_backendConnected == true)
                {
                    handleBackend();
                }
                else
                {
                    // Failed, so wait for just a moment
                    try
                    {
                        Thread.sleep( 100 );
                    }
                    catch ( InterruptedException e )
                    {
                    }
                }
            }
            catch ( ThreadDeath td )
            {
                m_outError.println( getRes().getString( "Server daemon killed" ) );
            }
            catch ( Throwable t )
            {
                if ( !isShuttingDown() )
                {
                    // Show a stack trace here because this is fairly critical
                    m_outError.println( getRes().getString( "Server daemon died!" ) );
                    t.printStackTrace( m_outError );
                }
                else
                {
                    if ( m_debug )
                    {
                        m_outDebug.println( getRes().getString( "Server daemon died!" ) );
                        t.printStackTrace( m_outDebug );
                    }
                }
            }
            finally
            {
                if ( m_debug )
                {
                    m_outDebug.println( getRes().getString( "Returned from backend handler." ) );
                }
                // Always close the backend here.
                closeBackend();
                if ( !isShuttingDown() )
                {
                    if ( m_detachStarted && m_started )
                    {
                        // This and all further output will not be visible anywhere as the Wrapper is now gone.
                        m_out.println( getRes().getString( "The backend was closed as expected." ) );
                       
                        try
                        {
                            nativeRedirectPipes();
                        }
                        catch ( UnsatisfiedLinkError t )
                        {
                            m_err.println( getRes().getString( "Failed to redirect stdout and stderr before the Wrapper exits.\nOutput from the JVM may block.\nPlease make sure the native library has been properly initialized."));
                        }
                    }
                    else
                    {
                        m_outError.println( getRes().getString( "The backend was closed unexpectedly.  Restart to resync with the Wrapper." ) );
                        restart();
                        // Will not get here.
                    }
                }
            }
        }
       
        // Make sure that noone is ever left waiting for this thread to start.
        synchronized( WrapperManager.class )
        {
            if ( !m_commRunnerStarted )
            {
                m_commRunnerStarted = true;
                WrapperManager.class.notifyAll();
            }
        }
       
        if ( m_debug )
        {
            m_outDebug.println( getRes().getString( "Server daemon shut down" ) );
        }
    }
   
    /*---------------------------------------------------------------
     * Inner Classes
     *-------------------------------------------------------------*/
    /**
     * Mapping between WrapperEventListeners and their registered masks.
     *  This is necessary to support the case where the same listener is
     *  registered more than once.   It also makes it possible to reference
     *  an array of these mappings without synchronization.
     */
    private static class WrapperEventListenerMask
    {
        private WrapperEventListener m_listener;
        private long m_mask;
    }
   
    private static class WrapperTickEventImpl
        extends WrapperTickEvent
    {
        private int m_ticks;
        private int m_tickOffset;
       
        /**
         * Returns the tick count at the point the event is fired.
         *
         * @return The tick count at the point the event is fired.
         */
        public int getTicks()
        {
            return m_ticks;
        }
       
        /**
         * Returns the offset between the tick count used by the Wrapper for time
         *  keeping and the tick count generated directly from the system time.
         *
         * This will be 0 in most cases.  But will be a positive value if the
         *  system time is ever set back for any reason.  It will be a negative
         *  value if the system time is set forward or if the system is under
         *  heavy load.  If the wrapper.use_system_time property is set to TRUE
         *  then the Wrapper will be using the system tick count for internal
         *  timing and this value will always be 0.
         *
         * @return The tick count offset.
         */
        public int getTickOffset()
        {
            return m_tickOffset;
        }
    }
   
    /**
     * When the JVM is being controlled by the Wrapper, stdin can not be used
     *  as it is undefined.  This class makes it possible to provide the user
     *  application with a descriptive error message if System.in is accessed.
     */
    private static class WrapperInputStream
        extends InputStream
    {
        /**
         * This method will always throw an IOException as the read method is
         *  not valid.
         */
        public int read()
            throws IOException
        {
            m_out.println( getRes().getString( "WARNING - System.in has been disabled by the wrapper.disable_console_input property.  Calls will block indefinitely." ) );
           
            // Go into a loop that will never return.
            while ( true )
            {
                synchronized( this )
                {
                    try
                    {
                        this.wait();
                    }
                    catch ( InterruptedException e )
                    {
                        // Ignore.
                    }
                }
            }
        }
    }
   
    /**
     * Scans the JVM for deadlocked threads.
     * <p>
     * Standard Edition feature.
     *
     * @see #isStandardEdition()
     * @since Wrapper 3.5.0
     */
    private static boolean checkDeadlocks()
        throws WrapperLicenseError
    {
        if ( isStandardEdition() )
        {
            boolean result = false;
            try
            {
                result = nativeCheckDeadLocks();
            }
            catch ( UnsatisfiedLinkError e )
            {
                if ( m_debug )
                {
                    m_outDebug.println( getRes().getString( "Deadlock check skipped.  Native call failed." ) );
                }
                result = false;
            }
            return result;
        }
        else
        {
            return false;
        }
    }
}

TOP

Related Classes of org.tanukisoftware.wrapper.WrapperManager$ShutdownLock

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.