001: /*
002: * $Id: Librarian.java 2056 2008-02-25 08:29:28Z jponge $
003: * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
004: *
005: * http://izpack.org/
006: * http://izpack.codehaus.org/
007: *
008: * Copyright 2002 Elmar Grom
009: *
010: * Licensed under the Apache License, Version 2.0 (the "License");
011: * you may not use this file except in compliance with the License.
012: * You may obtain a copy of the License at
013: *
014: * http://www.apache.org/licenses/LICENSE-2.0
015: *
016: * Unless required by applicable law or agreed to in writing, software
017: * distributed under the License is distributed on an "AS IS" BASIS,
018: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
019: * See the License for the specific language governing permissions and
020: * limitations under the License.
021: */
022:
023: package com.izforge.izpack.util;
024:
025: import java.io.File;
026: import java.io.FileNotFoundException;
027: import java.io.FileOutputStream;
028: import java.io.IOException;
029: import java.io.InputStream;
030: import java.io.OutputStream;
031: import java.net.URL;
032: import java.security.CodeSource;
033: import java.security.ProtectionDomain;
034: import java.text.CharacterIterator;
035: import java.text.StringCharacterIterator;
036: import java.util.Vector;
037:
038: /*---------------------------------------------------------------------------*/
039: /**
040: * This class handles loading of native libraries. There must only be one instance of
041: * <code>Librarian</code> per Java runtime, therefore this class is implemented as a 'Singleton'.
042: * <br>
043: * <br>
044: * <code>Librarian</code> is capable of loading native libraries from a variety of different
045: * source locations. However, you should place your library files in the 'native' directory. The
046: * primary reason for supporting different source locations is to facilitate testing in a
047: * development environment, without the need to actually packing the application into a *.jar file.
048: *
049: * @version 1.0 / 1/30/02
050: * @author Elmar Grom
051: */
052: /*---------------------------------------------------------------------------*/
053: public class Librarian implements CleanupClient {
054:
055: // ------------------------------------------------------------------------
056: // Constant Definitions
057: // ------------------------------------------------------------------------
058:
059: /** Used to identify jar URL protocols */
060: private static final String JAR_PROTOCOL = "jar";
061:
062: /** Used to identify file URL protocols */
063: private static final String FILE_PROTOCOL = "file";
064:
065: /**
066: * The key used to retrieve the location of temporary files form the system properties.
067: */
068: private static final String TEMP_LOCATION_KEY = "java.io.tmpdir";
069:
070: /**
071: * The extension appended to the client name when searching for it as a resource. Since the
072: * client is an object, the extension should always be '.class'
073: */
074: private static final String CLIENT_EXTENSION = ".class";
075:
076: /** The default directory for native library files. */
077: private static final String NATIVE = "native";
078:
079: /** The block size used for reading and writing data, 4k. */
080: private static final int BLOCK_SIZE = 4096;
081:
082: /** VM version needed to select clean up method. */
083: private static final float JAVA_SPECIFICATION_VERSION = Float
084: .parseFloat(System
085: .getProperty("java.specification.version"));
086:
087: // ------------------------------------------------------------------------
088: // Variable Declarations
089: // ------------------------------------------------------------------------
090:
091: /**
092: * The reference to the single instance of <code>Librarian</code>. Used in static methods in
093: * place of <code>this</code>.
094: */
095: private static Librarian me = null;
096:
097: /**
098: * A list that is used to track all libraries that have been loaded. This list is used to ensure
099: * that each library is loaded only once.
100: */
101: private Vector<String> trackList = new Vector<String>();
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<NativeLibraryClient> clients = new Vector<NativeLibraryClient>();
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<String> libraryNames = new Vector<String>();
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<String> temporaryFileNames = new Vector<String>();
122:
123: /** The extension to use for native libraries. */
124: private String extension = "";
125:
126: /** The directory that is used to hold all native libraries. */
127: private String 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: Housekeeper.getInstance().registerForCleanup(this );
141: extension = '.' + TargetFactory.getInstance()
142: .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: if (me == null) {
154: me = new Librarian();
155: }
156:
157: return (me);
158: }
159:
160: public synchronized void loadLibrary(String name,
161: NativeLibraryClient client) throws Exception {
162: try {
163: loadArchSpecificLibrary(name, client);
164: } catch (Exception ex) {
165: loadArchSpecificLibrary(name + "_x64", client);
166: }
167: }
168:
169: /*--------------------------------------------------------------------------*/
170: /**
171: * Loads the requested library. If the library is already loaded, this method returns
172: * immediately, without an attempt to load the library again. <br>
173: * <br>
174: * <b>Invocation Example:</b> This assumes that the call is made from the class that links with
175: * the library. If this is not the case, <code>this</code> must be replaced by the reference
176: * of the class that links with the library. <br>
177: * <br>
178: * <code>
179: * Librarian.getInstance ().loadLibrary ("MyLibrary", this);
180: * </code> <br>
181: * <br>
182: * Loading of a native library file works as follows:<br>
183: * <ul>
184: * <li>If the library is already loaded there is nothing to do.
185: * <li>An attempt is made to load the library by its name. If there is no system path set to
186: * the library, this attempt will fail.
187: * <li>If the client is located on the local file system, an attempt is made to load the
188: * library from the local files system as well.
189: * <li>If the library is located inside a *.jar file, it is extracted to 'java.io.tmpdir' and
190: * an attempt is made to load it from there.
191: * </ul>
192: * <br>
193: * <br>
194: * Loading from the local file system and from the *.jar file is attempted for the following
195: * potential locations of the library in this order:<br>
196: * <ol>
197: * <li>The same directory where the client is located
198: * <li>The native library directory
199: * </ol>
200: *
201: * @param name the name of the library. A file extension and path are not needed, in fact if
202: * supplied, both is stripped off. A specific extension is appended.
203: * @param client the object that made the load request
204: *
205: * @see #setNativeDirectory
206: *
207: * @exception Exception if all attempts to load the library fail.
208: */
209: /*--------------------------------------------------------------------------*/
210: public synchronized void loadArchSpecificLibrary(String name,
211: NativeLibraryClient client) throws Exception {
212: String libraryName = strip(name);
213: String tempFileName = "";
214:
215: // ----------------------------------------------------
216: // Return if the library is already loaded
217: // ----------------------------------------------------
218: if (loaded(libraryName)) {
219: return;
220: }
221:
222: if (System.getProperty("DLL_PATH") != null) {
223: String path = System.getProperty("DLL_PATH") + "/" + name
224: + extension;
225: path = path.replace('/', File.separatorChar);
226: Debug.trace("Try to load library " + path);
227: System.load(path);
228: return;
229:
230: }
231: // ----------------------------------------------------
232: // First try a straight load
233: // ----------------------------------------------------
234: try {
235: System.loadLibrary(libraryName);
236: return;
237: } catch (UnsatisfiedLinkError exception) {
238: } catch (SecurityException exception) {
239: }
240:
241: // ----------------------------------------------------
242: // Next, try to get the protocol for loading the resource.
243: // ----------------------------------------------------
244: Class clientClass = client.getClass();
245: String resourceName = clientClass.getName();
246: int nameStart = resourceName.lastIndexOf('.') + 1;
247: resourceName = resourceName.substring(nameStart, resourceName
248: .length())
249: + CLIENT_EXTENSION;
250: URL url = clientClass.getResource(resourceName);
251: if (url == null) {
252: throw (new Exception("can't identify load protocol for "
253: + libraryName + extension));
254: }
255: String protocol = url.getProtocol();
256:
257: // ----------------------------------------------------
258: // If it's a local file, load it from the current location
259: // ----------------------------------------------------
260: if (protocol.equalsIgnoreCase(FILE_PROTOCOL)) {
261: try {
262: System.load(getClientPath(name, url));
263: } catch (Throwable exception) {
264: try {
265: System.load(getNativePath(name, client));
266: } catch (Throwable exception2) {
267: throw (new Exception("error loading library"));
268: }
269: }
270: }
271:
272: // ----------------------------------------------------
273: // If it is in a *.jar file, extract it to 'java.io.tmpdir'
274: // ----------------------------------------------------
275:
276: else if (protocol.equalsIgnoreCase(JAR_PROTOCOL)) {
277: tempFileName = getTempFileName(libraryName);
278: try {
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),
285: tempFileName.length()));
286:
287: // --------------------------------------------------
288: // Try loading the temporary file from 'java.io.tmpdir'.
289: // --------------------------------------------------
290: System.load(tempFileName);
291: } catch (Throwable exception) {
292: throw (new Exception("error loading library\n"
293: + 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 name) {
310: if (trackList.contains(name)) {
311: return (true);
312: } else {
313: trackList.add(name);
314: return (false);
315: }
316: }
317:
318: /*--------------------------------------------------------------------------*/
319: /**
320: * Strips the extension of the library name, if it has one.
321: *
322: * @param name the name of the library
323: *
324: * @return the name without an extension
325: */
326: /*--------------------------------------------------------------------------*/
327: private String strip(String name) {
328: int extensionStart = name.lastIndexOf('.');
329: int nameStart = name.lastIndexOf('/');
330: if (nameStart < 0) {
331: nameStart = name.lastIndexOf('\\');
332: }
333: nameStart++;
334:
335: String shortName;
336:
337: if (extensionStart > 0) {
338: shortName = name.substring(nameStart, extensionStart);
339: } else {
340: shortName = name.substring(nameStart, name.length());
341: }
342:
343: return (shortName);
344: }
345:
346: /*--------------------------------------------------------------------------*/
347: /**
348: * Makes an attempt to extract the named library from the jar file and to store it on the local
349: * file system for temporary use. If the attempt is successful, the fully qualified file name of
350: * the library on the local file system is returned.
351: *
352: * @param name the simple name of the library
353: * @param destination the fully qualified name of the destination file.
354: * @param client the class that made the load request.
355: *
356: * @exception Exception if the library can not be extracted from the *.jar file.
357: * @exception FileNotFoundException if the *.jar file does not exist. The way things operate
358: * here, this should actually never happen.
359: */
360: /*--------------------------------------------------------------------------*/
361: private void extractFromJar(String name, String destination,
362: NativeLibraryClient client) throws Exception {
363: int bytesRead = 0;
364: OutputStream output = null;
365:
366: // ----------------------------------------------------
367: // open an input stream for the library file
368: // ----------------------------------------------------
369: InputStream input = openInputStream(name, client);
370:
371: // ----------------------------------------------------
372: // open an output stream for the temporary file
373: // ----------------------------------------------------
374: try {
375: output = new FileOutputStream(destination);
376: } catch (FileNotFoundException exception) {
377: input.close();
378: throw (new Exception("can't create destination file"));
379: } catch (SecurityException exception) {
380: input.close();
381: throw (new Exception("creation of destination file denied"));
382: } catch (Throwable exception) {
383: input.close();
384: throw (new Exception(
385: "unknown problem creating destination file\n"
386: + exception.toString()));
387: }
388:
389: // ----------------------------------------------------
390: // pump the data
391: // ----------------------------------------------------
392: byte[] buffer = new byte[BLOCK_SIZE];
393: try {
394: do {
395: bytesRead = input.read(buffer);
396: if (bytesRead > 0) {
397: output.write(buffer, 0, bytesRead);
398: }
399: } while (bytesRead > 0);
400: } catch (Throwable exception) {
401: throw (new Exception("error writing to destination file\n"
402: + exception.toString()));
403: }
404:
405: // ----------------------------------------------------
406: // flush the data and close both streams
407: // ----------------------------------------------------
408: finally {
409: input.close();
410: output.flush();
411: output.close();
412: }
413: }
414:
415: /*--------------------------------------------------------------------------*/
416: /**
417: * Returns the complete path (including file name) for the native library, assuming the native
418: * library is located in the same directory from which the client was loaded.
419: *
420: * @param name the simple name of the library
421: * @param clientURL a URL that points to the client class
422: *
423: * @return the path to the client
424: */
425: /*--------------------------------------------------------------------------*/
426: private String getClientPath(String name, URL clientURL) {
427: String path = clientURL.getFile();
428:
429: int nameStart = path.lastIndexOf('/') + 1;
430:
431: path = path.substring(0, nameStart);
432: path = path + name + extension;
433: path = path.replace('/', File.separatorChar);
434: // Revise the URI-path to a file path; needed in uninstaller because it
435: // writes the jar contents into a sandbox; may be with blanks in the
436: // path.
437: path = revisePath(path);
438:
439: return (path);
440: }
441:
442: /*--------------------------------------------------------------------------*/
443: /**
444: * Returns the complete path (including file name) for the native library, assuming the native
445: * library is located in a directory where native libraries are ordinarily expected.
446: *
447: * @param name the simple name of the library
448: * @param client the class that made the load request.
449: *
450: * @return the path to the location of the native libraries.
451: */
452: /*--------------------------------------------------------------------------*/
453: private String getNativePath(String name, NativeLibraryClient client) {
454: ProtectionDomain domain = client.getClass()
455: .getProtectionDomain();
456: CodeSource codeSource = domain.getCodeSource();
457: URL url = codeSource.getLocation();
458: String path = url.getPath();
459: path = path + nativeDirectory + '/' + name + extension;
460: path = path.replace('/', File.separatorChar);
461: // Revise the URI-path to a file path; needed in uninstaller because it
462: // writes the jar contents into a sandbox; may be with blanks in the
463: // path.
464: path = revisePath(path);
465:
466: return (path);
467: }
468:
469: /*--------------------------------------------------------------------------*/
470: /**
471: * Revises the given path to a file compatible path. In fact this method replaces URI-like
472: * entries with it chars (e.g. %20 with a space).
473: *
474: * @param in path to be revised
475: * @return revised path
476: */
477: /*--------------------------------------------------------------------------*/
478: private String revisePath(String in) {
479: // This was "stolen" from com.izforge.izpack.util.SelfModifier
480:
481: StringBuffer sb = new StringBuffer();
482: CharacterIterator iter = new StringCharacterIterator(in);
483: for (char c = iter.first(); c != CharacterIterator.DONE; c = iter
484: .next()) {
485: if (c == '%') {
486: char c1 = iter.next();
487: if (c1 != CharacterIterator.DONE) {
488: int i1 = Character.digit(c1, 16);
489: char c2 = iter.next();
490: if (c2 != CharacterIterator.DONE) {
491: int i2 = Character.digit(c2, 16);
492: sb.append((char) ((i1 << 4) + i2));
493: }
494: }
495: } else {
496: sb.append(c);
497: }
498: }
499: String path = sb.toString();
500: return path;
501: }
502:
503: /*--------------------------------------------------------------------------*/
504: /**
505: * Opens an <code>InputStream</code> to the native library.
506: *
507: * @param name the simple name of the library
508: * @param client the class that made the load request.
509: *
510: * @return an <code>InputStream</code> from which the library can be read.
511: *
512: * @exception Exception if the library can not be located.
513: */
514: /*--------------------------------------------------------------------------*/
515: private InputStream openInputStream(String name,
516: NativeLibraryClient client) throws Exception {
517: Class clientClass = client.getClass();
518: // ----------------------------------------------------
519: // try to open an input stream, assuming the library
520: // is located with the client
521: // ----------------------------------------------------
522: InputStream input = clientClass.getResourceAsStream(name
523: + extension);
524:
525: // ----------------------------------------------------
526: // if this is not successful, try to load from the
527: // location where all native libraries are supposed
528: // to be located.
529: // ----------------------------------------------------
530: if (input == null) {
531: input = clientClass.getResourceAsStream('/'
532: + nativeDirectory + '/' + name + extension);
533: }
534:
535: // ----------------------------------------------------
536: // if this fails as well, throw an exception
537: // ----------------------------------------------------
538: if (input == null) {
539: throw (new Exception("can't locate library"));
540: } else {
541: return (input);
542: }
543: }
544:
545: /*--------------------------------------------------------------------------*/
546: /**
547: * Builds a temporary file name for the native library.
548: *
549: * @param name the file name of the library
550: *
551: * @return a fully qualified file name that can be used to store the file on the local file
552: * system.
553: */
554: /*--------------------------------------------------------------------------*/
555: /*
556: * $ @design
557: *
558: * Avoid overwriting any existing files on the user's system. If by some remote chance a file by
559: * the same name should exist on the user's system, modify the temporary file name until a
560: * version is found that is unique on the system and thus won't interfere.
561: * --------------------------------------------------------------------------
562: */
563: private String getTempFileName(String name) {
564: StringBuffer fileName = new StringBuffer();
565: String path = System.getProperty(TEMP_LOCATION_KEY);
566: if (path.charAt(path.length() - 1) == File.separatorChar) {
567: path = path.substring(0, (path.length() - 1));
568: }
569: String modifier = "";
570: int counter = 0;
571: File file = null;
572:
573: do {
574: fileName.delete(0, fileName.length());
575: fileName.append(path);
576: fileName.append(File.separatorChar);
577: fileName.append(name);
578: fileName.append(modifier);
579: fileName.append(extension);
580:
581: modifier = Integer.toString(counter);
582: counter++;
583:
584: file = new File(fileName.toString());
585: } while (file.exists());
586:
587: return (fileName.toString());
588: }
589:
590: /*--------------------------------------------------------------------------*/
591: /**
592: * Sets the directory where <code>Librarian</code> will search for native files. Directories
593: * are denoted relative to the root, where the root is the same location where the top level
594: * Java package directory is located (usually called <code>com</code>). The default directory
595: * is <code>native</code>.
596: *
597: * @param directory the directory where native files are located.
598: */
599: /*--------------------------------------------------------------------------*/
600: public void setNativeDirectory(String directory) {
601: if (directory == null) {
602: nativeDirectory = "";
603: } else {
604: nativeDirectory = directory;
605: }
606: }
607:
608: /*--------------------------------------------------------------------------*/
609: /**
610: * This method attempts to remove all native libraries that have been temporarily created from
611: * the system.
612: * The used method for clean up depends on the VM version.
613: * If the ersion is 1.5.x or higher this process should be exit in one second, else
614: * the native libraries will be not deleted.
615: * Tests with the different methods produces hinds that the
616: * FreeLibraryAndExitThread (handle, 0) call in the dlls are the
617: * reason for VM crashes (version 1.5.x). May be this is a bug in the VM.
618: * But never seen a docu that this behavior is compatible with a VM.
619: * Since more than a year all 1.5 versions produce this crash. Therfore we make
620: * now a work around for it.
621: * But the idea to exit the thread for removing the file locking to give the
622: * possibility to delete the dlls are really nice. Therefore we use it with
623: * VMs which are compatible with it. (Klaus Bartz 2006.06.20)
624: */
625: /*--------------------------------------------------------------------------*/
626: public void cleanUp() {
627: if (JAVA_SPECIFICATION_VERSION < 1.5)
628: oldCleanUp();
629: else
630: newCleanUp();
631:
632: }
633:
634: /*--------------------------------------------------------------------------*/
635: /**
636: * This method attempts to remove all native libraries that have been temporarily created from
637: * the system.
638: * This method will be invoked if the VM has version 1.4.x or less. Version 1.5.x or higher
639: * uses newCleanUp.
640: * This method starts a new thread which calls a method in the dll which should unload the
641: * dll. The thread never returns.
642: */
643: /*--------------------------------------------------------------------------*/
644: private void oldCleanUp() {
645: for (int i = 0; i < clients.size(); i++) {
646: // --------------------------------------------------
647: // free the library
648: // --------------------------------------------------
649: NativeLibraryClient client = clients.elementAt(i);
650: String libraryName = libraryNames.elementAt(i);
651:
652: FreeThread free = new FreeThread(libraryName, client);
653: free.start();
654: try {
655: // give the thread some time to get the library
656: // freed before attempting to delete it.
657: free.join(50);
658: } catch (Throwable exception) {
659: } // nothing I can do
660:
661: // --------------------------------------------------
662: // delete the library
663: // --------------------------------------------------
664: String tempFileName = temporaryFileNames.elementAt(i);
665: try {
666: File file = new File(tempFileName);
667: file.delete();
668: } catch (Throwable exception) {
669: } // nothing I can do
670: }
671: }
672:
673: /*--------------------------------------------------------------------------*/
674: /**
675: * This method attempts to remove all native libraries that have been temporarily created from
676: * the system. This method will be invoked if the VM has version 1.5.x or higher. Version 1.4.x
677: * or less uses oldCleanUp. This method calls LibraryRemover which starts a new process which
678: * waits a little bit for exit of this process and tries than to delete the given files.
679: */
680: /*--------------------------------------------------------------------------*/
681: private void newCleanUp() {
682: // This method will be used the SelfModifier stuff of uninstall
683: // instead of killing the thread in the dlls which provokes a
684: // segmentation violation with a 1.5 (also known as 5.0) VM.
685:
686: try {
687: LibraryRemover.invoke(temporaryFileNames);
688: } catch (IOException e1) {
689: // TODO Auto-generated catch block
690: e1.printStackTrace();
691: }
692:
693: }
694: }
695: /*---------------------------------------------------------------------------*/
|