com.izforge.izpack.uninstaller
Class SelfModifier

java.lang.Object
  extended by com.izforge.izpack.uninstaller.SelfModifier

public class SelfModifier
extends java.lang.Object

Allows an application to modify the jar file from which it came, including outright deletion. The jar file of an app is usually locked when java is run so this is normally not possible.

Create a SelfModifier with a target method, then invoke the SelfModifier with arguments to be passed to the target method. The jar file containing the target method's class (obtained by reflection) will be extracted to a temporary directory, and a new java process will be spawned to invoke the target method. The original jar file may now be modified.

If the constructor or invoke() methods fail, it is generally because secondary java processes could not be started.

Requirements

There are three system processes (or "phases") involved, the first invoked by the user, the second and third by the SelfModifier.

Phase 1:

  1. Program is launched, SelfModifier is created, invoke(String[]) is called
  2. A temporary directory (or "sandbox") is created in the default temp directory, and the jar file contents ar extracted into it
  3. Phase 2 is spawned using the sandbox as it's classpath, SelfModifier as the main class, the arguments to "invoke(String[])" as the main arguments, and the SelfModifier system properties set.
  4. Immidiately exit so the system unlocks the jar file

Phase 2:

  1. Initializes from system properties.
  2. Spawn phase 3 exactly as phase 2 except the self.modifier.phase system properties set to 3.
  3. Wait for phase 3 to die
  4. Delete the temporary sandbox

Phase 3:

  1. Initializes from system properties.
  2. Redirect std err stream to the log
  3. Invoke the target method with arguments we were given
  4. The target method is expected to call exit(), or to not start any looping threads (e.g. AWT thread). In other words, the target is the new "main" method.

SelfModifier system properties used to pass information between processes.

Constant System property description
BASE_KEY self.mod.jar base path to log file and sandbox dir
JAR_KEY self.mod.class path to original jar file
CLASS_KEY self.mod.method class of target method
METHOD_KEY self.mod.phase name of method to be invoked in sandbox
PHASE_KEY self.mod.base phase of operation to run

Version:
1.0
Author:
Chadwick McHenry

Nested Class Summary
static class SelfModifier.StreamProxy
           
 
Field Summary
static java.lang.String BASE_KEY
          System property name of base for log and sandbox of secondary processes.
static java.lang.String CLASS_KEY
          System property name of class declaring target method.
private  java.util.Date date
           
private  java.text.SimpleDateFormat isoPoint
          For logging time.
static java.lang.String JAR_KEY
          System property name of original jar file containing application.
private  java.io.File jarFile
          Original jar file program was launched from.
private static java.lang.String JAVA_HOME
           
private static float JAVA_SPECIFICATION_VERSION
          ******************************************************************************************** --------------------------------------------------------------------- Apache ant code ---------------------------------------------------------------------
(package private)  java.io.PrintStream log
          ******************************************************************************************** --------------------------------------------------------------------- Logging ---------------------------------------------------------------------
private  java.io.File logFile
          Log for phase 2 and 3, because we can't capture the stdio from them.
private  java.lang.reflect.Method method
          Target method to be invoked in sandbox.
static java.lang.String METHOD_KEY
          System property name of target method to invoke in secondary process.
private  int phase
          Current phase of execution: 1, 2, or 3.
static java.lang.String PHASE_KEY
          System property name of phase (1, 2, or 3) indicator.
private  java.lang.String prefix
          Base prefix name for sandbox and log, used only in phase 1.
private  java.io.File sandbox
          Directory which we extract too, invoke from, and finally delete.
 
Constructor Summary
private SelfModifier()
          Internal constructor where target class and method are obtained from system properties.
  SelfModifier(java.lang.reflect.Method method)
          Creates a SelfModifier which will invoke the target method in a separate process from which it may modify it's own jar file.
 
Method Summary
private static java.lang.String addExtension(java.lang.String command)
           
private  java.io.PrintStream checkLog()
           
static boolean deleteTree(java.io.File file)
          Recursively delete a file structure.
private  void errlog(java.lang.String msg)
           
private  void extractJarFile()
           
static java.io.File findJarFile(java.lang.Class<MultiVolumeInstaller> clazz)
          Retrieve the jar file the specified class was loaded from.
static java.lang.String fromURI(java.lang.String uri)
          Constructs a file path from a file: URI.
private  void initJavaExec()
          This call ensures that java can be exec'd in a separate process.
private  void initMethod(java.lang.reflect.Method method)
          Check the method for the required properties (public, static, params:(String[])).
 void invoke(java.lang.String[] args)
          Invoke the target method in a separate process from which it may modify it's own jar file.
private  void invoke2(java.lang.String[] args)
          Invoke phase 2, which starts phase 3, then cleans up the sandbox.
private  void invoke3(java.lang.String[] args)
          Invoke the target method and let it run free!
private static java.lang.String javaCommand()
           
private  void log(java.lang.String msg)
           
private  void log(java.lang.Throwable t)
           
static void main(java.lang.String[] args)
           
private  java.lang.Process spawn(java.lang.String[] args, int nextPhase)
          Run a new jvm with all the system parameters needed for phases 2 and 3.
static void test(java.lang.String[] args)
           
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Field Detail

BASE_KEY

public static final java.lang.String BASE_KEY
System property name of base for log and sandbox of secondary processes.

See Also:
Constant Field Values

JAR_KEY

public static final java.lang.String JAR_KEY
System property name of original jar file containing application.

See Also:
Constant Field Values

CLASS_KEY

public static final java.lang.String CLASS_KEY
System property name of class declaring target method.

See Also:
Constant Field Values

METHOD_KEY

