KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > izforge > izpack > util > Librarian


1 /*
2  * $Id: Librarian.java 1708 2007-01-13 18:31:26Z jponge $
3  * IzPack - Copyright 2001-2007 Julien Ponge, All Rights Reserved.
4  *
5  * http://www.izforge.com/izpack/
6  * http://developer.berlios.de/projects/izpack/
7  *
8  * Copyright 2002 Elmar Grom
9  *
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  * http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  */

22
23 package com.izforge.izpack.util;
24
25 import java.io.File JavaDoc;
26 import java.io.FileNotFoundException JavaDoc;
27 import java.io.FileOutputStream JavaDoc;
28 import java.io.IOException JavaDoc;
29 import java.io.InputStream JavaDoc;
30 import java.io.OutputStream JavaDoc;
31 import java.net.URL JavaDoc;
32 import java.security.CodeSource JavaDoc;
33 import java.security.ProtectionDomain JavaDoc;
34 import java.text.CharacterIterator JavaDoc;
35 import java.text.StringCharacterIterator JavaDoc;
36 import java.util.Vector JavaDoc;
37
38 /*---------------------------------------------------------------------------*/
39 /**
40  * This class handles loading of native libraries. There must only be one instance of
41  * <code>Librarian</code> per Java runtime, therefore this class is implemented as a 'Singleton'.
42  * <br>
43  * <br>
44  * <code>Librarian</code> is capable of loading native libraries from a variety of different
45  * source locations. However, you should place your library files in the 'native' directory. The
46  * primary reason for supporting different source locations is to facilitate testing in a
47  * development environment, without the need to actually packing the application into a *.jar file.
48  *
49  * @version 1.0 / 1/30/02
50  * @author Elmar Grom
51  */

