source: main/trunk/release-kits/shared/core/ant-installer/src/org/tp23/antinstaller/selfextract/SelfExtractor.java@ 22945

Last change on this file since 22945 was 22945, checked in by sjm84, 14 years ago

ant-installer will now use the temp directory rather than the current directory when trying to extract files on a Mac

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