public static final java.lang.String METHOD_KEY
System property name of target method to invoke in secondary process.

See Also:
Constant Field Values

PHASE_KEY

public static final java.lang.String PHASE_KEY
System property name of phase (1, 2, or 3) indicator.

See Also:
Constant Field Values

prefix

private java.lang.String prefix
Base prefix name for sandbox and log, used only in phase 1.


method

private java.lang.reflect.Method method
Target method to be invoked in sandbox.


logFile

private java.io.File logFile
Log for phase 2 and 3, because we can't capture the stdio from them.


sandbox

private java.io.File sandbox
Directory which we extract too, invoke from, and finally delete.


jarFile

private java.io.File jarFile
Original jar file program was launched from.


phase

private int phase
Current phase of execution: 1, 2, or 3.


isoPoint

private java.text.SimpleDateFormat isoPoint
For logging time.


date

private java.util.Date date

log

java.io.PrintStream log
******************************************************************************************** --------------------------------------------------------------------- Logging ---------------------------------------------------------------------


JAVA_SPECIFICATION_VERSION

private static final float JAVA_SPECIFICATION_VERSION
******************************************************************************************** --------------------------------------------------------------------- Apache ant code ---------------------------------------------------------------------


JAVA_HOME

private static final java.lang.String JAVA_HOME
Constructor Detail

SelfModifier

private SelfModifier()
              throws java.io.IOException
Internal constructor where target class and method are obtained from system properties.

Throws:
java.io.IOException - for errors getting to the sandbox.
java.lang.SecurityException - if access to the target method is denied

SelfModifier

public SelfModifier(java.lang.reflect.Method method)
             throws java.io.IOException
Creates a SelfModifier which will invoke the target method in a separate process from which it may modify it's own jar file.

The target method must be public, static, and take a single array of strings as its only parameter. The class which declares the method must also be public. Reflection is used to ensure this.

Parameters:
method - a public, static method that accepts a String array as it's only parameter. Any return value is ignored.
Throws:
java.lang.NullPointerException - if method is null
java.lang.IllegalArgumentException - if method is not public, static, and take a String array as it's only argument, or of it's declaring class is not public.
java.lang.IllegalStateException - if process was not invoked from a jar file, or an IOExceptioin occured while accessing it
java.io.IOException - if java is unable to be executed as a separte process
java.lang.SecurityException - if access to the method, or creation of a subprocess is denied
Method Detail

test

public static void test(java.lang.String[] args)

main

public static void main(java.lang.String[] args)

initMethod

private void initMethod(java.lang.reflect.Method method)
Check the method for the required properties (public, static, params:(String[])).

Throws:
java.lang.NullPointerException - if method is null
java.lang.IllegalArgumentException - if method is not public, static, and take a String array as it's only argument, or of it's declaring class is not public.
java.lang.SecurityException - if access to the method is denied

initJavaExec

private void initJavaExec()
                   throws java.io.IOException
This call ensures that java can be exec'd in a separate process.

Throws:
java.io.IOException - if an I/O error occurs, indicating java is unable to be exec'd
java.lang.SecurityException - if a security manager exists and doesn't allow creation of a subprocess

invoke

public void invoke(java.lang.String[] args)
            throws java.io.IOException
Invoke the target method in a separate process from which it may modify it's own jar file. This method does not normally return. After spawning the secondary process, the current process must die before the jar file is unlocked, therefore calling this method is akin to calling System.exit(int).

The contents of the current jar file are extracted copied to a 'sandbox' directory from which the method is invoked. The path to the original jar file is placed in the system property JAR_KEY.

Parameters:
args - arguments to pass to the target method. May be empty or null to indicate no arguments.
Throws:
java.io.IOException - for lots of things
java.lang.IllegalStateException - if method's class was not loaded from a jar

spawn

private java.lang.Process spawn(java.lang.String[] args,
                                int nextPhase)
                         throws java.io.IOException
Run a new jvm with all the system parameters needed for phases 2 and 3.

Throws:
java.io.IOException - if there is an error getting the cononical name of a path

findJarFile

public static java.io.File findJarFile(java.lang.Class<MultiVolumeInstaller> clazz)
Retrieve the jar file the specified class was loaded from.

Returns:
null if file was not loaded from a jar file
Throws:
java.lang.SecurityException - if access to is denied by SecurityManager

extractJarFile

private void extractJarFile()
                     throws java.io.IOException
Throws:
java.io.IOException

invoke2

private void invoke2(java.lang.String[] args)
Invoke phase 2, which starts phase 3, then cleans up the sandbox. This is needed because GUI's often call the exit() method to kill the AWT thread, and early versions of java did not have exit hooks. In order to delete the sandbox on exit we invoke method in separate process and wait for that process to complete. Even worse, resources in the jar may be locked by the target process, which would prevent the sandbox from being deleted as well.


deleteTree

public static boolean deleteTree(java.io.File file)
Recursively delete a file structure.


invoke3

private void invoke3(java.lang.String[] args)
Invoke the target method and let it run free!


errlog

private void errlog(java.lang.String msg)

checkLog

private java.io.PrintStream checkLog()

log

private void log(java.lang.Throwable t)

log

private void log(java.lang.String msg)

fromURI

public static java.lang.String fromURI(java.lang.String uri)
Constructs a file path from a file: URI.

Will be an absolute path if the given URI is absolute.

Swallows '%' that are not followed by two characters, doesn't deal with non-ASCII characters.

Parameters:
uri - the URI designating a file in the local filesystem.
Returns:
the local file system path for the file.

addExtension

private static java.lang.String addExtension(java.lang.String command)

javaCommand

private static java.lang.String javaCommand()