52 /*---------------------------------------------------------------------------*/
53 public class Librarian implements CleanupClient
54 {
55
56     // ------------------------------------------------------------------------
57
// Constant Definitions
58
// ------------------------------------------------------------------------
59

60     /** Used to identify jar URL protocols */
61     private static final String JavaDoc JAR_PROTOCOL = "jar";
62
63     /** Used to identify file URL protocols */
64     private static final String JavaDoc FILE_PROTOCOL = "file";
65
66     /**
67      * The key used to retrieve the location of temporary files form the system properties.
68      */

69     private static final String JavaDoc TEMP_LOCATION_KEY = "java.io.tmpdir";
70
71     /**
72      * The extension appended to the client name when searching for it as a resource. Since the
73      * client is an object, the extension should always be '.class'
74      */

75     private static final String JavaDoc CLIENT_EXTENSION = ".class";
76
77     /** The default directory for native library files. */
78     private static final String JavaDoc NATIVE = "native";
79
80     /** The block size used for reading and writing data, 4k. */
81     private static final int BLOCK_SIZE = 4096;
82
83     /** VM version needed to select clean up method. */
84     private static final float JAVA_SPECIFICATION_VERSION = Float.parseFloat(System
85             .getProperty("java.specification.version"));
86
87     // ------------------------------------------------------------------------
88
// Variable Declarations
89
// ------------------------------------------------------------------------
90

91     /**
92      * The reference to the single instance of <code>Librarian</code>. Used in static methods in
93      * place of <code>this</code>.
94      */

95     private static Librarian me = null;
96
97     /**
98      * A list that is used to track all libraries that have been loaded. This list is used to ensure
99      * that each library is loaded only once.
100      */

101     private Vector JavaDoc trackList = new Vector JavaDoc();
102
103     /**
104      * A list of references to clients that use libraries that were extracted from a *.jar file.
105      * This is needed because the clients need to be called for freeing their libraries.
106      */

107     private Vector JavaDoc clients = new Vector JavaDoc();
108
109     /**
110      * A list of library names as they appear in the temporary directory. This is needed to free
111      * each library through the client. The index of each name corresponds to the index of the
112      * respective client in the <code>clients</code> list.
113      */

114     private Vector JavaDoc libraryNames = new Vector JavaDoc();
115
116     /**
117      * A list of fully qualified library names. This is needed to delete the temporary library files
118      * after use. The index of each name corresponds to the index of the respective client in the
119      * <code>clients</code> list.
120      */

121     private Vector JavaDoc temporaryFileNames = new Vector JavaDoc();
122
123     /** The extension to use for native libraries. */
124     private String JavaDoc extension = "";
125
126     /** The directory that is used to hold all native libraries. */
127     private String JavaDoc nativeDirectory = NATIVE;
128
129     /*--------------------------------------------------------------------------*/
130     /**
131      * This class is implemented as a 'Singleton'. Therefore the constructor is private to prevent
132      * instantiation of this class. Use <code>getInstance()</code> to obtain an instance for use.
133      * <br>
134      * <br>
135      * For more information about the 'Singleton' pattern I highly recommend the book Design
136      * Patterns by Gamma, Helm, Johnson and Vlissides ISBN 0-201-63361-2.
137      */

138     /*--------------------------------------------------------------------------*/
139     private Librarian()
140     {
141         Housekeeper.getInstance().registerForCleanup(this);
142         extension = '.' + TargetFactory.getInstance().getNativeLibraryExtension();
143     }
144
145     /*--------------------------------------------------------------------------*/
146     /**
147      * Returns an instance of <code>Librarian</code> to use.
148      *
149      * @return an instance of <code>Librarian</code>.
150      */

151     /*--------------------------------------------------------------------------*/
152     public static Librarian getInstance()
153     {
154         if (me == null)
155         {
156             me = new Librarian();
157         }
158
159         return (me);
160     }
161
162     /*--------------------------------------------------------------------------*/
163     /**
164      * Loads the requested library. If the library is already loaded, this method returns
165      * immediately, without an attempt to load the library again. <br>
166      * <br>
167      * <b>Invocation Example:</b> This assumes that the call is made from the class that links with
168      * the library. If this is not the case, <code>this</code> must be replaced by the reference
169      * of the class that links with the library. <br>
170      * <br>
171      * <code>
172      * Librarian.getInstance ().loadLibrary ("MyLibrary", this);
173      * </code> <br>
174      * <br>
175      * Loading of a native library file works as follows:<br>
176      * <ul>
177      * <li>If the library is already loaded there is nothing to do.
178      * <li>An attempt is made to load the library by its name. If there is no system path set to
179      * the library, this attempt will fail.
180      * <li>If the client is located on the local file system, an attempt is made to load the
181      * library from the local files system as well.
182      * <li>If the library is located inside a *.jar file, it is extracted to 'java.io.tmpdir' and
183      * an attempt is made to load it from there.
184      * </ul>
185      * <br>
186      * <br>
187      * Loading from the local file system and from the *.jar file is attempted for the following
188      * potential locations of the library in this order:<br>
189      * <ol>
190      * <li>The same directory where the client is located
191      * <li>The native library directory
192      * </ol>
193      *
194      * @param name the name of the library. A file extension and path are not needed, in fact if
195      * supplied, both is stripped off. A specific extension is appended.
196      * @param client the object that made the load request
197      *
198      * @see #setNativeDirectory
199      *
200      * @exception Exception if all attempts to load the library fail.
201      */

202     /*--------------------------------------------------------------------------*/
203     public synchronized void loadLibrary(String JavaDoc name, NativeLibraryClient client) throws Exception JavaDoc
204     {
205         String JavaDoc libraryName = strip(name);
206         String JavaDoc tempFileName = "";
207
208         // ----------------------------------------------------
209
// Return if the library is already loaded
210
// ----------------------------------------------------
211
if (loaded(libraryName)) { return; }
212
213         
214         if( System.getProperty("DLL_PATH") != null )
215         {
216             String JavaDoc path = System.getProperty("DLL_PATH") + "/" + name + extension;
217             path = path.replace('/', File.separatorChar);
218             Debug.trace("Try to load library " + path);
219             System.load(path);
220             return;
221             
222         }
223         // ----------------------------------------------------
224
// First try a straight load
225
// ----------------------------------------------------
226
try
227         {
228             System.loadLibrary(libraryName);
229             return;
230         }
231         catch (UnsatisfiedLinkError JavaDoc exception)
232         {}
233         catch (SecurityException JavaDoc exception)
234         {}
235
236         // ----------------------------------------------------
237
// Next, try to get the protocol for loading the resource.
238
// ----------------------------------------------------
239
Class JavaDoc clientClass = client.getClass();
240         String JavaDoc resourceName = clientClass.getName();
241         int nameStart = resourceName.lastIndexOf('.') + 1;
242         resourceName = resourceName.substring(nameStart, resourceName.length()) + CLIENT_EXTENSION;
243         URL JavaDoc url = clientClass.getResource(resourceName);
244         if (url == null) { throw (new Exception JavaDoc("can't identify load protocol for " + libraryName
245                 + extension)); }
246         String JavaDoc protocol = url.getProtocol();
247
248         // ----------------------------------------------------
249
// If it's a local file, load it from the current location
250
// ----------------------------------------------------
251
if (protocol.equalsIgnoreCase(FILE_PROTOCOL))
252         {
253             try
254             {
255                 System.load(getClientPath(name, url));
256             }
257             catch (Throwable JavaDoc exception)
258             {
259                 try
260                 {
261                     System.load(getNativePath(name, client));
262                 }
263                 catch (Throwable JavaDoc exception2)
264                 {
265                     throw (new Exception JavaDoc("error loading library"));
266                 }
267             }
268         }
269
270         // ----------------------------------------------------
271
// If it is in a *.jar file, extract it to 'java.io.tmpdir'
272
// ----------------------------------------------------
273

274         else if (protocol.equalsIgnoreCase(JAR_PROTOCOL))
275         {
276             tempFileName = getTempFileName(libraryName);
277             try
278             {
279                 extractFromJar(libraryName, tempFileName, client);
280
281                 clients.add(client);
282                 temporaryFileNames.add(tempFileName);
283                 libraryNames.add(tempFileName.substring((tempFileName
284                         .lastIndexOf(File.separatorChar) + 1), tempFileName.length()));
285
286                 // --------------------------------------------------
287
// Try loading the temporary file from 'java.io.tmpdir'.
288
// --------------------------------------------------
289
System.load(tempFileName);
290             }
291             catch (Throwable JavaDoc exception)
292             {
293                 throw (new Exception JavaDoc("error loading library\n" + exception.toString()));
294             }
295         }
296     }
297
298     /*--------------------------------------------------------------------------*/
299     /**
300      * Verifies if the library has already been loaded and keeps track of all libraries that are
301      * verified.
302      *
303      * @param name name of the library to verify
304      *
305      * @return <code>true</code> if the library had already been loaded, otherwise
306      * <code>false</code>.
307      */

308     /*--------------------------------------------------------------------------*/
309     private boolean loaded(String JavaDoc name)
310     {
311         if (trackList.contains(name))
312         {
313             return (true);
314         }
315         else
316         {
317             trackList.add(name);
318             return (false);
319         }
320     }
321
322     /*--------------------------------------------------------------------------*/
323     /**
324      * Strips the extension of the library name, if it has one.
325      *
326      * @param name the name of the library
327      *
328      * @return the name without an extension
329      */

330     /*--------------------------------------------------------------------------*/
331     private String JavaDoc strip(String JavaDoc name)
332     {
333         int extensionStart = name.lastIndexOf('.');
334         int nameStart = name.lastIndexOf('/');
335         if (nameStart < 0)
336         {
337             nameStart = name.lastIndexOf('\\');
338         }
339         nameStart++;
340
341         String JavaDoc shortName;
342
343         if (extensionStart > 0)
344         {
345             shortName = name.substring(nameStart, extensionStart);
346         }
347         else
348         {
349             shortName = name.substring(nameStart, name.length());
350         }
351
352         return (shortName);
353     }
354
355     /*--------------------------------------------------------------------------*/
356     /**
357      * Makes an attempt to extract the named library from the jar file and to store it on the local
358      * file system for temporary use. If the attempt is successful, the fully qualified file name of
359      * the library on the local file system is returned.
360      *
361      * @param name the simple name of the library
362      * @param destination the fully qualified name of the destination file.
363      * @param client the class that made the load request.
364      *
365      * @exception Exception if the library can not be extracted from the *.jar file.
366      * @exception FileNotFoundException if the *.jar file does not exist. The way things operate
367      * here, this should actually never happen.
368      */

369     /*--------------------------------------------------------------------------*/
370     private void extractFromJar(String JavaDoc name, String JavaDoc destination, NativeLibraryClient client)
371             throws Exception JavaDoc
372     {
373         int bytesRead = 0;
374         OutputStream JavaDoc output = null;
375
376         // ----------------------------------------------------
377
// open an input stream for the library file
378
// ----------------------------------------------------
379
InputStream JavaDoc input = openInputStream(name, client);
380
381         // ----------------------------------------------------
382
// open an output stream for the temporary file
383
// ----------------------------------------------------
384
try
385         {
386             output = new FileOutputStream JavaDoc(destination);
387         }
388         catch (FileNotFoundException JavaDoc exception)
389         {
390             input.close();
391             throw (new Exception JavaDoc("can't create destination file"));
392         }
393         catch (SecurityException JavaDoc exception)
394         {
395             input.close();
396             throw (new Exception JavaDoc("creation of destination file denied"));
397         }
398         catch (Throwable JavaDoc exception)
399         {
400             input.close();
401             throw (new Exception JavaDoc("unknown problem creating destination file\n"
402                     + exception.toString()));
403         }
404
405         // ----------------------------------------------------
406
// pump the data
407
// ----------------------------------------------------
408
byte[] buffer = new byte[BLOCK_SIZE];
409         try
410         {
411             do
412             {
413                 bytesRead = input.read(buffer);
414                 if (bytesRead > 0)
415                 {
416                     output.write(buffer, 0, bytesRead);
417                 }
418             }
419             while (bytesRead > 0);
420         }
421         catch (Throwable JavaDoc exception)
422         {
423             throw (new Exception JavaDoc("error writing to destination file\n" + exception.toString()));
424         }
425
426         // ----------------------------------------------------
427
// flush the data and close both streams
428
// ----------------------------------------------------
429
finally
430         {
431             input.close();
432             output.flush();
433             output.close();
434         }
435     }
436
437     /*--------------------------------------------------------------------------*/
438     /**
439      * Returns the complete path (including file name) for the native library, assuming the native
440      * library is located in the same directory from which the client was loaded.
441      *
442      * @param name the simple name of the library
443      * @param clientURL a URL that points to the client class
444      *
445      * @return the path to the client
446      */

447     /*--------------------------------------------------------------------------*/
448     private String JavaDoc getClientPath(String JavaDoc name, URL JavaDoc clientURL)
449     {
450         String JavaDoc path = clientURL.getFile();
451
452         int nameStart = path.lastIndexOf('/') + 1;
453
454         path = path.substring(0, nameStart);
455         path = path + name + extension;
456         path = path.replace('/', File.separatorChar);
457         // Revise the URI-path to a file path; needed in uninstaller because it
458
// writes the jar contents into a sandbox; may be with blanks in the
459
// path.
460
path = revisePath(path);
461
462         return (path);
463     }
464
465     /*--------------------------------------------------------------------------*/
466     /**
467      * Returns the complete path (including file name) for the native library, assuming the native
468      * library is located in a directory where native libraries are ordinarily expected.
469      *
470      * @param name the simple name of the library
471      * @param client the class that made the load request.
472      *
473      * @return the path to the location of the native libraries.
474      */

475     /*--------------------------------------------------------------------------*/
476     private String JavaDoc getNativePath(String JavaDoc name, NativeLibraryClient client)
477     {
478         ProtectionDomain JavaDoc domain = client.getClass().getProtectionDomain();
479         CodeSource JavaDoc codeSource = domain.getCodeSource();
480         URL JavaDoc url = codeSource.getLocation();
481         String JavaDoc path = url.getPath();
482         path = path + nativeDirectory + '/' + name + extension;
483         path = path.replace('/', File.separatorChar);
484         // Revise the URI-path to a file path; needed in uninstaller because it
485
// writes the jar contents into a sandbox; may be with blanks in the
486
// path.
487
path = revisePath(path);
488
489         return (path);
490     }
491
492     /*--------------------------------------------------------------------------*/
493     /**
494      * Revises the given path to a file compatible path. In fact this method replaces URI-like
495      * entries with it chars (e.g. %20 with a space).
496      *
497      * @param in path to be revised
498      * @return revised path
499      */

500     /*--------------------------------------------------------------------------*/
501     private String JavaDoc revisePath(String JavaDoc in)
502     {
503         // This was "stolen" from com.izforge.izpack.util.SelfModifier
504

505         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
506         CharacterIterator JavaDoc iter = new StringCharacterIterator JavaDoc(in);
507         for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next())
508         {
509             if (c == '%')
510             {
511                 char c1 = iter.next();
512                 if (c1 != CharacterIterator.DONE)
513                 {
514                     int i1 = Character.digit(c1, 16);
515                     char c2 = iter.next();
516                     if (c2 != CharacterIterator.DONE)
517                     {
518                         int i2 = Character.digit(c2, 16);
519                         sb.append((char) ((i1 << 4) + i2));
520                     }
521                 }
522             }
523             else
524             {
525                 sb.append(c);
526             }
527         }
528         String JavaDoc path = sb.toString();
529         return path;
530     }
531
532     /*--------------------------------------------------------------------------*/
533     /**
534      * Opens an <code>InputStream</code> to the native library.
535      *
536      * @param name the simple name of the library
537      * @param client the class that made the load request.
538      *
539      * @return an <code>InputStream</code> from which the library can be read.
540      *
541      * @exception Exception if the library can not be located.
542      */

