Statistics
| Revision:

root / trunk / build / distribution / IzPack / src / lib / com / izforge / izpack / util / Librarian.java @ 21757

History | View | Annotate | Download (24.7 KB)

1
/*
2
 * $Id: Librarian.java 5819 2006-06-14 07:29:09Z cesar $
3
 * IzPack
4
 * Copyright (C) 2002 by Elmar Grom
5
 *
6
 * File :               Librarian.java
7
 * Description :        Supports the loading of native libraries
8
 * Author's email :     elmar@grom.net
9
 * Website :            http://www.izforge.com
10
 *
11
 * This program is free software; you can redistribute it and/or
12
 * modify it under the terms of the GNU General Public License
13
 * as published by the Free Software Foundation; either version 2
14
 * of the License, or any later version.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU General Public License
22
 * along with this program; if not, write to the Free Software
23
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
24
 */
25

    
26
package   com.izforge.izpack.util;
27

    
28
import java.io.File;
29
import java.io.FileNotFoundException;
30
import java.io.FileOutputStream;
31
import java.io.InputStream;
32
import java.io.OutputStream;
33
import java.net.URL;
34
import java.security.CodeSource;
35
import java.security.ProtectionDomain;
36
import java.text.CharacterIterator;
37
import java.text.StringCharacterIterator;
38
import java.util.Vector;
39

    
40
/*---------------------------------------------------------------------------*/
41
/**
42
 * This class handles loading of native libraries. There must only be one
43
 * instance of <code>Librarian</code> per Java runtime, therefore this class is
44
 * implemented as a 'Singleton'. 
45
 * <br><br>
46
 * <code>Librarian</code> is capable of loading native libraries from a
47
 * variety of different source locations. However, you should place your
48
 * library files in the 'native' directory. The primary reason for supporting
49
 * different source locations is to facilitate testing in a development
50
 * environment, without the need to actually packing the application into
51
 * a *.jar file.
52
 *
53
 * @version  1.0 / 1/30/02
54
 * @author   Elmar Grom
55
 */
