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