543     /*--------------------------------------------------------------------------*/
544     private InputStream JavaDoc openInputStream(String JavaDoc name, NativeLibraryClient client) throws Exception JavaDoc
545     {
546         Class JavaDoc clientClass = client.getClass();
547         // ----------------------------------------------------
548
// try to open an input stream, assuming the library
549
// is located with the client
550
// ----------------------------------------------------
551
InputStream JavaDoc input = clientClass.getResourceAsStream(name + extension);
552
553         // ----------------------------------------------------
554
// if this is not successful, try to load from the
555
// location where all native libraries are supposed
556
// to be located.
557
// ----------------------------------------------------
558
if (input == null)
559         {
560             input = clientClass.getResourceAsStream('/' + nativeDirectory + '/' + name + extension);
561         }
562
563         // ----------------------------------------------------
564
// if this fails as well, throw an exception
565
// ----------------------------------------------------
566
if (input == null)
567         {
568             throw (new Exception JavaDoc("can't locate library"));
569         }
570         else
571         {
572             return (input);
573         }
574     }
575
576     /*--------------------------------------------------------------------------*/
577     /**
578      * Builds a temporary file name for the native library.
579      *
580      * @param name the file name of the library
581      *
582      * @return a fully qualified file name that can be used to store the file on the local file
583      * system.
584      */

