Statistics
| Revision:

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
}