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.ByteArrayOutputStream;
021    import java.io.File;
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.net.HttpURLConnection;
025    import java.net.JarURLConnection;
026    import java.net.MalformedURLException;
027    import java.net.URL;
028    import java.net.URLConnection;
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: 1150270 $ $Date: 2011-07-24 11:25:40 +0800 (Sun, 24 Jul 2011) $
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 '" + path + uri + "'");
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 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 (Map.Entry<String, String> entry : map.entrySet()) {
394                String string = entry.getKey();
395                String className = 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 (Map.Entry<String, String> entry : map.entrySet()) {
430                String string = entry.getKey();
431                String className = entry.getValue();
432                try {
433                    Class<?> clazz = classLoader.loadClass(className);
434                    classes.put(string, clazz);
435                } catch (Exception notAvailable) {
436                    resourcesNotLoaded.add(className);
437                }
438            }
439            return classes;
440        }
441    
442        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
443        //
444        //   Find Implementation
445        //
446        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
447    
448        /**
449         * Assumes the class specified points to a file in the classpath that contains
450         * the name of a class that implements or is a subclass of the specfied class.
451         * <p/>
452         * Any class that cannot be loaded will be cause an exception to be thrown.
453         * <p/>
454         * Example classpath:
455         * <p/>
456         * META-INF/java.io.InputStream    # contains the classname org.acme.AcmeInputStream
457         * META-INF/java.io.OutputStream
458         * <p/>
459         * ResourceFinder finder = new ResourceFinder("META-INF/");
460         * Class clazz = finder.findImplementation(java.io.InputStream.class);
461         * clazz.getName();  // returns "org.acme.AcmeInputStream"
462         *
463         * @param interfase a superclass or interface
464         * @return
465         * @throws IOException            if the URL cannot be read
466         * @throws ClassNotFoundException if the class found is not loadable
467         * @throws ClassCastException     if the class found is not assignable to the specified superclass or interface
468         */
469        public Class<?> findImplementation(Class<?> interfase) throws IOException, ClassNotFoundException {
470            String className = findString(interfase.getName());
471            Class<?> impl = classLoader.loadClass(className);
472            if (!interfase.isAssignableFrom(impl)) {
473                throw new ClassCastException("Class not of type: " + interfase.getName());
474            }
475            return impl;
476        }
477    
478        /**
479         * Assumes the class specified points to a file in the classpath that contains
480         * the name of a class that implements or is a subclass of the specfied class.
481         * <p/>
482         * Any class that cannot be loaded or assigned to the specified interface will be cause
483         * an exception to be thrown.
484         * <p/>
485         * Example classpath:
486         * <p/>
487         * META-INF/java.io.InputStream    # contains the classname org.acme.AcmeInputStream
488         * META-INF/java.io.InputStream    # contains the classname org.widget.NeatoInputStream
489         * META-INF/java.io.InputStream    # contains the classname com.foo.BarInputStream
490         * <p/>
491         * ResourceFinder finder = new ResourceFinder("META-INF/");
492         * List classes = finder.findAllImplementations(java.io.InputStream.class);
493         * classes.contains("org.acme.AcmeInputStream");  // true
494         * classes.contains("org.widget.NeatoInputStream");  // true
495         * classes.contains("com.foo.BarInputStream");  // true
496         *
497         * @param interfase a superclass or interface
498         * @return
499         * @throws IOException            if the URL cannot be read
500         * @throws ClassNotFoundException if the class found is not loadable
501         * @throws ClassCastException     if the class found is not assignable to the specified superclass or interface
502         */
503        public <T> List<Class<? extends T>> findAllImplementations(Class<T> interfase) throws IOException, ClassNotFoundException {
504            List<Class<? extends T>> implementations = new ArrayList<Class<? extends T>>();
505            List<String> strings = findAllStrings(interfase.getName());
506            for (String className : strings) {
507                Class<? extends T> impl = classLoader.loadClass(className).asSubclass(interfase);
508                implementations.add(impl);
509            }
510            return implementations;
511        }
512    
513        /**
514         * Assumes the class specified points to a file in the classpath that contains
515         * the name of a class that implements or is a subclass of the specfied class.
516         * <p/>
517         * Any class that cannot be loaded or are not assignable to the specified class will be
518         * skipped and placed in the 'resourcesNotLoaded' collection.
519         * <p/>
520         * Example classpath:
521         * <p/>
522         * META-INF/java.io.InputStream    # contains the classname org.acme.AcmeInputStream
523         * META-INF/java.io.InputStream    # contains the classname org.widget.NeatoInputStream
524         * META-INF/java.io.InputStream    # contains the classname com.foo.BarInputStream
525         * <p/>
526         * ResourceFinder finder = new ResourceFinder("META-INF/");
527         * List classes = finder.findAllImplementations(java.io.InputStream.class);
528         * classes.contains("org.acme.AcmeInputStream");  // true
529         * classes.contains("org.widget.NeatoInputStream");  // true
530         * classes.contains("com.foo.BarInputStream");  // true
531         *
532         * @param interfase a superclass or interface
533         * @return
534         * @throws IOException if classLoader.getResources throws an exception
535         */
536        public <T> List<Class<? extends T>> findAvailableImplementations(Class<T> interfase) throws IOException {
537            resourcesNotLoaded.clear();
538            List<Class<? extends T>> implementations = new ArrayList<Class<? extends T>>();
539            List<String> strings = findAvailableStrings(interfase.getName());
540            for (String className : strings) {
541                try {
542                    Class<?> impl = classLoader.loadClass(className);
543                    if (interfase.isAssignableFrom(impl)) {
544                        implementations.add(impl.asSubclass(interfase));
545                    } else {
546                        resourcesNotLoaded.add(className);
547                    }
548                } catch (Exception notAvailable) {
549                    resourcesNotLoaded.add(className);
550                }
551            }
552            return implementations;
553        }
554    
555        /**
556         * Assumes the class specified points to a directory in the classpath that holds files
557         * containing the name of a class that implements or is a subclass of the specfied class.
558         * <p/>
559         * Any class that cannot be loaded or assigned to the specified interface will be cause
560         * an exception to be thrown.
561         * <p/>
562         * Example classpath:
563         * <p/>
564         * META-INF/java.net.URLStreamHandler/jar
565         * META-INF/java.net.URLStreamHandler/file
566         * META-INF/java.net.URLStreamHandler/http
567         * <p/>
568         * ResourceFinder finder = new ResourceFinder("META-INF/");
569         * Map map = finder.mapAllImplementations(java.net.URLStreamHandler.class);
570         * Class jarUrlHandler = map.get("jar");
571         * Class fileUrlHandler = map.get("file");
572         * Class httpUrlHandler = map.get("http");
573         *
574         * @param interfase a superclass or interface
575         * @return
576         * @throws IOException            if the URL cannot be read
577         * @throws ClassNotFoundException if the class found is not loadable
578         * @throws ClassCastException     if the class found is not assignable to the specified superclass or interface
579         */
580        public <T> Map<String, Class<? extends T>> mapAllImplementations(Class<T> interfase) throws IOException, ClassNotFoundException {
581            Map<String, Class<? extends T>> implementations = new HashMap<String, Class<? extends T>>();
582            Map<String, String> map = mapAllStrings(interfase.getName());
583            for (Map.Entry<String, String> entry : map.entrySet()) {
584                String string = entry.getKey();
585                String className = entry.getValue();
586                Class<? extends T> impl = classLoader.loadClass(className).asSubclass(interfase);
587                implementations.put(string, impl);
588            }
589            return implementations;
590        }
591    
592        /**
593         * Assumes the class specified points to a directory in the classpath that holds files
594         * containing the name of a class that implements or is a subclass of the specfied class.
595         * <p/>
596         * Any class that cannot be loaded or are not assignable to the specified class will be
597         * skipped and placed in the 'resourcesNotLoaded' collection.
598         * <p/>
599         * Example classpath:
600         * <p/>
601         * META-INF/java.net.URLStreamHandler/jar
602         * META-INF/java.net.URLStreamHandler/file
603         * META-INF/java.net.URLStreamHandler/http
604         * <p/>
605         * ResourceFinder finder = new ResourceFinder("META-INF/");
606         * Map map = finder.mapAllImplementations(java.net.URLStreamHandler.class);
607         * Class jarUrlHandler = map.get("jar");
608         * Class fileUrlHandler = map.get("file");
609         * Class httpUrlHandler = map.get("http");
610         *
611         * @param interfase a superclass or interface
612         * @return
613         * @throws IOException if classLoader.getResources throws an exception
614         */
615        public <T> Map<String, Class<? extends T>> mapAvailableImplementations(Class<T> interfase) throws IOException {
616            resourcesNotLoaded.clear();
617            Map<String, Class<? extends T>> implementations = new HashMap<String, Class<? extends T>>();
618            Map<String, String> map = mapAvailableStrings(interfase.getName());
619            for (Map.Entry<String, String> entry : map.entrySet()) {
620                String string = entry.getKey();
621                String className = entry.getValue();
622                try {
623                    Class<?> impl = classLoader.loadClass(className);
624                    if (interfase.isAssignableFrom(impl)) {
625                        implementations.put(string, impl.asSubclass(interfase));
626                    } else {
627                        resourcesNotLoaded.add(className);
628                    }
629                } catch (Exception notAvailable) {
630                    resourcesNotLoaded.add(className);
631                }
632            }
633            return implementations;
634        }
635    
636        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
637        //
638        //   Find Properties
639        //
640        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
641    
642        /**
643         * Finds the corresponding resource and reads it in as a properties file
644         * <p/>
645         * Example classpath:
646         * <p/>
647         * META-INF/widget.properties
648         * <p/>
649         * ResourceFinder finder = new ResourceFinder("META-INF/");
650         * Properties widgetProps = finder.findProperties("widget.properties");
651         *
652         * @param uri
653         * @return
654         * @throws IOException if the URL cannot be read or is not in properties file format
655         */
656        public Properties findProperties(String uri) throws IOException {
657            String fulluri = path + uri;
658    
659            URL resource = getResource(fulluri);
660            if (resource == null) {
661                throw new IOException("Could not find resource: " + fulluri);
662            }
663    
664            return loadProperties(resource);
665        }
666    
667        /**
668         * Finds the corresponding resources and reads them in as a properties files
669         * <p/>
670         * Any URL that cannot be read in as a properties file will cause an exception to be thrown.
671         * <p/>
672         * Example classpath:
673         * <p/>
674         * META-INF/app.properties
675         * META-INF/app.properties
676         * META-INF/app.properties
677         * <p/>
678         * ResourceFinder finder = new ResourceFinder("META-INF/");
679         * List<Properties> appProps = finder.findAllProperties("app.properties");
680         *
681         * @param uri
682         * @return
683         * @throws IOException if the URL cannot be read or is not in properties file format
684         */
685        public List<Properties> findAllProperties(String uri) throws IOException {
686            String fulluri = path + uri;
687    
688            List<Properties> properties = new ArrayList<Properties>();
689    
690            Enumeration<URL> resources = getResources(fulluri);
691            while (resources.hasMoreElements()) {
692                URL url = resources.nextElement();
693                Properties props = loadProperties(url);
694                properties.add(props);
695            }
696            return properties;
697        }
698    
699        /**
700         * Finds the corresponding resources and reads them in as a properties files
701         * <p/>
702         * Any URL that cannot be read in as a properties file will be added to the
703         * 'resourcesNotLoaded' collection.
704         * <p/>
705         * Example classpath:
706         * <p/>
707         * META-INF/app.properties
708         * META-INF/app.properties
709         * META-INF/app.properties
710         * <p/>
711         * ResourceFinder finder = new ResourceFinder("META-INF/");
712         * List<Properties> appProps = finder.findAvailableProperties("app.properties");
713         *
714         * @param uri
715         * @return
716         * @throws IOException if classLoader.getResources throws an exception
717         */
718        public List<Properties> findAvailableProperties(String uri) throws IOException {
719            resourcesNotLoaded.clear();
720            String fulluri = path + uri;
721    
722            List<Properties> properties = new ArrayList<Properties>();
723    
724            Enumeration<URL> resources = getResources(fulluri);
725            while (resources.hasMoreElements()) {
726                URL url = resources.nextElement();
727                try {
728                    Properties props = loadProperties(url);
729                    properties.add(props);
730                } catch (Exception notAvailable) {
731                    resourcesNotLoaded.add(url.toExternalForm());
732                }
733            }
734            return properties;
735        }
736    
737        /**
738         * Finds the corresponding resources and reads them in as a properties files
739         * <p/>
740         * Any URL that cannot be read in as a properties file will cause an exception to be thrown.
741         * <p/>
742         * Example classpath:
743         * <p/>
744         * META-INF/jdbcDrivers/oracle.properties
745         * META-INF/jdbcDrivers/mysql.props
746         * META-INF/jdbcDrivers/derby
747         * <p/>
748         * ResourceFinder finder = new ResourceFinder("META-INF/");
749         * List<Properties> driversList = finder.findAvailableProperties("jdbcDrivers");
750         * Properties oracleProps = driversList.get("oracle.properties");
751         * Properties mysqlProps = driversList.get("mysql.props");
752         * Properties derbyProps = driversList.get("derby");
753         *
754         * @param uri
755         * @return
756         * @throws IOException if the URL cannot be read or is not in properties file format
757         */
758        public Map<String, Properties> mapAllProperties(String uri) throws IOException {
759            Map<String, Properties> propertiesMap = new HashMap<String, Properties>();
760            Map<String, URL> map = getResourcesMap(uri);
761            for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
762                Map.Entry entry = (Map.Entry) iterator.next();
763                String string = (String) entry.getKey();
764                URL url = (URL) entry.getValue();
765                Properties properties = loadProperties(url);
766                propertiesMap.put(string, properties);
767            }
768            return propertiesMap;
769        }
770    
771        /**
772         * Finds the corresponding resources and reads them in as a properties files
773         * <p/>
774         * Any URL that cannot be read in as a properties file will be added to the
775         * 'resourcesNotLoaded' collection.
776         * <p/>
777         * Example classpath:
778         * <p/>
779         * META-INF/jdbcDrivers/oracle.properties
780         * META-INF/jdbcDrivers/mysql.props
781         * META-INF/jdbcDrivers/derby
782         * <p/>
783         * ResourceFinder finder = new ResourceFinder("META-INF/");
784         * List<Properties> driversList = finder.findAvailableProperties("jdbcDrivers");
785         * Properties oracleProps = driversList.get("oracle.properties");
786         * Properties mysqlProps = driversList.get("mysql.props");
787         * Properties derbyProps = driversList.get("derby");
788         *
789         * @param uri
790         * @return
791         * @throws IOException if classLoader.getResources throws an exception
792         */
793        public Map<String, Properties> mapAvailableProperties(String uri) throws IOException {
794            resourcesNotLoaded.clear();
795            Map<String, Properties> propertiesMap = new HashMap<String, Properties>();
796            Map<String, URL> map = getResourcesMap(uri);
797            for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
798                Map.Entry entry = (Map.Entry) iterator.next();
799                String string = (String) entry.getKey();
800                URL url = (URL) entry.getValue();
801                try {
802                    Properties properties = loadProperties(url);
803                    propertiesMap.put(string, properties);
804                } catch (Exception notAvailable) {
805                    resourcesNotLoaded.add(url.toExternalForm());
806                }
807            }
808            return propertiesMap;
809        }
810    
811        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
812        //
813        //   Map Resources
814        //
815        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
816    
817        public Map<String, URL> getResourcesMap(String uri) throws IOException {
818            String basePath = path + uri;
819    
820            Map<String, URL> resources = new HashMap<String, URL>();
821            if (!basePath.endsWith("/")) {
822                basePath += "/";
823            }
824            Enumeration<URL> urls = getResources(basePath);
825    
826            while (urls.hasMoreElements()) {
827                URL location = urls.nextElement();
828    
829                try {
830                    if (location.getProtocol().equals("jar")) {
831    
832                        readJarEntries(location, basePath, resources);
833    
834                    } else if (location.getProtocol().equals("file")) {
835    
836                        readDirectoryEntries(location, resources);
837    
838                    }
839                } catch (Exception e) {
840                }
841            }
842    
843            return resources;
844        }
845    
846        private static void readDirectoryEntries(URL location, Map<String, URL> resources) throws MalformedURLException {
847            File dir = new File(decode(location.getPath()));
848            if (dir.isDirectory()) {
849                File[] files = dir.listFiles();
850                for (File file : files) {
851                    if (!file.isDirectory()) {
852                        String name = file.getName();
853                        URL url = file.toURI().toURL();
854                        resources.put(name, url);
855                    }
856                }
857            }
858        }
859    
860        private static void readJarEntries(URL location, String basePath, Map<String, URL> resources) throws IOException {
861            JarURLConnection conn = (JarURLConnection) location.openConnection();
862            JarFile jarfile = null;
863            jarfile = conn.getJarFile();
864    
865            Enumeration<JarEntry> entries = jarfile.entries();
866            while (entries != null && entries.hasMoreElements()) {
867                JarEntry entry = entries.nextElement();
868                String name = entry.getName();
869    
870                if (entry.isDirectory() || !name.startsWith(basePath) || name.length() == basePath.length()) {
871                    continue;
872                }
873    
874                name = name.substring(basePath.length());
875    
876                if (name.contains("/")) {
877                    continue;
878                }
879    
880                URL resource = new URL(location, name);
881                resources.put(name, resource);
882            }
883        }
884    
885        private Properties loadProperties(URL resource) throws IOException {
886            InputStream in = resource.openStream();
887    
888            BufferedInputStream reader = null;
889            try {
890                reader = new BufferedInputStream(in);
891                Properties properties = new Properties();
892                properties.load(reader);
893    
894                return properties;
895            } finally {
896                try {
897                    in.close();
898                    reader.close();
899                } catch (Exception e) {
900                }
901            }
902        }
903    
904        private String readContents(URL resource) throws IOException {
905            InputStream in = resource.openStream();
906            BufferedInputStream reader = null;
907            StringBuffer sb = new StringBuffer();
908    
909            try {
910                reader = new BufferedInputStream(in);
911    
912                int b = reader.read();
913                while (b != -1) {
914                    sb.append((char) b);
915                    b = reader.read();
916                }
917    
918                return sb.toString().trim();
919            } finally {
920                try {
921                    in.close();
922                    reader.close();
923                } catch (Exception e) {
924                }
925            }
926        }
927    
928        public URL getResource(String fullUri) {
929            if (urls == null){
930                return classLoader.getResource(fullUri);
931            }
932            return findResource(fullUri, urls);
933        }
934    
935        private Enumeration<URL> getResources(String fulluri) throws IOException {
936            if (urls == null) {
937                return classLoader.getResources(fulluri);
938            }
939            Vector<URL> resources = new Vector();
940            for (URL url : urls) {
941                URL resource = findResource(fulluri, url);
942                if (resource != null){
943                    resources.add(resource);
944                }
945            }
946            return resources.elements();
947        }
948    
949        private URL findResource(String resourceName, URL... search) {
950            for (int i = 0; i < search.length; i++) {
951                URL currentUrl = search[i];
952                if (currentUrl == null) {
953                    continue;
954                }            
955    
956                try {
957                    String protocol = currentUrl.getProtocol();
958                    if (protocol.equals("jar")) {
959                        /*
960                        * If the connection for currentUrl or resURL is
961                        * used, getJarFile() will throw an exception if the
962                        * entry doesn't exist.
963                        */
964                        URL jarURL = ((JarURLConnection) currentUrl.openConnection()).getJarFileURL();
965                        JarFile jarFile;
966                        JarURLConnection juc;
967                        try {
968                            juc = (JarURLConnection) new URL("jar", "", jarURL.toExternalForm() + "!/").openConnection();
969                            jarFile = juc.getJarFile();
970                        } catch (IOException e) {
971                            // Don't look for this jar file again
972                            search[i] = null;
973                            throw e;
974                        }
975    
976                        try {
977                            juc = (JarURLConnection) new URL("jar", "", jarURL.toExternalForm() + "!/").openConnection();                        
978                            jarFile = juc.getJarFile();
979                            String entryName;
980                            if (currentUrl.getFile().endsWith("!/")) {
981                                entryName = resourceName;
982                            } else {
983                                String file = currentUrl.getFile();
984                                int sepIdx = file.lastIndexOf("!/");
985                                if (sepIdx == -1) {
986                                    // Invalid URL, don't look here again
987                                    search[i] = null;
988                                    continue;
989                                }
990                                sepIdx += 2;
991                                StringBuffer sb = new StringBuffer(file.length() - sepIdx + resourceName.length());
992                                sb.append(file.substring(sepIdx));
993                                sb.append(resourceName);
994                                entryName = sb.toString();
995                            }
996                            if (entryName.equals("META-INF/") && jarFile.getEntry("META-INF/MANIFEST.MF") != null) {
997                                return targetURL(currentUrl, "META-INF/MANIFEST.MF");
998                            }
999                            if (jarFile.getEntry(entryName) != null) {
1000                                return targetURL(currentUrl, resourceName);
1001                            }
1002                        } finally {
1003                            if (!juc.getUseCaches()) {
1004                                try {
1005                                    jarFile.close();
1006                                } catch (Exception e) {
1007                                }
1008                            }
1009                        }
1010                        
1011                    } else if (protocol.equals("file")) {
1012                        String baseFile = currentUrl.getFile();
1013                        String host = currentUrl.getHost();
1014                        int hostLength = 0;
1015                        if (host != null) {
1016                            hostLength = host.length();
1017                        }
1018                        StringBuffer buf = new StringBuffer(2 + hostLength + baseFile.length() + resourceName.length());
1019    
1020                        if (hostLength > 0) {
1021                            buf.append("//").append(host);
1022                        }
1023                        // baseFile always ends with '/'
1024                        buf.append(baseFile);
1025                        String fixedResName = resourceName;
1026                        // Do not create a UNC path, i.e. \\host
1027                        while (fixedResName.startsWith("/") || fixedResName.startsWith("\\")) {
1028                            fixedResName = fixedResName.substring(1);
1029                        }
1030                        buf.append(fixedResName);
1031                        String filename = buf.toString();
1032                        File file = new File(filename);
1033                        File file2 = new File(decode(filename));
1034    
1035                        if (file.exists() || file2.exists()) {
1036                            return targetURL(currentUrl, fixedResName);
1037                        }
1038                    } else {
1039                        URL resourceURL = targetURL(currentUrl, resourceName);
1040                        URLConnection urlConnection = resourceURL.openConnection();
1041    
1042                        try {
1043                            urlConnection.getInputStream().close();
1044                        } catch (SecurityException e) {
1045                            return null;
1046                        }
1047                        // HTTP can return a stream on a non-existent file
1048                        // So check for the return code;
1049                        if (!resourceURL.getProtocol().equals("http")) {
1050                            return resourceURL;
1051                        }
1052    
1053                        int code = ((HttpURLConnection) urlConnection).getResponseCode();
1054                        if (code >= 200 && code < 300) {
1055                            return resourceURL;
1056                        }
1057                    }
1058                } catch (MalformedURLException e) {
1059                    // Keep iterating through the URL list
1060                } catch (IOException e) {
1061                } catch (SecurityException e) {
1062                }
1063            }
1064            return null;
1065        }
1066    
1067        private URL targetURL(URL base, String name) throws MalformedURLException {
1068            StringBuffer sb = new StringBuffer(base.getFile().length() + name.length());
1069            sb.append(base.getFile());
1070            sb.append(name);
1071            String file = sb.toString();
1072            return new URL(base.getProtocol(), base.getHost(), base.getPort(), file, null);
1073        }
1074    
1075        public static String decode(String fileName) {
1076            if (fileName.indexOf('%') == -1) return fileName;
1077    
1078            StringBuilder result = new StringBuilder(fileName.length());
1079            ByteArrayOutputStream out = new ByteArrayOutputStream();
1080    
1081            for (int i = 0; i < fileName.length();) {
1082                char c = fileName.charAt(i);
1083    
1084                if (c == '%') {
1085                    out.reset();
1086                    do {
1087                        if (i + 2 >= fileName.length()) {
1088                            throw new IllegalArgumentException("Incomplete % sequence at: " + i);
1089                        }
1090    
1091                        int d1 = Character.digit(fileName.charAt(i + 1), 16);
1092                        int d2 = Character.digit(fileName.charAt(i + 2), 16);
1093    
1094                        if (d1 == -1 || d2 == -1) {
1095                            throw new IllegalArgumentException("Invalid % sequence (" + fileName.substring(i, i + 3) + ") at: " + String.valueOf(i));
1096                        }
1097    
1098                        out.write((byte) ((d1 << 4) + d2));
1099    
1100                        i += 3;
1101    
1102                    } while (i < fileName.length() && fileName.charAt(i) == '%');
1103    
1104    
1105                    result.append(out.toString());
1106    
1107                    continue;
1108                } else {
1109                    result.append(c);
1110                }
1111    
1112                i++;
1113            }
1114            return result.toString();
1115        }
1116    
1117    }