56
/*---------------------------------------------------------------------------*/
57
public class Librarian implements CleanupClient
58
{
59
  // ------------------------------------------------------------------------
60
  // Constant Definitions
61
  // ------------------------------------------------------------------------
62

    
63
  /** Used to identify jar URL protocols */
64
  private static final String     JAR_PROTOCOL            = "jar";
65
  /** Used to identify file URL protocols */
66
  private static final String     FILE_PROTOCOL           = "file";
67
  /** The key used to retrieve the location of temporary files form the
68
      system properties. */
69
  private static final String     TEMP_LOCATION_KEY       = "java.io.tmpdir";
70
  /** The extension appended to the client name when searching for it as a
71
      resource. Since the client is an object, the extension should always
72
      be '.class' */
73
  private static final String     CLIENT_EXTENSION        = ".class";
74
  /** The default directory for native library files. */
75
  private static final String     NATIVE                  = "native";
76
  /** The block size used for reading and writing data, 4k. */
77
  private static final int        BLOCK_SIZE              = 4096;
78
  
79
  // ------------------------------------------------------------------------
80
  // Variable Declarations
81
  // ------------------------------------------------------------------------
82

    
83
  /** The reference to the single instance of <code>Librarian</code>. Used
84
      in static methods in place of <code>this</code>. */
85
  private static Librarian        me                      = null;
86
  /** A list that is used to track all libraries that have been loaded.
87
      This list is used to ensure that each library is loaded only once. */  
88
  private Vector                  trackList               = new Vector ();
89
  /** A list of references to clients that use libraries that were
90
      extracted from a *.jar file. This is needed because the clients
91
      need to be called for freeing their libraries. */
92
  private Vector                  clients                 = new Vector ();
93
  /** A list of library names as they appear in the temporary directory.
94
      This is needed to free each library through the client. The index
95
      of each name corresponds to the index of the respective client in
96
      the <code>clients</code> list. */
97
  private Vector                  libraryNames            = new Vector ();
98
  /** A list of fully qualified library names. This is needed to delete
99
      the temporary library files after use. The index of each name
100
      corresponds to the index of the respective client in the
101
      <code>clients</code> list. */
102
  private Vector                  temporaryFileNames      = new Vector ();
103
  /** The extension to use for native libraries. */
104
  private String                  extension               = "";
105
  /** The directory that is used to hold all native libraries. */
106
  private String                  nativeDirectory         = NATIVE;
107

    
108
 /*--------------------------------------------------------------------------*/
109
 /**
110
  * This class is implemented as a 'Singleton'. Therefore the constructor is
111
  * private to prevent instantiation of this class. Use <code>getInstance()</code>
112
  * to obtain an instance for use.
113
  * <br><br>
114
  * For more information about the 'Singleton' pattern I highly recommend
115
  * the book Design Patterns by Gamma, Helm, Johnson and Vlissides 
116
  * ISBN 0-201-63361-2. 
117
  */
118
 /*--------------------------------------------------------------------------*/
119
  private Librarian ()
120
  {
121
    Housekeeper.getInstance ().registerForCleanup (this);
122
    extension = '.' + TargetFactory.getInstance ().getNativeLibraryExtension ();
123
  }
124
 /*--------------------------------------------------------------------------*/
125
 /**
126
  * Returns an instance of <code>Librarian</code> to use.
127
  *
128
  * @return    an instance of <code>Librarian</code>.
129
  */
130
 /*--------------------------------------------------------------------------*/
131
  public static Librarian getInstance ()
132
  {
133
    if (me == null)
134
    {
135
      me = new Librarian ();
136
    }
137
    
138
    return (me);
139
  }
140
 /*--------------------------------------------------------------------------*/
141
 /**
142
  * Loads the requested library. If the library is already loaded, this
143
  * method returns immediately, without an attempt to load the library again. 
144
  * <br><br>
145
  * <b>Invocation Example:</b>
146
  * This assumes that the call is made from the class that links with the
147
  * library. If this is not the case, <code>this</code> must be replaced by
148
  * the reference of the class that links with the library.
149
  * <br><br><code>
150
  * Librarian.getInstance ().loadLibrary ("MyLibrary", this);
151
  * </code>
152
  * <br><br>
153
  * Loading of a native library file works as follows:<br>
154
  * <ul>
155
  * <li>If the library is already loaded there is nothing to do.
156
  * <li>An attempt is made to load the library by its name. If there is no
157
  *     system path set to the library, this attempt will fail.
158
  * <li>If the client is located on the local file system, an attempt is
159
  *     made to load the library from the local files system as well.
160
  * <li>If the library is located inside a *.jar file, it is extracted to
161
  *     'java.io.tmpdir' and an attempt is made to load it from there.
162
  * </ul>
163
  * <br><br>
164
  * Loading from the local file system and from the *.jar file is attempted
165
  * for the following potential locations of the library in this order:<br>
166
  * <ol>
167
  * <li>The same directory where the client is located
168
  * <li>The native library directory
169
  * </ol>
170
  *
171
  * @param     name       the name of the library. A file extension and path
172
  *                       are not needed, in fact if supplied, both is
173
  *                       stripped off. A specific extension is
174
  *                       appended.
175
  * @param     client  the object that made the load request
176
  *
177
  * @see       #setNativeDirectory
178
  *
179
  * @exception Exception if all attempts to load the library fail.
180
  */
181
 /*--------------------------------------------------------------------------*/
182
  public synchronized void loadLibrary (String              name,
183
                                        NativeLibraryClient client) throws Exception
184
  {
185
    String libraryName  = strip (name);
186
    String tempFileName = "";
187

    
188
    // ----------------------------------------------------
189
    // Return if the library is already loaded
190
    // ----------------------------------------------------
191
    if (loaded (libraryName))
192
    {
193
      return;
194
    }
195

    
196
    // ----------------------------------------------------
197
    // First try a straight load
198
    // ----------------------------------------------------
199
    try
200
    {
201
      System.loadLibrary (libraryName);
202
      return;
203
    }
204
    catch (UnsatisfiedLinkError exception) {}
205
    catch (SecurityException exception) {}
206

    
207
    // ----------------------------------------------------
208
    // Next, try to get the protocol for loading the resource.
209
    // ----------------------------------------------------
210
    Class   clientClass   = client.getClass ();
211
    String  resourceName  = clientClass.getName ();
212
    int     nameStart     = resourceName.lastIndexOf ('.') + 1;
213
    resourceName          = resourceName.substring (nameStart, resourceName.length ()) + CLIENT_EXTENSION;
214
    URL     url           = clientClass.getResource (resourceName);
215
    if (url == null)
216
    {
217
      throw (new Exception ("can't identify load protocol for " + libraryName + extension));
218
    }
219
    String  protocol        = url.getProtocol ();
220

    
221
    // ----------------------------------------------------
222
    // If it's a local file, load it from the current location
223
    // ----------------------------------------------------
224
    if (protocol.equalsIgnoreCase (FILE_PROTOCOL))
225
    {
226
      try
227
      {
228
        System.load (getClientPath (name, url));
229
      }
230
      catch (Throwable exception)
231
      {
232
        try
233
        {
234
          System.load (getNativePath (name, client));
235
        }
236
        catch (Throwable exception2)
237
        {
238
          throw (new Exception ("error loading library"));
239
        }
240
      }
241
    }
242
    
243
    // ----------------------------------------------------
244
    // If it is in a *.jar file, extract it to 'java.io.tmpdir'
245
    // ----------------------------------------------------
246
    
247
    else if (protocol.equalsIgnoreCase (JAR_PROTOCOL))
248
    {
249
      tempFileName = getTempFileName (libraryName);
250
      try
251
      {
252
        extractFromJar (libraryName, tempFileName, client);
253

    
254
        clients.add             (client);
255
        temporaryFileNames.add  (tempFileName);
256
        libraryNames.add        (tempFileName.substring ((tempFileName.lastIndexOf (File.separatorChar) + 1), tempFileName.length ()));
257
        
258
      // --------------------------------------------------
259
      // Try loading the temporary file from 'java.io.tmpdir'.
260
      // --------------------------------------------------
261
        System.load  (tempFileName);
262
      }
263
      catch (Throwable exception)
264
      {
265
        throw (new Exception ("error loading library\n" + exception.toString ()));
266
      }
267
    }
268
  }
269
 /*--------------------------------------------------------------------------*/
270
 /**
271
  * Verifies if the library has already been loaded and keeps track of all
272
  * libraries that are verified. 
273
  *
274
  * @param     name  name of the library to verify
275
  *
276
  * @return    <code>true</code> if the library had already been loaded,
277
  *            otherwise <code>false</code>.
278
  */
279
 /*--------------------------------------------------------------------------*/
280
  private boolean loaded (String name)
281
  {
282
    if (trackList.contains (name))
283
    {
284
      return (true);
285
    }
286
    else
287
    {
288
      trackList.add (name);
289
      return (false);
290
    }
291
  }
292
 /*--------------------------------------------------------------------------*/
293
 /**
294
  * Strips the extension of the library name, if it has one.
295
  *
296
  * @param     name  the name of the library
297
  *
298
  * @return    the name without an extension
299
  */
300
 /*--------------------------------------------------------------------------*/
301
  private String strip (String name)
302
  {
303
    int     extensionStart = name.lastIndexOf ('.');
304
    int     nameStart      = name.lastIndexOf ('/');
305
    if (nameStart < 0)
306
    {
307
      nameStart            = name.lastIndexOf ('\\');
308
    }
309
    nameStart++;
310
    
311
    String  shortName;
312

    
313
    if (extensionStart > 0)
314
    {
315
      shortName = name.substring (nameStart, extensionStart);
316
    }
317
    else
318
    {
319
      shortName = name.substring (nameStart, name.length ());
320
    }
321
    
322
    return (shortName);
323
  }
324
 /*--------------------------------------------------------------------------*/
325
 /**
326
  * Makes an attempt to extract the named library from the jar file and to
327
  * store it on the local file system for temporary use. If the attempt is
328
  * successful, the fully qualified file name of the library on the local
329
  * file system is returned.
330
  *
331
  * @param     name         the simple name of the library
332
  * @param     destination  the fully qualified name of the destination file.
333
  * @param     client       the class that made the load request.
334
  *
335
  * @exception Exception  if the library can not be extracted from
336
  *                       the *.jar file.
337
  * @exception FileNotFoundException if the *.jar file does not exist. The
338
  *                                  way things operate here, this should
339
  *                                  actually never happen.
340
  */
341
 /*--------------------------------------------------------------------------*/
342
  private void extractFromJar (String               name,
343
                               String               destination,
344
                               NativeLibraryClient  client)       throws  Exception
345
  {
346
    int           bytesRead = 0;
347
    OutputStream  output    = null;
348

    
349
    // ----------------------------------------------------
350
    // open an input stream for the library file
351
    // ----------------------------------------------------
352
    InputStream input = openInputStream (name, client);
353

    
354
    // ----------------------------------------------------
355
    // open an output stream for the temporary file
356
    // ----------------------------------------------------
357
    try
358
    {
359
      output = new FileOutputStream (destination);
360
    }
361
    catch (FileNotFoundException exception)
362
    {
363
      input.close ();
364
      throw (new Exception ("can't create destination file"));
365
    }
366
    catch (SecurityException exception)
367
    {
368
      input.close ();
369
      throw (new Exception ("creation of destination file denied"));
370
    }
371
    catch (Throwable exception)
372
    { 
373
      input.close ();
374
      throw (new Exception ("unknown problem creating destination file\n" + exception.toString ())); 
375
    }
376

    
377
    // ----------------------------------------------------
378
    // pump the data
379
    // ----------------------------------------------------
380
    byte [] buffer = new byte [BLOCK_SIZE];
381
    try
382
    {
383
      do
384
      {
385
        bytesRead = input.read (buffer);
386
        if (bytesRead > 0)
387
        {
388
          output.write (buffer, 0, bytesRead);
389
        }
390
      }
391
      while (bytesRead > 0);
392
    }
393
    catch (Throwable exception)
394
    {
395
      throw (new Exception ("error writing to destination file\n" + exception.toString ()));
396
    }
397
    
398
    // ----------------------------------------------------
399
    // flush the data and close both streams
400
    // ----------------------------------------------------
401
    finally
402
    {
403
      input.close ();
404
      output.flush ();
405
      output.close ();
406
    }
407
  }
408
 /*--------------------------------------------------------------------------*/
409
 /**
410
  * Returns the complete path (including file name) for the native library, 
411
  * assuming the native library is located in the same directory from which
412
  * the client was loaded.
413
  *
414
  * @param     name         the simple name of the library
415
  * @param     clientURL    a URL that points to the client class
416
  *
417
  * @return    the path to the client
418
  */
419
 /*--------------------------------------------------------------------------*/
420
  private String getClientPath (String  name,
421
                                URL     clientURL)
422
  {
423
    String path = clientURL.getFile ();
424

    
425
    int nameStart = path.lastIndexOf ('/') + 1;
426
    
427
    path = path.substring (0, nameStart);
428
    path = path + name + extension;
429
    path = path.replace ('/', File.separatorChar);
430
    // Revise the URI-path to a file path; needed in uninstaller because it
431
    // writes the jar contents into a sandbox; may be with blanks in the path.
432
    path = revisePath(path);
433
    
434
    return (path);
435
  }
436
 /*--------------------------------------------------------------------------*/
437
 /**
438
  * Returns the complete path (including file name) for the native library, 
439
  * assuming the native library is located in a directory where native
440
  * libraries are ordinarily expected.
441
  *
442
  * @param     name         the simple name of the library
443
  * @param     client       the class that made the load request.
444
  *
445
  * @return    the path to the location of the native libraries.
446
  */
447
 /*--------------------------------------------------------------------------*/
448
  private String getNativePath (String              name,
449
                                NativeLibraryClient client)
450
  {
451
    ProtectionDomain  domain      = client.getClass ().getProtectionDomain ();
452
    CodeSource        codeSource  = domain.getCodeSource ();
453
    URL               url         = codeSource.getLocation ();
454
    String            path        = url.getPath ();
455
    path                          = path + nativeDirectory + '/' + 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 path.
459
    path                          = revisePath(path);
460

    
461
    return (path);
462
  }
463
 /*--------------------------------------------------------------------------*/
464
 /**
465
  * Revises the given path to a file compatible path.
466
  * In fact this method replaces URI-like entries with it chars 
467
  * (e.g. %20 with a space).
468
  * @param in path to be revised
469
  * @return revised path
470
  */
471
 /*--------------------------------------------------------------------------*/
472
  private String revisePath( String in )
473
  {
474
    // This was "stolen" from com.izforge.izpack.util.SelfModifier
475

    
476
    StringBuffer sb = new StringBuffer();
477
    CharacterIterator iter = new StringCharacterIterator(in);
478
    for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next())
479
    {
480
      if (c == '%')
481
      {
482
        char c1 = iter.next();
483
        if (c1 != CharacterIterator.DONE)
484
        {
485
          int i1 = Character.digit(c1, 16);
486
          char c2 = iter.next();
487
          if (c2 != CharacterIterator.DONE)
488
          {
489
            int i2 = Character.digit(c2, 16);
490
            sb.append((char) ((i1 << 4) + i2));
491
          }
492
        }
493
      } else
494
      {
495
        sb.append(c);
496
      }
497
    }
498
    String path = sb.toString();
499
    return path;
500
  }