585     /*--------------------------------------------------------------------------*/
586     /*
587      * $ @design
588      *
589      * Avoid overwriting any existing files on the user's system. If by some remote chance a file by
590      * the same name should exist on the user's system, modify the temporary file name until a
591      * version is found that is unique on the system and thus won't interfere.
592      * --------------------------------------------------------------------------
593      */

594     private String JavaDoc getTempFileName(String JavaDoc name)
595     {
596         StringBuffer JavaDoc fileName = new StringBuffer JavaDoc();
597         String JavaDoc path = System.getProperty(TEMP_LOCATION_KEY);
598         if (path.charAt(path.length() - 1) == File.separatorChar)
599         {
600             path = path.substring(0, (path.length() - 1));
601         }
602         String JavaDoc modifier = "";
603         int counter = 0;
604         File JavaDoc file = null;
605
606         do
607         {
608             fileName.delete(0, fileName.length());
609             fileName.append(path);
610             fileName.append(File.separatorChar);
611             fileName.append(name);
612             fileName.append(modifier);
613             fileName.append(extension);
614
615             modifier = Integer.toString(counter);
616             counter++;
617
618             file = new File JavaDoc(fileName.toString());
619         }
620         while (file.exists());
621
622         return (fileName.toString());
623     }
624
625     /*--------------------------------------------------------------------------*/
626     /**
627      * Sets the directory where <code>Librarian</code> will search for native files. Directories
628      * are denoted relative to the root, where the root is the same location where the top level
629      * Java package directory is located (usually called <code>com</code>). The default directory
630      * is <code>native</code>.
631      *
632      * @param directory the directory where native files are located.
633      */

