KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > de > schlichtherle > io > DefaultArchiveDetector


1 /*
2  * DefaultArchiveDetector.java
3  *
4  * Created on 24. Dezember 2005, 00:01
5  */

6 /*
7  * Copyright 2005-2006 Schlichtherle IT Services
8  *
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  * http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  */

21
22 package de.schlichtherle.io;
23
24 import de.schlichtherle.io.archive.spi.ArchiveDriver;
25
26 import java.io.IOException JavaDoc;
27 import java.io.InputStream JavaDoc;
28 import java.io.ObjectInputStream JavaDoc;
29 import java.io.Serializable JavaDoc;
30 import java.net.URL JavaDoc;
31 import java.text.MessageFormat JavaDoc;
32 import java.util.Collection JavaDoc;
33 import java.util.Enumeration JavaDoc;
34 import java.util.HashMap JavaDoc;
35 import java.util.Iterator JavaDoc;
36 import java.util.Map JavaDoc;
37 import java.util.Properties JavaDoc;
38 import java.util.ResourceBundle JavaDoc;
39 import java.util.Set JavaDoc;
40 import java.util.TreeSet JavaDoc;
41 import java.util.logging.Level JavaDoc;
42 import java.util.logging.Logger JavaDoc;
43 import java.util.regex.Matcher JavaDoc;
44 import java.util.regex.Pattern JavaDoc;
45
46 /**
47  * An {@link ArchiveDetector} which matches the names of files against the
48  * suffix pattern given to its constructors in order to detect prospective
49  * archive files and look up its registry to locate the corresponding
50  * {@link ArchiveDriver}.
51  * <p>
52  * Where a constructor expects a <code>suffixes</code> parameter, this must
53  * consist of the form <code>"suffix[|suffix]*"</code> where
54  * <code>suffix</code> is a combination of case insensitive letters without
55  * a leading dot.
56  * Zero length or duplicated suffixes are silently ignored.
57  * If this parameter is <code>null</code>, no archives are recognized.
58  * As an example, the parameter <code>"zip|jar"</code> would cause the
59  * archive detector to recognize ZIP and JAR files only.
60  * <p>
61  * For information on how to configure the plug-in archive driver registry,
62  * please refer to the default configuration file
63  * <code>META-INF/services/de.schlichtherle.io.archive.spi.ArchiveDriver.properties</code>
64  * in the <code>truezip.jar</code>.
65  * <p>
66  * {@link ArchiveDriver} classes are loaded on demand by the
67  * {@link #getArchiveDriver} method using the current thread's context class
68  * loader. This usually happens when a client application instantiates the
69  * {@link File} class.
70  * <p>
71  * This implementation is (virtually) immutable and thread safe.
72  * <p>
73  * Since TrueZIP 6.4, this class is serializable in order to meet the
74  * requirements of the {@link de.schlichtherle.io.File} class.
75  * However, it's not recommended to serialize DefaultArchiveDetector instances:
76  * Together with the instance, all associated archive drivers are serialized,
77  * too, which is pretty inefficient for a single instance.
78  *
79  * @author Christian Schlichtherle
80  * @version @version@
81  * @since TrueZIP 6.0
82  */

