001: /*
002: * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
003: *
004: * http://izpack.org/
005: * http://izpack.codehaus.org/
006: *
007: * Copyright 2004 Chadwick McHenry
008: *
009: * Licensed under the Apache License, Version 2.0 (the "License");
010: * you may not use this file except in compliance with the License.
011: * You may obtain a copy of the License at
012: *
013: * http://www.apache.org/licenses/LICENSE-2.0
014: *
015: * Unless required by applicable law or agreed to in writing, software
016: * distributed under the License is distributed on an "AS IS" BASIS,
017: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018: * See the License for the specific language governing permissions and
019: * limitations under the License.
020: */
021:
022: package com.izforge.izpack.uninstaller;
023:
024: import java.io.BufferedOutputStream;
025: import java.io.BufferedReader;
026: import java.io.File;
027: import java.io.FileOutputStream;
028: import java.io.IOException;
029: import java.io.InputStream;
030: import java.io.InputStreamReader;
031: import java.io.OutputStream;
032: import java.io.PrintStream;
033: import java.io.PrintWriter;
034: import java.io.RandomAccessFile;
035: import java.lang.reflect.Method;
036: import java.lang.reflect.Modifier;
037: import java.net.URI;
038: import java.net.URL;
039: import java.text.CharacterIterator;
040: import java.text.SimpleDateFormat;
041: import java.text.StringCharacterIterator;
042: import java.util.Date;
043: import java.util.Enumeration;
044: import java.util.jar.JarFile;
045: import java.util.jar.JarEntry;
046: import java.util.zip.ZipEntry;
047:
048: import com.izforge.izpack.util.OsVersion;
049: import com.izforge.izpack.installer.MultiVolumeInstaller;
050:
051: /**
052: * Allows an application to modify the jar file from which it came, including outright deletion. The
053: * jar file of an app is usually locked when java is run so this is normally not possible.
054: * <p>
055: *
056: * Create a SelfModifier with a target method, then invoke the SelfModifier with arguments to be
057: * passed to the target method. The jar file containing the target method's class (obtained by
058: * reflection) will be extracted to a temporary directory, and a new java process will be spawned to
059: * invoke the target method. The original jar file may now be modified.
060: * <p>
061: *
062: * If the constructor or invoke() methods fail, it is generally because secondary java processes
063: * could not be started.
064: *
065: * <b>Requirements</b>
066: * <ul>
067: * <li>The target method, and all it's required classes must be in a jar file.
068: * <li>The Self Modifier, and its inner classes must also be in the jar file.
069: * </ul>
070: *
071: * There are three system processes (or "phases") involved, the first invoked by the user, the
072: * second and third by the SelfModifier.
073: * <p>
074: *
075: * <b>Phase 1:</b>
076: * <ol>
077: * <li>Program is launched, SelfModifier is created, invoke(String[]) is called
078: * <li>A temporary directory (or "sandbox") is created in the default temp directory, and the jar
079: * file contents ar extracted into it
080: * <li>Phase 2 is spawned using the sandbox as it's classpath, SelfModifier as the main class, the
081: * arguments to "invoke(String[])" as the main arguments, and the <a
082: * href="#selfmodsysprops">SelfModifier system properties</a> set.
083: * <li>Immidiately exit so the system unlocks the jar file
084: * </ol>
085: *
086: * <b>Phase 2:</b>
087: * <ol>
088: * <li>Initializes from system properties.
089: * <li>Spawn phase 3 exactly as phase 2 except the self.modifier.phase system properties set to 3.
090: * <li>Wait for phase 3 to die
091: * <li>Delete the temporary sandbox
092: * </ol>
093: *
094: * <b>Phase 3:</b>
095: * <ol>
096: * <li>Initializes from system properties.
097: * <li>Redirect std err stream to the log
098: * <li>Invoke the target method with arguments we were given
099: * <li>The target method is expected to call exit(), or to not start any looping threads (e.g. AWT
100: * thread). In other words, the target is the new "main" method.
101: * </ol>
102: *
103: * <a name="selfmodsysprops"><b>SelfModifier system properties</b></a> used to pass information
104: * between processes. <table border="1">
105: * <tr>
106: * <th>Constant
107: * <th>System property
108: * <th>description</tr>
109: * <tr>
110: * <td><a href="#BASE_KEY">BASE_KEY</a>
111: * <td>self.mod.jar
112: * <td>base path to log file and sandbox dir</tr>
113: * <tr>
114: * <td><a href="#JAR_KEY">JAR_KEY</a>
115: * <td>self.mod.class
116: * <td>path to original jar file</tr>
117: * <tr>
118: * <td><a href="#CLASS_KEY">CLASS_KEY</a>
119: * <td>self.mod.method
120: * <td>class of target method</tr>
121: * <tr>
122: * <td><a href="#METHOD_KEY">METHOD_KEY</a>
123: * <td>self.mod.phase
124: * <td>name of method to be invoked in sandbox</tr>
125: * <tr>
126: * <td><a href="#PHASE_KEY">PHASE_KEY</a>
127: * <td>self.mod.base
128: * <td>phase of operation to run</tr>
129: * </table>
130: *
131: * @author Chadwick McHenry
132: * @version 1.0
133: */
134: public class SelfModifier {
135:
136: /** System property name of base for log and sandbox of secondary processes. */
137: public static final String BASE_KEY = "self.mod.base";
138:
139: /** System property name of original jar file containing application. */
140: public static final String JAR_KEY = "self.mod.jar";
141:
142: /** System property name of class declaring target method. */
143: public static final String CLASS_KEY = "self.mod.class";
144:
145: /** System property name of target method to invoke in secondary process. */
146: public static final String METHOD_KEY = "self.mod.method";
147:
148: /** System property name of phase (1, 2, or 3) indicator. */
149: public static final String PHASE_KEY = "self.mod.phase";
150:
151: /** Base prefix name for sandbox and log, used only in phase 1. */
152: private String prefix = "izpack";
153:
154: /** Target method to be invoked in sandbox. */
155: private Method method = null;
156:
157: /** Log for phase 2 and 3, because we can't capture the stdio from them. */
158: private File logFile = null;
159:
160: /** Directory which we extract too, invoke from, and finally delete. */
161: private File sandbox = null;
162:
163: /** Original jar file program was launched from. */
164: private File jarFile = null;
165:
166: /** Current phase of execution: 1, 2, or 3. */
167: private int phase = 0;
168:
169: /** For logging time. */
170: private SimpleDateFormat isoPoint = new SimpleDateFormat(
171: "yyyy-MM-dd'T'HH:mm:ss.SSS");
172:
173: private Date date = new Date();
174:
175: public static void test(String[] args) {
176: // open a File for random access in the sandbox, which will cause
177: // deletion
178: // of the file and its parent directories to fail until it is closed (by
179: // virtue of this java process halting)
180: try {
181: File sandbox = new File(System.getProperty(BASE_KEY) + ".d");
182: File randFile = new File(sandbox, "RandomAccess.tmp");
183: RandomAccessFile rand = new RandomAccessFile(randFile, "rw");
184: rand
185: .writeChars("Just a test: The jvm has to close 'cuz I won't!\n");
186:
187: System.err.print("Deleting sandbox: ");
188: deleteTree(sandbox);
189: System.err.println(sandbox.exists() ? "FAILED"
190: : "SUCCEEDED");
191: } catch (Exception x) {
192: System.err.println(x.getMessage());
193: x.printStackTrace();
194: }
195: }
196:
197: public static void main(String[] args) {
198: // phase 1 already set up the sandbox and spawned phase 2.
199: // phase 2 creates the log, spawns phase 3 and waits
200: // phase 3 invokes method and returns. method must kill all it's threads
201:
202: try {
203: // all it's attributes are retrieved from system properties
204: SelfModifier selfModifier = new SelfModifier();
205:
206: // phase 2: invoke a process for phase 3, wait, and clean up
207: if (selfModifier.phase == 2)
208: selfModifier.invoke2(args);
209:
210: // phase 3: invoke method and die
211: else if (selfModifier.phase == 3)
212: selfModifier.invoke3(args);
213: } catch (IOException ioe) {
214: System.err.println("Error invoking a secondary phase");
215: System.err
216: .println("Note that this program is only intended as a secondary process");
217: ioe.printStackTrace();
218: }
219: }
220:
221: /**
222: * Internal constructor where target class and method are obtained from system properties.
223: *
224: * @throws IOException for errors getting to the sandbox.
225: * @throws SecurityException if access to the target method is denied
226: */
227: private SelfModifier() throws IOException {
228: phase = Integer.parseInt(System.getProperty(PHASE_KEY));
229:
230: String cName = System.getProperty(CLASS_KEY);
231: String tName = System.getProperty(METHOD_KEY);
232:
233: jarFile = new File(System.getProperty(JAR_KEY));
234: logFile = new File(System.getProperty(BASE_KEY) + ".log");
235: sandbox = new File(System.getProperty(BASE_KEY) + ".d");
236:
237: // retrieve refrence to target method
238: try {
239: Class clazz = Class.forName(cName);
240: Method method = clazz.getMethod(tName,
241: new Class[] { String[].class });
242:
243: initMethod(method);
244: } catch (ClassNotFoundException x1) {
245: log("No class found for " + cName);
246: } catch (NoSuchMethodException x2) {
247: log("No method " + tName + " found in " + cName);
248: }
249: }
250:
251: /**
252: * Creates a SelfModifier which will invoke the target method in a separate process from which
253: * it may modify it's own jar file.
254: *
255: * The target method must be public, static, and take a single array of strings as its only
256: * parameter. The class which declares the method must also be public. Reflection is used to
257: * ensure this.
258: *
259: * @param method a public, static method that accepts a String array as it's only parameter. Any
260: * return value is ignored.
261: *
262: * @throws NullPointerException if <code>method</code> is null
263: * @throws IllegalArgumentException if <code>method</code> is not public, static, and take a
264: * String array as it's only argument, or of it's declaring class is not public.
265: * @throws IllegalStateException if process was not invoked from a jar file, or an IOExceptioin
266: * occured while accessing it
267: * @throws IOException if java is unable to be executed as a separte process
268: * @throws SecurityException if access to the method, or creation of a subprocess is denied
269: */
270: public SelfModifier(Method method) throws IOException {
271: phase = 1;
272: initJavaExec();
273: initMethod(method);
274: }
275:
276: /**
277: * Check the method for the required properties (public, static, params:(String[])).
278: *
279: * @throws NullPointerException if <code>method</code> is null
280: * @throws IllegalArgumentException if <code>method</code> is not public, static, and take a
281: * String array as it's only argument, or of it's declaring class is not public.
282: * @throws SecurityException if access to the method is denied
283: */
284: private void initMethod(Method method) {
285: int mod = method.getModifiers();
286: if ((mod & Modifier.PUBLIC) == 0
287: || (mod & Modifier.STATIC) == 0)
288: throw new IllegalArgumentException(
289: "Method not public and static");
290:
291: Class[] params = method.getParameterTypes();
292: if (params.length != 1
293: || !params[0].isArray()
294: || !"java.lang.String".equals(params[0]
295: .getComponentType().getName()))
296: throw new IllegalArgumentException(
297: "Method must accept String array");
298:
299: Class clazz = method.getDeclaringClass();
300: mod = clazz.getModifiers();
301: if ((mod & Modifier.PUBLIC) == 0
302: || (mod & Modifier.INTERFACE) != 0)
303: throw new IllegalArgumentException(
304: "Method must be in a public class");
305:
306: this .method = method;
307: }
308:
309: /**
310: * This call ensures that java can be exec'd in a separate process.
311: *
312: * @throws IOException if an I/O error occurs, indicating java is unable to be exec'd
313: * @throws SecurityException if a security manager exists and doesn't allow creation of a
314: * subprocess
315: */
316: private void initJavaExec() throws IOException {
317: try {
318: Process p = Runtime.getRuntime().exec(javaCommand());
319:
320: new StreamProxy(p.getErrorStream(), "err").start();
321: new StreamProxy(p.getInputStream(), "out").start();
322: p.getOutputStream().close();
323:
324: // even if it returns an error code, it was at least found
325: p.waitFor();
326: } catch (InterruptedException ie) {
327: throw new IOException("Unable to create a java subprocess");
328: }
329: }
330:
331: /***********************************************************************************************
332: * --------------------------------------------------------------------- Phase 1 (call from
333: * external spawn phase 2) ---------------------------------------------------------------------
334: */
335:
336: /**
337: * Invoke the target method in a separate process from which it may modify it's own jar file.
338: * This method does not normally return. After spawning the secondary process, the current
339: * process must die before the jar file is unlocked, therefore calling this method is akin to
340: * calling {@link System#exit(int)}.
341: * <p>
342: *
343: * The contents of the current jar file are extracted copied to a 'sandbox' directory from which
344: * the method is invoked. The path to the original jar file is placed in the system property
345: * {@link #JAR_KEY}.
346: * <p>
347: *
348: * @param args arguments to pass to the target method. May be empty or null to indicate no
349: * arguments.
350: *
351: * @throws IOException for lots of things
352: * @throws IllegalStateException if method's class was not loaded from a jar
353: */
354: public void invoke(String[] args) throws IOException {
355: // Initialize sandbox and log file to be unique, but similarly named
356: while (true) {
357: logFile = File.createTempFile(prefix, ".log");
358: String f = logFile.toString();
359: sandbox = new File(f.substring(0, f.length() - 4) + ".d");
360:
361: // check if the similarly named directory is free
362: if (!sandbox.exists())
363: break;
364:
365: logFile.delete();
366: }
367: if (!sandbox.mkdir())
368: throw new RuntimeException("Failed to create temp dir: "
369: + sandbox);
370:
371: sandbox = sandbox.getCanonicalFile();
372: logFile = logFile.getCanonicalFile();
373:
374: jarFile = findJarFile(
375: (Class<MultiVolumeInstaller>) method
376: .getDeclaringClass()).getCanonicalFile();
377: if (jarFile == null)
378: throw new IllegalStateException(
379: "SelfModifier must be in a jar file");
380: log("JarFile: " + jarFile);
381:
382: extractJarFile();
383:
384: if (args == null)
385: args = new String[0];
386: spawn(args, 2);
387:
388: // finally, if all went well, the invoking process must exit
389: log("Exit");
390: System.exit(0);
391: }
392:
393: /**
394: * Run a new jvm with all the system parameters needed for phases 2 and 3.
395: *
396: * @throws IOException if there is an error getting the cononical name of a path
397: */
398: private Process spawn(String[] args, int nextPhase)
399: throws IOException {
400: String base = logFile.getAbsolutePath();
401: base = base.substring(0, base.length() - 4);
402:
403: // invoke from tmpdir, passing target method arguments as args, and
404: // SelfModifier parameters as sustem properties
405: String[] javaCmd = new String[] {
406: javaCommand(),
407: "-classpath",
408: sandbox.getAbsolutePath(),
409: "-D" + BASE_KEY + "=" + base,
410: "-D" + JAR_KEY + "=" + jarFile.getPath(),
411: "-D" + CLASS_KEY + "="
412: + method.getDeclaringClass().getName(),
413: "-D" + METHOD_KEY + "=" + method.getName(),
414: "-D" + PHASE_KEY + "=" + nextPhase,
415: getClass().getName() };
416:
417: String[] entireCmd = new String[javaCmd.length + args.length];
418: System.arraycopy(javaCmd, 0, entireCmd, 0, javaCmd.length);
419: System.arraycopy(args, 0, entireCmd, javaCmd.length,
420: args.length);
421:
422: StringBuffer sb = new StringBuffer("Spawning phase ");
423: sb.append(nextPhase).append(": ");
424: for (String anEntireCmd : entireCmd) {
425: sb.append("\n\t").append(anEntireCmd);
426: }
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<MultiVolumeInstaller> clazz) {
444: String resource = clazz.getName().replace('.', '/') + ".class";
445:
446: URL url = ClassLoader.getSystemResource(resource);
447: if (!"jar".equals(url.getProtocol()))
448: return null;
449:
450: String path = url.getFile();
451: // starts at "file:..." (use getPath() as of 1.3)
452: path = path.substring(0, path.lastIndexOf('!'));
453:
454: File file;
455:
456: // getSystemResource() returns a valid URL (eg. spaces are %20), but a
457: // file
458: // Constructed w/ it will expect "%20" in path. URI and File(URI)
459: // properly
460: // deal with escaping back and forth, but didn't exist until 1.4
461: if (JAVA_SPECIFICATION_VERSION < 1.4)
462: file = new File(fromURI(path));
463: else
464: file = new File(URI.create(path));
465:
466: return file;
467: }
468:
469: /**
470: * @throws IOException
471: */
472: private void extractJarFile() throws IOException {
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: Enumeration<JarEntry> entries = jar.entries();
483: while (entries.hasMoreElements()) {
484: ZipEntry entry = entries.nextElement();
485: if (entry.isDirectory())
486: continue;
487:
488: String pathname = entry.getName();
489: if (MANIFEST.equals(pathname.toUpperCase()))
490: continue;
491:
492: in = jar.getInputStream(entry);
493:
494: File outFile = new File(sandbox, pathname);
495: File parent = outFile.getParentFile();
496: if (parent != null && !parent.exists())
497: parent.mkdirs();
498:
499: out = new BufferedOutputStream(new FileOutputStream(
500: outFile));
501:
502: int n;
503: while ((n = in.read(buf, 0, buf.length)) > 0)
504: out.write(buf, 0, n);
505:
506: out.close();
507: extracted++;
508: }
509: jar.close();
510:
511: log("Extracted " + extracted + " file"
512: + (extracted > 1 ? "s" : "") + " into "
513: + sandbox.getPath());
514: } finally {
515: try {
516: jar.close();
517: } catch (IOException ioe) {
518: }
519: if (out != null) {
520: try {
521: out.close();
522: } catch (IOException ioe) {
523: }
524: }
525: if (in != null) {
526: try {
527: in.close();
528: } catch (IOException ioe) {
529: }
530: }
531: }
532: }
533:
534: /***********************************************************************************************
535: * --------------------------------------------------------------------- Phase 2 (spawn the
536: * phase 3 and clean up) ---------------------------------------------------------------------
537: */
538:
539: /**
540: * Invoke phase 2, which starts phase 3, then cleans up the sandbox. This is needed because
541: * GUI's often call the exit() method to kill the AWT thread, and early versions of java did not
542: * have exit hooks. In order to delete the sandbox on exit we invoke method in separate process
543: * and wait for that process to complete. Even worse, resources in the jar may be locked by the
544: * target process, which would prevent the sandbox from being deleted as well.
545: */
546: private void invoke2(String[] args) {
547:
548: int retVal = -1;
549: try {
550: // TODO: in jre 1.2, Phs1 consistently needs more time to unlock the
551: // original jar. Phs2 should wait to invoke Phs3 until it knows its
552: // parent (Phs1) has died, but Process.waitFor() only works on
553: // children. Can we see when a parent dies, or /this/ Process
554: // becomes
555: // orphaned?
556: try {
557: Thread.sleep(1000);
558: } catch (Exception x) {
559: }
560:
561: // spawn phase 3, capture its stdio and wait for it to exit
562: Process p = spawn(args, 3);
563:
564: new StreamProxy(p.getErrorStream(), "err", log).start();
565: new StreamProxy(p.getInputStream(), "out", log).start();
566: p.getOutputStream().close();
567:
568: try {
569: retVal = p.waitFor();
570: } catch (InterruptedException e) {
571: log(e);
572: }
573:
574: // clean up and go
575: log("deleteing sandbox");
576: deleteTree(sandbox);
577: } catch (Exception e) {
578: log(e);
579: }
580: log("Phase 3 return value = " + retVal);
581: }
582:
583: /** Recursively delete a file structure. */
584: public static boolean deleteTree(File file) {
585: if (file.isDirectory()) {
586: File[] files = file.listFiles();
587: for (File file1 : files) {
588: deleteTree(file1);
589: }
590: }
591: return file.delete();
592: }
593:
594: /***********************************************************************************************
595: * --------------------------------------------------------------------- Phase 3 (invoke method,
596: * let it go as long as it likes)
597: * ---------------------------------------------------------------------
598: */
599:
600: /**
601: * Invoke the target method and let it run free!
602: */
603: private void invoke3(String[] args) {
604: // std io is being redirected to the log
605: try {
606: errlog("Invoking method: "
607: + method.getDeclaringClass().getName() + "."
608: + method.getName() + "(String[] args)");
609:
610: method.invoke(null, new Object[] { args });
611: } catch (Throwable t) {
612: errlog(t.getMessage());
613: t.printStackTrace();
614: errlog("exiting");
615: System.err.flush();
616: System.exit(31);
617: }
618:
619: errlog("Method returned, waiting for other threads");
620: System.err.flush();
621: // now let the method call exit...
622: }
623:
624: /***********************************************************************************************
625: * --------------------------------------------------------------------- Logging
626: * ---------------------------------------------------------------------
627: */
628:
629: PrintStream log = null;
630:
631: private void errlog(String msg) {
632: date.setTime(System.currentTimeMillis());
633: System.err.println(isoPoint.format(date) + " Phase " + phase
634: + ": " + msg);
635: }
636:
637: private PrintStream checkLog() {
638: try {
639: if (log == null)
640: log = new PrintStream(new FileOutputStream(logFile
641: .toString(), true));
642: } catch (IOException x) {
643: System.err.println("Phase " + phase + " log err: "
644: + x.getMessage());
645: x.printStackTrace();
646: }
647: date.setTime(System.currentTimeMillis());
648: return log;
649: }
650:
651: private void log(Throwable t) {
652: if (checkLog() != null) {
653: log.println(isoPoint.format(date) + " Phase " + phase
654: + ": " + t.getMessage());
655: t.printStackTrace(log);
656: }
657: }
658:
659: private void log(String msg) {
660: if (checkLog() != null)
661: log.println(isoPoint.format(date) + " Phase " + phase
662: + ": " + msg);
663: }
664:
665: public static class StreamProxy extends Thread {
666:
667: InputStream in;
668:
669: String name;
670:
671: OutputStream out;
672:
673: public StreamProxy(InputStream in, String name) {
674: this (in, name, null);
675: }
676:
677: public StreamProxy(InputStream in, String name, OutputStream out) {
678: this .in = in;
679: this .name = name;
680: this .out = out;
681: }
682:
683: public void run() {
684: try {
685: PrintWriter pw = null;
686: if (out != null)
687: pw = new PrintWriter(out);
688:
689: BufferedReader br = new BufferedReader(
690: new InputStreamReader(in));
691: String line;
692: while ((line = br.readLine()) != null) {
693: if (pw != null)
694: pw.println(line);
695: // System.out.println(name + ">" + line);
696: }
697: if (pw != null)
698: pw.flush();
699: } catch (IOException ioe) {
700: ioe.printStackTrace();
701: }
702: }
703: }
704:
705: /***********************************************************************************************
706: * --------------------------------------------------------------------- Apache ant code
707: * ---------------------------------------------------------------------
708: */
709: // This was stolen (and specialized from much more modular code) from the
710: // jakarta ant class org.apache.tools.ant.taskdefs.condition.Os
711: // See the javaCommand() method.
712: private static final float JAVA_SPECIFICATION_VERSION = Float
713: .parseFloat(System
714: .getProperty("java.specification.version"));
715:
716: private static final String JAVA_HOME = System
717: .getProperty("java.home");
718:
719: /**
720: * Constructs a file path from a <code>file:</code> URI.
721: *
722: * <p>
723: * Will be an absolute path if the given URI is absolute.
724: * </p>
725: *
726: * <p>
727: * Swallows '%' that are not followed by two characters, doesn't deal with non-ASCII characters.
728: * </p>
729: *
730: * @param uri the URI designating a file in the local filesystem.
731: * @return the local file system path for the file.
732: */
733: public static String fromURI(String uri) {
734: if (!uri.startsWith("file:"))
735: throw new IllegalArgumentException(
736: "Can only handle file: URIs");
737:
738: if (uri.startsWith("file://"))
739: uri = uri.substring(7);
740: else
741: uri = uri.substring(5);
742:
743: uri = uri.replace('/', File.separatorChar);
744: if (File.pathSeparatorChar == ';' && uri.startsWith("\\")
745: && uri.length() > 2
746: && Character.isLetter(uri.charAt(1))
747: && uri.lastIndexOf(':') > -1) {
748: uri = uri.substring(1);
749: }
750:
751: StringBuffer sb = new StringBuffer();
752: CharacterIterator iter = new StringCharacterIterator(uri);
753: for (char c = iter.first(); c != CharacterIterator.DONE; c = iter
754: .next()) {
755: if (c == '%') {
756: char c1 = iter.next();
757: if (c1 != CharacterIterator.DONE) {
758: int i1 = Character.digit(c1, 16);
759: char c2 = iter.next();
760: if (c2 != CharacterIterator.DONE) {
761: int i2 = Character.digit(c2, 16);
762: sb.append((char) ((i1 << 4) + i2));
763: }
764: }
765: } else {
766: sb.append(c);
767: }
768: }
769:
770: String path = sb.toString();
771: return path;
772: }
773:
774: private static String addExtension(String command) {
775: // This is the most common extension case - exe for windows and OS/2,
776: // nothing for *nix.
777: return command
778: + (OsVersion.IS_WINDOWS || OsVersion.IS_OS2 ? ".exe"
779: : "");
780: }
781:
782: private static String javaCommand() {
783: // This was stolen (and specialized from much more modular code) from
784: // the
785: // jakarta ant classes Os & JavaEnvUtils. Also see the following
786: // org.apache.tools.ant.taskdefs.Java
787: // org.apache.tools.ant.taskdefs.Execute
788: // org.apache.tools.ant.taskdefs.condition.Os
789: // org.apache.tools.ant.util.CommandlineJava
790: // org.apache.tools.ant.util.JavaEnvUtils
791: // org.apache.tools.ant.util.FileUtils
792: // TODO: I didn't copy nearly all of their conditions
793: String executable = addExtension("java");
794: String dir = new File(JAVA_HOME + "/bin").getAbsolutePath();
795: File jExecutable = new File(dir, executable);
796:
797: // Unfortunately on Windows java.home doesn't always refer
798: // to the correct location, so we need to fall back to
799: // assuming java is somewhere on the PATH.
800: if (!jExecutable.exists())
801: return executable;
802: return jExecutable.getAbsolutePath();
803: }
804: }
|