634     /*--------------------------------------------------------------------------*/
635     public void setNativeDirectory(String JavaDoc directory)
636     {
637         if (directory == null)
638         {
639             nativeDirectory = "";
640         }
641         else
642         {
643             nativeDirectory = directory;
644         }
645     }
646
647     /*--------------------------------------------------------------------------*/
648     /**
649      * This method attempts to remove all native libraries that have been temporarily created from
650      * the system.
651      * The used method for clean up depends on the VM version.
652      * If the ersion is 1.5.x or higher this process should be exit in one second, else
653      * the native libraries will be not deleted.
654      * Tests with the different methods produces hinds that the
655      * FreeLibraryAndExitThread (handle, 0) call in the dlls are the
656      * reason for VM crashes (version 1.5.x). May be this is a bug in the VM.
657      * But never seen a docu that this behavior is compatible with a VM.
658      * Since more than a year all 1.5 versions produce this crash. Therfore we make
659      * now a work around for it.
660      * But the idea to exit the thread for removing the file locking to give the
661      * possibility to delete the dlls are really nice. Therefore we use it with
662      * VMs which are compatible with it. (Klaus Bartz 2006.06.20)
663      */

664     /*--------------------------------------------------------------------------*/
665     public void cleanUp()
666     {
667         if (JAVA_SPECIFICATION_VERSION < 1.5)
668             oldCleanUp();
669         else
670             newCleanUp();
671
672     }
673
674     /*--------------------------------------------------------------------------*/
675     /**
676      * This method attempts to remove all native libraries that have been temporarily created from
677      * the system.
678      * This method will be invoked if the VM has version 1.4.x or less. Version 1.5.x or higher
679      * uses newCleanUp.
680      * This method starts a new thread which calls a method in the dll which should unload the
681      * dll. The thread never returns.
682      */

