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 }