root / import / ext3D / trunk / install-extension3d / IzPack / src / lib / com / izforge / izpack / uninstaller / SelfModifier.java @ 15280
History | View | Annotate | Download (26.9 KB)
1 |
/*
|
---|---|
2 |
* $Id: SelfModifier.java,v 1.1 2006/06/14 07:29:07 cesar Exp $
|
3 |
* IzPack
|
4 |
* Copyright (C) 2004 Chadwick A. McHenry
|
5 |
*
|
6 |
* File : SelfModifier.java
|
7 |
* Description : Uninstaller helper.
|
8 |
* Author's email : mchenryc@acm.org
|
9 |
* Author's Website : http://www.acm.org/
|
10 |
*
|
11 |
* This program is free software; you can redistribute it and/or
|
12 |
* modify it under the terms of the GNU General Public License
|
13 |
* as published by the Free Software Foundation; either version 2
|
14 |
* of the License, or any later version.
|
15 |
*
|
16 |
* This program is distributed in the hope that it will be useful,
|
17 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
18 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
19 |
* GNU General Public License for more details.
|
20 |
*
|
21 |
* You should have received a copy of the GNU General Public License
|
22 |
* along with this program; if not, write to the Free Software
|
23 |
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
24 |
*/
|
25 |
|
26 |
package com.izforge.izpack.uninstaller; |
27 |
|
28 |
import java.io.BufferedOutputStream; |
29 |
import java.io.BufferedReader; |
30 |
import java.io.File; |
31 |
import java.io.FileOutputStream; |
32 |
import java.io.IOException; |
33 |
import java.io.InputStream; |
34 |
import java.io.InputStreamReader; |
35 |
import java.io.OutputStream; |
36 |
import java.io.PrintStream; |
37 |
import java.io.PrintWriter; |
38 |
import java.io.RandomAccessFile; |
39 |
import java.lang.reflect.Method; |
40 |
import java.lang.reflect.Modifier; |
41 |
import java.net.URI; |
42 |
import java.net.URL; |
43 |
import java.text.CharacterIterator; |
44 |
import java.text.SimpleDateFormat; |
45 |
import java.text.StringCharacterIterator; |
46 |
import java.util.Date; |
47 |
import java.util.Enumeration; |
48 |
import java.util.jar.JarFile; |
49 |
import java.util.zip.ZipEntry; |
50 |
|
51 |
import com.izforge.izpack.util.OsVersion; |
52 |
|
53 |
/**
|
54 |
* Allows an application to modify the jar file from which it came, including
|
55 |
* outright deletion. The jar file of an app is usually locked when java is
|
56 |
* run so this is normally not possible.<p>
|
57 |
*
|
58 |
* Create a SelfModifier with a target method, then invoke the SelfModifier
|
59 |
* with arguments to be passed to the target method. The jar file containing
|
60 |
* the target method's class (obtained by reflection) will be extracted to a
|
61 |
* temporary directory, and a new java process will be spawned to invoke the
|
62 |
* target method. The original jar file may now be modified.<p>
|
63 |
*
|
64 |
* If the constructor or invoke() methods fail, it is generally because
|
65 |
* secondary java processes could not be started.
|
66 |
*
|
67 |
* <b>Requirements</b>
|
68 |
* <ul>
|
69 |
* <li>The target method, and all it's required classes must be in a jar file.
|
70 |
* <li>The Self Modifier, and its inner classes must also be in the jar file.
|
71 |
* </ul>
|
72 |
*
|
73 |
* There are three system processes (or "phases") involved, the first invoked
|
74 |
* by the user, the second and third by the SelfModifier.
|
75 |
* <p>
|
76 |
*
|
77 |
* <b>Phase 1:</b>
|
78 |
* <ol>
|
79 |
* <li>Program is launched, SelfModifier is created, invoke(String[]) is
|
80 |
* called
|
81 |
* <li>A temporary directory (or "sandbox") is created in the default temp
|
82 |
* directory, and the jar file contents ar extracted into it
|
83 |
* <li>Phase 2 is spawned using the sandbox as it's
|
84 |
* classpath, SelfModifier as the main class, the arguments to
|
85 |
* "invoke(String[])" as the main arguments, and the <a
|
86 |
* href="#selfmodsysprops">SelfModifier system properties</a> set.
|
87 |
* <li>Immidiately exit so the system unlocks the jar file
|
88 |
* </ol>
|
89 |
*
|
90 |
* <b>Phase 2:</b>
|
91 |
* <ol>
|
92 |
* <li>Initializes from system properties.
|
93 |
* <li>Spawn phase 3 exactly as phase 2 except the self.modifier.phase system
|
94 |
* properties set to 3.
|
95 |
* <li>Wait for phase 3 to die
|
96 |
* <li>Delete the temporary sandbox
|
97 |
* </ol>
|
98 |
*
|
99 |
* <b>Phase 3:</b>
|
100 |
* <ol>
|
101 |
* <li>Initializes from system properties.
|
102 |
* <li>Redirect std err stream to the log
|
103 |
* <li>Invoke the target method with arguments we were given
|
104 |
* <li>The target method is expected to call exit(), or to not start any
|
105 |
* looping threads (e.g. AWT thread). In other words, the target is the
|
106 |
* new "main" method.
|
107 |
* </ol>
|
108 |
*
|
109 |
* <a name="selfmodsysprops"><b>SelfModifier system properties</b></a> used to
|
110 |
* pass information between processes.
|
111 |
* <table border="1">
|
112 |
* <tr><th>Constant <th>System property <th>description</tr>
|
113 |
* <tr><td><a href="#BASE_KEY">BASE_KEY</a> <td>self.mod.jar
|
114 |
* <td>base path to log file and sandbox dir</tr>
|
115 |
* <tr><td><a href="#JAR_KEY">JAR_KEY</a> <td>self.mod.class
|
116 |
* <td>path to original jar file</tr>
|
117 |
* <tr><td><a href="#CLASS_KEY">CLASS_KEY</a> <td>self.mod.method
|
118 |
* <td>class of target method</tr>
|
119 |
* <tr><td><a href="#METHOD_KEY">METHOD_KEY</a> <td>self.mod.phase
|
120 |
* <td>name of method to be invoked in sandbox</tr>
|
121 |
* <tr><td><a href="#PHASE_KEY">PHASE_KEY</a> <td>self.mod.base
|
122 |
* <td>phase of operation to run</tr>
|
123 |
* </table>
|
124 |
*
|
125 |
* @author Chadwick McHenry
|
126 |
* @version 1.0
|
127 |
*/
|
128 |
public class SelfModifier |
129 |
{ |
130 |
|
131 |
/** System property name of base for log and sandbox of secondary processes. */
|
132 |
public static final String BASE_KEY = "self.mod.base"; |
133 |
|
134 |
/** System property name of original jar file containing application. */
|
135 |
public static final String JAR_KEY = "self.mod.jar"; |
136 |
|
137 |
/** System property name of class declaring target method. */
|
138 |
public static final String CLASS_KEY = "self.mod.class"; |
139 |
|
140 |
/** System property name of target method to invoke in secondary process. */
|
141 |
public static final String METHOD_KEY = "self.mod.method"; |
142 |
|
143 |
/** System property name of phase (1, 2, or 3) indicator. */
|
144 |
public static final String PHASE_KEY = "self.mod.phase"; |
145 |
|
146 |
/** Base prefix name for sandbox and log, used only in phase 1. */
|
147 |
private String prefix = "izpack"; |
148 |
|
149 |
/** Target method to be invoked in sandbox. */
|
150 |
private Method method = null; |
151 |
|
152 |
/** Log for phase 2 and 3, because we can't capture the stdio from them. */
|
153 |
private File logFile = null; |
154 |
|
155 |
/** Directory which we extract too, invoke from, and finally delete. */
|
156 |
private File sandbox = null; |
157 |
|
158 |
/** Original jar file program was launched from. */
|
159 |
private File jarFile = null; |
160 |
|
161 |
/** Current phase of execution: 1, 2, or 3. */
|
162 |
private int phase = 0; |
163 |
|
164 |
/** For logging time. */
|
165 |
private SimpleDateFormat isoPoint = |
166 |
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); |
167 |
private Date date = new Date(); |
168 |
|
169 |
public static void test(String[] args) |
170 |
{ |
171 |
// open a File for random access in the sandbox, which will cause deletion
|
172 |
// of the file and its parent directories to fail until it is closed (by
|
173 |
// virtue of this java process halting)
|
174 |
try
|
175 |
{ |
176 |
File sandbox = new File(System.getProperty(BASE_KEY) + ".d"); |
177 |
File randFile = new File(sandbox, "RandomAccess.tmp"); |
178 |
RandomAccessFile rand = new RandomAccessFile(randFile, "rw"); |
179 |
rand.writeChars("Just a test: The jvm has to close 'cuz I won't!\n");
|
180 |
|
181 |
System.err.print("Deleting sandbox: "); |
182 |
deleteTree(sandbox); |
183 |
System.err.println(sandbox.exists() ? "FAILED" : "SUCCEEDED"); |
184 |
} catch (Exception x) |
185 |
{ |
186 |
System.err.println(x.getMessage());
|
187 |
x.printStackTrace(); |
188 |
} |
189 |
} |
190 |
|
191 |
public static void main(String[] args) |
192 |
{ |
193 |
// phase 1 already set up the sandbox and spawned phase 2.
|
194 |
// phase 2 creates the log, spawns phase 3 and waits
|
195 |
// phase 3 invokes method and returns. method must kill all it's threads
|
196 |
|
197 |
try
|
198 |
{ |
199 |
// all it's attributes are retrieved from system properties
|
200 |
SelfModifier selfModifier = new SelfModifier();
|
201 |
|
202 |
// phase 2: invoke a process for phase 3, wait, and clean up
|
203 |
if (selfModifier.phase == 2) |
204 |
selfModifier.invoke2(args); |
205 |
|
206 |
// phase 3: invoke method and die
|
207 |
else if (selfModifier.phase == 3) |
208 |
selfModifier.invoke3(args); |
209 |
} catch (IOException ioe) |
210 |
{ |
211 |
System.err.println("Error invoking a secondary phase"); |
212 |
System.err.println(
|
213 |
"Note that this program is only intended as a secondary process");
|
214 |
ioe.printStackTrace(); |
215 |
} |
216 |
} |
217 |
|
218 |
/**
|
219 |
* Internal constructor where target class and method are obtained from
|
220 |
* system properties.
|
221 |
*
|
222 |
* @throws IOException for errors getting to the sandbox.
|
223 |
* @throws SecurityException if access to the target method is denied
|
224 |
*/
|
225 |
private SelfModifier() throws IOException |
226 |
{ |
227 |
phase = Integer.parseInt(System.getProperty(PHASE_KEY)); |
228 |
|
229 |
String cName = System.getProperty(CLASS_KEY); |
230 |
String tName = System.getProperty(METHOD_KEY); |
231 |
|
232 |
jarFile = new File(System.getProperty(JAR_KEY)); |
233 |
logFile = new File(System.getProperty(BASE_KEY) + ".log"); |
234 |
sandbox = new File(System.getProperty(BASE_KEY) + ".d"); |
235 |
|
236 |
// retrieve refrence to target method
|
237 |
try
|
238 |
{ |
239 |
Class clazz = Class.forName(cName); |
240 |
Method method = clazz.getMethod(tName, new Class[] { String[].class }); |
241 |
|
242 |
initMethod(method); |
243 |
} catch (ClassNotFoundException x1) |
244 |
{ |
245 |
log("No class found for " + cName);
|
246 |
} catch (NoSuchMethodException x2) |
247 |
{ |
248 |
log("No method " + tName + " found in " + cName); |
249 |
} |
250 |
} |
251 |
|
252 |
/**
|
253 |
* Creates a SelfModifier which will invoke the target method in a separate
|
254 |
* process from which it may modify it's own jar file.
|
255 |
*
|
256 |
* The target method must be public, static, and take a single array of
|
257 |
* strings as its only parameter. The class which declares the method must
|
258 |
* also be public. Reflection is used to ensure this.
|
259 |
*
|
260 |
* @param method a public, static method that accepts a String array as it's
|
261 |
* only parameter. Any return value is ignored.
|
262 |
*
|
263 |
* @throws NullPointerException if <code>method</code> is null
|
264 |
* @throws IllegalArgumentException if <code>method</code> is not public,
|
265 |
* static, and take a String array as it's only argument, or of it's
|
266 |
* declaring class is not public.
|
267 |
* @throws IllegalStateException if process was not invoked from a jar file,
|
268 |
* or an IOExceptioin occured while accessing it
|
269 |
* @throws IOException if java is unable to be executed as a separte process
|
270 |
* @throws SecurityException if access to the method, or creation of a
|
271 |
* subprocess is denied
|
272 |
*/
|
273 |
public SelfModifier(Method method) throws IOException |
274 |
{ |
275 |
phase = 1;
|
276 |
initJavaExec(); |
277 |
initMethod(method); |
278 |
} |
279 |
|
280 |
/**
|
281 |
* Check the method for the required properties (public, static,
|
282 |
* params:(String[])).
|
283 |
*
|
284 |
* @throws NullPointerException if <code>method</code> is null
|
285 |
* @throws IllegalArgumentException if <code>method</code> is not public,
|
286 |
* static, and take a String array as it's only argument, or of it's
|
287 |
* declaring class is not public.
|
288 |
* @throws SecurityException if access to the method is denied
|
289 |
*/
|
290 |
private void initMethod(Method method) |
291 |
{ |
292 |
int mod = method.getModifiers();
|
293 |
if ((mod & Modifier.PUBLIC) == 0 || (mod & Modifier.STATIC) == 0) |
294 |
throw new IllegalArgumentException("Method not public and static"); |
295 |
|
296 |
Class[] params = method.getParameterTypes(); |
297 |
if (params.length != 1 |
298 |
|| !params[0].isArray()
|
299 |
|| !params[0].getComponentType().getName().equals("java.lang.String")) |
300 |
throw new IllegalArgumentException("Method must accept String array"); |
301 |
|
302 |
Class clazz = method.getDeclaringClass();
|
303 |
mod = clazz.getModifiers(); |
304 |
if ((mod & Modifier.PUBLIC) == 0 || (mod & Modifier.INTERFACE) != 0) |
305 |
throw new IllegalArgumentException("Method must be in a public class"); |
306 |
|
307 |
this.method = method;
|
308 |
} |
309 |
|
310 |
/**
|
311 |
* This call ensures that java can be exec'd in a separate process.
|
312 |
*
|
313 |
* @throws IOException if an I/O error occurs, indicating java is unable to
|
314 |
* be exec'd
|
315 |
* @throws SecurityException if a security manager exists and doesn't allow
|
316 |
* creation of a subprocess
|
317 |
*/
|
318 |
private void initJavaExec() throws IOException |
319 |
{ |
320 |
try
|
321 |
{ |
322 |
Process p = Runtime.getRuntime().exec(javaCommand()); |
323 |
|
324 |
new StreamProxy(p.getErrorStream(), "err").start(); |
325 |
new StreamProxy(p.getInputStream(), "out").start(); |
326 |
p.getOutputStream().close(); |
327 |
|
328 |
// even if it returns an error code, it was at least found
|
329 |
int eval = p.waitFor();
|
330 |
} catch (InterruptedException ie) |
331 |
{ |
332 |
throw new IOException("Unable to create a java subprocess"); |
333 |
} |
334 |
} |
335 |
|
336 |
/* --------------------------------------------------------------------- *
|
337 |
* Phase 1 (call from external spawn phase 2)
|
338 |
* --------------------------------------------------------------------- */
|
339 |
|
340 |
/**
|
341 |
* Invoke the target method in a separate process from which it may modify
|
342 |
* it's own jar file. This method does not normally return. After spawning
|
343 |
* the secondary process, the current process must die before the jar file is
|
344 |
* unlocked, therefore calling this method is akin to calling
|
345 |
* {@link System#exit(int)}.<p>
|
346 |
*
|
347 |
* The contents of the current jar file are extracted copied to a 'sandbox'
|
348 |
* directory from which the method is invoked. The path to the original jar
|
349 |
* file is placed in the system property {@link #JAR_KEY}.<p>
|
350 |
*
|
351 |
* @param args arguments to pass to the target method. May be empty or null
|
352 |
* to indicate no arguments.
|
353 |
*
|
354 |
* @throws IOException for lots of things
|
355 |
* @throws IllegalStateException if method's class was not loaded from a jar
|
356 |
*/
|
357 |
public void invoke(String[] args) throws IOException |
358 |
{ |
359 |
// Initialize sandbox and log file to be unique, but similarly named
|
360 |
while (true) |
361 |
{ |
362 |
logFile = File.createTempFile(prefix, ".log"); |
363 |
String f = logFile.toString();
|
364 |
sandbox = new File(f.substring(0, f.length() - 4) + ".d"); |
365 |
|
366 |
// check if the similarly named directory is free
|
367 |
if (!sandbox.exists())
|
368 |
break;
|
369 |
|
370 |
logFile.delete(); |
371 |
} |
372 |
if (!sandbox.mkdir())
|
373 |
throw new RuntimeException("Failed to create temp dir: " + sandbox); |
374 |
|
375 |
sandbox = sandbox.getCanonicalFile(); |
376 |
logFile = logFile.getCanonicalFile(); |
377 |
|
378 |
jarFile = findJarFile(method.getDeclaringClass()).getCanonicalFile(); |
379 |
if (jarFile == null) |
380 |
throw new IllegalStateException("SelfModifier must be in a jar file"); |
381 |
log("JarFile: " + jarFile);
|
382 |
|
383 |
extractJarFile(); |
384 |
|
385 |
if (args == null) |
386 |
args = new String[0]; |
387 |
spawn(args, 2);
|
388 |
|
389 |
// finally, if all went well, the invoking process must exit
|
390 |
log("Exit");
|
391 |
System.exit(0); |
392 |
} |
393 |
|
394 |
/**
|
395 |
* Run a new jvm with all the system parameters needed for phases 2 and 3.
|
396 |
*
|
397 |
* @throws IOException if there is an error getting the cononical name of a
|
398 |
* path
|
399 |
*/
|
400 |
private Process spawn(String[] args, int nextPhase) throws IOException |
401 |
{ |
402 |
String base = logFile.getAbsolutePath();
|
403 |
base = base.substring(0, base.length() - 4); |
404 |
|
405 |
// invoke from tmpdir, passing target method arguments as args, and
|
406 |
// SelfModifier parameters as sustem properties
|
407 |
String[] javaCmd = |
408 |
new String[] { |
409 |
javaCommand(), |
410 |
"-classpath",
|
411 |
sandbox.getAbsolutePath(), |
412 |
"-D" + BASE_KEY + "=" + base, |
413 |
"-D" + JAR_KEY + "=" + jarFile.getPath(), |
414 |
"-D" + CLASS_KEY + "=" + method.getDeclaringClass().getName(), |
415 |
"-D" + METHOD_KEY + "=" + method.getName(), |
416 |
"-D" + PHASE_KEY + "=" + nextPhase, |
417 |
getClass().getName()}; |
418 |
|
419 |
String[] entireCmd = new String[javaCmd.length + args.length]; |
420 |
System.arraycopy(javaCmd, 0, entireCmd, 0, javaCmd.length); |
421 |
System.arraycopy(args, 0, entireCmd, javaCmd.length, args.length); |
422 |
|
423 |
StringBuffer sb = new StringBuffer("Spawning phase "); |
424 |
sb.append(nextPhase).append(": ");
|
425 |
for (int i = 0; i < entireCmd.length; i++) |
426 |
sb.append("\n\t").append(entireCmd[i]);
|
427 |
log(sb.toString()); |
428 |
|
429 |
// Just invoke it and let it go, the exception will be caught above
|
430 |
// Won't compile on < jdk1.3, but will run on jre1.2
|
431 |
if (JAVA_SPECIFICATION_VERSION < 1.3) |
432 |
return Runtime.getRuntime().exec(entireCmd, null); |
433 |
else
|
434 |
return Runtime.getRuntime().exec(entireCmd, null, null); //workDir); |
435 |
} |
436 |
|
437 |
/**
|
438 |
* Retrieve the jar file the specified class was loaded from.
|
439 |
*
|
440 |
* @return null if file was not loaded from a jar file
|
441 |
* @throws SecurityException if access to is denied by SecurityManager
|
442 |
*/
|
443 |
public static File findJarFile(Class clazz) |
444 |
{ |
445 |
String resource = clazz.getName().replace('.', '/') + ".class"; |
446 |
|
447 |
URL url = ClassLoader.getSystemResource(resource); |
448 |
if (!url.getProtocol().equals("jar")) |
449 |
return null; |
450 |
|
451 |
String path = url.getFile();
|
452 |
// starts at "file:..." (use getPath() as of 1.3)
|
453 |
path = path.substring(0, path.lastIndexOf('!')); |
454 |
|
455 |
File file;
|
456 |
|
457 |
// getSystemResource() returns a valid URL (eg. spaces are %20), but a file
|
458 |
// Constructed w/ it will expect "%20" in path. URI and File(URI) properly
|
459 |
// deal with escaping back and forth, but didn't exist until 1.4
|
460 |
if (JAVA_SPECIFICATION_VERSION < 1.4) |
461 |
file = new File(fromURI(path)); |
462 |
else
|
463 |
file = new File(URI.create(path)); |
464 |
|
465 |
return file;
|
466 |
} |
467 |
|
468 |
/**
|
469 |
* @throws IOException
|
470 |
*/
|
471 |
private void extractJarFile() throws IOException |
472 |
{ |
473 |
byte[] buf = new byte[5120]; |
474 |
int extracted = 0; |
475 |
InputStream in = null; |
476 |
OutputStream out = null; |
477 |
String MANIFEST = "META-INF/MANIFEST.MF"; |
478 |
|
479 |
JarFile jar = new JarFile(jarFile, true); |
480 |
|
481 |
try
|
482 |
{ |
483 |
Enumeration entries = jar.entries();
|
484 |
while (entries.hasMoreElements())
|
485 |
{ |
486 |
ZipEntry entry = (ZipEntry)entries.nextElement(); |
487 |
if (entry.isDirectory())
|
488 |
continue;
|
489 |
|
490 |
String pathname = entry.getName();
|
491 |
if (MANIFEST.equals(pathname.toUpperCase()))
|
492 |
continue;
|
493 |
|
494 |
in = jar.getInputStream(entry); |
495 |
|
496 |
File outFile = new File(sandbox, pathname); |
497 |
File parent = outFile.getParentFile();
|
498 |
if (parent != null && !parent.exists()) |
499 |
parent.mkdirs(); |
500 |
|
501 |
out = new BufferedOutputStream(new FileOutputStream(outFile)); |
502 |
|
503 |
int n;
|
504 |
while ((n = in.read(buf, 0, buf.length)) > 0) |
505 |
out.write(buf, 0, n);
|
506 |
|
507 |
out.close(); |
508 |
extracted++; |
509 |
} |
510 |
jar.close(); |
511 |
|
512 |
log( |
513 |
"Extracted "
|
514 |
+ extracted |
515 |
+ " file"
|
516 |
+ (extracted > 1 ? "s" : "") |
517 |
+ " into "
|
518 |
+ sandbox.getPath()); |
519 |
} finally
|
520 |
{ |
521 |
try
|
522 |
{ |
523 |
jar.close(); |
524 |
} catch (IOException ioe) |
525 |
{ |
526 |
} |
527 |
if (out != null) |
528 |
{ |
529 |
try
|
530 |
{ |
531 |
out.close(); |
532 |
} catch (IOException ioe) |
533 |
{ |
534 |
} |
535 |
} |
536 |
if (in != null) |
537 |
{ |
538 |
try
|
539 |
{ |
540 |
in.close(); |
541 |
} catch (IOException ioe) |
542 |
{ |
543 |
} |
544 |
} |
545 |
} |
546 |
} |
547 |
|
548 |
/* --------------------------------------------------------------------- *
|
549 |
* Phase 2 (spawn the phase 3 and clean up)
|
550 |
* --------------------------------------------------------------------- */
|
551 |
|
552 |
/**
|
553 |
* Invoke phase 2, which starts phase 3, then cleans up the sandbox. This is
|
554 |
* needed because GUI's often call the exit() method to kill the AWT thread,
|
555 |
* and early versions of java did not have exit hooks. In order to delete the
|
556 |
* sandbox on exit we invoke method in separate process and wait for that
|
557 |
* process to complete. Even worse, resources in the jar may be locked by
|
558 |
* the target process, which would prevent the sandbox from being deleted as
|
559 |
* well.
|
560 |
*/
|
561 |
private void invoke2(String[] args) |
562 |
{ |
563 |
|
564 |
int retVal = -1; |
565 |
try
|
566 |
{ |
567 |
// TODO: in jre 1.2, Phs1 consistently needs more time to unlock the
|
568 |
// original jar. Phs2 should wait to invoke Phs3 until it knows its
|
569 |
// parent (Phs1) has died, but Process.waitFor() only works on
|
570 |
// children. Can we see when a parent dies, or /this/ Process becomes
|
571 |
// orphaned?
|
572 |
try
|
573 |
{ |
574 |
Thread.sleep(1000); |
575 |
} catch (Exception x) |
576 |
{ |
577 |
} |
578 |
|
579 |
// spawn phase 3, capture its stdio and wait for it to exit
|
580 |
Process p = spawn(args, 3); |
581 |
|
582 |
new StreamProxy(p.getErrorStream(), "err", log).start(); |
583 |
new StreamProxy(p.getInputStream(), "out", log).start(); |
584 |
p.getOutputStream().close(); |
585 |
|
586 |
try
|
587 |
{ |
588 |
retVal = p.waitFor(); |
589 |
} catch (InterruptedException e) |
590 |
{ |
591 |
log(e); |
592 |
} |
593 |
|
594 |
// clean up and go
|
595 |
log("deleteing sandbox");
|
596 |
deleteTree(sandbox); |
597 |
} catch (Exception e) |
598 |
{ |
599 |
log(e); |
600 |
} |
601 |
log("Phase 3 return value = " + retVal);
|
602 |
} |
603 |
|
604 |
/** Recursively delete a file structure. */
|
605 |
public static boolean deleteTree(File file) |
606 |
{ |
607 |
if (file.isDirectory())
|
608 |
{ |
609 |
File[] files = file.listFiles(); |
610 |
for (int i = 0; i < files.length; i++) |
611 |
deleteTree(files[i]); |
612 |
} |
613 |
return file.delete();
|
614 |
} |
615 |
|
616 |
/* --------------------------------------------------------------------- *
|
617 |
* Phase 3 (invoke method, let it go as long as it likes)
|
618 |
* --------------------------------------------------------------------- */
|
619 |
|
620 |
/**
|
621 |
* Invoke the target method and let it run free!
|
622 |
*/
|
623 |
private void invoke3(String[] args) |
624 |
{ |
625 |
// std io is being redirected to the log
|
626 |
try
|
627 |
{ |
628 |
errlog( |
629 |
"Invoking method: "
|
630 |
+ method.getDeclaringClass().getName() |
631 |
+ "."
|
632 |
+ method.getName() |
633 |
+ "(String[] args)");
|
634 |
|
635 |
method.invoke(null, new Object[] { args }); |
636 |
} catch (Throwable t) |
637 |
{ |
638 |
errlog(t.getMessage()); |
639 |
t.printStackTrace(); |
640 |
errlog("exiting");
|
641 |
System.err.flush();
|
642 |
System.exit(31); |
643 |
} |
644 |
|
645 |
errlog("Method returned, waiting for other threads");
|
646 |
System.err.flush();
|
647 |
// now let the method call exit...
|
648 |
} |
649 |
|
650 |
/* --------------------------------------------------------------------- *
|
651 |
* Logging
|
652 |
* --------------------------------------------------------------------- */
|
653 |
|
654 |
PrintStream log = null; |
655 |
|
656 |
private void errlog(String msg) |
657 |
{ |
658 |
date.setTime(System.currentTimeMillis());
|
659 |
System.err.println(isoPoint.format(date) + " Phase " + phase + ": " + msg); |
660 |
} |
661 |
private PrintStream checkLog() |
662 |
{ |
663 |
try
|
664 |
{ |
665 |
if (log == null) |
666 |
log = new PrintStream(new FileOutputStream(logFile.toString(), true)); |
667 |
} catch (IOException x) |
668 |
{ |
669 |
System.err.println("Phase " + phase + " log err: " + x.getMessage()); |
670 |
x.printStackTrace(); |
671 |
} |
672 |
date.setTime(System.currentTimeMillis());
|
673 |
return log;
|
674 |
} |
675 |
private void log(Throwable t) |
676 |
{ |
677 |
if (checkLog() != null) |
678 |
{ |
679 |
log.println( |
680 |
isoPoint.format(date) + " Phase " + phase + ": " + t.getMessage()); |
681 |
t.printStackTrace(log); |
682 |
} |
683 |
} |
684 |
private void log(String msg) |
685 |
{ |
686 |
if (checkLog() != null) |
687 |
log.println(isoPoint.format(date) + " Phase " + phase + ": " + msg); |
688 |
} |
689 |
|
690 |
public static class StreamProxy extends Thread |
691 |
{ |
692 |
InputStream in;
|
693 |
String name;
|
694 |
OutputStream out;
|
695 |
|
696 |
public StreamProxy(InputStream in, String name) |
697 |
{ |
698 |
this(in, name, null); |
699 |
} |
700 |
public StreamProxy(InputStream in, String name, OutputStream out) |
701 |
{ |
702 |
this.in = in;
|
703 |
this.name = name;
|
704 |
this.out = out;
|
705 |
} |
706 |
public void run() |
707 |
{ |
708 |
try
|
709 |
{ |
710 |
PrintWriter pw = null; |
711 |
if (out != null) |
712 |
pw = new PrintWriter(out); |
713 |
|
714 |
BufferedReader br = new BufferedReader(new InputStreamReader(in)); |
715 |
String line;
|
716 |
while ((line = br.readLine()) != null) |
717 |
{ |
718 |
if (pw != null) |
719 |
pw.println(line); |
720 |
//System.out.println(name + ">" + line);
|
721 |
} |
722 |
if (pw != null) |
723 |
pw.flush(); |
724 |
} catch (IOException ioe) |
725 |
{ |
726 |
ioe.printStackTrace(); |
727 |
} |
728 |
} |
729 |
} |
730 |
|
731 |
/* --------------------------------------------------------------------- *
|
732 |
* Apache ant code
|
733 |
* --------------------------------------------------------------------- */
|
734 |
// TODO: comply with licensing issues
|
735 |
|
736 |
// This was stolen (and specialized from much more modular code) from the
|
737 |
// jakarta ant class org.apache.tools.ant.taskdefs.condition.Os
|
738 |
// See the javaCommand() method.
|
739 |
private static final float JAVA_SPECIFICATION_VERSION = |
740 |
Float.parseFloat(System.getProperty("java.specification.version")); |
741 |
private static final String JAVA_HOME = System.getProperty("java.home"); |
742 |
|
743 |
/**
|
744 |
* Constructs a file path from a <code>file:</code> URI.
|
745 |
*
|
746 |
* <p>Will be an absolute path if the given URI is absolute.</p>
|
747 |
*
|
748 |
* <p>Swallows '%' that are not followed by two characters,
|
749 |
* doesn't deal with non-ASCII characters.</p>
|
750 |
*
|
751 |
* @param uri the URI designating a file in the local filesystem.
|
752 |
* @return the local file system path for the file.
|
753 |
*/
|
754 |
public static String fromURI(String uri) |
755 |
{ |
756 |
if (!uri.startsWith("file:")) |
757 |
throw new IllegalArgumentException("Can only handle file: URIs"); |
758 |
|
759 |
if (uri.startsWith("file://")) |
760 |
uri = uri.substring(7);
|
761 |
else
|
762 |
uri = uri.substring(5);
|
763 |
|
764 |
uri = uri.replace('/', File.separatorChar); |
765 |
if (File.pathSeparatorChar == ';' |
766 |
&& uri.startsWith("\\")
|
767 |
&& uri.length() > 2
|
768 |
&& Character.isLetter(uri.charAt(1)) |
769 |
&& uri.lastIndexOf(':') > -1) |
770 |
{ |
771 |
uri = uri.substring(1);
|
772 |
} |
773 |
|
774 |
StringBuffer sb = new StringBuffer(); |
775 |
CharacterIterator iter = new StringCharacterIterator(uri); |
776 |
for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) |
777 |
{ |
778 |
if (c == '%') |
779 |
{ |
780 |
char c1 = iter.next();
|
781 |
if (c1 != CharacterIterator.DONE) |
782 |
{ |
783 |
int i1 = Character.digit(c1, 16); |
784 |
char c2 = iter.next();
|
785 |
if (c2 != CharacterIterator.DONE) |
786 |
{ |
787 |
int i2 = Character.digit(c2, 16); |
788 |
sb.append((char) ((i1 << 4) + i2)); |
789 |
} |
790 |
} |
791 |
} else
|
792 |
{ |
793 |
sb.append(c); |
794 |
} |
795 |
} |
796 |
|
797 |
String path = sb.toString();
|
798 |
return path;
|
799 |
} |
800 |
|
801 |
private static String addExtension(String command) |
802 |
{ |
803 |
// This is the most common extension case - exe for windows and OS/2,
|
804 |
// nothing for *nix.
|
805 |
return command + (OsVersion.IS_WINDOWS || OsVersion.IS_OS2 ? ".exe" : ""); |
806 |
} |
807 |
|
808 |
private static String javaCommand() |
809 |
{ |
810 |
// This was stolen (and specialized from much more modular code) from the
|
811 |
// jakarta ant classes Os & JavaEnvUtils. Also see the following
|
812 |
// org.apache.tools.ant.taskdefs.Java
|
813 |
// org.apache.tools.ant.taskdefs.Execute
|
814 |
// org.apache.tools.ant.taskdefs.condition.Os
|
815 |
// org.apache.tools.ant.util.CommandlineJava
|
816 |
// org.apache.tools.ant.util.JavaEnvUtils
|
817 |
// org.apache.tools.ant.util.FileUtils
|
818 |
// TODO: I didn't copy nearly all of their conditions
|
819 |
String executable = addExtension("java"); |
820 |
String dir = new File(JAVA_HOME + "/bin").getAbsolutePath(); |
821 |
File jExecutable = new File(dir, executable); |
822 |
|
823 |
// Unfortunately on Windows java.home doesn't always refer
|
824 |
// to the correct location, so we need to fall back to
|
825 |
// assuming java is somewhere on the PATH.
|
826 |
if (!jExecutable.exists())
|
827 |
return executable;
|
828 |
return jExecutable.getAbsolutePath();
|
829 |
} |
830 |
} |