683     /*--------------------------------------------------------------------------*/
684     private void oldCleanUp()
685     {
686         for (int i = 0; i < clients.size(); i++)
687         {
688             // --------------------------------------------------
689
// free the library
690
// --------------------------------------------------
691
NativeLibraryClient client = (NativeLibraryClient) clients.elementAt(i);
692             String JavaDoc libraryName = (String JavaDoc) libraryNames.elementAt(i);
693
694             FreeThread free = new FreeThread(libraryName, client);
695             free.start();
696             try
697             {
698                 // give the thread some time to get the library
699
// freed before attempting to delete it.
700
free.join(50);
701             }
702             catch (Throwable JavaDoc exception)
703             {} // nothing I can do
704

705             // --------------------------------------------------
706
// delete the library
707
// --------------------------------------------------
708
String JavaDoc tempFileName = (String JavaDoc) temporaryFileNames.elementAt(i);
709             try
710             {
711                 File JavaDoc file = new File JavaDoc(tempFileName);
712                 file.delete();
713             }
714             catch (Throwable JavaDoc exception)
715             {} // nothing I can do
716
}
717     }
718
719     /*--------------------------------------------------------------------------*/
720     /**
721      * This method attempts to remove all native libraries that have been temporarily created from
722      * the system. This method will be invoked if the VM has version 1.5.x or higher. Version 1.4.x
723      * or less uses oldCleanUp. This method calls LibraryRemover which starts a new process which
724      * waits a little bit for exit of this process and tries than to delete the given files.
725      */

726     /*--------------------------------------------------------------------------*/
727     private void newCleanUp()
728     {
729         // This method will be used the SelfModifier stuff of uninstall
730
// instead of killing the thread in the dlls which provokes a
731
// segmentation violation with a 1.5 (also known as 5.0) VM.
732

733         try
734         {
735             LibraryRemover.invoke(temporaryFileNames);
736         }
737         catch (IOException JavaDoc e1)
738         {
739             // TODO Auto-generated catch block
740
e1.printStackTrace();
741         }
742
743     }
744 }
745 /*---------------------------------------------------------------------------*/
746
Popular Tags