501
/*--------------------------------------------------------------------------*/
502
 /**
503
  * Opens an <code>InputStream</code> to the native library.
504
  *
505
  * @param     name         the simple name of the library
506
  * @param     client       the class that made the load request.
507
  *
508
  * @return    an <code>InputStream</code> from which the library can be read.
509
  *
510
  * @exception Exception if the library can not be located.
511
  */
512
 /*--------------------------------------------------------------------------*/
513
  private InputStream openInputStream (String               name,
514
                                       NativeLibraryClient  client) throws Exception
515
  {
516
    Class clientClass = client.getClass ();
517
    // ----------------------------------------------------
518
    // try to open an input stream, assuming the library
519
    // is located with the client
520
    // ----------------------------------------------------
521
    InputStream input = clientClass.getResourceAsStream (name + extension);
522

    
523
    // ----------------------------------------------------
524
    // if this is not successful, try to load from the
525
    // location where all native libraries are supposed
526
    // to be located.
527
    // ----------------------------------------------------
528
    if (input == null)
529
    {
530
      input = clientClass.getResourceAsStream ('/' + nativeDirectory + '/' + name + extension);
531
    }
532

    
533
    // ----------------------------------------------------
534
    // if this fails as well, throw an exception
535
    // ----------------------------------------------------
536
    if (input == null)
537
    {
538
      throw (new Exception ("can't locate library"));
539
    }
540
    else
541
    {
542
      return (input);
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
552
  *            on the local file system.
553
  */
554
 /*--------------------------------------------------------------------------*/
555
 /*$
556
  * @design
557
  *
558
  * Avoid overwriting any existing files on the user's system. If by some
559
  * remote chance a file by the same name should exist on the user's system,
560
  * modify the temporary file name until a version is found that is unique
561
  * on the system and thus won't interfere. 
562
  *--------------------------------------------------------------------------*/
563
  private String getTempFileName (String name)
564
  {
565
    StringBuffer fileName = new StringBuffer ();
566
    String       path     = System.getProperty (TEMP_LOCATION_KEY);
567
    if (path.charAt (path.length () - 1) == File.separatorChar)
568
    {
569
      path = path.substring (0, (path.length () - 1));
570
    }
571
    String       modifier = "";
572
    int          counter  = 0;
573
    File         file     = null;
574
    
575
    do
576
    {
577
      fileName.delete (0, fileName.length ());
578
      fileName.append (path);
579
      fileName.append (File.separatorChar);
580
      fileName.append (name);
581
      fileName.append (modifier);
582
      fileName.append (extension);
583
      
584
      modifier = Integer.toString (counter);
585
      counter++;
586
      
587
      file = new File (fileName.toString ());
588
    }
589
    while (file.exists ());
590
    
591
    return (fileName.toString ());
592
  }
593
 /*--------------------------------------------------------------------------*/
594
 /**
595
  * Sets the directory where <code>Librarian</code> will search for native
596
  * files. Directories are denoted relative to the root, where the root is
597
  * the same location where the top level Java package directory is located
598
  * (usually called <code>com</code>). The default directory is
599
  * <code>native</code>.
600
  *
601
  * @param     directory  the directory where native files are located.
602
  */
603
 /*--------------------------------------------------------------------------*/
604
  public void setNativeDirectory (String directory)
605
  {
606
    if (directory == null)
607
    {
608
      nativeDirectory = "";
609
    }
610
    else
611
    {
612
      nativeDirectory = directory;
613
    }
614
  }
615
 /*--------------------------------------------------------------------------*/
616
 /**
617
  * This method attempts to remove all native libraries that have been
618
  *  temporarily created from the system.
619
  */
620
 /*--------------------------------------------------------------------------*/
621
  public void cleanUp ()
622
  {
623
    for (int i = 0; i < clients.size (); i++)
624
    {
625
      // --------------------------------------------------
626
      // free the library
627
      // --------------------------------------------------
628
      NativeLibraryClient client      = (NativeLibraryClient)clients.elementAt (i);
629
      String              libraryName = (String)libraryNames.elementAt (i);
630

    
631
      FreeThread free = new FreeThread (libraryName, client);
632
      free.start ();
633
      try
634
      {
635
        // give the thread some time to get the library
636
        // freed before attempting to delete it.
637
        free.join (50); 
638
      }
639
      catch (Throwable exception) {} // nothing I can do
640

    
641
      // --------------------------------------------------
642
      // delete the library  
643
      // --------------------------------------------------
644
      String tempFileName = (String)temporaryFileNames.elementAt (i);
645
      try
646
      {
647
        File file = new File (tempFileName);
648
        file.delete ();
649
      }
650
      catch (Throwable exception) {} // nothing I can do
651
    }
652
  }
653
}
654
/*---------------------------------------------------------------------------*/