83 public class DefaultArchiveDetector
84         extends AbstractArchiveDetector
85         implements Serializable JavaDoc {
86
87     //
88
// Static fields.
89
//
90

91     private static final String JavaDoc CLASS_NAME
92             = "de/schlichtherle/io/DefaultArchiveDetector".replace('/', '.'); // support code obfuscation!
93
private static final ResourceBundle JavaDoc resources
94             = ResourceBundle.getBundle(CLASS_NAME);
95     private static final Logger JavaDoc logger
96             = Logger.getLogger(CLASS_NAME, CLASS_NAME);
97     
98     private static final String JavaDoc SERVICE =
99             "META-INF/services/de.schlichtherle.io.archive.spi.ArchiveDriver.properties";
100
101     private static final Object JavaDoc INVALID_DRIVER = new Object JavaDoc();
102
103     /**
104      * The global map of all archive drivers configured in the set of all
105      * configuration files.
106      * Maps single suffixes (not sets) [<code>String</code>] to the <em>class
107      * name</em> of an {@link ArchiveDriver} [<code>String</code>].
108      */

109     private static final Map JavaDoc allDrivers;
110
111     /**
112      * All archive types registered with this class as a suffix set.
113      * <p>
114      * This value is determined by processing all configuration files on the
115      * class path which are provided by the plug-in archive drivers and
116      * (optionally) the client application.
117      * For more information, please refer to the default configuration file
118      * <code>META-INF/services/de.schlichtherle.io.archive.spi.ArchiveDriver.properties</code>
119      * on the class path of the TrueZIP JAR.
120      * <p>
121      * A suffix set is of the form "suffix[|suffix]*",
122      * where each suffix does not contain the leading dot.
123      * It's a set because it never contains duplicate suffixes.
124      * E.g. "zip", "zip|jar", "tar" or "tgz|tar.gz" could be produced by the
125      * configuration files.
126      *
127      * @deprecated This field is not for public use and will vanish in the
128      * next major release.
129      * Use <code>ArchiveDetector.ALL.getSuffixes()</code> instead.
130      */

131     public static final String JavaDoc ALL_SUFFIXES; // init by static initializer!
132

133     /**
134      * The archive types recognized by default as a suffix set.
135      * <p>
136      * This value is determined by processing all configuration files on the
137      * class path which are provided by the plug-in archive drivers and
138      * (optionally) the client application.
139      * For more information, please refer to the default configuration file
140      * <code>META-INF/services/de.schlichtherle.io.archive.spi.ArchiveDriver.properties</code>
141      * on the class path of the TrueZIP JAR.
142      * <p>
143      * A suffix set is of the form "suffix[|suffix]*",
144      * where each suffix does not contain the leading dot.
145      * It's a set because it never contains duplicate suffixes.
146      * E.g. "zip", "zip|jar", "tar" or "tgz|tar.gz" could be produced by the
147      * configuration files.
148      *
149      * @deprecated This field is not for public use and will vanish in the
150      * next major release.
151      * Use <code>ArchiveDetector.DEFAULT.getSuffixes()</code> instead.
152      */

153     public static final String JavaDoc DEFAULT_SUFFIXES; // init by static initializer!
154

155     //
156
// Instance fields.
157
//
158

159     /**
160      * The delegate used to lookup archive drivers when no driver is
161      * configured locally.
162      */

163     private final DefaultArchiveDetector delegate;
164
165     /**
166      * The set of suffixes recognized by this archive detector.
167      */

168     private final String JavaDoc suffixes;
169
170     /**
171      * The map of configured or cached drivers, mapping from single suffix
172      * {@link String strings} (not sets) to {@link ArchiveDriver}s.
173      * This may be shared by multiple archive detectors and threads,
174      * so all access must be synchronized on the map instance
175      * (<em>not</em> <code>this</code> instance)!
176      */

177     private final Map JavaDoc drivers;
178
179     /**
180      * The thread local matcher used to match archive file name suffixes.
181      * This field should be considered final!
182      */

183     private transient ThreadLocalMatcher matcher; // never transmit this over the wire!
184

185     //
186
// Constructors.
187
//
188

189     /**
190      * Creates a new instance of <code>DefaultArchiveDetector</code>.
191      * This constructor recognizes any file which's path name matches the
192      * given suffix list as an archive and uses the respective
193      * {@link ArchiveDriver} as determined by the configuration files.
194      *
195      * @param suffixes A list of suffixes which shall identify prospective
196      * archive files. May be <code>null</code> or empty.
197      * @throws IllegalArgumentException If any of the suffixes in the suffix
198      * list names a suffix for which no {@link ArchiveDriver} is
199      * configured in the configuration files.
200      * @see DefaultArchiveDetector Syntax explanation for <code>suffix</code>
201      * parameter.
202      */

203     public DefaultArchiveDetector(final String JavaDoc suffixes) {
204         final SuffixSet suffixSet = new SuffixSet(suffixes);
205         final Set JavaDoc allSuffixes = allDrivers.keySet();
206         if (suffixSet.retainAll(allSuffixes)) {
207             final SuffixSet unknown = new SuffixSet(suffixes);
208             unknown.removeAll(allSuffixes);
209             throw new IllegalArgumentException JavaDoc(unknown + " (no archive driver installed for these suffixes)");
210         }
211
212         assert configurationOK(allDrivers);
213
214         this.delegate = this;
215         this.drivers = allDrivers;
216         this.suffixes = suffixSet.toString();
217         this.matcher = new ThreadLocalMatcher(suffixSet.toRegex());
218     }
219
220     /**
221      * Equivalent to
222      * {@link #DefaultArchiveDetector(DefaultArchiveDetector, String, ArchiveDriver)
223      * DefaultArchiveDetector(ArchiveDetector.NULL, suffixes, driver)}.
224      */

225     public DefaultArchiveDetector(String JavaDoc suffixes, ArchiveDriver driver) {
226         this(NULL, suffixes, driver);
227     }
228
229     /**
230      * Creates a new instance of <code>DefaultArchiveDetector</code> by
231      * inheriting its settings from the given <code>template</code> and
232      * adding a mapping for the given suffix list and driver to it.
233      *
234      * @param delegate The <code>DefaultArchiveDetector</code> which's
235      * configuration is to be inherited.
236      * @param suffixes A non-empty suffix list, following the usual syntax.
237      * @param driver The archive driver to register for the suffix list.
238      * This must either be an archive driver instance, a string
239      * with the class name of an archive driver implementation or a
240      * class instance of an archive driver implementation.
241      * @throws IllegalArgumentException If any parameter is <code>null</code>.
242      * @see DefaultArchiveDetector Syntax explanation for <code>suffix</code>
243      * parameter.
244      */

245     public DefaultArchiveDetector(
246             DefaultArchiveDetector delegate,
247             String JavaDoc suffixes,
248             ArchiveDriver driver) {
249         this(delegate, new Object JavaDoc[] { suffixes, driver});
250     }
251
252     /**
253      * Creates a new instance of <code>DefaultArchiveDetector</code> by
254      * inheriting its settings from the given <code>template</code> and
255      * amending it by the mappings in <code>configuration</code>.
256      * On any exception, processing is continued with the remaining entries in
257      * the configuration and finally the last exception catched is (re)thrown.
258      *
259      * @param delegate The <code>DefaultArchiveDetector</code> which's
260      * configuration is to be inherited.
261      * @param configuration The array of suffix lists and archive driver IDs.
262      * Each key in this map must be a non-empty suffix list,
263      * following the usual syntax.
264      * Each value must either be an archive driver instance, a class
265      * instance of an archive driver implementation, or a string
266      * with the class name of an archive driver implementation.
267      * @throws NullPointerException If any parameter or configuration element
268      * is <code>null</code>.
269      * @throws IllegalArgumentException If any other preconditions on the
270      * <code>configuration</code> does not hold.
271      * @see DefaultArchiveDetector Syntax explanation for <code>suffix</code>
272      * parameter.
273      */

274     public DefaultArchiveDetector(
275             final DefaultArchiveDetector delegate,
276             Object JavaDoc[] configuration) {
277         this(delegate, toMap(configuration));
278     }
279
280     /**
281      * Creates a new instance of <code>DefaultArchiveDetector</code> by
282      * inheriting its settings from the given <code>template</code> and
283      * amending it by the mappings in <code>configuration</code>.
284      *
285      * @param delegate The <code>DefaultArchiveDetector</code> which's
286      * configuration is to be inherited.
287      * @param configuration The map of suffix lists and archive driver IDs.
288      * Each key in this map must be a non-empty suffix list,
289      * following the usual syntax.
290      * Each value must either be an archive driver instance, a class
291      * instance of an archive driver implementation, or a string
292      * with the class name of an archive driver implementation.
293      * @throws NullPointerException If any parameter or configuration element
294      * is <code>null</code>.
295      * @throws IllegalArgumentException If any other preconditions on the
296      * <code>configuration</code> does not hold.
297      * @see DefaultArchiveDetector Syntax explanation for <code>suffix</code>
298      * parameter.
299      */

300     public DefaultArchiveDetector(
301             final DefaultArchiveDetector delegate,
302             final Map JavaDoc configuration) {
303         if (delegate == null)
304             throw new NullPointerException JavaDoc("delegate");
305         checkConfiguration(configuration);
306
307         this.delegate = delegate;
308         drivers = new HashMap JavaDoc();
309         registerArchiveDrivers(configuration, drivers);
310         final SuffixSet suffixSet = new SuffixSet(delegate.suffixes); // may be a subset of delegate.drivers.keySet()!
311
suffixSet.addAll(drivers.keySet());
312         suffixes = suffixSet.toString();
313         matcher = new ThreadLocalMatcher(suffixSet.toRegex());
314     }
315
316     //
317
// Methods.
318
//
319

320     private static Map JavaDoc toMap(Object JavaDoc[] configuration) {
321         if (configuration == null)
322             return null;
323
324         final Map JavaDoc map = new HashMap JavaDoc((int) (configuration.length / (2 * .75)));
325         for (int i = 0, l = configuration.length; i < l; i += 2)
326             map.put(configuration[i], configuration[i + 1]);
327
328         return map;
329     }
330
331     private static boolean configurationOK(Map JavaDoc configuration) {
332         try {
333             checkConfiguration(configuration);
334             return true;
335         } catch (RuntimeException JavaDoc failure) {
336             return false;
337         }
338     }
339
340     private static void checkConfiguration(final Map JavaDoc configuration) {
341         if (configuration == null)
342             throw new NullPointerException JavaDoc("configuration");
343
344         for (Iterator JavaDoc it = configuration.entrySet().iterator(); it.hasNext();) {
345             final Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
346             final Object JavaDoc key = entry.getKey();
347             if (!(key instanceof String JavaDoc))
348                 if (key != null)
349                     throw new IllegalArgumentException JavaDoc("configuration key is not a string!");
350                 else
351                     throw new NullPointerException JavaDoc("configuration key");
352             final String JavaDoc suffixes = (String JavaDoc) key;
353             if (suffixes.length() <= 0)
354                 throw new IllegalArgumentException JavaDoc("configuration key is empty!");
355             if ("DRIVER".equals(suffixes))
356                 throw new IllegalArgumentException JavaDoc("DRIVER directive not allowed in configuration key!");
357             if ("DEFAULT".equals(suffixes))
358                 throw new IllegalArgumentException JavaDoc("DEFAULT directive not allowed in configuration key!");
359
360             final Object JavaDoc value = entry.getValue();
361             if (value == null)
362                 throw new NullPointerException JavaDoc("configuration value");
363             if (value instanceof ArchiveDriver)
364                 continue;
365             if (value instanceof String JavaDoc) {
366                 if (((String JavaDoc) value).length() <= 0)
367                     throw new IllegalArgumentException JavaDoc("configuration string value is empty!");
368                 continue;
369             }
370             if (value instanceof Class JavaDoc) {
371                 if (!ArchiveDriver.class.isAssignableFrom((Class JavaDoc) value))
372                     throw new IllegalArgumentException JavaDoc("configuration class value is not an archive driver!");
373                 continue;
374             }
375             throw new IllegalArgumentException JavaDoc("configuration value is not an archive driver, class or string!");
376         }
377     }
378
379     /**
380      * Implements the {@link ArchiveDetector#getArchiveDriver} method.
381      * {@link ArchiveDriver} classes are loaded on demand by this method
382      * using the current thread's context class loader.
383      * <p>
384      * An archive driver is looked up as follows:
385      * <ul>
386      * <li>
387      * If the configuration holds an instance of an <code>ArchiveDriver</code>
388      * implementation, it is returned.
389      * <li>
390      * Otherwise, if the configuration holds a string, it is supposed to be
391      * the fully qualified class name of an <code>ArchiveDriver</code>
392      * implementation. The class will be loaded using the context class
393      * loader of the current thread.
394      * <li>
395      * If the configuration holds a class instance, it will be instantiated
396      * with its no-arguments constructor.
397      * </ul>
398      *
399      * @throws NullPointerException If <code>pathname</code> is <code>null</code>.
400      */

401     public ArchiveDriver getArchiveDriver(final String JavaDoc pathname) {
402         final Matcher JavaDoc m = matcher.reset(pathname);
403         if (!m.matches())
404             return null;
405         return lookupArchiveDriver(m.group(1).toLowerCase());
406     }
407
408     private ArchiveDriver lookupArchiveDriver(final String JavaDoc suffix) {
409         assert matcher.reset("." + suffix).matches();
410
411         synchronized (drivers) {
412             // Lookup driver locally.
413
Object JavaDoc driver = drivers.get(suffix);
414             if (driver instanceof ArchiveDriver) {
415                 return (ArchiveDriver) driver;
416             } else if (driver == INVALID_DRIVER) {
417                 return null;
418             } else if (driver == null) {
419                 // Lookup driver in delegate and cache.
420
// Note that the delegate cannot not be null since otherwise
421
// the matcher wouldn't have matched.
422
driver = delegate.lookupArchiveDriver(suffix);
423                 drivers.put(suffix, driver != null ? driver : INVALID_DRIVER);
424                 return (ArchiveDriver) driver;
425             } else {
426                 // We have found an entry in the drivers map, but it isn't
427
// an ArchiveDriver, so we probably need to load its class first
428
// and instantiate it.
429
// Note that we will install drivers in this detector's local
430
// drivers map in order to avoid multithreading issues when accessing
431
// the global drivers map.
432
try {
433                     if (driver instanceof String JavaDoc)
434                         driver = Thread.currentThread().getContextClassLoader()
435                                 .loadClass((String JavaDoc) driver);
436
437                     assert driver instanceof Class JavaDoc
438                             : "The constructor failed to ensure that all values in the drivers map are either ArchiveDriver, Class or String instances!";
439
440                     driver = (ArchiveDriver) ((Class JavaDoc) driver).newInstance();
441                     drivers.put(suffix, driver);
442                     logger.log(Level.FINE, "driverInstalled",
443                             new Object JavaDoc[] { suffix, driver });
444                     return (ArchiveDriver) driver;
445                 } catch (Exception JavaDoc failure) {
446                     // Map INVALID_DRIVER in order to prevent repeated loading or
447
// instantiation of this driver!
448
drivers.put(suffix, INVALID_DRIVER);
449                     final String JavaDoc message = MessageFormat.format(
450                             resources.getString("driverInstallationFailed"),
451                             new Object JavaDoc[] { suffix, driver });
452                     logger.log(Level.WARNING, message, failure);
453                     return null;
454                 }
455             }
456         } // synchronized (drivers)
457
}
458
459     /**
460      * Returns the set of suffixes identifying the archive types recognized
461      * by this instance.
462      *
463      * @return suffixes A pattern of file name suffixes which shall identify
464      * prospective archive files detected by this instance.
465      * The pattern consists of the form
466      * <code>"suffix[|suffix]*"</code> where <code>suffix</code> is a
467      * combination of lower case letters without the leading dot.
468      * It's actually a set because it never contains duplicate suffixes.
469      * It also never contains empty suffixes.
470      * If <code>null</code>, this ArchiveDetector does not recognize
471      * any archives.
472      *
473      * @see #DefaultArchiveDetector(String)
474      */

475     public String JavaDoc getSuffixes() {
476         return suffixes;
477     }
478
479     private void readObject(final ObjectInputStream JavaDoc in)
480     throws IOException JavaDoc, ClassNotFoundException JavaDoc {
481         in.defaultReadObject();
482         matcher = new ThreadLocalMatcher(new SuffixSet(suffixes).toRegex());
483     }
484
485     static {
486         logger.config("banner");
487         
488         // Process configuration files on the class path of the current
489
// thread's context class loader.
490
allDrivers = registerArchiveDrivers();
491
492         // Init suffixes.
493
// Not that retrieval of the default suffix must be done first in
494
// order to remove the DEFAULT key from the drivers map if present.
495
// The driver installation would throw an exception
496
// on this entry otherwise.
497
DEFAULT_SUFFIXES = defaultSuffixes(allDrivers);
498         ALL_SUFFIXES = new SuffixSet(allDrivers.keySet()).toString();
499
500         // Log registered drivers.
501
final Iterator JavaDoc it = allDrivers.entrySet().iterator();
502         if (it.hasNext()) {
503             do {
504                 final Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
505                 logger.log(Level.CONFIG, "driverRegistered",
506                         new Object JavaDoc[] { entry.getKey(), entry.getValue() });
507             } while (it.hasNext());
508
509             logger.log(Level.CONFIG, "allSuffixes", ALL_SUFFIXES);
510             logger.log(Level.CONFIG, "defaultSuffixes", DEFAULT_SUFFIXES);
511         } else {
512             logger.warning("noDriversRegistered");
513         }
514     }
515
516     /**
517      * Iterates through all resource URLs found for <code>SERVICE</code>
518      * and calls {@link #registerArchiveDrivers(URL, Map, Map)
519      * registerArchiveDrivers(url, driverDrivers, clientDrivers)}
520      * for each of them.
521      */

522     private static Map JavaDoc registerArchiveDrivers() {
523         final Map JavaDoc driverDrivers = new HashMap JavaDoc();
524         final Map JavaDoc clientDrivers = new HashMap JavaDoc();
525
526         final Enumeration JavaDoc urls;
527         try {
528             urls = Thread.currentThread().getContextClassLoader()
529                     .getResources(SERVICE);
530         } catch (IOException JavaDoc failure) {
531             logger.log(Level.WARNING, "resourceLookupFailed", SERVICE);
532             return driverDrivers;
533         }
534
535         while (urls.hasMoreElements()) {
536             final URL JavaDoc url = (URL JavaDoc) urls.nextElement();
537             registerArchiveDrivers(
538                     url, driverDrivers, clientDrivers);
539         }
540
541         // Ensure that client specified drivers always override
542
// driver specified drivers.
543
driverDrivers.putAll(clientDrivers);
544         return driverDrivers;
545     }
546
547     /**
548      * Loads and processes the given <code>url</code> in order to register
549      * the archive drivers in its configuration file.
550      */

551     private static void registerArchiveDrivers(
552             final URL JavaDoc url,
553             final Map JavaDoc driverDrivers,
554             final Map JavaDoc clientDrivers) {
555         assert url != null;
556         assert driverDrivers != null;
557         assert clientDrivers != null;
558
559         logger.log(Level.CONFIG, "loadingConfiguration", url);
560         // Load the configuration map from the properties file.
561
final Properties JavaDoc configuration = new Properties JavaDoc();
562         try {
563             final InputStream JavaDoc in = url.openStream();
564             try {
565                 configuration.load(in);
566                 registerArchiveDrivers(
567                         configuration, driverDrivers, clientDrivers);
568             } finally {
569                 in.close();
570             }
571         } catch (IOException JavaDoc failure) {
572             logger.log(Level.WARNING, "loadingConfigurationFailed", failure);
573             // Continue normally.
574
}
575     }
576
577     /**
578      * Processes the given <code>configuration</code> in order to register
579      * its archive drivers.
580      */

581     private static void registerArchiveDrivers(
582             final Map JavaDoc configuration,
583             final Map JavaDoc driverDrivers,
584             final Map JavaDoc clientDrivers) {
585         assert configuration != null;
586         assert driverDrivers != null;
587         assert clientDrivers != null;
588
589         // Consume and process DRIVER entry.
590
final String JavaDoc driver = (String JavaDoc) configuration.remove("DRIVER");
591         final boolean isDriver = Boolean.TRUE.equals(Boolean.valueOf(driver));
592
593         // Select registry.
594
final Map JavaDoc drivers;
595         if (isDriver)
596             drivers = driverDrivers;
597         else
598             drivers = clientDrivers;
599
600         registerArchiveDrivers(configuration, drivers);
601     }
602
603     /**
604      * Processes the given map <code>configuration</code> with archive driver
605      * mappings.
606      * On any exception, processing is continued with the remaining entries in
607      * the configuration and finally the last exception catched is (re)thrown.
608      *
609      *
610      * @throws NullPointerException If any archive driver ID in the
611      * configuration is <code>null</code>.
612      */

613     private static void registerArchiveDrivers(
614             final Map JavaDoc configuration,
615             final Map JavaDoc drivers) {
616         assert configuration != null;
617         assert drivers != null;
618
619         for (final Iterator JavaDoc i = configuration.entrySet().iterator(); i.hasNext(); ) {
620             final Map.Entry JavaDoc entry = (Map.Entry JavaDoc) i.next();
621             final String JavaDoc key = (String JavaDoc) entry.getKey();
622             final Object JavaDoc id = entry.getValue();
623             assert !"DRIVER".equals(key) : "DRIVER should have been removed from this map before this method was called!";
624             if ("DEFAULT".equals(key)) {
625                 // Process manually - registerArchiveDriver would put the
626
// lowercase representation of the key into the map and
627
// register the keyword in the set of all suffixes!
628
drivers.put(key, id);
629             } else {
630                 registerArchiveDriver(key, id, drivers);
631             }
632         }
633     }
634
635     /**
636      * Registers the given archive <code>id</code> for the given
637      * <code>suffixes</code>.
638      *
639      * @throws NullPointerException If <code>id</code> is <code>null</code>.
640      */

641     private static void registerArchiveDriver(
642             final String JavaDoc suffixes,
643             final Object JavaDoc id,
644             final Map JavaDoc drivers) {
645         assert drivers != null;
646
647         if (id == null)
648             throw new NullPointerException JavaDoc("Archive driver ID must not be null!");
649
650         final SuffixSet suffixSet = new SuffixSet(suffixes);
651         for (final Iterator JavaDoc it = suffixSet.iterator(); it.hasNext();) {
652             final String JavaDoc suffix = (String JavaDoc) it.next();
653             drivers.put(suffix, id);
654         }
655     }
656
657     private static String JavaDoc defaultSuffixes(final Map JavaDoc drivers) {
658         assert drivers != null;
659
660         final String JavaDoc suffixes = (String JavaDoc) drivers.remove("DEFAULT");
661         if (suffixes == null)
662             return null;
663
664         if ("NULL".equals(suffixes)) {
665             return null;
666         } else if ("ALL".equals(suffixes)) {
667             return new SuffixSet(drivers.keySet()).toString();
668         } else {
669             final Set JavaDoc suffixSet = new SuffixSet(suffixes);
670             for (final Iterator JavaDoc it = suffixSet.iterator(); it.hasNext();) {
671                 final String JavaDoc suffix = (String JavaDoc) it.next();
672                 if (!drivers.containsKey(suffix)) {
673                     it.remove();
674                     logger.log(Level.WARNING, "unknownSuffix", suffix);
675                 }
676             }
677             return suffixSet.toString();
678         }
679     }
680
681     //
682
// Member classes.
683
//
684

685     /**
686      * An ordered set of normalized suffixes.
687      */

688     private static final class SuffixSet extends TreeSet JavaDoc {
689         /**
690          * Constructs a new suffix set from the given suffix list.
691          */

692         public SuffixSet(final String JavaDoc suffixes) {
693             if (suffixes == null)
694                 return;
695
696             final String JavaDoc[] split = suffixes.split("\\|");
697             for (int i = 0, l = split.length; i < l; i++) {
698                 final String JavaDoc suffix = split[i];
699                 if (suffix.length() > 0)
700                     add(suffix);
701             }
702         }
703
704         public SuffixSet(final Collection JavaDoc c) {
705             super(c);
706         }
707
708         /**
709          * @deprecated Use {@link #add(String)} instead!
710          */

711         public boolean add(Object JavaDoc o) {
712             return add((String JavaDoc) o);
713         }
714
715         /**
716          * Adds the normalized lowercase representation of suffix.
717          *
718          * @returns <code>true</code> iff a normalized suffix was added.
719          * @throws NullPointerException If <code>suffix</code> is <code>null</code>.
720          */

721         public boolean add(String JavaDoc suffix) {
722             // Normalize suffixes before adding them to the set.
723
suffix = suffix.replaceAll("\\\\\\.", "\\.");
724             if (suffix.length() > 0 && suffix.charAt(0) == '.')
725                 suffix = suffix.substring(1);
726             if (suffix.length() > 0)
727                 return super.add(suffix.toLowerCase());
728             return false;
729         }
730
731         public String JavaDoc toRegex() {
732             if (isEmpty())
733                 return "\\00"; // NOT "\00"! Effectively never matches anything.
734

735             final StringBuffer JavaDoc sb = new StringBuffer JavaDoc(".*\\.(");
736             int c = 0;
737             for (Iterator JavaDoc i = iterator(); i.hasNext(); ) {
738                 final String JavaDoc suffix = (String JavaDoc) i.next();
739                 if (c > 0)
740                     sb.append('|');
741                 sb.append("\\Q");
742                 sb.append(suffix);
743                 sb.append("\\E");
744                 c++;
745             }
746             sb.append(')');
747
748             return sb.toString();
749         }
750         
751         public String JavaDoc toString() {
752             if (isEmpty())
753                 return null;
754
755             final StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
756             int c = 0;
757             for (Iterator JavaDoc i = iterator(); i.hasNext(); ) {
758                 final String JavaDoc suffix = (String JavaDoc) i.next();
759                 if (c > 0)
760                     sb.append('|');
761                 sb.append(suffix);
762                 c++;
763             }
764             assert c > 0;
765
766             return sb.toString();
767         }
768     }
769
770     private static final class ThreadLocalMatcher extends ThreadLocal JavaDoc {
771         private final String JavaDoc regex;
772
773         private ThreadLocalMatcher(final String JavaDoc regex) {
774             this.regex = regex;
775         }
776
777         protected final Object JavaDoc initialValue() {
778             // Not the most efficient implementation to reconstruct a
779
// SuffixSet from the suffixes list again, but by doing so
780
// we get the serialization of this ThreadLocalMatcher for free,
781
// i.e. without implementing readObject in the enclosing class.
782
return Pattern.compile(regex,
783                     Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE)
784                     .matcher("");
785         }
786
787         public final Matcher JavaDoc reset(CharSequence JavaDoc input) {
788             return ((Matcher JavaDoc) get()).reset(input);
789         }
790
791         /*public final boolean matches() {
792             return ((Matcher) tl.get()).matches();
793         }
794
795         public final String group(int i) {
796             return ((Matcher) tl.get()).group(i);
797         }*/

798     }
799 }
800
Popular Tags