Index: stripes/src/net/sourceforge/stripes/util/ResolverUtil.java
===================================================================
--- stripes/src/net/sourceforge/stripes/util/ResolverUtil.java	(revision 1148)
+++ stripes/src/net/sourceforge/stripes/util/ResolverUtil.java	(working copy)
@@ -15,9 +15,9 @@
 package net.sourceforge.stripes.util;
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.lang.annotation.Annotation;
+import java.net.URISyntaxException;
 import java.net.URL;
 import java.util.Enumeration;
 import java.util.HashSet;
@@ -214,27 +214,29 @@
         }
 
         while (urls.hasMoreElements()) {
-            String urlPath = urls.nextElement().getFile();
-            urlPath = StringUtil.urlDecode(urlPath);
+            URL currentUrl = urls.nextElement();
+            log.info("Scanning for classes in [", currentUrl, "] matching criteria: ", test);
 
-            // If it's a file in a directory, trim the stupid file: spec
-            if ( urlPath.startsWith("file:") ) {
-                urlPath = urlPath.substring(5);
+            try {
+                if (currentUrl.getProtocol().equals("file")) {
+                    // If it's a file URL, and the file is a directory, then
+                    // don't load the jar as a stream.
+                    File file = new File(currentUrl.toURI());
+                    if (file.isDirectory()) {
+                        loadImplementationsInDirectory(test, packageName, file);
+                    }
+                    else {
+                        loadImplementationsInJar(test, packageName, currentUrl);
+                    }
+                }
+                else {
+                    // this is most likely a jar file, so pass it off to the stream processor
+                    loadImplementationsInJar(test, packageName, currentUrl);
+                }
             }
-
-            // Else it's in a JAR, grab the path to the jar
-            if (urlPath.indexOf('!') > 0) {
-                urlPath = urlPath.substring(0, urlPath.indexOf('!'));
+            catch (URISyntaxException e) {
+                log.warn("Bad URI syntax", e);
             }
-
-            log.info("Scanning for classes in [", urlPath, "] matching criteria: ", test);
-            File file = new File(urlPath);
-            if ( file.isDirectory() ) {
-                loadImplementationsInDirectory(test, packageName, file);
-            }
-            else {
-                loadImplementationsInJar(test, packageName, file);
-            }
         }
         
         return this;
@@ -279,19 +281,29 @@
     }
 
     /**
-     * Finds matching classes within a jar files that contains a folder structure
-     * matching the package structure.  If the File is not a JarFile or does not exist a warning
-     * will be logged, but no error will be raised.
+     * Finds matching classes within a jar identified by a URL. A stream will be opened
+     * to the given URL and wrapped in a {@link JarInputStream}, which is then processed
+     * for matching classes. If the URL does not point to a jar file or does not exist,
+     * a warning will be logged but no error will be raised.
      *
      * @param test a Test used to filter the classes that are discovered
      * @param parent the parent package under which classes must be in order to be considered
-     * @param jarfile the jar file to be examined for classes
+     * @param url the URL to the jar file to be examined for classes
      */
-    private void loadImplementationsInJar(Test test, String parent, File jarfile) {
-
+    private void loadImplementationsInJar(Test test, String parent, URL url) {
+        JarInputStream jarStream = null;
         try {
+            String urlPath = url.toString();
+            // If the url ends with a jar file followed by the parent pacakge
+            // (possibly separated by an exclamation point (!), strip all of that,
+            // because we want to look in the jar.
+            urlPath = urlPath.replace("\\.jar!?/" + parent + "/?$", ".jar");
+            // Also, jar:file:/path/to/jarfile.jar!/some/package needs to have the
+            // jar: protocol stripped once the subpackage is stripped.
+            urlPath = urlPath.replace("^jar:", "");
+            url = new URL(urlPath);
             JarEntry entry;
-            JarInputStream jarStream = new JarInputStream(new FileInputStream(jarfile));
+            jarStream = new JarInputStream(url.openStream());
 
             while ( (entry = jarStream.getNextJarEntry() ) != null) {
                 String name = entry.getName();
@@ -301,9 +313,19 @@
             }
         }
         catch (IOException ioe) {
-            log.error("Could not search jar file '", jarfile, "' for classes matching criteria: ",
+            log.error("Could not search URL '", url, "' for classes matching criteria: ",
                       test, "due to an IOException: ", ioe.getMessage());
         }
+        finally {
+            if (jarStream != null) {
+                try {
+                    jarStream.close();
+                }
+                catch (Exception e) {
+                    // no-op.... couldn't close a stream we're done with anyway
+                }
+            }
+        }
     }
 
     /**
Index: tests/src/net/sourceforge/stripes/util/ResolverUtilTest.java
===================================================================
--- tests/src/net/sourceforge/stripes/util/ResolverUtilTest.java	(revision 1148)
+++ tests/src/net/sourceforge/stripes/util/ResolverUtilTest.java	(working copy)
@@ -1,5 +1,6 @@
 package net.sourceforge.stripes.util;
 
+import net.sourceforge.stripes.extensions.MyIntegerTypeConverter;
 import net.sourceforge.stripes.validation.BooleanTypeConverter;
 import net.sourceforge.stripes.validation.DateTypeConverter;
 import net.sourceforge.stripes.validation.LocalizableError;
@@ -33,7 +34,11 @@
                           "DateTypeConverter went missing.");
         Assert.assertTrue(impls.contains(BooleanTypeConverter.class),
                           "ShortTypeConverter went missing.");
+        // This tests the scanning of class directories; the above were all in a jar
+        Assert.assertTrue(impls.contains(MyIntegerTypeConverter.class),
+                          "MyIntegerTypeConverter went missing.");
 
         Assert.assertTrue(impls.size() >= 10,
                           "Did not find all the built in TypeConverters.");
     }
@@ -53,6 +58,8 @@
                           "DateTypeConverter went missing.");
         Assert.assertTrue(impls.contains(BooleanTypeConverter.class),
                           "ShortTypeConverter went missing.");
+        Assert.assertFalse(impls.contains(MyIntegerTypeConverter.class),
+                          "MyIntegerTypeConverter was found and should not have been.");
 
         Assert.assertTrue(impls.size() >= 10,
                           "Did not find all the built in TypeConverters.");
@@ -73,7 +80,7 @@
                           "SimpleError itself should have been found.");
     }
 
-    /** Test interface used with the testFindZeroImplementatios() method. */
+    /** Test interface used with the testFindZeroImplementations() method. */
     private static interface ZeroImplementations {}
 
     @Test(groups="fast")

