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

Last change on this file since 15144 was 15144, checked in by oranfry, 16 years ago

use the current directory to work in, not the system temp directory

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