001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.xbean.finder;
018    
019    import java.io.BufferedInputStream;
020    import java.io.File;
021    import java.io.IOException;
022    import java.io.InputStream;
023    import java.net.HttpURLConnection;
024    import java.net.JarURLConnection;
025    import java.net.MalformedURLException;
026    import java.net.URL;
027    import java.net.URLConnection;
028    import java.util.ArrayList;
029    import java.util.Collections;
030    import java.util.Enumeration;
031    import java.util.HashMap;
032    import java.util.Iterator;
033    import java.util.List;
034    import java.util.Map;
035    import java.util.Properties;
036    import java.util.Vector;
037    import java.util.jar.JarEntry;
038    import java.util.jar.JarFile;
039    
040    /**
041     * @author David Blevins
042     * @version $Rev: 475549 $ $Date: 2006-11-16 04:02:32 +0100 (Thu, 16 Nov 2006) $
043     */
044    public class ResourceFinder {
045    
046        private final URL[] urls;
047        private final String path;
048        private final ClassLoader classLoader;
049        private final List<String> resourcesNotLoaded = new ArrayList<String>();
050    
051        public ResourceFinder(URL... urls) {
052            this(null, Thread.currentThread().getContextClassLoader(), urls);
053        }
054    
055        public ResourceFinder(String path) {
056            this(path, Thread.currentThread().getContextClassLoader(), null);
057        }
058    
059        public ResourceFinder(String path, URL... urls) {
060            this(path, Thread.currentThread().getContextClassLoader(), urls);
061        }
062    
063        public ResourceFinder(String path, ClassLoader classLoader) {
064            this(path, classLoader, null);
065        }
066    
067        public ResourceFinder(String path, ClassLoader classLoader, URL... urls) {
068            if (path == null){
069                path = "";
070            } else if (path.length() > 0 && !path.endsWith("/")) {
071                path += "/";
072            }
073            this.path = path;
074    
075            if (classLoader == null) {
076                classLoader = Thread.currentThread().getContextClassLoader();
077            }
078            this.classLoader = classLoader;
079    
080            for (int i = 0; urls != null && i < urls.length; i++) {
081                URL url = urls[i];
082                if (url == null || isDirectory(url) || url.getProtocol().equals("jar")) {
083                    continue;
084                }
085                try {
086                    urls[i] = new URL("jar", "", -1, url.toString() + "!/");
087                } catch (MalformedURLException e) {
088                }
089            }
090            this.urls = (urls == null || urls.length == 0)? null : urls;
091        }
092    
093        private static boolean isDirectory(URL url) {
094            String file = url.getFile();
095            return (file.length() > 0 && file.charAt(file.length() - 1) == '/');
096        }
097    
098        /**
099         * Returns a list of resources that could not be loaded in the last invoked findAvailable* or
100         * mapAvailable* methods.
101         * <p/>
102         * The list will only contain entries of resources that match the requirements
103         * of the last invoked findAvailable* or mapAvailable* methods, but were unable to be
104         * loaded and included in their results.
105         * <p/>
106         * The list returned is unmodifiable and the results of this method will change
107         * after each invocation of a findAvailable* or mapAvailable* methods.
108         * <p/>
109         * This method is not thread safe.
110         */
111        public List<String> getResourcesNotLoaded() {
112            return Collections.unmodifiableList(resourcesNotLoaded);
113        }
114    
115        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
116        //
117        //   Find
118        //
119        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
120    
121        public URL find(String uri) throws IOException {
122            String fullUri = path + uri;
123    
124            URL resource = getResource(fullUri);
125            if (resource == null) {
126                throw new IOException("Could not find resource '" + fullUri + "'");
127            }
128    
129            return resource;
130        }
131    
132        public List<URL> findAll(String uri) throws IOException {
133            String fullUri = path + uri;
134    
135            Enumeration<URL> resources = getResources(fullUri);
136            List<URL> list = new ArrayList();
137            while (resources.hasMoreElements()) {
138                URL url = resources.nextElement();
139                list.add(url);
140            }
141            return list;
142        }
143    
144    
145        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
146        //
147        //   Find String
148        //
149        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
150    
151        /**
152         * Reads the contents of the URL as a {@link String}'s and returns it.
153         *
154         * @param uri
155         * @return a stringified content of a resource
156         * @throws IOException if a resource pointed out by the uri param could not be find
157         * @see ClassLoader#getResource(String)
158         */
159        public String findString(String uri) throws IOException {
160            String fullUri = path + uri;
161    
162            URL resource = getResource(fullUri);
163            if (resource == null) {
164                throw new IOException("Could not find a resource in : " + fullUri);
165            }
166    
167            return readContents(resource);
168        }
169    
170        /**
171         * Reads the contents of the found URLs as a list of {@link String}'s and returns them.
172         *
173         * @param uri
174         * @return a list of the content of each resource URL found
175         * @throws IOException if any of the found URLs are unable to be read.
176         */
177        public List<String> findAllStrings(String uri) throws IOException {
178            String fulluri = path + uri;
179    
180            List<String> strings = new ArrayList<String>();
181    
182            Enumeration<URL> resources = getResources(fulluri);
183            while (resources.hasMoreElements()) {
184                URL url = resources.nextElement();
185                String string = readContents(url);
186                strings.add(string);
187            }
188            return strings;
189        }
190    
191        /**
192         * Reads the contents of the found URLs as a Strings and returns them.
193         * Individual URLs that cannot be read are skipped and added to the
194         * list of 'resourcesNotLoaded'
195         *
196         * @param uri
197         * @return a list of the content of each resource URL found
198         * @throws IOException if classLoader.getResources throws an exception
199         */
200        public List<String> findAvailableStrings(String uri) throws IOException {
201            resourcesNotLoaded.clear();
202            String fulluri = path + uri;
203    
204            List<String> strings = new ArrayList<String>();
205    
206            Enumeration<URL> resources = getResources(fulluri);
207            while (resources.hasMoreElements()) {
208                URL url = resources.nextElement();
209                try {
210                    String string = readContents(url);
211                    strings.add(string);
212                } catch (IOException notAvailable) {
213                    resourcesNotLoaded.add(url.toExternalForm());
214                }
215            }
216            return strings;
217        }
218    
219        /**
220         * Reads the contents of all non-directory URLs immediately under the specified
221         * location and returns them in a map keyed by the file name.
222         * <p/>
223         * Any URLs that cannot be read will cause an exception to be thrown.
224         * <p/>
225         * Example classpath:
226         * <p/>
227         * META-INF/serializables/one
228         * META-INF/serializables/two
229         * META-INF/serializables/three
230         * META-INF/serializables/four/foo.txt
231         * <p/>
232         * ResourceFinder finder = new ResourceFinder("META-INF/");
233         * Map map = finder.mapAvailableStrings("serializables");
234         * map.contains("one");  // true
235         * map.contains("two");  // true
236         * map.contains("three");  // true
237         * map.contains("four");  // false
238         *
239         * @param uri
240         * @return a list of the content of each resource URL found
241         * @throws IOException if any of the urls cannot be read
242         */
243        public Map<String, String> mapAllStrings(String uri) throws IOException {
244            Map<String, String> strings = new HashMap<String, String>();
245            Map<String, URL> resourcesMap = getResourcesMap(uri);
246            for (Iterator iterator = resourcesMap.entrySet().iterator(); iterator.hasNext();) {
247                Map.Entry entry = (Map.Entry) iterator.next();
248                String name = (String) entry.getKey();
249                URL url = (URL) entry.getValue();
250                String value = readContents(url);
251                strings.put(name, value);
252            }
253            return strings;
254        }
255    
256        /**
257         * Reads the contents of all non-directory URLs immediately under the specified
258         * location and returns them in a map keyed by the file name.
259         * <p/>
260         * Individual URLs that cannot be read are skipped and added to the
261         * list of 'resourcesNotLoaded'
262         * <p/>
263         * Example classpath:
264         * <p/>
265         * META-INF/serializables/one
266         * META-INF/serializables/two      # not readable
267         * META-INF/serializables/three
268         * META-INF/serializables/four/foo.txt
269         * <p/>
270         * ResourceFinder finder = new ResourceFinder("META-INF/");
271         * Map map = finder.mapAvailableStrings("serializables");
272         * map.contains("one");  // true
273         * map.contains("two");  // false
274         * map.contains("three");  // true
275         * map.contains("four");  // false
276         *
277         * @param uri
278         * @return a list of the content of each resource URL found
279         * @throws IOException if classLoader.getResources throws an exception
280         */
281        public Map<String, String> mapAvailableStrings(String uri) throws IOException {
282            resourcesNotLoaded.clear();
283            Map<String, String> strings = new HashMap<String, String>();
284            Map<String, URL> resourcesMap = getResourcesMap(uri);
285            for (Iterator iterator = resourcesMap.entrySet().iterator(); iterator.hasNext();) {
286                Map.Entry entry = (Map.Entry) iterator.next();
287                String name = (String) entry.getKey();
288                URL url = (URL) entry.getValue();
289                try {
290                    String value = readContents(url);
291                    strings.put(name, value);
292                } catch (IOException notAvailable) {
293                    resourcesNotLoaded.add(url.toExternalForm());
294                }
295            }
296            return strings;
297        }
298    
299        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
300        //
301        //   Find Class
302        //
303        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
304    
305        /**
306         * Executes {@link #findString(String)} assuming the contents URL found is the name of
307         * a class that should be loaded and returned.
308         *
309         * @param uri
310         * @return
311         * @throws IOException
312         * @throws ClassNotFoundException
313         */
314        public Class findClass(String uri) throws IOException, ClassNotFoundException {
315            String className = findString(uri);
316            return (Class) classLoader.loadClass(className);
317        }
318    
319        /**
320         * Executes findAllStrings assuming the strings are
321         * the names of a classes that should be loaded and returned.
322         * <p/>
323         * Any URL or class that cannot be loaded will cause an exception to be thrown.
324         *
325         * @param uri
326         * @return
327         * @throws IOException
328         * @throws ClassNotFoundException
329         */
330        public List<Class> findAllClasses(String uri) throws IOException, ClassNotFoundException {
331            List<Class> classes = new ArrayList<Class>();
332            List<String> strings = findAllStrings(uri);
333            for (String className : strings) {
334                Class clazz = classLoader.loadClass(className);
335                classes.add(clazz);
336            }
337            return classes;
338        }
339    
340        /**
341         * Executes findAvailableStrings assuming the strings are
342         * the names of a classes that should be loaded and returned.
343         * <p/>
344         * Any class that cannot be loaded will be skipped and placed in the
345         * 'resourcesNotLoaded' collection.
346         *
347         * @param uri
348         * @return
349         * @throws IOException if classLoader.getResources throws an exception
350         */
351        public List<Class> findAvailableClasses(String uri) throws IOException {
352            resourcesNotLoaded.clear();
353            List<Class> classes = new ArrayList<Class>();
354            List<String> strings = findAvailableStrings(uri);
355            for (String className : strings) {
356                try {
357                    Class clazz = classLoader.loadClass(className);
358                    classes.add(clazz);
359                } catch (Exception notAvailable) {
360                    resourcesNotLoaded.add(className);
361                }
362            }
363            return classes;
364        }
365    
366        /**
367         * Executes mapAllStrings assuming the value of each entry in the
368         * map is the name of a class that should be loaded.
369         * <p/>
370         * Any class that cannot be loaded will be cause an exception to be thrown.
371         * <p/>
372         * Example classpath:
373         * <p/>
374         * META-INF/xmlparsers/xerces
375         * META-INF/xmlparsers/crimson
376         * <p/>
377         * ResourceFinder finder = new ResourceFinder("META-INF/");
378         * Map map = finder.mapAvailableStrings("xmlparsers");
379         * map.contains("xerces");  // true
380         * map.contains("crimson");  // true
381         * Class xercesClass = map.get("xerces");
382         * Class crimsonClass = map.get("crimson");
383         *
384         * @param uri
385         * @return
386         * @throws IOException
387         * @throws ClassNotFoundException
388         */
389        public Map<String, Class> mapAllClasses(String uri) throws IOException, ClassNotFoundException {
390            Map<String, Class> classes = new HashMap<String, Class>();
391            Map<String, String> map = mapAllStrings(uri);
392            for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
393                Map.Entry entry = (Map.Entry) iterator.next();
394                String string = (String) entry.getKey();
395                String className = (String) entry.getValue();
396                Class clazz = classLoader.loadClass(className);
397                classes.put(string, clazz);
398            }
399            return classes;
400        }
401    
402        /**
403         * Executes mapAvailableStrings assuming the value of each entry in the
404         * map is the name of a class that should be loaded.
405         * <p/>
406         * Any class that cannot be loaded will be skipped and placed in the
407         * 'resourcesNotLoaded' collection.
408         * <p/>
409         * Example classpath:
410         * <p/>
411         * META-INF/xmlparsers/xerces
412         * META-INF/xmlparsers/crimson
413         * <p/>
414         * ResourceFinder finder = new ResourceFinder("META-INF/");
415         * Map map = finder.mapAvailableStrings("xmlparsers");
416         * map.contains("xerces");  // true
417         * map.contains("crimson");  // true
418         * Class xercesClass = map.get("xerces");
419         * Class crimsonClass = map.get("crimson");
420         *
421         * @param uri
422         * @return
423         * @throws IOException if classLoader.getResources throws an exception
424         */
425        public Map<String, Class> mapAvailableClasses(String uri) throws IOException {
426            resourcesNotLoaded.clear();
427            Map<String, Class> classes = new HashMap<String, Class>();
428            Map<String, String> map = mapAvailableStrings(uri);
429            for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
430                Map.Entry entry = (Map.Entry) iterator.next();
431                String string = (String) entry.getKey();
432                String className = (String) entry.getValue();
433                try {
434                    Class clazz = classLoader.loadClass(className);
435                    classes.put(string, clazz);
436                } catch (Exception notAvailable) {
437                    resourcesNotLoaded.add(className);
438                }
439            }
440            return classes;
441        }
442    
443        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
444        //
445        //   Find Implementation
446        //
447        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
448    
449        /**
450         * Assumes the class specified points to a file in the classpath that contains
451         * the name of a class that implements or is a subclass of the specfied class.
452         * <p/>
453         * Any class that cannot be loaded will be cause an exception to be thrown.
454         * <p/>
455         * Example classpath:
456         * <p/>
457         * META-INF/java.io.InputStream    # contains the classname org.acme.AcmeInputStream
458         * META-INF/java.io.OutputStream
459         * <p/>
460         * ResourceFinder finder = new ResourceFinder("META-INF/");
461         * Class clazz = finder.findImplementation(java.io.InputStream.class);
462         * clazz.getName();  // returns "org.acme.AcmeInputStream"
463         *
464         * @param interfase a superclass or interface
465         * @return
466         * @throws IOException            if the URL cannot be read
467         * @throws ClassNotFoundException if the class found is not loadable
468         * @throws ClassCastException     if the class found is not assignable to the specified superclass or interface
469         */
470        public Class findImplementation(Class interfase) throws IOException, ClassNotFoundException {
471            String className = findString(interfase.getName());
472            Class impl = classLoader.loadClass(className);
473            if (!interfase.isAssignableFrom(impl)) {
474                throw new ClassCastException("Class not of type: " + interfase.getName());
475            }
476            return impl;
477        }
478    
479        /**
480         * Assumes the class specified points to a file in the classpath that contains
481         * the name of a class that implements or is a subclass of the specfied class.
482         * <p/>
483         * Any class that cannot be loaded or assigned to the specified interface will be cause
484         * an exception to be thrown.
485         * <p/>
486         * Example classpath:
487         * <p/>
488         * META-INF/java.io.InputStream    # contains the classname org.acme.AcmeInputStream
489         * META-INF/java.io.InputStream    # contains the classname org.widget.NeatoInputStream
490         * META-INF/java.io.InputStream    # contains the classname com.foo.BarInputStream
491         * <p/>
492         * ResourceFinder finder = new ResourceFinder("META-INF/");
493         * List classes = finder.findAllImplementations(java.io.InputStream.class);
494         * classes.contains("org.acme.AcmeInputStream");  // true
495         * classes.contains("org.widget.NeatoInputStream");  // true
496         * classes.contains("com.foo.BarInputStream");  // true
497         *
498         * @param interfase a superclass or interface
499         * @return
500         * @throws IOException            if the URL cannot be read
501         * @throws ClassNotFoundException if the class found is not loadable
502         * @throws ClassCastException     if the class found is not assignable to the specified superclass or interface
503         */
504        public List<Class> findAllImplementations(Class interfase) throws IOException, ClassNotFoundException {
505            List<Class> implementations = new ArrayList<Class>();
506            List<String> strings = findAllStrings(interfase.getName());
507            for (String className : strings) {
508                Class impl = classLoader.loadClass(className);
509                if (!interfase.isAssignableFrom(impl)) {
510                    throw new ClassCastException("Class not of type: " + interfase.getName());
511                }
512                implementations.add(impl);
513            }
514            return implementations;
515        }
516    
517        /**
518         * Assumes the class specified points to a file in the classpath that contains
519         * the name of a class that implements or is a subclass of the specfied class.
520         * <p/>
521         * Any class that cannot be loaded or are not assignable to the specified class will be
522         * skipped and placed in the 'resourcesNotLoaded' collection.
523         * <p/>
524         * Example classpath:
525         * <p/>
526         * META-INF/java.io.InputStream    # contains the classname org.acme.AcmeInputStream
527         * META-INF/java.io.InputStream    # contains the classname org.widget.NeatoInputStream
528         * META-INF/java.io.InputStream    # contains the classname com.foo.BarInputStream
529         * <p/>
530         * ResourceFinder finder = new ResourceFinder("META-INF/");
531         * List classes = finder.findAllImplementations(java.io.InputStream.class);
532         * classes.contains("org.acme.AcmeInputStream");  // true
533         * classes.contains("org.widget.NeatoInputStream");  // true
534         * classes.contains("com.foo.BarInputStream");  // true
535         *
536         * @param interfase a superclass or interface
537         * @return
538         * @throws IOException if classLoader.getResources throws an exception
539         */
540        public List<Class> findAvailableImplementations(Class interfase) throws IOException {
541            resourcesNotLoaded.clear();
542            List<Class> implementations = new ArrayList<Class>();
543            List<String> strings = findAvailableStrings(interfase.getName());
544            for (String className : strings) {
545                try {
546                    Class impl = classLoader.loadClass(className);
547                    if (interfase.isAssignableFrom(impl)) {
548                        implementations.add(impl);
549                    } else {
550                        resourcesNotLoaded.add(className);
551                    }
552                } catch (Exception notAvailable) {
553                    resourcesNotLoaded.add(className);
554                }
555            }
556            return implementations;
557        }
558    
559        /**
560         * Assumes the class specified points to a directory in the classpath that holds files
561         * containing the name of a class that implements or is a subclass of the specfied class.
562         * <p/>
563         * Any class that cannot be loaded or assigned to the specified interface will be cause
564         * an exception to be thrown.
565         * <p/>
566         * Example classpath:
567         * <p/>
568         * META-INF/java.net.URLStreamHandler/jar
569         * META-INF/java.net.URLStreamHandler/file
570         * META-INF/java.net.URLStreamHandler/http
571         * <p/>
572         * ResourceFinder finder = new ResourceFinder("META-INF/");
573         * Map map = finder.mapAllImplementations(java.net.URLStreamHandler.class);
574         * Class jarUrlHandler = map.get("jar");
575         * Class fileUrlHandler = map.get("file");
576         * Class httpUrlHandler = map.get("http");
577         *
578         * @param interfase a superclass or interface
579         * @return
580         * @throws IOException            if the URL cannot be read
581         * @throws ClassNotFoundException if the class found is not loadable
582         * @throws ClassCastException     if the class found is not assignable to the specified superclass or interface
583         */
584        public Map<String, Class> mapAllImplementations(Class interfase) throws IOException, ClassNotFoundException {
585            Map<String, Class> implementations = new HashMap<String, Class>();
586            Map<String, String> map = mapAllStrings(interfase.getName());
587            for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
588                Map.Entry entry = (Map.Entry) iterator.next();
589                String string = (String) entry.getKey();
590                String className = (String) entry.getValue();
591                Class impl = classLoader.loadClass(className);
592                if (!interfase.isAssignableFrom(impl)) {
593                    throw new ClassCastException("Class not of type: " + interfase.getName());
594                }
595                implementations.put(string, impl);
596            }
597            return implementations;
598        }
599    
600        /**
601         * Assumes the class specified points to a directory in the classpath that holds files
602         * containing the name of a class that implements or is a subclass of the specfied class.
603         * <p/>
604         * Any class that cannot be loaded or are not assignable to the specified class will be
605         * skipped and placed in the 'resourcesNotLoaded' collection.
606         * <p/>
607         * Example classpath:
608         * <p/>
609         * META-INF/java.net.URLStreamHandler/jar
610         * META-INF/java.net.URLStreamHandler/file
611         * META-INF/java.net.URLStreamHandler/http
612         * <p/>
613         * ResourceFinder finder = new ResourceFinder("META-INF/");
614         * Map map = finder.mapAllImplementations(java.net.URLStreamHandler.class);
615         * Class jarUrlHandler = map.get("jar");
616         * Class fileUrlHandler = map.get("file");
617         * Class httpUrlHandler = map.get("http");
618         *
619         * @param interfase a superclass or interface
620         * @return
621         * @throws IOException if classLoader.getResources throws an exception
622         */
623        public Map<String, Class> mapAvailableImplementations(Class interfase) throws IOException {
624            resourcesNotLoaded.clear();
625            Map<String, Class> implementations = new HashMap<String, Class>();
626            Map<String, String> map = mapAvailableStrings(interfase.getName());
627            for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
628                Map.Entry entry = (Map.Entry) iterator.next();
629                String string = (String) entry.getKey();
630                String className = (String) entry.getValue();
631                try {
632                    Class impl = classLoader.loadClass(className);
633                    if (interfase.isAssignableFrom(impl)) {
634                        implementations.put(string, impl);
635                    } else {
636                        resourcesNotLoaded.add(className);
637                    }
638                } catch (Exception notAvailable) {
639                    resourcesNotLoaded.add(className);
640                }
641            }
642            return implementations;
643        }
644    
645        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
646        //
647        //   Find Properties
648        //
649        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
650    
651        /**
652         * Finds the corresponding resource and reads it in as a properties file
653         * <p/>
654         * Example classpath:
655         * <p/>
656         * META-INF/widget.properties
657         * <p/>
658         * ResourceFinder finder = new ResourceFinder("META-INF/");
659         * Properties widgetProps = finder.findProperties("widget.properties");
660         *
661         * @param uri
662         * @return
663         * @throws IOException if the URL cannot be read or is not in properties file format
664         */
665        public Properties findProperties(String uri) throws IOException {
666            String fulluri = path + uri;
667    
668            URL resource = getResource(fulluri);
669            if (resource == null) {
670                throw new IOException("Could not find command in : " + fulluri);
671            }
672    
673            return loadProperties(resource);
674        }
675    
676        /**
677         * Finds the corresponding resources and reads them in as a properties files
678         * <p/>
679         * Any URL that cannot be read in as a properties file will cause an exception to be thrown.
680         * <p/>
681         * Example classpath:
682         * <p/>
683         * META-INF/app.properties
684         * META-INF/app.properties
685         * META-INF/app.properties
686         * <p/>
687         * ResourceFinder finder = new ResourceFinder("META-INF/");
688         * List<Properties> appProps = finder.findAllProperties("app.properties");
689         *
690         * @param uri
691         * @return
692         * @throws IOException if the URL cannot be read or is not in properties file format
693         */
694        public List<Properties> findAllProperties(String uri) throws IOException {
695            String fulluri = path + uri;
696    
697            List<Properties> properties = new ArrayList<Properties>();
698    
699            Enumeration<URL> resources = getResources(fulluri);
700            while (resources.hasMoreElements()) {
701                URL url = resources.nextElement();
702                Properties props = loadProperties(url);
703                properties.add(props);
704            }
705            return properties;
706        }
707    
708        /**
709         * Finds the corresponding resources and reads them in as a properties files
710         * <p/>
711         * Any URL that cannot be read in as a properties file will be added to the
712         * 'resourcesNotLoaded' collection.
713         * <p/>
714         * Example classpath:
715         * <p/>
716         * META-INF/app.properties
717         * META-INF/app.properties
718         * META-INF/app.properties
719         * <p/>
720         * ResourceFinder finder = new ResourceFinder("META-INF/");
721         * List<Properties> appProps = finder.findAvailableProperties("app.properties");
722         *
723         * @param uri
724         * @return
725         * @throws IOException if classLoader.getResources throws an exception
726         */
727        public List<Properties> findAvailableProperties(String uri) throws IOException {
728            resourcesNotLoaded.clear();
729            String fulluri = path + uri;
730    
731            List<Properties> properties = new ArrayList<Properties>();
732    
733            Enumeration<URL> resources = getResources(fulluri);
734            while (resources.hasMoreElements()) {
735                URL url = resources.nextElement();
736                try {
737                    Properties props = loadProperties(url);
738                    properties.add(props);
739                } catch (Exception notAvailable) {
740                    resourcesNotLoaded.add(url.toExternalForm());
741                }
742            }
743            return properties;
744        }
745    
746        /**
747         * Finds the corresponding resources and reads them in as a properties files
748         * <p/>
749         * Any URL that cannot be read in as a properties file will cause an exception to be thrown.
750         * <p/>
751         * Example classpath:
752         * <p/>
753         * META-INF/jdbcDrivers/oracle.properties
754         * META-INF/jdbcDrivers/mysql.props
755         * META-INF/jdbcDrivers/derby
756         * <p/>
757         * ResourceFinder finder = new ResourceFinder("META-INF/");
758         * List<Properties> driversList = finder.findAvailableProperties("jdbcDrivers");
759         * Properties oracleProps = driversList.get("oracle.properties");
760         * Properties mysqlProps = driversList.get("mysql.props");
761         * Properties derbyProps = driversList.get("derby");
762         *
763         * @param uri
764         * @return
765         * @throws IOException if the URL cannot be read or is not in properties file format
766         */
767        public Map<String, Properties> mapAllProperties(String uri) throws IOException {
768            Map<String, Properties> propertiesMap = new HashMap<String, Properties>();
769            Map<String, URL> map = getResourcesMap(uri);
770            for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
771                Map.Entry entry = (Map.Entry) iterator.next();
772                String string = (String) entry.getKey();
773                URL url = (URL) entry.getValue();
774                Properties properties = loadProperties(url);
775                propertiesMap.put(string, properties);
776            }
777            return propertiesMap;
778        }
779    
780        /**
781         * Finds the corresponding resources and reads them in as a properties files
782         * <p/>
783         * Any URL that cannot be read in as a properties file will be added to the
784         * 'resourcesNotLoaded' collection.
785         * <p/>
786         * Example classpath:
787         * <p/>
788         * META-INF/jdbcDrivers/oracle.properties
789         * META-INF/jdbcDrivers/mysql.props
790         * META-INF/jdbcDrivers/derby
791         * <p/>
792         * ResourceFinder finder = new ResourceFinder("META-INF/");
793         * List<Properties> driversList = finder.findAvailableProperties("jdbcDrivers");
794         * Properties oracleProps = driversList.get("oracle.properties");
795         * Properties mysqlProps = driversList.get("mysql.props");
796         * Properties derbyProps = driversList.get("derby");
797         *
798         * @param uri
799         * @return
800         * @throws IOException if classLoader.getResources throws an exception
801         */
802        public Map<String, Properties> mapAvailableProperties(String uri) throws IOException {
803            resourcesNotLoaded.clear();
804            Map<String, Properties> propertiesMap = new HashMap<String, Properties>();
805            Map<String, URL> map = getResourcesMap(uri);
806            for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
807                Map.Entry entry = (Map.Entry) iterator.next();
808                String string = (String) entry.getKey();
809                URL url = (URL) entry.getValue();
810                try {
811                    Properties properties = loadProperties(url);
812                    propertiesMap.put(string, properties);
813                } catch (Exception notAvailable) {
814                    resourcesNotLoaded.add(url.toExternalForm());
815                }
816            }
817            return propertiesMap;
818        }
819    
820        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
821        //
822        //   Map Resources
823        //
824        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
825    
826        public Map<String, URL> getResourcesMap(String uri) throws IOException {
827            String basePath = path + uri;
828    
829            Map<String, URL> resources = new HashMap<String, URL>();
830            if (!basePath.endsWith("/")) {
831                basePath += "/";
832            }
833            Enumeration<URL> urls = getResources(basePath);
834    
835            while (urls.hasMoreElements()) {
836                URL location = urls.nextElement();
837    
838                try {
839                    if (location.getProtocol().equals("jar")) {
840    
841                        readJarEntries(location, basePath, resources);
842    
843                    } else if (location.getProtocol().equals("file")) {
844    
845                        readDirectoryEntries(location, resources);
846    
847                    }
848                } catch (Exception e) {
849                }
850            }
851    
852            return resources;
853        }
854    
855        private static void readDirectoryEntries(URL location, Map<String, URL> resources) throws MalformedURLException {
856            File dir = new File(location.getPath());
857            if (dir.isDirectory()) {
858                File[] files = dir.listFiles();
859                for (File file : files) {
860                    if (!file.isDirectory()) {
861                        String name = file.getName();
862                        URL url = file.toURL();
863                        resources.put(name, url);
864                    }
865                }
866            }
867        }
868    
869        private static void readJarEntries(URL location, String basePath, Map<String, URL> resources) throws IOException {
870            JarURLConnection conn = (JarURLConnection) location.openConnection();
871            JarFile jarfile = null;
872            jarfile = conn.getJarFile();
873    
874            Enumeration<JarEntry> entries = jarfile.entries();
875            while (entries != null && entries.hasMoreElements()) {
876                JarEntry entry = entries.nextElement();
877                String name = entry.getName();
878    
879                if (entry.isDirectory() || !name.startsWith(basePath) || name.length() == basePath.length()) {
880                    continue;
881                }
882    
883                name = name.substring(basePath.length());
884    
885                if (name.contains("/")) {
886                    continue;
887                }
888    
889                URL resource = new URL(location, name);
890                resources.put(name, resource);
891            }
892        }
893    
894        private Properties loadProperties(URL resource) throws IOException {
895            InputStream in = resource.openStream();
896    
897            BufferedInputStream reader = null;
898            try {
899                reader = new BufferedInputStream(in);
900                Properties properties = new Properties();
901                properties.load(reader);
902    
903                return properties;
904            } finally {
905                try {
906                    in.close();
907                    reader.close();
908                } catch (Exception e) {
909                }
910            }
911        }
912    
913        private String readContents(URL resource) throws IOException {
914            InputStream in = resource.openStream();
915            BufferedInputStream reader = null;
916            StringBuffer sb = new StringBuffer();
917    
918            try {
919                reader = new BufferedInputStream(in);
920    
921                int b = reader.read();
922                while (b != -1) {
923                    sb.append((char) b);
924                    b = reader.read();
925                }
926    
927                return sb.toString().trim();
928            } finally {
929                try {
930                    in.close();
931                    reader.close();
932                } catch (Exception e) {
933                }
934            }
935        }
936    
937        private URL getResource(String fullUri) {
938            if (urls == null){
939                return classLoader.getResource(fullUri);
940            }
941            return findResource(fullUri, urls);
942        }
943    
944        private Enumeration<URL> getResources(String fulluri) throws IOException {
945            if (urls == null) {
946                return classLoader.getResources(fulluri);
947            }
948            Vector<URL> resources = new Vector();
949            for (URL url : urls) {
950                URL resource = findResource(fulluri, url);
951                if (resource != null){
952                    resources.add(resource);
953                }
954            }
955            return resources.elements();
956        }
957    
958        private URL findResource(String resourceName, URL... search) {
959            for (int i = 0; i < search.length; i++) {
960                URL currentUrl = search[i];
961                if (currentUrl == null) {
962                    continue;
963                }
964                JarFile jarFile = null;
965                try {
966                    String protocol = currentUrl.getProtocol();
967                    if (protocol.equals("jar")) {
968                        /*
969                        * If the connection for currentUrl or resURL is
970                        * used, getJarFile() will throw an exception if the
971                        * entry doesn't exist.
972                        */
973                        URL jarURL = ((JarURLConnection) currentUrl.openConnection()).getJarFileURL();
974                        try {
975                            JarURLConnection juc = (JarURLConnection) new URL("jar", "", jarURL.toExternalForm() + "!/").openConnection();
976                            jarFile = juc.getJarFile();
977                        } catch (IOException e) {
978                            // Don't look for this jar file again
979                            search[i] = null;
980                            throw e;
981                        }
982    
983                        String entryName;
984                        if (currentUrl.getFile().endsWith("!/")) {
985                            entryName = resourceName;
986                        } else {
987                            String file = currentUrl.getFile();
988                            int sepIdx = file.lastIndexOf("!/");
989                            if (sepIdx == -1) {
990                                // Invalid URL, don't look here again
991                                search[i] = null;
992                                continue;
993                            }
994                            sepIdx += 2;
995                            StringBuffer sb = new StringBuffer(file.length() - sepIdx + resourceName.length());
996                            sb.append(file.substring(sepIdx));
997                            sb.append(resourceName);
998                            entryName = sb.toString();
999                        }
1000                        if (entryName.equals("META-INF/") && jarFile.getEntry("META-INF/MANIFEST.MF") != null){
1001                            return targetURL(currentUrl, "META-INF/MANIFEST.MF");
1002                        }
1003                        if (jarFile.getEntry(entryName) != null) {
1004                            return targetURL(currentUrl, resourceName);
1005                        }
1006                    } else if (protocol.equals("file")) {
1007                        String baseFile = currentUrl.getFile();
1008                        String host = currentUrl.getHost();
1009                        int hostLength = 0;
1010                        if (host != null) {
1011                            hostLength = host.length();
1012                        }
1013                        StringBuffer buf = new StringBuffer(2 + hostLength + baseFile.length() + resourceName.length());
1014    
1015                        if (hostLength > 0) {
1016                            buf.append("//").append(host);
1017                        }
1018                        // baseFile always ends with '/'
1019                        buf.append(baseFile);
1020                        String fixedResName = resourceName;
1021                        // Do not create a UNC path, i.e. \\host
1022                        while (fixedResName.startsWith("/") || fixedResName.startsWith("\\")) {
1023                            fixedResName = fixedResName.substring(1);
1024                        }
1025                        buf.append(fixedResName);
1026                        String filename = buf.toString();
1027                        File file = new File(filename);
1028                        if (file.exists()) {
1029                            return targetURL(currentUrl, fixedResName);
1030                        }
1031                    } else {
1032                        URL resourceURL = targetURL(currentUrl, resourceName);
1033                        URLConnection urlConnection = resourceURL.openConnection();
1034    
1035                        try {
1036                            urlConnection.getInputStream().close();
1037                        } catch (SecurityException e) {
1038                            return null;
1039                        }
1040                        // HTTP can return a stream on a non-existent file
1041                        // So check for the return code;
1042                        if (!resourceURL.getProtocol().equals("http")) {
1043                            return resourceURL;
1044                        }
1045    
1046                        int code = ((HttpURLConnection) urlConnection).getResponseCode();
1047                        if (code >= 200 && code < 300) {
1048                            return resourceURL;
1049                        }
1050                    }
1051                } catch (MalformedURLException e) {
1052                    // Keep iterating through the URL list
1053                } catch (IOException e) {
1054                } catch (SecurityException e) {
1055                }
1056            }
1057            return null;
1058        }
1059    
1060        private URL targetURL(URL base, String name) throws MalformedURLException {
1061            StringBuffer sb = new StringBuffer(base.getFile().length() + name.length());
1062            sb.append(base.getFile());
1063            sb.append(name);
1064            String file = sb.toString();
1065            return new URL(base.getProtocol(), base.getHost(), base.getPort(), file, null);
1066        }
1067    }