source: release-kits/shared/ant-installer/src/org/tp23/antinstaller/selfextract/SelfExtractor.java@ 17897

Last change on this file since 17897 was 17897, checked in by oranfry, 15 years ago

a patched ant-installer which can handle being run in paths with foreign characters

File size: 14.2 KB
Line 
1/*
2 * Copyright 2005 Paul Hinds
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.tp23.antinstaller.selfextract;
17
18import java.awt.HeadlessException;
19import java.io.BufferedOutputStream;
20import java.io.File;
21import java.io.FileFilter;
22import java.io.FileInputStream;
23import java.io.FileNotFoundException;
24import java.io.FileOutputStream;
25import java.io.IOException;
26import java.net.URL;
27import java.util.ArrayList;
28import java.util.jar.JarEntry;
29import java.util.jar.JarFile;
30import java.util.jar.JarInputStream;
31
32import javax.swing.JOptionPane;
33import javax.swing.UIManager;
34
35import org.tp23.antinstaller.InstallException;
36import org.tp23.antinstaller.renderer.swing.plaf.LookAndFeelFactory;
37import org.tp23.antinstaller.runtime.ExecInstall;
38import org.tp23.antinstaller.runtime.exe.FilterChain;
39import org.tp23.antinstaller.runtime.exe.FilterFactory;
40
41/**
42 *
43 * <p>Finds a file reference to the Jar that loads this class and then extracts that Jar
44 * to a temporary directory </p>
45 * <p> </p>
46 * @author Paul Hinds
47 * @version $Id: SelfExtractor.java,v 1.10 2007/01/28 08:44:40 teknopaul Exp $
48 */
49public class SelfExtractor {
50
51 public static final String CONFIG_RESOURCE = "/org/tp23/antinstaller/runtime/exe/selfextractor.fconfig";
52
53 private File extractDir;
54 private File archiveFile;
55 private boolean overwrite = true;
56
57 private static int DEFAULT_BUFFER_SIZE = 1024;
58 private int BUFFER_SIZE = DEFAULT_BUFFER_SIZE;
59 private static boolean graphicsEnv = false;
60 private static String lookAndFeel = null;
61
62 /**
63 * returns the Jar that the reference object was loaded from. If it was not
64 * loaded from a jar this methods behaviour is undefined
65 * @TODO define what happens
66 * @param reference
67 * @return A java.io.File reference to the Jar
68 */
69 public static File getEnclosingJar(Object reference) {
70 String thisClass = "/" + reference.getClass().getName().replace('.','/') + ".class";
71 URL jarUrl = reference.getClass().getResource(thisClass);
72 String stringForm = jarUrl.toString();
73 //String fileForm = jarUrl.getFile();
74
75
76/*
77 File file = new File( fileForm );
78 if ( !file.exists() ) {
79 throw new RuntimeException( "Failed expanding Jar.\n File: '" + file.getAbsolutePath() + "'\nString: '" + stringForm + "'" );
80 }
81 return file;
82*/
83
84 File file = null;
85 int endIdx = stringForm.indexOf("!/");
86 if( endIdx == -1 ) {
87 throw new RuntimeException("Failed expanding Jar.");
88 }
89 String unescaped = null;
90 String fileNamePart = stringForm.substring("jar:file:".length(), endIdx);
91 file = new File(fileNamePart);
92 file = new File( file.getName() );
93
94/*
95 if ( !file.exists()) {
96 // try to unescape encase the URL Handler has escaped the " " to %20
97 unescaped = unescape(fileNamePart);
98 file = new File(unescaped);
99 }
100*/
101 return file;
102
103 }
104
105 /**
106 * Constructor for the SelfExtractor object. Directly after constructing
107 * an instance the init() method should be called unless subclassing
108 */
109 public SelfExtractor() {
110 }
111
112 /**
113 * This has been moved from the default constructor to facilitate subclassing
114 * @return true if the lookAndFeel worked
115 */
116 public void init(){
117 System.out.println("Loading self extractor...");
118 archiveFile = getEnclosingJar(this);
119 makeTempDir();
120 try {
121 JarFile thisJar = new JarFile(archiveFile);
122 lookAndFeel = thisJar.getManifest().getMainAttributes().getValue("Look-And-Feel");
123 lookAndFeel = LookAndFeelFactory.getLafFromToken(lookAndFeel);
124 if(lookAndFeel != null) {
125 UIManager.setLookAndFeel(lookAndFeel);
126 }
127 }
128 catch (Throwable ex) {
129 // not concerned about Look and Feel
130 }
131 }
132
133 /**
134 * Creates a new empty temporary directory for the file extraction
135 * @return
136 */
137 protected File makeTempDir(){
138 // String tempDir = System.getProperty("java.io.tmpdir");
139 File curDir = new File("t.tmp").getParentFile();
140 extractDir = new File(curDir, "antinstall");
141 for ( int i=0; extractDir.exists(); i++) {
142 extractDir = new File(curDir, "antinstall" + i);
143 }
144 extractDir.mkdirs();
145 extractDir.deleteOnExit();
146 return extractDir;
147 }
148
149 /**
150 * Constructor for the SelfExtractor object that sets the buffersize in use.
151 * The write buffer is the same size as the write buffer size because the read buffer reads
152 * decompressed bytes
153 * @param newBufferSize the size of the read buffer
154 */
155 public SelfExtractor(int newBufferSize) {
156 BUFFER_SIZE = newBufferSize;
157 archiveFile = getEnclosingJar(this);
158 }
159
160 /**
161 * Sets the Directory into which the file will be extracted
162 *
163 *@param newExtractDir The new extract directory
164 */
165 public void setExtractDir(File newExtractDir) {
166 extractDir = newExtractDir;
167 }
168
169 /**
170 * changes the archive to be extracted
171 *@param newArchiveFile The new archiveFile value
172 */
173 public void setArchiveFile(File newArchiveFile) {
174 archiveFile = newArchiveFile;
175 }
176
177 /**
178 * Gets the Directory into which the files will be extracted that
179 * is currently set in the ZipExtractor object
180 *@return The extract directory value
181 */
182 public File getExtractDir() {
183 return extractDir;
184 }
185
186 /**
187 * Gets the set in the ZipExtractor
188 *@return The archiveFile value
189 */
190 public boolean isOverwrite() {
191 return overwrite;
192 }
193
194 /**
195 * Gets the Directory into which the files will be extracted that
196 * is currently set in the ZipExtractor object
197 *@return The extract directory value
198 */
199 public void setOverwrite(boolean overwrite) {
200 this.overwrite = overwrite;
201 }
202
203 /**
204 * Gets the set in the ZipExtractor
205 *@return The archiveFile value
206 */
207 public File getArchiveFile() {
208 return archiveFile;
209 }
210
211 /**
212 * Opens up the zip and gets a list of the files in it. If the zip file
213 * or the temp file have not been set NullPointerExceptions will get thrown
214 *@param vebose if true Prints out a list of the zips
215 * contents on to the command line
216 *@return an ArrayList of String objects that will
217 * be as per the path in the zip
218 *@exception FileNotFoundException Description of Exception
219 *@exception IOException Description of Exception
220 */
221 public ArrayList getList(boolean vebose) throws FileNotFoundException, IOException {
222 JarInputStream zis = new JarInputStream(new FileInputStream(archiveFile));
223 JarEntry entry = null;
224 ArrayList result = new ArrayList();
225 while ( (entry = zis.getNextJarEntry()) != null) {
226 if (vebose) {
227 System.out.println(entry.getName());
228 }
229 result.add(entry.getName());
230 }
231 return result;
232 }
233
234 /**
235 * @return the number of files in the jar
236 * @throws FileNotFoundException
237 * @throws IOException
238 */
239 public int getFileCount() throws FileNotFoundException, IOException {
240 JarInputStream zis = new JarInputStream(new FileInputStream(archiveFile));
241 int count = 0;
242 while ( zis.getNextJarEntry() != null) {
243 count++;
244 }
245 return count;
246 }
247
248 /**
249 * Opens up the zip and extracts the files to the temp dir.
250 *
251 *@param vebose if true Prints out a list of the zips contents on to System.out
252 *@return an ArrayList of java.io.File objects that
253 * will be as per the path in the zip with the root being the temp dir
254 *@exception FileNotFoundException
255 *@exception IOException
256 */
257 public ArrayList extract(boolean vebose, boolean isX) throws FileNotFoundException, IOException {
258 int fileCount = getFileCount();
259 ProgressIndicator indicator = null;
260 if(isX){
261 try {
262 indicator = new ProgressIndicator(fileCount);
263 indicator.show();
264 }
265 catch ( Exception exc ) {
266 /*
267 * Chances are, there are problems with the graphics environment
268 * so trying falling back to text mode
269 */
270 graphicsEnv = false;
271 isX = false;
272 }
273
274 }
275 JarInputStream zis = new JarInputStream(new FileInputStream(archiveFile));
276 JarEntry entry = null;
277 ArrayList result = new ArrayList();
278 while ( (entry = zis.getNextJarEntry()) != null) {
279 if (vebose) {
280 System.out.println("Extracting:" + entry.getName());
281 }
282 result.add(extract(zis, entry));
283 if (isX) {
284 indicator.tick();
285 }
286 }
287 if (isX) {
288 indicator.hide();
289 }
290 zis.close();
291 return result;
292 }
293
294
295
296 /**
297 * Extract a single file from the stream. N.B. the stream must be in the correct
298 * position for this to work
299 *@param zis ZipInputStream open and ready
300 *@param entry A valid entry read from the stream
301 *@return The inflated file generated in the temp dir
302 *@exception FileNotFoundException
303 *@exception IOException
304 */
305 private File extract(JarInputStream zis, JarEntry entry) throws FileNotFoundException, IOException {
306 createPath(entry.getName());
307 File fileToUse = new File(extractDir, entry.getName());
308 if (fileToUse.exists()) {
309 if (!overwrite) {
310 return fileToUse;
311 }
312 }
313 else {
314 fileToUse.createNewFile();
315 }
316 if (fileToUse.isDirectory()) {
317 return fileToUse;
318 }
319
320 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fileToUse), BUFFER_SIZE);
321 byte[] bytes = new byte[BUFFER_SIZE];
322 int len = 0;
323 while ( (len = zis.read(bytes)) >= 0) {
324 bos.write(bytes, 0, len);
325 }
326 bos.close();
327 zis.closeEntry();
328 return fileToUse;
329 }
330
331 /**
332 * This adds all the necessary directories in the root of the zip path to the
333 * temp dir.
334 *@param entryName The string name in the Zip file (virtual path)
335 *@exception IOException if the directories can not be made
336 */
337 private void createPath(String entryName) throws IOException {
338 int slashIdx = entryName.lastIndexOf('/');
339 if (slashIdx >= 0) {
340 // there is path info
341 String firstPath = entryName.substring(0, slashIdx);
342 File dir = new File(extractDir, firstPath);
343 if (!dir.exists()) {
344 dir.mkdirs();
345 }
346 }
347 }
348
349 /**
350 * Run method to use from the command line. This is fired via an entry in the
351 * MANIFEST.MF in the Jar
352 *@param args The command line arguments
353 */
354 public static void main(String[] args) {
355 testX();
356 // FIXME move after parseArgs() and set graphicsEnv if text selected
357 // will need to test SelfExtractor and comment parseArgs() to ensure
358 // no side effects in the future.
359 SelfExtractor extractor = null;
360 try {
361 boolean verbose = false;
362 extractor = new SelfExtractor();
363 extractor.init();
364 extractor.extract(verbose, graphicsEnv);
365 }
366 catch (Exception e) {
367 e.printStackTrace();
368 String tempDir = "unknown";
369 if(extractor != null){
370 tempDir = extractor.getExtractDir().getAbsolutePath();
371 }
372 String warning = "Could not extract Jar file to directory:" + tempDir;
373 printXorTextWarning(warning);
374 }
375
376 try {
377 FilterChain chain = FilterFactory.factory(CONFIG_RESOURCE);
378 ExecInstall installExec = new ExecInstall(chain);
379 installExec.parseArgs(args, false);
380 installExec.setInstallRoot(extractor.getExtractDir());
381 // removes files on exit
382 installExec.setTempRoot(extractor.getExtractDir());
383
384 installExec.exec();
385 }
386 catch (InstallException e1) {
387 System.out.println("Cant load filter chain:/org/tp23/antinstaller/runtime/exe/selfextractor.fconfig");
388 e1.printStackTrace();
389 }
390 }
391
392 /**
393 * This tests for the existence of a graphics environment and sets an
394 * internal flag so the test does not have to be repeated, it may be expensive.
395 * Prior to running this method the isGraphicsEnv() method will be invalid.
396 */
397 protected static void testX(){
398 try {
399 java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
400 try {
401 boolean headless = java.awt.GraphicsEnvironment.isHeadless();
402 if(headless) {
403 graphicsEnv = false;
404 return;
405 }
406 } catch (Throwable e) {
407 // JDK 1.3 does not have the isHeadless() method but may still work in other situations
408 }
409 graphicsEnv = true;
410 }
411 catch (Throwable e) {
412 // thus graphicsEnv stays false;
413 }
414 }
415
416 /**
417 * @see #testX()
418 * @return true if an X or windows environment is available
419 */
420 protected boolean isGraphicsEnv(){
421 return graphicsEnv;
422 }
423
424 protected static void printXorTextWarning(String warning){
425 if(graphicsEnv){
426 try {
427 JOptionPane.showMessageDialog(null, warning);
428 }
429 catch( HeadlessException headlessExc ) {
430 graphicsEnv = false;
431 System.out.println(warning);
432 }
433 }
434 else {
435 System.out.println(warning);
436 }
437 }
438
439 public static int deleteRecursive(File directory) {
440 int count = 0;
441 File[] files = directory.listFiles(new FileFilter() {
442 public boolean accept(File file) {
443 return!file.isDirectory();
444 }
445 });
446 for (int i = 0; i < files.length; i++) {
447 files[i].delete();
448 count++;
449 }
450 File[] dirs = directory.listFiles(new FileFilter() {
451 public boolean accept(File file) {
452 return file.isDirectory();
453 }
454 });
455 for (int i = 0; i < dirs.length; i++) {
456 count += deleteRecursive(dirs[i]);
457 }
458 directory.delete();
459 return count;
460 }
461
462 /**
463 * UN-URL encode string
464 * TODO should this not support UNICODE escapes
465 */
466 private static String unescape(final String s) {
467 StringBuffer sb = new StringBuffer(s.length());
468
469 for (int i = 0; i < s.length(); i++) {
470 char c = s.charAt(i);
471 switch (c) {
472 case '%': {
473 try {
474 sb.append( (char) Integer.parseInt(s.substring(i + 1, i + 3), 16));
475 i += 2;
476 break;
477 }
478 catch (NumberFormatException nfe) {
479 throw new IllegalArgumentException();
480 }
481 catch (StringIndexOutOfBoundsException siob) {
482 String end = s.substring(i);
483 sb.append(end);
484 if (end.length() == 2) i++;
485 }
486 break;
487 }
488 default: {
489 sb.append(c);
490 break;
491 }
492 }
493 }
494 return sb.toString();
495 }
496
497}
Note: See TracBrowser for help on using the repository browser.