You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2011/12/02 17:33:45 UTC

svn commit: r1209569 [29/50] - in /struts/struts2/branches/STRUTS_3_X: apps/blank/src/main/java/example/ apps/blank/src/test/java/example/ apps/jboss-blank/src/main/java/example/ apps/jboss-blank/src/test/java/example/ apps/mailreader/src/main/java/mai...

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/ResourceFinder.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/ResourceFinder.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/ResourceFinder.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/ResourceFinder.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,1153 @@
+/*
+ * Copyright 2002-2003,2009 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.struts2.xwork2.util.finder;
+
+import org.apache.commons.lang.StringUtils;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.*;
+import java.util.*;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import org.apache.struts2.xwork2.util.logging.Logger;
+import org.apache.struts2.xwork2.util.logging.LoggerFactory;
+
+/**
+ * @author David Blevins
+ * @version $Rev: 1209415 $ $Date: 2011-12-02 12:24:48 +0100 (Fri, 02 Dec 2011) $
+ */
+public class ResourceFinder {
+    private static final Logger LOG = LoggerFactory.getLogger(ResourceFinder.class);
+
+    private final URL[] urls;
+    private final String path;
+    private final ClassLoaderInterface classLoaderInterface;
+    private final List<String> resourcesNotLoaded = new ArrayList<String>();
+
+    public ResourceFinder(URL... urls) {
+        this(null, new ClassLoaderInterfaceDelegate(Thread.currentThread().getContextClassLoader()), urls);
+    }
+
+    public ResourceFinder(String path) {
+        this(path, new ClassLoaderInterfaceDelegate(Thread.currentThread().getContextClassLoader()), null);
+    }
+
+    public ResourceFinder(String path, URL... urls) {
+        this(path, new ClassLoaderInterfaceDelegate(Thread.currentThread().getContextClassLoader()), urls);
+    }
+
+    public ResourceFinder(String path, ClassLoaderInterface classLoaderInterface) {
+        this(path, classLoaderInterface, null);
+    }
+
+    public ResourceFinder(String path, ClassLoaderInterface classLoaderInterface, URL... urls) {
+        if (path == null){
+            path = "";
+        } else if (path.length() > 0 && !path.endsWith("/")) {
+            path += "/";
+        }
+        this.path = path;
+
+        this.classLoaderInterface = classLoaderInterface == null ? new ClassLoaderInterfaceDelegate(Thread.currentThread().getContextClassLoader()) : classLoaderInterface ;
+
+        for (int i = 0; urls != null && i < urls.length; i++) {
+            URL url = urls[i];
+            if (url == null || isDirectory(url) || "jar".equals(url.getProtocol())) {
+                continue;
+            }
+            try {
+                urls[i] = new URL("jar", "", -1, url.toString() + "!/");
+            } catch (MalformedURLException e) {
+            }
+        }
+        this.urls = (urls == null || urls.length == 0)? null : urls;
+    }
+
+    private static boolean isDirectory(URL url) {
+        String file = url.getFile();
+        return (file.length() > 0 && file.charAt(file.length() - 1) == '/');
+    }
+
+    /**
+     * Returns a list of resources that could not be loaded in the last invoked findAvailable* or
+     * mapAvailable* methods.
+     * <p/>
+     * The list will only contain entries of resources that match the requirements
+     * of the last invoked findAvailable* or mapAvailable* methods, but were unable to be
+     * loaded and included in their results.
+     * <p/>
+     * The list returned is unmodifiable and the results of this method will change
+     * after each invocation of a findAvailable* or mapAvailable* methods.
+     * <p/>
+     * This method is not thread safe.
+     */
+    public List<String> getResourcesNotLoaded() {
+        return Collections.unmodifiableList(resourcesNotLoaded);
+    }
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    //
+    //   Find
+    //
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    public URL find(String uri) throws IOException {
+        String fullUri = path + uri;
+
+        return getResource(fullUri);
+    }
+
+    public List<URL> findAll(String uri) throws IOException {
+        String fullUri = path + uri;
+
+        Enumeration<URL> resources = getResources(fullUri);
+        List<URL> list = new ArrayList<URL>();
+        while (resources.hasMoreElements()) {
+            URL url = resources.nextElement();
+            list.add(url);
+        }
+        return list;
+    }
+
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    //
+    //   Find String
+    //
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    /**
+     * Reads the contents of the URL as a {@link String}'s and returns it.
+     *
+     * @param uri
+     * @return a stringified content of a resource
+     * @throws IOException if a resource pointed out by the uri param could not be find
+     * @see ClassLoader#getResource(String)
+     */
+    public String findString(String uri) throws IOException {
+        String fullUri = path + uri;
+
+        URL resource = getResource(fullUri);
+        if (resource == null) {
+            throw new IOException("Could not find a resource in : " + fullUri);
+        }
+
+        return readContents(resource);
+    }
+
+    /**
+     * Reads the contents of the found URLs as a list of {@link String}'s and returns them.
+     *
+     * @param uri
+     * @return a list of the content of each resource URL found
+     * @throws IOException if any of the found URLs are unable to be read.
+     */
+    public List<String> findAllStrings(String uri) throws IOException {
+        String fulluri = path + uri;
+
+        List<String> strings = new ArrayList<String>();
+
+        Enumeration<URL> resources = getResources(fulluri);
+        while (resources.hasMoreElements()) {
+            URL url = resources.nextElement();
+            String string = readContents(url);
+            strings.add(string);
+        }
+        return strings;
+    }
+
+    /**
+     * Reads the contents of the found URLs as a Strings and returns them.
+     * Individual URLs that cannot be read are skipped and added to the
+     * list of 'resourcesNotLoaded'
+     *
+     * @param uri
+     * @return a list of the content of each resource URL found
+     * @throws IOException if classLoader.getResources throws an exception
+     */
+    public List<String> findAvailableStrings(String uri) throws IOException {
+        resourcesNotLoaded.clear();
+        String fulluri = path + uri;
+
+        List<String> strings = new ArrayList<String>();
+
+        Enumeration<URL> resources = getResources(fulluri);
+        while (resources.hasMoreElements()) {
+            URL url = resources.nextElement();
+            try {
+                String string = readContents(url);
+                strings.add(string);
+            } catch (IOException notAvailable) {
+                resourcesNotLoaded.add(url.toExternalForm());
+            }
+        }
+        return strings;
+    }
+
+    /**
+     * Reads the contents of all non-directory URLs immediately under the specified
+     * location and returns them in a map keyed by the file name.
+     * <p/>
+     * Any URLs that cannot be read will cause an exception to be thrown.
+     * <p/>
+     * Example classpath:
+     * <p/>
+     * META-INF/serializables/one
+     * META-INF/serializables/two
+     * META-INF/serializables/three
+     * META-INF/serializables/four/foo.txt
+     * <p/>
+     * ResourceFinder finder = new ResourceFinder("META-INF/");
+     * Map map = finder.mapAvailableStrings("serializables");
+     * map.contains("one");  // true
+     * map.contains("two");  // true
+     * map.contains("three");  // true
+     * map.contains("four");  // false
+     *
+     * @param uri
+     * @return a list of the content of each resource URL found
+     * @throws IOException if any of the urls cannot be read
+     */
+    public Map<String, String> mapAllStrings(String uri) throws IOException {
+        Map<String, String> strings = new HashMap<String, String>();
+        Map<String, URL> resourcesMap = getResourcesMap(uri);
+        for (Map.Entry<String, URL> entry : resourcesMap.entrySet()) {
+            String name = entry.getKey();
+            URL url = entry.getValue();
+            String value = readContents(url);
+            strings.put(name, value);
+        }
+        return strings;
+    }
+
+    /**
+     * Reads the contents of all non-directory URLs immediately under the specified
+     * location and returns them in a map keyed by the file name.
+     * <p/>
+     * Individual URLs that cannot be read are skipped and added to the
+     * list of 'resourcesNotLoaded'
+     * <p/>
+     * Example classpath:
+     * <p/>
+     * META-INF/serializables/one
+     * META-INF/serializables/two      # not readable
+     * META-INF/serializables/three
+     * META-INF/serializables/four/foo.txt
+     * <p/>
+     * ResourceFinder finder = new ResourceFinder("META-INF/");
+     * Map map = finder.mapAvailableStrings("serializables");
+     * map.contains("one");  // true
+     * map.contains("two");  // false
+     * map.contains("three");  // true
+     * map.contains("four");  // false
+     *
+     * @param uri
+     * @return a list of the content of each resource URL found
+     * @throws IOException if classLoader.getResources throws an exception
+     */
+    public Map<String, String> mapAvailableStrings(String uri) throws IOException {
+        resourcesNotLoaded.clear();
+        Map<String, String> strings = new HashMap<String, String>();
+        Map<String, URL> resourcesMap = getResourcesMap(uri);
+        for (Map.Entry<String, URL> entry  : resourcesMap.entrySet()) {
+            String name = entry.getKey();
+            URL url = entry.getValue();
+            try {
+                String value = readContents(url);
+                strings.put(name, value);
+            } catch (IOException notAvailable) {
+                resourcesNotLoaded.add(url.toExternalForm());
+            }
+        }
+        return strings;
+    }
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    //
+    //   Find Class
+    //
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    /**
+     * Executes {@link #findString(String)} assuming the contents URL found is the name of
+     * a class that should be loaded and returned.
+     *
+     * @param uri
+     * @return
+     * @throws IOException
+     * @throws ClassNotFoundException
+     */
+    public Class findClass(String uri) throws IOException, ClassNotFoundException {
+        String className = findString(uri);
+        return (Class) classLoaderInterface.loadClass(className);
+    }
+
+    /**
+     * Executes findAllStrings assuming the strings are
+     * the names of a classes that should be loaded and returned.
+     * <p/>
+     * Any URL or class that cannot be loaded will cause an exception to be thrown.
+     *
+     * @param uri
+     * @return
+     * @throws IOException
+     * @throws ClassNotFoundException
+     */
+    public List<Class> findAllClasses(String uri) throws IOException, ClassNotFoundException {
+        List<Class> classes = new ArrayList<Class>();
+        List<String> strings = findAllStrings(uri);
+        for (String className : strings) {
+            Class clazz = classLoaderInterface.loadClass(className);
+            classes.add(clazz);
+        }
+        return classes;
+    }
+
+    /**
+     * Executes findAvailableStrings assuming the strings are
+     * the names of a classes that should be loaded and returned.
+     * <p/>
+     * Any class that cannot be loaded will be skipped and placed in the
+     * 'resourcesNotLoaded' collection.
+     *
+     * @param uri
+     * @return
+     * @throws IOException if classLoader.getResources throws an exception
+     */
+    public List<Class> findAvailableClasses(String uri) throws IOException {
+        resourcesNotLoaded.clear();
+        List<Class> classes = new ArrayList<Class>();
+        List<String> strings = findAvailableStrings(uri);
+        for (String className : strings) {
+            try {
+                Class clazz = classLoaderInterface.loadClass(className);
+                classes.add(clazz);
+            } catch (Exception notAvailable) {
+                resourcesNotLoaded.add(className);
+            }
+        }
+        return classes;
+    }
+
+    /**
+     * Executes mapAllStrings assuming the value of each entry in the
+     * map is the name of a class that should be loaded.
+     * <p/>
+     * Any class that cannot be loaded will be cause an exception to be thrown.
+     * <p/>
+     * Example classpath:
+     * <p/>
+     * META-INF/xmlparsers/xerces
+     * META-INF/xmlparsers/crimson
+     * <p/>
+     * ResourceFinder finder = new ResourceFinder("META-INF/");
+     * Map map = finder.mapAvailableStrings("xmlparsers");
+     * map.contains("xerces");  // true
+     * map.contains("crimson");  // true
+     * Class xercesClass = map.get("xerces");
+     * Class crimsonClass = map.get("crimson");
+     *
+     * @param uri
+     * @return
+     * @throws IOException
+     * @throws ClassNotFoundException
+     */
+    public Map<String, Class> mapAllClasses(String uri) throws IOException, ClassNotFoundException {
+        Map<String, Class> classes = new HashMap<String, Class>();
+        Map<String, String> map = mapAllStrings(uri);
+        for (Map.Entry<String, String> entry : map.entrySet()) {
+            String string = entry.getKey();
+            String className = entry.getValue();
+            Class clazz = classLoaderInterface.loadClass(className);
+            classes.put(string, clazz);
+        }
+        return classes;
+    }
+
+    /**
+     * Executes mapAvailableStrings assuming the value of each entry in the
+     * map is the name of a class that should be loaded.
+     * <p/>
+     * Any class that cannot be loaded will be skipped and placed in the
+     * 'resourcesNotLoaded' collection.
+     * <p/>
+     * Example classpath:
+     * <p/>
+     * META-INF/xmlparsers/xerces
+     * META-INF/xmlparsers/crimson
+     * <p/>
+     * ResourceFinder finder = new ResourceFinder("META-INF/");
+     * Map map = finder.mapAvailableStrings("xmlparsers");
+     * map.contains("xerces");  // true
+     * map.contains("crimson");  // true
+     * Class xercesClass = map.get("xerces");
+     * Class crimsonClass = map.get("crimson");
+     *
+     * @param uri
+     * @return
+     * @throws IOException if classLoader.getResources throws an exception
+     */
+    public Map<String, Class> mapAvailableClasses(String uri) throws IOException {
+        resourcesNotLoaded.clear();
+        Map<String, Class> classes = new HashMap<String, Class>();
+        Map<String, String> map = mapAvailableStrings(uri);
+        for (Map.Entry<String, String> entry : map.entrySet()) {
+            String string = entry.getKey();
+            String className = entry.getValue();
+            try {
+                Class clazz = classLoaderInterface.loadClass(className);
+                classes.put(string, clazz);
+            } catch (Exception notAvailable) {
+                resourcesNotLoaded.add(className);
+            }
+        }
+        return classes;
+    }
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    //
+    //   Find Implementation
+    //
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    /**
+     * Assumes the class specified points to a file in the classpath that contains
+     * the name of a class that implements or is a subclass of the specfied class.
+     * <p/>
+     * Any class that cannot be loaded will be cause an exception to be thrown.
+     * <p/>
+     * Example classpath:
+     * <p/>
+     * META-INF/java.io.InputStream    # contains the classname org.acme.AcmeInputStream
+     * META-INF/java.io.OutputStream
+     * <p/>
+     * ResourceFinder finder = new ResourceFinder("META-INF/");
+     * Class clazz = finder.findImplementation(java.io.InputStream.class);
+     * clazz.getName();  // returns "org.acme.AcmeInputStream"
+     *
+     * @param interfase a superclass or interface
+     * @return
+     * @throws IOException            if the URL cannot be read
+     * @throws ClassNotFoundException if the class found is not loadable
+     * @throws ClassCastException     if the class found is not assignable to the specified superclass or interface
+     */
+    public Class findImplementation(Class interfase) throws IOException, ClassNotFoundException {
+        String className = findString(interfase.getName());
+        Class impl = classLoaderInterface.loadClass(className);
+        if (!interfase.isAssignableFrom(impl)) {
+            throw new ClassCastException("Class not of type: " + interfase.getName());
+        }
+        return impl;
+    }
+
+    /**
+     * Assumes the class specified points to a file in the classpath that contains
+     * the name of a class that implements or is a subclass of the specfied class.
+     * <p/>
+     * Any class that cannot be loaded or assigned to the specified interface will be cause
+     * an exception to be thrown.
+     * <p/>
+     * Example classpath:
+     * <p/>
+     * META-INF/java.io.InputStream    # contains the classname org.acme.AcmeInputStream
+     * META-INF/java.io.InputStream    # contains the classname org.widget.NeatoInputStream
+     * META-INF/java.io.InputStream    # contains the classname com.foo.BarInputStream
+     * <p/>
+     * ResourceFinder finder = new ResourceFinder("META-INF/");
+     * List classes = finder.findAllImplementations(java.io.InputStream.class);
+     * classes.contains("org.acme.AcmeInputStream");  // true
+     * classes.contains("org.widget.NeatoInputStream");  // true
+     * classes.contains("com.foo.BarInputStream");  // true
+     *
+     * @param interfase a superclass or interface
+     * @return
+     * @throws IOException            if the URL cannot be read
+     * @throws ClassNotFoundException if the class found is not loadable
+     * @throws ClassCastException     if the class found is not assignable to the specified superclass or interface
+     */
+    public List<Class> findAllImplementations(Class interfase) throws IOException, ClassNotFoundException {
+        List<Class> implementations = new ArrayList<Class>();
+        List<String> strings = findAllStrings(interfase.getName());
+        for (String className : strings) {
+            Class impl = classLoaderInterface.loadClass(className);
+            if (!interfase.isAssignableFrom(impl)) {
+                throw new ClassCastException("Class not of type: " + interfase.getName());
+            }
+            implementations.add(impl);
+        }
+        return implementations;
+    }
+
+    /**
+     * Assumes the class specified points to a file in the classpath that contains
+     * the name of a class that implements or is a subclass of the specfied class.
+     * <p/>
+     * Any class that cannot be loaded or are not assignable to the specified class will be
+     * skipped and placed in the 'resourcesNotLoaded' collection.
+     * <p/>
+     * Example classpath:
+     * <p/>
+     * META-INF/java.io.InputStream    # contains the classname org.acme.AcmeInputStream
+     * META-INF/java.io.InputStream    # contains the classname org.widget.NeatoInputStream
+     * META-INF/java.io.InputStream    # contains the classname com.foo.BarInputStream
+     * <p/>
+     * ResourceFinder finder = new ResourceFinder("META-INF/");
+     * List classes = finder.findAllImplementations(java.io.InputStream.class);
+     * classes.contains("org.acme.AcmeInputStream");  // true
+     * classes.contains("org.widget.NeatoInputStream");  // true
+     * classes.contains("com.foo.BarInputStream");  // true
+     *
+     * @param interfase a superclass or interface
+     * @return
+     * @throws IOException if classLoader.getResources throws an exception
+     */
+    public List<Class> findAvailableImplementations(Class interfase) throws IOException {
+        resourcesNotLoaded.clear();
+        List<Class> implementations = new ArrayList<Class>();
+        List<String> strings = findAvailableStrings(interfase.getName());
+        for (String className : strings) {
+            try {
+                Class impl = classLoaderInterface.loadClass(className);
+                if (interfase.isAssignableFrom(impl)) {
+                    implementations.add(impl);
+                } else {
+                    resourcesNotLoaded.add(className);
+                }
+            } catch (Exception notAvailable) {
+                resourcesNotLoaded.add(className);
+            }
+        }
+        return implementations;
+    }
+
+    /**
+     * Assumes the class specified points to a directory in the classpath that holds files
+     * containing the name of a class that implements or is a subclass of the specfied class.
+     * <p/>
+     * Any class that cannot be loaded or assigned to the specified interface will be cause
+     * an exception to be thrown.
+     * <p/>
+     * Example classpath:
+     * <p/>
+     * META-INF/java.net.URLStreamHandler/jar
+     * META-INF/java.net.URLStreamHandler/file
+     * META-INF/java.net.URLStreamHandler/http
+     * <p/>
+     * ResourceFinder finder = new ResourceFinder("META-INF/");
+     * Map map = finder.mapAllImplementations(java.net.URLStreamHandler.class);
+     * Class jarUrlHandler = map.get("jar");
+     * Class fileUrlHandler = map.get("file");
+     * Class httpUrlHandler = map.get("http");
+     *
+     * @param interfase a superclass or interface
+     * @return
+     * @throws IOException            if the URL cannot be read
+     * @throws ClassNotFoundException if the class found is not loadable
+     * @throws ClassCastException     if the class found is not assignable to the specified superclass or interface
+     */
+    public Map<String, Class> mapAllImplementations(Class interfase) throws IOException, ClassNotFoundException {
+        Map<String, Class> implementations = new HashMap<String, Class>();
+        Map<String, String> map = mapAllStrings(interfase.getName());
+        for (Map.Entry<String, String> entry : map.entrySet()) {
+            String string = entry.getKey();
+            String className = entry.getValue();
+            Class impl = classLoaderInterface.loadClass(className);
+            if (!interfase.isAssignableFrom(impl)) {
+                throw new ClassCastException("Class not of type: " + interfase.getName());
+            }
+            implementations.put(string, impl);
+        }
+        return implementations;
+    }
+
+    /**
+     * Assumes the class specified points to a directory in the classpath that holds files
+     * containing the name of a class that implements or is a subclass of the specfied class.
+     * <p/>
+     * Any class that cannot be loaded or are not assignable to the specified class will be
+     * skipped and placed in the 'resourcesNotLoaded' collection.
+     * <p/>
+     * Example classpath:
+     * <p/>
+     * META-INF/java.net.URLStreamHandler/jar
+     * META-INF/java.net.URLStreamHandler/file
+     * META-INF/java.net.URLStreamHandler/http
+     * <p/>
+     * ResourceFinder finder = new ResourceFinder("META-INF/");
+     * Map map = finder.mapAllImplementations(java.net.URLStreamHandler.class);
+     * Class jarUrlHandler = map.get("jar");
+     * Class fileUrlHandler = map.get("file");
+     * Class httpUrlHandler = map.get("http");
+     *
+     * @param interfase a superclass or interface
+     * @return
+     * @throws IOException if classLoader.getResources throws an exception
+     */
+    public Map<String, Class> mapAvailableImplementations(Class interfase) throws IOException {
+        resourcesNotLoaded.clear();
+        Map<String, Class> implementations = new HashMap<String, Class>();
+        Map<String, String> map = mapAvailableStrings(interfase.getName());
+        for (Map.Entry<String, String> entry : map.entrySet()) {
+            String string = entry.getKey();
+            String className = entry.getValue();
+            try {
+                Class impl = classLoaderInterface.loadClass(className);
+                if (interfase.isAssignableFrom(impl)) {
+                    implementations.put(string, impl);
+                } else {
+                    resourcesNotLoaded.add(className);
+                }
+            } catch (Exception notAvailable) {
+                resourcesNotLoaded.add(className);
+            }
+        }
+        return implementations;
+    }
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    //
+    //   Find Properties
+    //
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    /**
+     * Finds the corresponding resource and reads it in as a properties file
+     * <p/>
+     * Example classpath:
+     * <p/>
+     * META-INF/widget.properties
+     * <p/>
+     * ResourceFinder finder = new ResourceFinder("META-INF/");
+     * Properties widgetProps = finder.findProperties("widget.properties");
+     *
+     * @param uri
+     * @return
+     * @throws IOException if the URL cannot be read or is not in properties file format
+     */
+    public Properties findProperties(String uri) throws IOException {
+        String fulluri = path + uri;
+
+        URL resource = getResource(fulluri);
+        if (resource == null) {
+            throw new IOException("Could not find command in : " + fulluri);
+        }
+
+        return loadProperties(resource);
+    }
+
+    /**
+     * Finds the corresponding resources and reads them in as a properties files
+     * <p/>
+     * Any URL that cannot be read in as a properties file will cause an exception to be thrown.
+     * <p/>
+     * Example classpath:
+     * <p/>
+     * META-INF/app.properties
+     * META-INF/app.properties
+     * META-INF/app.properties
+     * <p/>
+     * ResourceFinder finder = new ResourceFinder("META-INF/");
+     * List<Properties> appProps = finder.findAllProperties("app.properties");
+     *
+     * @param uri
+     * @return
+     * @throws IOException if the URL cannot be read or is not in properties file format
+     */
+    public List<Properties> findAllProperties(String uri) throws IOException {
+        String fulluri = path + uri;
+
+        List<Properties> properties = new ArrayList<Properties>();
+
+        Enumeration<URL> resources = getResources(fulluri);
+        while (resources.hasMoreElements()) {
+            URL url = resources.nextElement();
+            Properties props = loadProperties(url);
+            properties.add(props);
+        }
+        return properties;
+    }
+
+    /**
+     * Finds the corresponding resources and reads them in as a properties files
+     * <p/>
+     * Any URL that cannot be read in as a properties file will be added to the
+     * 'resourcesNotLoaded' collection.
+     * <p/>
+     * Example classpath:
+     * <p/>
+     * META-INF/app.properties
+     * META-INF/app.properties
+     * META-INF/app.properties
+     * <p/>
+     * ResourceFinder finder = new ResourceFinder("META-INF/");
+     * List<Properties> appProps = finder.findAvailableProperties("app.properties");
+     *
+     * @param uri
+     * @return
+     * @throws IOException if classLoader.getResources throws an exception
+     */
+    public List<Properties> findAvailableProperties(String uri) throws IOException {
+        resourcesNotLoaded.clear();
+        String fulluri = path + uri;
+
+        List<Properties> properties = new ArrayList<Properties>();
+
+        Enumeration<URL> resources = getResources(fulluri);
+        while (resources.hasMoreElements()) {
+            URL url = resources.nextElement();
+            try {
+                Properties props = loadProperties(url);
+                properties.add(props);
+            } catch (Exception notAvailable) {
+                resourcesNotLoaded.add(url.toExternalForm());
+            }
+        }
+        return properties;
+    }
+
+    /**
+     * Finds the corresponding resources and reads them in as a properties files
+     * <p/>
+     * Any URL that cannot be read in as a properties file will cause an exception to be thrown.
+     * <p/>
+     * Example classpath:
+     * <p/>
+     * META-INF/jdbcDrivers/oracle.properties
+     * META-INF/jdbcDrivers/mysql.props
+     * META-INF/jdbcDrivers/derby
+     * <p/>
+     * ResourceFinder finder = new ResourceFinder("META-INF/");
+     * List<Properties> driversList = finder.findAvailableProperties("jdbcDrivers");
+     * Properties oracleProps = driversList.get("oracle.properties");
+     * Properties mysqlProps = driversList.get("mysql.props");
+     * Properties derbyProps = driversList.get("derby");
+     *
+     * @param uri
+     * @return
+     * @throws IOException if the URL cannot be read or is not in properties file format
+     */
+    public Map<String, Properties> mapAllProperties(String uri) throws IOException {
+        Map<String, Properties> propertiesMap = new HashMap<String, Properties>();
+        Map<String, URL> map = getResourcesMap(uri);
+        for (Map.Entry<String, URL> entry : map.entrySet()) {
+            String string = entry.getKey();
+            URL url = entry.getValue();
+            Properties properties = loadProperties(url);
+            propertiesMap.put(string, properties);
+        }
+        return propertiesMap;
+    }
+
+    /**
+     * Finds the corresponding resources and reads them in as a properties files
+     * <p/>
+     * Any URL that cannot be read in as a properties file will be added to the
+     * 'resourcesNotLoaded' collection.
+     * <p/>
+     * Example classpath:
+     * <p/>
+     * META-INF/jdbcDrivers/oracle.properties
+     * META-INF/jdbcDrivers/mysql.props
+     * META-INF/jdbcDrivers/derby
+     * <p/>
+     * ResourceFinder finder = new ResourceFinder("META-INF/");
+     * List<Properties> driversList = finder.findAvailableProperties("jdbcDrivers");
+     * Properties oracleProps = driversList.get("oracle.properties");
+     * Properties mysqlProps = driversList.get("mysql.props");
+     * Properties derbyProps = driversList.get("derby");
+     *
+     * @param uri
+     * @return
+     * @throws IOException if classLoader.getResources throws an exception
+     */
+    public Map<String, Properties> mapAvailableProperties(String uri) throws IOException {
+        resourcesNotLoaded.clear();
+        Map<String, Properties> propertiesMap = new HashMap<String, Properties>();
+        Map<String, URL> map = getResourcesMap(uri);
+        for (Map.Entry<String, URL> entry : map.entrySet()) {
+            String string = entry.getKey();
+            URL url = entry.getValue();
+            try {
+                Properties properties = loadProperties(url);
+                propertiesMap.put(string, properties);
+            } catch (Exception notAvailable) {
+                resourcesNotLoaded.add(url.toExternalForm());
+            }
+        }
+        return propertiesMap;
+    }
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    //
+    //   Map Resources
+    //
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    public Map<String, URL> getResourcesMap(String uri) throws IOException {
+        String basePath = path + uri;
+
+        Map<String, URL> resources = new HashMap<String, URL>();
+        if (!basePath.endsWith("/")) {
+            basePath += "/";
+        }
+        Enumeration<URL> urls = getResources(basePath);
+
+        while (urls.hasMoreElements()) {
+            URL location = urls.nextElement();
+
+            try {
+                if ("jar".equals(location.getProtocol())) {
+
+                    readJarEntries(location, basePath, resources);
+
+                } else if ("file".equals(location.getProtocol())) {
+
+                    readDirectoryEntries(location, resources);
+
+                }
+            } catch (Exception e) {
+            }
+        }
+
+        return resources;
+    }
+
+    /**
+     * Gets a list of subpckages from jars or dirs
+     */
+    public Set<String> findPackages(String uri) throws IOException {
+        String basePath = path + uri;
+
+        Set<String> resources = new HashSet<String>();
+        if (!basePath.endsWith("/")) {
+            basePath += "/";
+        }
+        Enumeration<URL> urls = getResources(basePath);
+
+        while (urls.hasMoreElements()) {
+            URL location = urls.nextElement();
+
+            try {
+                if ("jar".equals(location.getProtocol())) {
+
+                    readJarDirectoryEntries(location, basePath, resources);
+
+                } else if ("file".equals(location.getProtocol())) {
+
+                    readSubDirectories(new File(location.toURI()), uri, resources);
+
+                }
+            } catch (Exception e) {
+            }
+        }
+
+        return convertPathsToPackages(resources);
+    }
+
+    /**
+     * Gets a list of subpckages from jars or dirs
+     */
+    public Map<URL, Set<String>> findPackagesMap(String uri) throws IOException {
+        String basePath = path + uri;
+
+        if (!basePath.endsWith("/")) {
+            basePath += "/";
+        }
+        Enumeration<URL> urls = getResources(basePath);
+        Map<URL, Set<String>> result = new HashMap<URL, Set<String>>();
+
+        while (urls.hasMoreElements()) {
+            URL location = urls.nextElement();
+
+            try {
+                if ("jar".equals(location.getProtocol())) {
+                    Set<String> resources = new HashSet<String>();
+                    readJarDirectoryEntries(location, basePath, resources);
+                    result.put(location, convertPathsToPackages(resources));
+                } else if ("file".equals(location.getProtocol())) {
+                    Set<String> resources = new HashSet<String>();
+                    readSubDirectories(new File(location.toURI()), uri, resources);
+                    result.put(location, convertPathsToPackages(resources));
+                }
+            } catch (Exception e) {
+            }
+        }
+
+        return result;
+    }
+
+    private Set<String> convertPathsToPackages(Set<String> resources) {
+        Set<String> packageNames = new HashSet<String>(resources.size());
+        for(String resource : resources) {
+            packageNames.add(StringUtils.chomp(StringUtils.replace(resource, "/", "."), "."));
+        }
+
+        return packageNames;
+    }
+
+    private static void readDirectoryEntries(URL location, Map<String, URL> resources) throws MalformedURLException {
+        File dir = new File(URLDecoder.decode(location.getPath()));
+        if (dir.isDirectory()) {
+            File[] files = dir.listFiles();
+            for (File file : files) {
+                if (!file.isDirectory()) {
+                    String name = file.getName();
+                    URL url = file.toURL();
+                    resources.put(name, url);
+                }
+            }
+        }
+    }
+
+    /**
+     * Reads subdirectories of a file. The output is a list of subdirectories, relative to the basepath
+     */
+     private static void readSubDirectories(File dir, String basePath, Set<String> resources) throws MalformedURLException {
+        if (dir.isDirectory()) {
+            File[] files = dir.listFiles();
+            for (File file : files) {
+                if (file.isDirectory()) {
+                    String name = file.getName();
+                    String subName = StringUtils.chomp(basePath, "/") + "/" + name;
+                    resources.add(subName);
+                    readSubDirectories(file, subName, resources);
+                }
+            }
+        }
+    }
+
+    private static void readJarEntries(URL location, String basePath, Map<String, URL> resources) throws IOException {
+        JarURLConnection conn = (JarURLConnection) location.openConnection();
+        JarFile jarfile = null;
+        jarfile = conn.getJarFile();
+
+        Enumeration<JarEntry> entries = jarfile.entries();
+        while (entries != null && entries.hasMoreElements()) {
+            JarEntry entry = entries.nextElement();
+            String name = entry.getName();
+
+            if (entry.isDirectory() || !name.startsWith(basePath) || name.length() == basePath.length()) {
+                continue;
+            }
+
+            name = name.substring(basePath.length());
+
+            if (name.contains("/")) {
+                continue;
+            }
+
+            URL resource = new URL(location, name);
+            resources.put(name, resource);
+        }
+    }
+
+    //read directories in the jar that start with the basePath
+    private static void readJarDirectoryEntries(URL location, String basePath, Set<String> resources) throws IOException {
+        JarURLConnection conn = (JarURLConnection) location.openConnection();
+        JarFile jarfile = null;
+        jarfile = conn.getJarFile();
+
+        Enumeration<JarEntry> entries = jarfile.entries();
+        while (entries != null && entries.hasMoreElements()) {
+            JarEntry entry = entries.nextElement();
+            String name = entry.getName();
+
+            if (entry.isDirectory() && StringUtils.startsWith(name, basePath)) {
+                resources.add(name);
+            }
+        }
+    }
+
+    private Properties loadProperties(URL resource) throws IOException {
+        InputStream in = resource.openStream();
+
+        BufferedInputStream reader = null;
+        try {
+            reader = new BufferedInputStream(in);
+            Properties properties = new Properties();
+            properties.load(reader);
+
+            return properties;
+        } finally {
+            try {
+                in.close();
+                reader.close();
+            } catch (Exception e) {
+            }
+        }
+    }
+
+    private String readContents(URL resource) throws IOException {
+        InputStream in = resource.openStream();
+        BufferedInputStream reader = null;
+        StringBuilder sb = new StringBuilder();
+
+        try {
+            reader = new BufferedInputStream(in);
+
+            int b = reader.read();
+            while (b != -1) {
+                sb.append((char) b);
+                b = reader.read();
+            }
+
+            return sb.toString().trim();
+        } finally {
+            try {
+                in.close();
+                reader.close();
+            } catch (Exception e) {
+            }
+        }
+    }
+
+    private URL getResource(String fullUri) {
+        if (urls == null){
+            return classLoaderInterface.getResource(fullUri);
+        }
+        return findResource(fullUri, urls);
+    }
+
+    private Enumeration<URL> getResources(String fulluri) throws IOException {
+        if (urls == null) {
+            return classLoaderInterface.getResources(fulluri);
+        }
+        Vector<URL> resources = new Vector();
+        for (URL url : urls) {
+            URL resource = findResource(fulluri, url);
+            if (resource != null){
+                resources.add(resource);
+            }
+        }
+        return resources.elements();
+    }
+
+    private URL findResource(String resourceName, URL... search) {
+        for (int i = 0; i < search.length; i++) {
+            URL currentUrl = search[i];
+            if (currentUrl == null) {
+                continue;
+            }
+            JarFile jarFile = null;
+            try {
+                String protocol = currentUrl.getProtocol();
+                if ("jar".equals(protocol)) {
+                    /*
+                    * If the connection for currentUrl or resURL is
+                    * used, getJarFile() will throw an exception if the
+                    * entry doesn't exist.
+                    */
+                    URL jarURL = ((JarURLConnection) currentUrl.openConnection()).getJarFileURL();
+                    try {
+                        JarURLConnection juc = (JarURLConnection) new URL("jar", "", jarURL.toExternalForm() + "!/").openConnection();
+                        jarFile = juc.getJarFile();
+                    } catch (IOException e) {
+                        // Don't look for this jar file again
+                        search[i] = null;
+                        throw e;
+                    }
+
+                    String entryName;
+                    if (currentUrl.getFile().endsWith("!/")) {
+                        entryName = resourceName;
+                    } else {
+                        String file = currentUrl.getFile();
+                        int sepIdx = file.lastIndexOf("!/");
+                        if (sepIdx == -1) {
+                            // Invalid URL, don't look here again
+                            search[i] = null;
+                            continue;
+                        }
+                        sepIdx += 2;
+                        StringBuilder sb = new StringBuilder(file.length() - sepIdx + resourceName.length());
+                        sb.append(file.substring(sepIdx));
+                        sb.append(resourceName);
+                        entryName = sb.toString();
+                    }
+                    if ("META-INF/".equals(entryName) && jarFile.getEntry("META-INF/MANIFEST.MF") != null){
+                        return targetURL(currentUrl, "META-INF/MANIFEST.MF");
+                    }
+                    if (jarFile.getEntry(entryName) != null) {
+                        return targetURL(currentUrl, resourceName);
+                    }
+                } else if ("file".equals(protocol)) {
+                    String baseFile = currentUrl.getFile();
+                    String host = currentUrl.getHost();
+                    int hostLength = 0;
+                    if (host != null) {
+                        hostLength = host.length();
+                    }
+                    StringBuilder buf = new StringBuilder(2 + hostLength + baseFile.length() + resourceName.length());
+
+                    if (hostLength > 0) {
+                        buf.append("//").append(host);
+                    }
+                    // baseFile always ends with '/'
+                    buf.append(baseFile);
+                    String fixedResName = resourceName;
+                    // Do not create a UNC path, i.e. \\host
+                    while (fixedResName.startsWith("/") || fixedResName.startsWith("\\")) {
+                        fixedResName = fixedResName.substring(1);
+                    }
+                    buf.append(fixedResName);
+                    String filename = buf.toString();
+                    File file = new File(filename);
+                    File file2 = new File(URLDecoder.decode(filename));
+
+                    if (file.exists() || file2.exists()) {
+                        return targetURL(currentUrl, fixedResName);
+                    }
+                } else {
+                    URL resourceURL = targetURL(currentUrl, resourceName);
+                    URLConnection urlConnection = resourceURL.openConnection();
+
+                    try {
+                        urlConnection.getInputStream().close();
+                    } catch (SecurityException e) {
+                        return null;
+                    }
+                    // HTTP can return a stream on a non-existent file
+                    // So check for the return code;
+                    if (!"http".equals(resourceURL.getProtocol())) {
+                        return resourceURL;
+                    }
+
+                    int code = ((HttpURLConnection) urlConnection).getResponseCode();
+                    if (code >= 200 && code < 300) {
+                        return resourceURL;
+                    }
+                }
+            } catch (MalformedURLException e) {
+                // Keep iterating through the URL list
+            } catch (IOException e) {
+            } catch (SecurityException e) {
+            }
+        }
+        return null;
+    }
+
+    private URL targetURL(URL base, String name) throws MalformedURLException {
+        StringBuilder sb = new StringBuilder(base.getFile().length() + name.length());
+        sb.append(base.getFile());
+        sb.append(name);
+        String file = sb.toString();
+        return new URL(base.getProtocol(), base.getHost(), base.getPort(), file, null);
+    }
+}

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/Test.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/Test.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/Test.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/Test.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2002-2003,2009 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.struts2.xwork2.util.finder;
+
+/**
+ * This is the testing interface that is used to accept or reject resources.
+ */
+public interface Test<T> {
+    /**
+     * The test method.
+     *
+     * @param   t The resource object to test.
+     * @return  True if the resource should be accepted, false otherwise.
+     */
+    public boolean test(T t);
+}
\ No newline at end of file

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/UrlSet.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/UrlSet.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/UrlSet.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/finder/UrlSet.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2002-2003,2009 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.struts2.xwork2.util.finder;
+
+import org.apache.struts2.xwork2.util.logging.Logger;
+import org.apache.struts2.xwork2.util.logging.LoggerFactory;
+import org.apache.struts2.xwork2.util.URLUtil;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.ObjectUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
+
+/**
+ * Use with ClassFinder to filter the Urls to be scanned, example:
+ * <pre>
+ * UrlSet urlSet = new UrlSet(classLoader);
+ * urlSet = urlSet.exclude(ClassLoader.getSystemClassLoader().getParent());
+ * urlSet = urlSet.excludeJavaExtDirs();
+ * urlSet = urlSet.excludeJavaEndorsedDirs();
+ * urlSet = urlSet.excludeJavaHome();
+ * urlSet = urlSet.excludePaths(System.getProperty("sun.boot.class.path", ""));
+ * urlSet = urlSet.exclude(".*?/JavaVM.framework/.*");
+ * urlSet = urlSet.exclude(".*?/activemq-(core|ra)-[\\d.]+.jar(!/)?");
+ * </pre>
+ * @author David Blevins
+ * @version $Rev: 1209415 $ $Date: 2011-12-02 12:24:48 +0100 (Fri, 02 Dec 2011) $
+ */
+public class UrlSet {
+    private static final Logger LOG = LoggerFactory.getLogger(UrlSet.class);
+    private final Map<String,URL> urls;
+    private Set<String> protocols;
+    
+
+    public UrlSet(ClassLoaderInterface classLoader) throws IOException {
+        this(getUrls(classLoader));
+    }
+
+    public UrlSet(ClassLoaderInterface classLoader, Set<String> protocols) throws IOException {
+        this(getUrls(classLoader, protocols));
+        this.protocols = protocols;
+    }
+
+    public UrlSet(URL... urls){
+        this(Arrays.asList(urls));
+    }
+    /**
+     * Ignores all URLs that are not "jar" or "file"
+     * @param urls
+     */
+    public UrlSet(Collection<URL> urls){
+        this.urls = new HashMap<String,URL>();
+        for (URL location : urls) {
+            try {
+//                if (location.getProtocol().equals("file")) {
+//                    try {
+//                        // See if it's actually a jar
+//                        URL jarUrl = new URL("jar", "", location.toExternalForm() + "!/");
+//                        JarURLConnection juc = (JarURLConnection) jarUrl.openConnection();
+//                        juc.getJarFile();
+//                        location = jarUrl;
+//                    } catch (IOException e) {
+//                    }
+//                    this.urls.put(location.toExternalForm(), location);
+//                }
+                this.urls.put(location.toExternalForm(), location);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private UrlSet(Map<String, URL> urls) {
+        this.urls = urls;
+    }
+
+    public UrlSet include(UrlSet urlSet){
+        Map<String, URL> urls = new HashMap<String, URL>(this.urls);
+        urls.putAll(urlSet.urls);
+        return new UrlSet(urls);
+    }
+
+    public UrlSet exclude(UrlSet urlSet) {
+        Map<String, URL> urls = new HashMap<String, URL>(this.urls);
+        Map<String, URL> parentUrls = urlSet.urls;
+        for (String url : parentUrls.keySet()) {
+            urls.remove(url);
+        }
+        return new UrlSet(urls);
+    }
+
+    public UrlSet exclude(ClassLoaderInterface parent) throws IOException {
+        return exclude(new UrlSet(parent, this.protocols));
+    }
+
+    public UrlSet exclude(File file) throws MalformedURLException {
+        return exclude(relative(file));
+    }
+
+    public UrlSet exclude(String pattern) throws MalformedURLException {
+        return exclude(matching(pattern));
+    }
+
+    /**
+     * Calls excludePaths(System.getProperty("java.ext.dirs"))
+     * @return
+     * @throws MalformedURLException
+     */
+    public UrlSet excludeJavaExtDirs() throws MalformedURLException {
+        return excludePaths(System.getProperty("java.ext.dirs", ""));
+    }
+
+    /**
+     * Calls excludePaths(System.getProperty("java.endorsed.dirs"))
+     *
+     * @return
+     * @throws MalformedURLException
+     */
+    public UrlSet excludeJavaEndorsedDirs() throws MalformedURLException {
+        return excludePaths(System.getProperty("java.endorsed.dirs", ""));
+    }
+
+    public UrlSet excludeJavaHome() throws MalformedURLException {
+        String path = System.getProperty("java.home");
+        if (path != null) {
+
+            File java = new File(path);
+
+            if (path.matches("/System/Library/Frameworks/JavaVM.framework/Versions/[^/]+/Home")){
+                java = java.getParentFile();
+            }
+            return exclude(java);
+        } else {
+            return this;
+        }
+    }
+
+    public UrlSet excludePaths(String pathString) throws MalformedURLException {
+        String[] paths = pathString.split(File.pathSeparator);
+        UrlSet urlSet = this;
+        for (String path : paths) {
+            if (StringUtils.isNotEmpty(path)) {
+                File file = new File(path);
+                urlSet = urlSet.exclude(file);
+            }
+        }
+        return urlSet;
+    }
+
+    public UrlSet matching(String pattern) {
+        Map<String, URL> urls = new HashMap<String, URL>();
+        for (Map.Entry<String, URL> entry : this.urls.entrySet()) {
+            String url = entry.getKey();
+            if (url.matches(pattern)){
+                urls.put(url, entry.getValue());
+            }
+        }
+        return new UrlSet(urls);
+    }
+
+    /**
+     * Try to find a classes directory inside a war file add its normalized url to this set
+     */
+    public UrlSet includeClassesUrl(ClassLoaderInterface classLoaderInterface) throws IOException {
+        Enumeration<URL> rootUrlEnumeration = classLoaderInterface.getResources("");
+        while (rootUrlEnumeration.hasMoreElements()) {
+            URL url = rootUrlEnumeration.nextElement();
+            String externalForm = StringUtils.removeEnd(url.toExternalForm(), "/");
+            if (externalForm.endsWith(".war/WEB-INF/classes")) {
+                //if it is inside a war file, get the url to the file
+                externalForm = StringUtils.substringBefore(externalForm, "/WEB-INF/classes");
+                URL warUrl = new URL(externalForm);
+                URL normalizedUrl = URLUtil.normalizeToFileProtocol(warUrl);
+                URL finalUrl = (URL) ObjectUtils.defaultIfNull(normalizedUrl, warUrl);
+
+                Map<String, URL> newUrls = new HashMap<String, URL>(this.urls);
+                newUrls.put(finalUrl.toExternalForm(), finalUrl);
+                return new UrlSet(newUrls);
+            }
+        }
+
+        return this;
+    }
+
+    public UrlSet relative(File file) throws MalformedURLException {
+        String urlPath = file.toURL().toExternalForm();
+        Map<String, URL> urls = new HashMap<String, URL>();
+        for (Map.Entry<String, URL> entry : this.urls.entrySet()) {
+            String url = entry.getKey();
+            if (url.startsWith(urlPath) || url.startsWith("jar:"+urlPath)){
+                urls.put(url, entry.getValue());
+            }
+        }
+        return new UrlSet(urls);
+    }
+
+    public List<URL> getUrls() {
+        return new ArrayList<URL>(urls.values());
+    }
+
+    private static List<URL> getUrls(ClassLoaderInterface classLoader) throws IOException {
+        List<URL> list = new ArrayList<URL>();
+
+        //find jars
+        ArrayList<URL> urls = Collections.list(classLoader.getResources("META-INF"));
+
+        for (URL url : urls) {
+            if ("jar".equalsIgnoreCase(url.getProtocol())) {
+                String externalForm = url.toExternalForm();
+                //build a URL pointing to the jar, instead of the META-INF dir
+                url = new URL(StringUtils.substringBefore(externalForm, "META-INF"));
+                list.add(url);
+            } else if (LOG.isDebugEnabled())
+                LOG.debug("Ignoring URL [#0] because it is not a jar", url.toExternalForm());
+
+        }
+
+        //usually the "classes" dir
+        list.addAll(Collections.list(classLoader.getResources("")));
+        return list;
+    }
+
+    private static List<URL> getUrls(ClassLoaderInterface classLoader, Set<String> protocols) throws IOException {
+
+        if (protocols == null) {
+            return getUrls(classLoader);
+        }
+
+        List<URL> list = new ArrayList<URL>();
+
+        //find jars
+        ArrayList<URL> urls = Collections.list(classLoader.getResources("META-INF"));
+
+        for (URL url : urls) {
+            if (protocols.contains(url.getProtocol())) {
+                String externalForm = url.toExternalForm();
+                //build a URL pointing to the jar, instead of the META-INF dir
+                url = new URL(StringUtils.substringBefore(externalForm, "META-INF"));
+                list.add(url);
+            } else if (LOG.isDebugEnabled())
+                LOG.debug("Ignoring URL [#0] because it is not a valid protocol", url.toExternalForm());
+
+        }
+
+        //usually the "classes" dir
+        list.addAll(Collections.list(classLoader.getResources("")));
+        return list;
+    }
+
+}

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/Locatable.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/Locatable.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/Locatable.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/Locatable.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.struts2.xwork2.util.location;
+
+/**
+ * A interface that should be implemented by objects knowning their location (i.e. where they
+ * have been created from).
+ */
+public interface Locatable {
+    /**
+     * Get the location of this object
+     * 
+     * @return the location
+     */
+    public Location getLocation();
+}

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/LocatableProperties.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/LocatableProperties.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/LocatableProperties.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/LocatableProperties.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,76 @@
+package org.apache.struts2.xwork2.util.location;
+
+import org.apache.struts2.xwork2.util.PropertiesReader;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Properties implementation that remembers the location of each property.  When
+ * loaded, a custom properties file parser is used to remember both the line number
+ * and preceeding comments for each property entry.
+ */
+public class LocatableProperties extends Properties implements Locatable {
+
+    Location location;
+    Map<String,Location> propLocations;
+    
+    public LocatableProperties() {
+        this(null);
+    }
+    
+    public LocatableProperties(Location loc) {
+        super();
+        this.location = loc;
+        this.propLocations = new HashMap<String,Location>();
+    }
+    
+    @Override
+    public void load(InputStream in) throws IOException {
+        Reader reader = new InputStreamReader(in);
+        PropertiesReader pr = new PropertiesReader(reader);
+        while (pr.nextProperty()) {
+            String name = pr.getPropertyName();
+            String val = pr.getPropertyValue();
+            int line = pr.getLineNumber();
+            String desc = convertCommentsToString(pr.getCommentLines());
+            
+            Location loc = new LocationImpl(desc, location.getURI(), line, 0);
+            setProperty(name, val, loc);
+        }
+    }
+    
+    String convertCommentsToString(List<String> lines) {
+        StringBuilder sb = new StringBuilder();
+        if (lines != null && lines.size() > 0) {
+            for (String line : lines) {
+                sb.append(line).append('\n');
+            }
+        }
+        return sb.toString();
+    }
+    
+    public Object setProperty(String key, String value, Object locationObj) {
+        Object obj = super.setProperty(key, value);
+        if (location != null) {
+            Location loc = LocationUtils.getLocation(locationObj);
+            propLocations.put(key, loc);
+        }
+        return obj;
+    }
+    
+    public Location getPropertyLocation(String key) {
+        return propLocations.get(key);
+    }
+    
+    public Location getLocation() {
+        return location;
+    }
+
+}

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/Located.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/Located.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/Located.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/Located.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.struts2.xwork2.util.location;
+
+/**
+ * Base class for location aware objects
+ */
+public abstract class Located implements Locatable {
+    
+    protected Location location;
+    
+    /**
+     * Get the location of this object
+     * 
+     * @return the location
+     */
+    public Location getLocation() {
+        return location;
+    }
+    
+    /**
+     * Set the location of this object
+     * 
+     * @param loc the location
+     */
+    public void setLocation(Location loc) {
+        this.location = loc;
+    }
+}

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/Location.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/Location.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/Location.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/Location.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.struts2.xwork2.util.location;
+
+import java.util.List;
+
+
+/**
+ * A location in a resource. The location is composed of the URI of the resource, and 
+ * the line and column numbers within that resource (when available), along with a description.
+ * <p>
+ * Locations are mostly provided by {@link Locatable}s objects.
+ */
+public interface Location {
+    
+    /**
+     * Constant for unknown locations.
+     */
+    public static final Location UNKNOWN = LocationImpl.UNKNOWN;
+    
+    /**
+     * Get the description of this location
+     * 
+     * @return the description (can be <code>null</code>)
+     */
+    String getDescription();
+    
+    /**
+     * Get the URI of this location
+     * 
+     * @return the URI (<code>null</code> if unknown).
+     */
+    String getURI();
+
+    /**
+     * Get the line number of this location
+     * 
+     * @return the line number (<code>-1</code> if unknown)
+     */
+    int getLineNumber();
+    
+    /**
+     * Get the column number of this location
+     * 
+     * @return the column number (<code>-1</code> if unknown)
+     */
+    int getColumnNumber();
+    
+    /**
+     * Gets a source code snippet with the default padding
+     *
+     * @param padding The amount of lines before and after the error to include
+     * @return A list of source lines
+     */
+    List<String> getSnippet(int padding);
+}

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/LocationAttributes.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/LocationAttributes.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/LocationAttributes.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/LocationAttributes.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,348 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.struts2.xwork2.util.location;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * A class to handle location information stored in attributes.
+ * These attributes are typically setup using {@link LocationAttributes.Pipe}
+ * which augments the SAX stream with additional attributes, e.g.:
+ * <pre>
+ * &lt;root xmlns:loc="http://struts.apache.org/xwork/location"
+ *       loc:src="file://path/to/file.xml"
+ *       loc:line="1" loc:column="1"&gt;
+ *   &lt;foo loc:src="file://path/to/file.xml" loc:line="2" loc:column="3"/&gt;
+ * &lt;/root&gt;
+ * </pre>
+ * 
+ * @see LocationAttributes.Pipe
+ * @since 2.1.8
+ * @version $Id: LocationAttributes.java 1209415 2011-12-02 11:24:48Z lukaszlenart $
+ */
+public class LocationAttributes {
+    /** Prefix for the location namespace */
+    public static final String PREFIX = "loc";
+    /** Namespace URI for location attributes */
+    public static final String URI = "http://struts.apache.org/xwork/location";
+
+    /** Attribute name for the location URI */
+    public static final String SRC_ATTR  = "src";
+    /** Attribute name for the line number */
+    public static final String LINE_ATTR = "line";
+    /** Attribute name for the column number */
+    public static final String COL_ATTR  = "column";
+
+    /** Attribute qualified name for the location URI */
+    public static final String Q_SRC_ATTR  = "loc:src";
+    /** Attribute qualified name for the line number */
+    public static final String Q_LINE_ATTR = "loc:line";
+    /** Attribute qualified name for the column number */
+    public static final String Q_COL_ATTR  = "loc:column";
+    
+    // Private constructor, we only have static methods
+    private LocationAttributes() {
+        // Nothing
+    }
+    
+    /**
+     * Add location attributes to a set of SAX attributes.
+     * 
+     * @param locator the <code>Locator</code> (can be null)
+     * @param attrs the <code>Attributes</code> where locator information should be added
+     * @return Location enabled Attributes.
+     */
+    public static Attributes addLocationAttributes(Locator locator, Attributes attrs) {
+        if (locator == null || attrs.getIndex(URI, SRC_ATTR) != -1) {
+            // No location information known, or already has it
+            return attrs;
+        }
+        
+        // Get an AttributeImpl so that we can add new attributes.
+        AttributesImpl newAttrs = attrs instanceof AttributesImpl ?
+            (AttributesImpl)attrs : new AttributesImpl(attrs);
+
+        newAttrs.addAttribute(URI, SRC_ATTR, Q_SRC_ATTR, "CDATA", locator.getSystemId());
+        newAttrs.addAttribute(URI, LINE_ATTR, Q_LINE_ATTR, "CDATA", Integer.toString(locator.getLineNumber()));
+        newAttrs.addAttribute(URI, COL_ATTR, Q_COL_ATTR, "CDATA", Integer.toString(locator.getColumnNumber()));
+        
+        return newAttrs;
+    }
+    
+    /**
+     * Returns the {@link Location} of an element (SAX flavor).
+     * 
+     * @param attrs the element's attributes that hold the location information
+     * @param description a description for the location (can be null)
+     * @return a {@link Location} object
+     */
+    public static Location getLocation(Attributes attrs, String description) {
+        String src = attrs.getValue(URI, SRC_ATTR);
+        if (src == null) {
+            return Location.UNKNOWN;
+        }
+        
+        return new LocationImpl(description, src, getLine(attrs), getColumn(attrs));
+    }
+
+    /**
+     * Returns the location of an element (SAX flavor). If the location is to be kept
+     * into an object built from this element, consider using {@link #getLocation(Attributes, String)}
+     * and the {@link Locatable} interface.
+     * 
+     * @param attrs the element's attributes that hold the location information
+     * @return a location string as defined by {@link Location}.
+     */
+    public static String getLocationString(Attributes attrs) {
+        String src = attrs.getValue(URI, SRC_ATTR);
+        if (src == null) {
+            return LocationUtils.UNKNOWN_STRING;
+        }
+        
+        return src + ":" + attrs.getValue(URI, LINE_ATTR) + ":" + attrs.getValue(URI, COL_ATTR);
+    }
+    
+    /**
+     * Returns the URI of an element (SAX flavor)
+     * 
+     * @param attrs the element's attributes that hold the location information
+     * @return the element's URI or "<code>[unknown location]</code>" if <code>attrs</code>
+     *         has no location information.
+     */
+    public static String getURI(Attributes attrs) {
+        String src = attrs.getValue(URI, SRC_ATTR);
+        return src != null ? src : LocationUtils.UNKNOWN_STRING;
+    }
+    
+    /**
+     * Returns the line number of an element (SAX flavor)
+     * 
+     * @param attrs the element's attributes that hold the location information
+     * @return the element's line number or <code>-1</code> if <code>attrs</code>
+     *         has no location information.
+     */
+    public static int getLine(Attributes attrs) {
+        String line = attrs.getValue(URI, LINE_ATTR);
+        return line != null ? Integer.parseInt(line) : -1;
+    }
+    
+    /**
+     * Returns the column number of an element (SAX flavor)
+     * 
+     * @param attrs the element's attributes that hold the location information
+     * @return the element's column number or <code>-1</code> if <code>attrs</code>
+     *         has no location information.
+     */
+    public static int getColumn(Attributes attrs) {
+        String col = attrs.getValue(URI, COL_ATTR);
+        return col != null ? Integer.parseInt(col) : -1;
+    }
+    
+    /**
+     * Returns the {@link Location} of an element (DOM flavor).
+     * 
+     * @param elem the element that holds the location information
+     * @param description a description for the location (if <code>null</code>, the element's name is used)
+     * @return a {@link Location} object
+     */
+    public static Location getLocation(Element elem, String description) {
+        Attr srcAttr = elem.getAttributeNodeNS(URI, SRC_ATTR);
+        if (srcAttr == null) {
+            return Location.UNKNOWN;
+        }
+
+        return new LocationImpl(description == null ? elem.getNodeName() : description,
+                srcAttr.getValue(), getLine(elem), getColumn(elem));
+    }
+    
+    /**
+     * Same as <code>getLocation(elem, null)</code>.
+     */
+    public static Location getLocation(Element elem) {
+        return getLocation(elem, null);
+    }
+   
+
+    /**
+     * Returns the location of an element that has been processed by this pipe (DOM flavor).
+     * If the location is to be kept into an object built from this element, consider using
+     * {@link #getLocation(Element)} and the {@link Locatable} interface.
+     * 
+     * @param elem the element that holds the location information
+     * @return a location string as defined by {@link Location}.
+     */
+    public static String getLocationString(Element elem) {
+        Attr srcAttr = elem.getAttributeNodeNS(URI, SRC_ATTR);
+        if (srcAttr == null) {
+            return LocationUtils.UNKNOWN_STRING;
+        }
+        
+        return srcAttr.getValue() + ":" + elem.getAttributeNS(URI, LINE_ATTR) + ":" + elem.getAttributeNS(URI, COL_ATTR);
+    }
+    
+    /**
+     * Returns the URI of an element (DOM flavor)
+     * 
+     * @param elem the element that holds the location information
+     * @return the element's URI or "<code>[unknown location]</code>" if <code>elem</code>
+     *         has no location information.
+     */
+    public static String getURI(Element elem) {
+        Attr attr = elem.getAttributeNodeNS(URI, SRC_ATTR);
+        return attr != null ? attr.getValue() : LocationUtils.UNKNOWN_STRING;
+    }
+
+    /**
+     * Returns the line number of an element (DOM flavor)
+     * 
+     * @param elem the element that holds the location information
+     * @return the element's line number or <code>-1</code> if <code>elem</code>
+     *         has no location information.
+     */
+    public static int getLine(Element elem) {
+        Attr attr = elem.getAttributeNodeNS(URI, LINE_ATTR);
+        return attr != null ? Integer.parseInt(attr.getValue()) : -1;
+    }
+
+    /**
+     * Returns the column number of an element (DOM flavor)
+     * 
+     * @param elem the element that holds the location information
+     * @return the element's column number or <code>-1</code> if <code>elem</code>
+     *         has no location information.
+     */
+    public static int getColumn(Element elem) {
+        Attr attr = elem.getAttributeNodeNS(URI, COL_ATTR);
+        return attr != null ? Integer.parseInt(attr.getValue()) : -1;
+    }
+    
+    /**
+     * Remove the location attributes from a DOM element.
+     * 
+     * @param elem the element to remove the location attributes from.
+     * @param recurse if <code>true</code>, also remove location attributes on descendant elements.
+     */
+    public static void remove(Element elem, boolean recurse) {
+        elem.removeAttributeNS(URI, SRC_ATTR);
+        elem.removeAttributeNS(URI, LINE_ATTR);
+        elem.removeAttributeNS(URI, COL_ATTR);
+        if (recurse) {
+            NodeList children = elem.getChildNodes();
+            for (int i = 0; i < children.getLength(); i++) {
+                Node child = children.item(i);
+                if (child.getNodeType() == Node.ELEMENT_NODE) {
+                    remove((Element)child, recurse);
+                }
+            }
+        }
+    }
+
+    /**
+     * A SAX filter that adds the information available from the <code>Locator</code> as attributes.
+     * The purpose of having location as attributes is to allow this information to survive transformations
+     * of the document (an XSL could copy these attributes over) or conversion of SAX events to a DOM.
+     * <p>
+     * The location is added as 3 attributes in a specific namespace to each element.
+     * <pre>
+     * &lt;root xmlns:loc="http://opensymphony.com/xwork/location"
+     *       loc:src="file://path/to/file.xml"
+     *       loc:line="1" loc:column="1"&gt;
+     *   &lt;foo loc:src="file://path/to/file.xml" loc:line="2" loc:column="3"/&gt;
+     * &lt;/root&gt;
+     * </pre>
+     * <strong>Note:</strong> Although this adds a lot of information to the serialized form of the document,
+     * the overhead in SAX events is not that big, as attribute names are interned, and all <code>src</code>
+     * attributes point to the same string.
+     * 
+     * @see LocationAttributes
+     */
+    public static class Pipe implements ContentHandler {
+        
+        private Locator locator;
+        
+        private ContentHandler nextHandler;
+        
+        /**
+         * Create a filter. It has to be chained to another handler to be really useful.
+         */
+        public Pipe() {
+        }
+
+        /**
+         * Create a filter that is chained to another handler.
+         * @param next the next handler in the chain.
+         */
+        public Pipe(ContentHandler next) {
+            nextHandler = next;
+        }
+
+        public void setDocumentLocator(Locator locator) {
+            this.locator = locator;
+            nextHandler.setDocumentLocator(locator);
+        }
+        
+        public void startDocument() throws SAXException {
+            nextHandler.startDocument();
+            nextHandler.startPrefixMapping(LocationAttributes.PREFIX, LocationAttributes.URI);
+        }
+        
+        public void endDocument() throws SAXException {
+            endPrefixMapping(LocationAttributes.PREFIX);
+            nextHandler.endDocument();
+        }
+
+        public void startElement(String uri, String loc, String raw, Attributes attrs) throws SAXException {
+            // Add location attributes to the element
+            nextHandler.startElement(uri, loc, raw, LocationAttributes.addLocationAttributes(locator, attrs));
+        }
+
+        public void endElement(String arg0, String arg1, String arg2) throws SAXException {
+            nextHandler.endElement(arg0, arg1, arg2);
+        }
+
+        public void startPrefixMapping(String arg0, String arg1) throws SAXException {
+            nextHandler.startPrefixMapping(arg0, arg1);
+        }
+
+        public void endPrefixMapping(String arg0) throws SAXException {
+            nextHandler.endPrefixMapping(arg0);
+        }
+
+        public void characters(char[] arg0, int arg1, int arg2) throws SAXException {
+            nextHandler.characters(arg0, arg1, arg2);
+        }
+
+        public void ignorableWhitespace(char[] arg0, int arg1, int arg2) throws SAXException {
+            nextHandler.ignorableWhitespace(arg0, arg1, arg2);
+        }
+
+        public void processingInstruction(String arg0, String arg1) throws SAXException {
+            nextHandler.processingInstruction(arg0, arg1);
+        }
+
+        public void skippedEntity(String arg0) throws SAXException {
+            nextHandler.skippedEntity(arg0);
+        }
+    }
+}

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/LocationImpl.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/LocationImpl.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/LocationImpl.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/location/LocationImpl.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.struts2.xwork2.util.location;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Serializable;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A simple immutable and serializable implementation of {@link Location}.
+ */
+public class LocationImpl implements Location, Serializable {
+    private final String uri;
+    private final int line;
+    private final int column;
+    private final String description;
+    
+    // Package private: outside this package, use Location.UNKNOWN.
+    static final LocationImpl UNKNOWN = new LocationImpl(null, null, -1, -1);
+
+    /**
+     * Build a location for a given URI, with unknown line and column numbers.
+     * 
+     * @param uri the resource URI
+     */
+    public LocationImpl(String description, String uri) {
+        this(description, uri, -1, -1);
+    }
+
+    /**
+     * Build a location for a given URI and line and column numbers.
+     * 
+     * @param uri the resource URI
+     * @param line the line number (starts at 1)
+     * @param column the column number (starts at 1)
+     */
+    public LocationImpl(String description, String uri, int line, int column) {
+        if (uri == null || uri.length() == 0) {
+            this.uri = null;
+            this.line = -1;
+            this.column = -1;
+        } else {
+            this.uri = uri;
+            this.line = line;
+            this.column = column;
+        }
+        
+        if (description != null && description.length() == 0) {
+            description = null;
+        }
+        this.description = description;
+    }
+    
+    /**
+     * Copy constructor.
+     * 
+     * @param location the location to be copied
+     */
+    public LocationImpl(Location location) {
+        this(location.getDescription(), location.getURI(), location.getLineNumber(), location.getColumnNumber());
+    }
+    
+    /**
+     * Create a location from an existing one, but with a different description
+     */
+    public LocationImpl(String description, Location location) {
+        this(description, location.getURI(), location.getLineNumber(), location.getColumnNumber());
+    }
+    
+    /**
+     * Obtain a <code>LocationImpl</code> from a {@link Location}. If <code>location</code> is
+     * already a <code>LocationImpl</code>, it is returned, otherwise it is copied.
+     * <p>
+     * This method is useful when an immutable and serializable location is needed, such as in locatable
+     * exceptions.
+     * 
+     * @param location the location
+     * @return an immutable and serializable version of <code>location</code>
+     */
+    public static LocationImpl get(Location location) {
+        if (location instanceof LocationImpl) {
+            return (LocationImpl)location;
+        } else if (location == null) {
+            return UNKNOWN;
+        } else {
+            return new LocationImpl(location);
+        }
+    }
+    
+    /**
+     * Get the description of this location
+     * 
+     * @return the description (can be <code>null</code>)
+     */
+    public String getDescription() {
+        return this.description;
+    }
+    
+    /**
+     * Get the URI of this location
+     * 
+     * @return the URI (<code>null</code> if unknown).
+     */
+    public String getURI() {
+        return this.uri;
+    }
+
+    /**
+     * Get the line number of this location
+     * 
+     * @return the line number (<code>-1</code> if unknown)
+     */
+    public int getLineNumber() {
+        return this.line;
+    }
+    
+    /**
+     * Get the column number of this location
+     * 
+     * @return the column number (<code>-1</code> if unknown)
+     */
+    public int getColumnNumber() {
+        return this.column;
+    }
+    
+    /**
+     * Gets a source code snippet with the default padding
+     *
+     * @param padding The amount of lines before and after the error to include
+     */
+    public List<String> getSnippet(int padding) {
+        List<String> snippet = new ArrayList<String>();
+        if (getLineNumber() > 0) {
+            try {
+                InputStream in = new URL(getURI()).openStream();
+                BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+                
+                int lineno = 0;
+                int errno = getLineNumber();
+                String line;
+                while ((line = reader.readLine()) != null) {
+                    lineno++;
+                    if (lineno >= errno - padding && lineno <= errno + padding) {
+                        snippet.add(line);
+                    }
+                }
+            } catch (Exception ex) {
+                // ignoring as snippet not available isn't a big deal
+            }
+        }
+        return snippet;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+
+        if (obj instanceof Location) {
+            Location other = (Location)obj;
+            return this.line == other.getLineNumber() && this.column == other.getColumnNumber()
+                   && testEquals(this.uri, other.getURI())
+                   && testEquals(this.description, other.getDescription());
+        }
+        
+        return false;
+    }
+    
+    @Override
+    public int hashCode() {
+        int hash = line ^ column;
+        if (uri != null) hash ^= uri.hashCode();
+        if (description != null) hash ^= description.hashCode();
+        
+        return hash;
+    }
+    
+    @Override
+    public String toString() {
+        return LocationUtils.toString(this);
+    }
+    
+    /**
+     * Ensure serialized unknown location resolve to {@link Location#UNKNOWN}.
+     */
+    private Object readResolve() {
+        return this.equals(Location.UNKNOWN) ? Location.UNKNOWN : this;
+    }
+    
+    private boolean testEquals(Object object1, Object object2) {
+        if (object1 == object2) {
+            return true;
+        }
+        if ((object1 == null) || (object2 == null)) {
+            return false;
+        }
+        return object1.equals(object2);
+    }
+}