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 [27/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/ResolverUtil.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/ResolverUtil.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/ResolverUtil.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/ResolverUtil.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,466 @@
+/* Copyright 2005-2006 Tim Fennell
+ *
+ * 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;
+
+import org.apache.struts2.xwork2.util.logging.Logger;
+import org.apache.struts2.xwork2.util.logging.LoggerFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+/**
+ * <p>ResolverUtil is used to locate classes that are available in the/a class path and meet
+ * arbitrary conditions. The two most common conditions are that a class implements/extends
+ * another class, or that is it annotated with a specific annotation. However, through the use
+ * of the {@link Test} class it is possible to search using arbitrary conditions.</p>
+ *
+ * <p>A ClassLoader is used to locate all locations (directories and jar files) in the class
+ * path that contain classes within certain packages, and then to load those classes and
+ * check them. By default the ClassLoader returned by
+ *  {@code Thread.currentThread().getContextClassLoader()} is used, but this can be overridden
+ * by calling {@link #setClassLoader(ClassLoader)} prior to invoking any of the {@code find()}
+ * methods.</p>
+ *
+ * <p>General searches are initiated by calling the
+ * {@link #find(ResolverUtil.Test, String...)} ()} method and supplying
+ * a package name and a Test instance. This will cause the named package <b>and all sub-packages</b>
+ * to be scanned for classes that meet the test. There are also utility methods for the common
+ * use cases of scanning multiple packages for extensions of particular classes, or classes
+ * annotated with a specific annotation.</p>
+ *
+ * <p>The standard usage pattern for the ResolverUtil class is as follows:</p>
+ *
+ *<pre>
+ *ResolverUtil&lt;ActionBean&gt; resolver = new ResolverUtil&lt;ActionBean&gt;();
+ *resolver.findImplementation(ActionBean.class, pkg1, pkg2);
+ *resolver.find(new CustomTest(), pkg1);
+ *resolver.find(new CustomTest(), pkg2);
+ *Collection&lt;ActionBean&gt; beans = resolver.getClasses();
+ *</pre> 
+ *
+ * <p>This class was copied from Stripes - http://stripes.mc4j.org/confluence/display/stripes/Home
+ * </p>
+ * 
+ * @author Tim Fennell
+ */
+public class ResolverUtil<T> {
+    /** An instance of Log to use for logging in this class. */
+    private static final Logger LOG = LoggerFactory.getLogger(ResolverUtil.class);
+
+    /**
+     * A simple interface that specifies how to test classes to determine if they
+     * are to be included in the results produced by the ResolverUtil.
+     */
+    public static interface Test {
+        /**
+         * Will be called repeatedly with candidate classes. Must return True if a class
+         * is to be included in the results, false otherwise.
+         */
+        boolean matches(Class type);
+        
+        boolean matches(URL resource);
+
+        boolean doesMatchClass();
+        boolean doesMatchResource();
+    }
+    
+    public static abstract class ClassTest implements Test {
+        public boolean matches(URL resource) {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean doesMatchClass() {
+            return true;
+        }
+        public boolean doesMatchResource() {
+            return false;
+        }
+    }
+    
+    public static abstract class ResourceTest implements Test {
+        public boolean matches(Class cls) {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean doesMatchClass() {
+            return false;
+        }
+        public boolean doesMatchResource() {
+            return true;
+        }
+    }
+
+    /**
+     * A Test that checks to see if each class is assignable to the provided class. Note
+     * that this test will match the parent type itself if it is presented for matching.
+     */
+    public static class IsA extends ClassTest {
+        private Class parent;
+
+        /** Constructs an IsA test using the supplied Class as the parent class/interface. */
+        public IsA(Class parentType) { this.parent = parentType; }
+
+        /** Returns true if type is assignable to the parent type supplied in the constructor. */
+        public boolean matches(Class type) {
+            return type != null && parent.isAssignableFrom(type);
+        }
+
+        @Override public String toString() {
+            return "is assignable to " + parent.getSimpleName();
+        }
+    }
+    
+    /**
+     * A Test that checks to see if each class name ends with the provided suffix.
+     */
+    public static class NameEndsWith extends ClassTest {
+        private String suffix;
+
+        /** Constructs a NameEndsWith test using the supplied suffix. */
+        public NameEndsWith(String suffix) { this.suffix = suffix; }
+
+        /** Returns true if type name ends with the suffix supplied in the constructor. */
+        public boolean matches(Class type) {
+            return type != null && type.getName().endsWith(suffix);
+        }
+
+        @Override public String toString() {
+            return "ends with the suffix " + suffix;
+        }
+    }
+
+    /**
+     * A Test that checks to see if each class is annotated with a specific annotation. If it
+     * is, then the test returns true, otherwise false.
+     */
+    public static class AnnotatedWith extends ClassTest {
+        private Class<? extends Annotation> annotation;
+
+        /** Construts an AnnotatedWith test for the specified annotation type. */
+        public AnnotatedWith(Class<? extends Annotation> annotation) { this.annotation = annotation; }
+
+        /** Returns true if the type is annotated with the class provided to the constructor. */
+        public boolean matches(Class type) {
+            return type != null && type.isAnnotationPresent(annotation);
+        }
+
+        @Override public String toString() {
+            return "annotated with @" + annotation.getSimpleName();
+        }
+    }
+    
+    public static class NameIs extends ResourceTest {
+        private String name;
+        
+        public NameIs(String name) { this.name = "/" + name; }
+        
+        public boolean matches(URL resource) {
+            return (resource.getPath().endsWith(name));
+        }
+        
+        @Override public String toString() {
+            return "named " + name;
+        }
+    }
+
+    /** The set of matches being accumulated. */
+    private Set<Class<? extends T>> classMatches = new HashSet<Class<?extends T>>();
+    
+    /** The set of matches being accumulated. */
+    private Set<URL> resourceMatches = new HashSet<URL>();
+
+    /**
+     * The ClassLoader to use when looking for classes. If null then the ClassLoader returned
+     * by Thread.currentThread().getContextClassLoader() will be used.
+     */
+    private ClassLoader classloader;
+
+    /**
+     * Provides access to the classes discovered so far. If no calls have been made to
+     * any of the {@code find()} methods, this set will be empty.
+     *
+     * @return the set of classes that have been discovered.
+     */
+    public Set<Class<? extends T>> getClasses() {
+        return classMatches;
+    }
+    
+    public Set<URL> getResources() {
+        return resourceMatches;
+    }
+    
+
+    /**
+     * Returns the classloader that will be used for scanning for classes. If no explicit
+     * ClassLoader has been set by the calling, the context class loader will be used.
+     *
+     * @return the ClassLoader that will be used to scan for classes
+     */
+    public ClassLoader getClassLoader() {
+        return classloader == null ? Thread.currentThread().getContextClassLoader() : classloader;
+    }
+
+    /**
+     * Sets an explicit ClassLoader that should be used when scanning for classes. If none
+     * is set then the context classloader will be used.
+     *
+     * @param classloader a ClassLoader to use when scanning for classes
+     */
+    public void setClassLoader(ClassLoader classloader) { this.classloader = classloader; }
+
+    /**
+     * Attempts to discover classes that are assignable to the type provided. In the case
+     * that an interface is provided this method will collect implementations. In the case
+     * of a non-interface class, subclasses will be collected.  Accumulated classes can be
+     * accessed by calling {@link #getClasses()}.
+     *
+     * @param parent the class of interface to find subclasses or implementations of
+     * @param packageNames one or more package names to scan (including subpackages) for classes
+     */
+    public void findImplementations(Class parent, String... packageNames) {
+        if (packageNames == null) return;
+
+        Test test = new IsA(parent);
+        for (String pkg : packageNames) {
+            findInPackage(test, pkg);
+        }
+    }
+    
+    /**
+     * Attempts to discover classes who's name ends with the provided suffix. Accumulated classes can be
+     * accessed by calling {@link #getClasses()}.
+     *
+     * @param suffix The class name suffix to match
+     * @param packageNames one or more package names to scan (including subpackages) for classes
+     */
+    public void findSuffix(String suffix, String... packageNames) {
+        if (packageNames == null) return;
+
+        Test test = new NameEndsWith(suffix);
+        for (String pkg : packageNames) {
+            findInPackage(test, pkg);
+        }
+    }
+
+    /**
+     * Attempts to discover classes that are annotated with to the annotation. Accumulated
+     * classes can be accessed by calling {@link #getClasses()}.
+     *
+     * @param annotation the annotation that should be present on matching classes
+     * @param packageNames one or more package names to scan (including subpackages) for classes
+     */
+    public void findAnnotated(Class<? extends Annotation> annotation, String... packageNames) {
+        if (packageNames == null) return;
+
+        Test test = new AnnotatedWith(annotation);
+        for (String pkg : packageNames) {
+            findInPackage(test, pkg);
+        }
+    }
+    
+    public void findNamedResource(String name, String... pathNames) {
+        if (pathNames == null) return;
+        
+        Test test = new NameIs(name);
+        for (String pkg : pathNames) {
+            findInPackage(test, pkg);
+        }
+    }
+    
+    /**
+     * Attempts to discover classes that pass the test. Accumulated
+     * classes can be accessed by calling {@link #getClasses()}.
+     *
+     * @param test the test to determine matching classes
+     * @param packageNames one or more package names to scan (including subpackages) for classes
+     */
+    public void find(Test test, String... packageNames) {
+        if (packageNames == null) return;
+
+        for (String pkg : packageNames) {
+            findInPackage(test, pkg);
+        }
+    }
+
+    /**
+     * Scans for classes starting at the package provided and descending into subpackages.
+     * Each class is offered up to the Test as it is discovered, and if the Test returns
+     * true the class is retained.  Accumulated classes can be fetched by calling
+     * {@link #getClasses()}.
+     *
+     * @param test an instance of {@link Test} that will be used to filter classes
+     * @param packageName the name of the package from which to start scanning for
+     *        classes, e.g. {@code net.sourceforge.stripes}
+     */
+    public void findInPackage(Test test, String packageName) {
+        packageName = packageName.replace('.', '/');
+        ClassLoader loader = getClassLoader();
+        Enumeration<URL> urls;
+
+        try {
+            urls = loader.getResources(packageName);
+        }
+        catch (IOException ioe) {
+            if (LOG.isWarnEnabled()) {
+        	LOG.warn("Could not read package: " + packageName, ioe);
+            }
+            return;
+        }
+
+        while (urls.hasMoreElements()) {
+            try {
+                String urlPath = urls.nextElement().getFile();
+                urlPath = URLDecoder.decode(urlPath, "UTF-8");
+
+                // If it's a file in a directory, trim the stupid file: spec
+                if ( urlPath.startsWith("file:") ) {
+                    urlPath = urlPath.substring(5);
+                }
+
+                // Else it's in a JAR, grab the path to the jar
+                if (urlPath.indexOf('!') > 0) {
+                    urlPath = urlPath.substring(0, urlPath.indexOf('!'));
+                }
+
+                if (LOG.isInfoEnabled()) {
+                    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);
+                }
+            }
+            catch (IOException ioe) {
+                if (LOG.isWarnEnabled()) {
+                    LOG.warn("could not read entries", ioe);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Finds matches in a physical directory on a filesystem.  Examines all
+     * files within a directory - if the File object is not a directory, and ends with <i>.class</i>
+     * the file is loaded and tested to see if it is acceptable according to the Test.  Operates
+     * recursively to find classes within a folder structure matching the package structure.
+     *
+     * @param test a Test used to filter the classes that are discovered
+     * @param parent the package name up to this directory in the package hierarchy.  E.g. if
+     *        /classes is in the classpath and we wish to examine files in /classes/org/apache then
+     *        the values of <i>parent</i> would be <i>org/apache</i>
+     * @param location a File object representing a directory
+     */
+    private void loadImplementationsInDirectory(Test test, String parent, File location) {
+        File[] files = location.listFiles();
+        StringBuilder builder = null;
+
+        for (File file : files) {
+            builder = new StringBuilder(100);
+            builder.append(parent).append("/").append(file.getName());
+            String packageOrClass = ( parent == null ? file.getName() : builder.toString() );
+
+            if (file.isDirectory()) {
+                loadImplementationsInDirectory(test, packageOrClass, file);
+            }
+            else if (isTestApplicable(test, file.getName())) {
+                addIfMatching(test, packageOrClass);
+            }
+        }
+    }
+
+    private boolean isTestApplicable(Test test, String path) {
+        return test.doesMatchResource() || path.endsWith(".class") && test.doesMatchClass();
+    }
+
+    /**
+     * 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.
+     *
+     * @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
+     */
+    private void loadImplementationsInJar(Test test, String parent, File jarfile) {
+
+        try {
+            JarEntry entry;
+            JarInputStream jarStream = new JarInputStream(new FileInputStream(jarfile));
+
+            while ( (entry = jarStream.getNextJarEntry() ) != null) {
+                String name = entry.getName();
+                if (!entry.isDirectory() && name.startsWith(parent) && isTestApplicable(test, name)) {
+                    addIfMatching(test, name);
+                }
+            }
+        }
+        catch (IOException ioe) {
+            LOG.error("Could not search jar file '" + jarfile + "' for classes matching criteria: " +
+                      test + " due to an IOException", ioe);
+        }
+    }
+
+    /**
+     * Add the class designated by the fully qualified class name provided to the set of
+     * resolved classes if and only if it is approved by the Test supplied.
+     *
+     * @param test the test used to determine if the class matches
+     * @param fqn the fully qualified name of a class
+     */
+    protected void addIfMatching(Test test, String fqn) {
+        try {
+            ClassLoader loader = getClassLoader();
+            if (test.doesMatchClass()) {
+                String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
+                }
+    
+                Class type = loader.loadClass(externalName);
+                if (test.matches(type) ) {
+                    classMatches.add( (Class<T>) type);
+                }
+            }
+            if (test.doesMatchResource()) {
+                URL url = loader.getResource(fqn);
+                if (url == null) {
+                    url = loader.getResource(fqn.substring(1));
+                }
+                if (url != null && test.matches(url)) {
+                    resourceMatches.add(url);
+                }
+            }
+        }
+        catch (Throwable t) {
+            if (LOG.isWarnEnabled()) {
+        	LOG.warn("Could not examine class '" + fqn + "' due to a " +
+                     t.getClass().getName() + " with message: " + t.getMessage());
+            }
+        }
+    }
+}
\ No newline at end of file

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/TextParseUtil.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/TextParseUtil.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/TextParseUtil.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/TextParseUtil.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2002-2006,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;
+
+import org.apache.struts2.xwork2.ActionContext;
+import org.apache.struts2.xwork2.conversion.impl.XWorkConverter;
+import org.apache.struts2.xwork2.inject.Container;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.commons.lang.StringUtils;
+
+
+/**
+ * Utility class for text parsing.
+ *
+ * @author Jason Carreira
+ * @author Rainer Hermanns
+ * @author tm_jee
+ *
+ * @version $Date: 2011-12-02 12:24:48 +0100 (Fri, 02 Dec 2011) $ $Id: TextParseUtil.java 1209415 2011-12-02 11:24:48Z lukaszlenart $
+ */
+public class TextParseUtil {
+
+    private static final int MAX_RECURSION = 1;
+
+    /**
+     * Converts all instances of ${...}, and %{...} in <code>expression</code> to the value returned
+     * by a call to {@link ValueStack#findValue(java.lang.String)}. If an item cannot
+     * be found on the stack (null is returned), then the entire variable ${...} is not
+     * displayed, just as if the item was on the stack but returned an empty string.
+     *
+     * @param expression an expression that hasn't yet been translated
+     * @return the parsed expression
+     */
+    public static String translateVariables(String expression, ValueStack stack) {
+        return translateVariables(new char[]{'$', '%'}, expression, stack, String.class, null).toString();
+    }
+
+
+    /**
+     * Function similarly as {@link #translateVariables(char, String, ValueStack)}
+     * except for the introduction of an additional <code>evaluator</code> that allows
+     * the parsed value to be evaluated by the <code>evaluator</code>. The <code>evaluator</code>
+     * could be null, if it is it will just be skipped as if it is just calling
+     * {@link #translateVariables(char, String, ValueStack)}.
+     *
+     * <p/>
+     *
+     * A typical use-case would be when we need to URL Encode the parsed value. To do so
+     * we could just supply a URLEncodingEvaluator for example.
+     *
+     * @param expression
+     * @param stack
+     * @param evaluator The parsed Value evaluator (could be null).
+     * @return the parsed (and possibly evaluated) variable String.
+     */
+    public static String translateVariables(String expression, ValueStack stack, ParsedValueEvaluator evaluator) {
+    	return translateVariables(new char[]{'$', '%'}, expression, stack, String.class, evaluator).toString();
+    }
+
+    /**
+     * Converts all instances of ${...} in <code>expression</code> to the value returned
+     * by a call to {@link ValueStack#findValue(java.lang.String)}. If an item cannot
+     * be found on the stack (null is returned), then the entire variable ${...} is not
+     * displayed, just as if the item was on the stack but returned an empty string.
+     *
+     * @param open
+     * @param expression
+     * @param stack
+     * @return Translated variable String
+     */
+    public static String translateVariables(char open, String expression, ValueStack stack) {
+        return translateVariables(open, expression, stack, String.class, null).toString();
+    }
+
+    /**
+     * Converted object from variable translation.
+     *
+     * @param open
+     * @param expression
+     * @param stack
+     * @param asType
+     * @return Converted object from variable translation.
+     */
+    public static Object translateVariables(char open, String expression, ValueStack stack, Class asType) {
+    	return translateVariables(open, expression, stack, asType, null);
+    }
+
+    /**
+     * Converted object from variable translation.
+     *
+     * @param open
+     * @param expression
+     * @param stack
+     * @param asType
+     * @param evaluator
+     * @return Converted object from variable translation.
+     */
+    public static Object translateVariables(char open, String expression, ValueStack stack, Class asType, ParsedValueEvaluator evaluator) {
+        return translateVariables(new char[]{open} , expression, stack, asType, evaluator, MAX_RECURSION);
+    }
+
+    /**
+     * Converted object from variable translation.
+     *
+     * @param open
+     * @param expression
+     * @param stack
+     * @param asType
+     * @param evaluator
+     * @return Converted object from variable translation.
+     */
+    public static Object translateVariables(char[] openChars, String expression, ValueStack stack, Class asType, ParsedValueEvaluator evaluator) {
+        return translateVariables(openChars, expression, stack, asType, evaluator, MAX_RECURSION);
+    }
+
+    /**
+     * Converted object from variable translation.
+     *
+     * @param open
+     * @param expression
+     * @param stack
+     * @param asType
+     * @param evaluator
+     * @return Converted object from variable translation.
+     */
+    public static Object translateVariables(char open, String expression, ValueStack stack, Class asType, ParsedValueEvaluator evaluator, int maxLoopCount) {
+        return translateVariables(new char[]{open}, expression, stack, asType, evaluator, maxLoopCount);
+    }
+
+    /**
+     * Converted object from variable translation.
+     *
+     * @param open
+     * @param expression
+     * @param stack
+     * @param asType
+     * @param evaluator
+     * @return Converted object from variable translation.
+     */
+    public static Object translateVariables(char[] openChars, String expression, ValueStack stack, Class asType, ParsedValueEvaluator evaluator, int maxLoopCount) {
+        // deal with the "pure" expressions first!
+        //expression = expression.trim();
+        Object result = expression;
+        for (char open : openChars) {
+            int loopCount = 1;
+            int pos = 0;
+
+            //this creates an implicit StringBuffer and shouldn't be used in the inner loop
+            final String lookupChars = open + "{";
+
+            while (true) {
+                int start = expression.indexOf(lookupChars, pos);
+                if (start == -1) {
+                    pos = 0;
+                    loopCount++;
+                    start = expression.indexOf(lookupChars);
+                }
+                if (loopCount > maxLoopCount) {
+                    // translateVariables prevent infinite loop / expression recursive evaluation
+                    break;
+                }
+                int length = expression.length();
+                int x = start + 2;
+                int end;
+                char c;
+                int count = 1;
+                while (start != -1 && x < length && count != 0) {
+                    c = expression.charAt(x++);
+                    if (c == '{') {
+                        count++;
+                    } else if (c == '}') {
+                        count--;
+                    }
+                }
+                end = x - 1;
+
+                if ((start != -1) && (end != -1) && (count == 0)) {
+                    String var = expression.substring(start + 2, end);
+
+                    Object o = stack.findValue(var, asType);
+                    if (evaluator != null) {
+                    	o = evaluator.evaluate(o);
+                    }
+
+
+                    String left = expression.substring(0, start);
+                    String right = expression.substring(end + 1);
+                    String middle = null;
+                    if (o != null) {
+                        middle = o.toString();
+                        if (StringUtils.isEmpty(left)) {
+                            result = o;
+                        } else {
+                            result = left.concat(middle);
+                        }
+
+                        if (StringUtils.isNotEmpty(right)) {
+                            result = result.toString().concat(right);
+                        }
+
+                        expression = left.concat(middle).concat(right);
+                    } else {
+                        // the variable doesn't exist, so don't display anything
+                        expression = left.concat(right);
+                        result = expression;
+                    }
+                    pos = (left != null && left.length() > 0 ? left.length() - 1: 0) +
+                          (middle != null && middle.length() > 0 ? middle.length() - 1: 0) +
+                          1;
+                    pos = Math.max(pos, 1);
+                } else {
+                    break;
+                }
+            }
+        }
+
+        XWorkConverter conv = ((Container)stack.getContext().get(ActionContext.CONTAINER)).getInstance(XWorkConverter.class);
+        return conv.convertValue(stack.getContext(), result, asType);
+    }
+
+    /**
+     * Returns a set from comma delimted Strings.
+     * @param s The String to parse.
+     * @return A set from comma delimted Strings.
+     */
+    public static Set<String> commaDelimitedStringToSet(String s) {
+        Set<String> set = new HashSet<String>();
+        String[] split = s.split(",");
+        for (String aSplit : split) {
+            String trimmed = aSplit.trim();
+            if (trimmed.length() > 0)
+                set.add(trimmed);
+        }
+        return set;
+    }
+
+
+    /**
+     * A parsed value evaluator for {@link TextParseUtil}. It could be supplied by
+     * calling {@link TextParseUtil#translateVariables(char, String, ValueStack, Class, ParsedValueEvaluator)}.
+     *
+     * <p/>
+     *
+     * By supplying this <code>ParsedValueEvaluator</code>, the parsed value
+     * (parsed against the value stack) value will be
+     * given to <code>ParsedValueEvaluator</code> to be evaluated before the
+     * translateVariable process goes on.
+     *
+     * <p/>
+     *
+     * A typical use-case would be to have a custom <code>ParseValueEvaluator</code>
+     * to URL Encode the parsed value.
+     *
+     * @author tm_jee
+     *
+     * @version $Date: 2011-12-02 12:24:48 +0100 (Fri, 02 Dec 2011) $ $Id: TextParseUtil.java 1209415 2011-12-02 11:24:48Z lukaszlenart $
+     */
+    public static interface ParsedValueEvaluator {
+
+    	/**
+    	 * Evaluated the value parsed by Ognl value stack.
+    	 *
+    	 * @param parsedValue - value parsed by ognl value stack
+    	 * @return return the evaluted value.
+    	 */
+    	Object evaluate(Object parsedValue);
+    }
+}

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/URLUtil.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/URLUtil.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/URLUtil.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/URLUtil.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,108 @@
+/*
+ * 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;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+import java.net.URL;
+import java.net.MalformedURLException;
+
+/**
+ * Helper class to extract file paths from different urls
+ */
+public class URLUtil {
+
+    /**
+     * Prefix for Jar files in JBoss Virtual File System
+     */
+    public static final String JBOSS5_VFS = "vfs";
+    public static final String JBOSS5_VFSZIP = "vfszip";
+    public static final String JBOSS5_VFSMEMORY = "vfsmemory";
+    public static final String JBOSS5_VFSFILE = "vfsfile";
+
+    private static final Pattern JAR_PATTERN = Pattern.compile("^(jar:|wsjar:|zip:|vfsfile:|code-source:)?(file:)?(.*?)(\\!/|\\.jar/)(.*)");
+    private static final int JAR_FILE_PATH = 3;
+
+    /**
+     * Convert URLs to URLs with "file" protocol
+     * @param url URL to convert to a jar url
+     * @return a URL to a file, or null if the URL external form cannot be parsed
+     */
+    public static URL normalizeToFileProtocol(URL url) {
+        String fileName = url.toExternalForm();
+        Matcher jarMatcher = JAR_PATTERN.matcher(fileName);
+        try {
+            if (isJBoss5Url(url)){
+                return new URL("file", null, fileName.substring(fileName.indexOf(":") + 1));
+            } else  if (jarMatcher.matches()) {
+                String path = jarMatcher.group(JAR_FILE_PATH);
+                return new URL("file", "", path);
+            } else {
+                //it is not a jar or zip file
+                return null;
+            }
+        } catch (MalformedURLException e) {
+            //can this ever happen?
+            return null;
+        }
+    }
+
+    /**
+     * Verify That the given String is in valid URL format.
+     * @param url The url string to verify.
+     * @return a boolean indicating whether the URL seems to be incorrect.
+     */
+    public static boolean verifyUrl(String url) {
+        if (url == null) {
+            return false;
+        }
+
+        if (url.startsWith("https://")) {
+            // URL doesn't understand the https protocol, hack it
+            url = "http://" + url.substring(8);
+        }
+
+        try {
+            new URL(url);
+
+            return true;
+        } catch (MalformedURLException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Check if given URL is matching Jar pattern for different servers
+     * @param fileUrl
+     * @return
+     */
+    public static boolean isJarURL(URL fileUrl) {
+        Matcher jarMatcher = URLUtil.JAR_PATTERN.matcher(fileUrl.getPath());
+        return jarMatcher.matches(); 
+    }
+
+    /**
+     * Check if given URL is pointing to JBoss 5 VFS resource
+     * @param fileUrl
+     * @return
+     */
+    public static boolean isJBoss5Url(URL fileUrl) {
+        final String protocol = fileUrl.getProtocol();
+        return JBOSS5_VFSZIP.equals(protocol) || JBOSS5_VFSMEMORY.equals(protocol) || JBOSS5_VFS.equals(protocol)
+                || ("true".equals(System.getProperty("jboss.vfs.forceVfsJar")) && JBOSS5_VFSFILE.equals(protocol));
+    }
+
+}

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/ValueStack.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/ValueStack.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/ValueStack.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/ValueStack.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2002-2007,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;
+
+import java.util.Map;
+
+/**
+ * ValueStack allows multiple beans to be pushed in and dynamic EL expressions to be evaluated against it. When
+ * evaluating an expression, the stack will be searched down the stack, from the latest objects pushed in to the
+ * earliest, looking for a bean with a getter or setter for the given property or a method of the given name (depending
+ * on the expression being evaluated).
+ */
+public interface ValueStack {
+
+    public static final String VALUE_STACK = "org.apache.struts2.xwork2.util.ValueStack.ValueStack";
+
+    public static final String REPORT_ERRORS_ON_NO_PROP = "org.apache.struts2.xwork2.util.ValueStack.ReportErrorsOnNoProp";
+
+    /**
+     * Gets the context for this value stack. The context holds all the information in the value stack and it's surroundings.
+     *
+     * @return  the context.
+     */
+    public abstract Map<String, Object> getContext();
+
+    /**
+     * Sets the default type to convert to if no type is provided when getting a value.
+     *
+     * @param defaultType the new default type
+     */
+    public abstract void setDefaultType(Class defaultType);
+
+    /**
+     * Set a override map containing <code>key -> values</code> that takes precedent when doing find operations on the ValueStack.
+     * <p/>
+     * See the unit test for ValueStackTest for examples.
+     *
+     * @param overrides  overrides map.
+     */
+    public abstract void setExprOverrides(Map<Object, Object> overrides);
+
+    /**
+     * Gets the override map if anyone exists.
+     *
+     * @return the override map, <tt>null</tt> if not set.
+     */
+    public abstract Map<Object, Object> getExprOverrides();
+
+    /**
+     * Get the CompoundRoot which holds the objects pushed onto the stack
+     *
+     * @return the root
+     */
+    public abstract CompoundRoot getRoot();
+
+    /**
+     * Attempts to set a property on a bean in the stack with the given expression using the default search order.
+     *
+     * @param expr  the expression defining the path to the property to be set.
+     * @param value the value to be set into the named property
+     */
+    public abstract void setValue(String expr, Object value);
+
+    /**
+     * Attempts to set a property on a bean in the stack with the given expression using the default search order.
+     *
+     * @param expr                    the expression defining the path to the property to be set.
+     * @param value                   the value to be set into the named property
+     * @param throwExceptionOnFailure a flag to tell whether an exception should be thrown if there is no property with
+     *                                the given name.
+     */
+    public abstract void setValue(String expr, Object value, boolean throwExceptionOnFailure);
+
+    public abstract String findString(String expr);
+    public abstract String findString(String expr, boolean throwExceptionOnFailure);
+
+    /**
+     * Find a value by evaluating the given expression against the stack in the default search order.
+     *
+     * @param expr the expression giving the path of properties to navigate to find the property value to return
+     * @return the result of evaluating the expression
+     */
+    public abstract Object findValue(String expr);
+
+    public abstract Object findValue(String expr, boolean throwExceptionOnFailure);
+
+    /**
+     * Find a value by evaluating the given expression against the stack in the default search order.
+     *
+     * @param expr   the expression giving the path of properties to navigate to find the property value to return
+     * @param asType the type to convert the return value to
+     * @return the result of evaluating the expression
+     */
+    public abstract Object findValue(String expr, Class asType);
+    public abstract Object findValue(String expr, Class asType,  boolean throwExceptionOnFailure);
+
+    /**
+     * Get the object on the top of the stack <b>without</b> changing the stack.
+     *
+     * @return the object on the top.
+     * @see CompoundRoot#peek()
+     */
+    public abstract Object peek();
+
+    /**
+     * Get the object on the top of the stack and <b>remove</b> it from the stack.
+     *
+     * @return the object on the top of the stack
+     * @see CompoundRoot#pop()
+     */
+    public abstract Object pop();
+
+    /**
+     * Put this object onto the top of the stack
+     *
+     * @param o the object to be pushed onto the stack
+     * @see CompoundRoot#push(Object)
+     */
+    public abstract void push(Object o);
+
+    /**
+     * Sets an object on the stack with the given key
+     * so it is retrievable by {@link #findValue(String)}, {@link #findValue(String, Class)}
+     *
+     * @param key  the key
+     * @param o    the object
+     */
+    public abstract void set(String key, Object o);
+
+    /**
+     * Get the number of objects in the stack
+     *
+     * @return the number of objects in the stack
+     */
+    public abstract int size();
+
+}
\ No newline at end of file

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/ValueStackFactory.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/ValueStackFactory.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/ValueStackFactory.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/ValueStackFactory.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2002-2007,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;
+
+/**
+ * Factory that creates a value stack, defaulting to the OgnlValueStackFactory
+ */
+public interface ValueStackFactory {
+
+    /**
+     * Get a new instance of {@link ValueStack}
+     *
+     * @return  a new {@link ValueStack}.
+     */
+    ValueStack createValueStack();
+    
+    /**
+     * Get a new instance of {@link ValueStack}
+     *
+     * @param stack an existing stack to include.
+     * @return  a new {@link ValueStack}.
+     */
+    ValueStack createValueStack(ValueStack stack);
+    
+}

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/WildcardHelper.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/WildcardHelper.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/WildcardHelper.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/WildcardHelper.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,463 @@
+/*
+ * $Id: WildcardHelper.java 1209415 2011-12-02 11:24:48Z lukaszlenart $
+ *
+ * Copyright 2003-2004 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;
+
+import java.util.Map;
+
+/**
+ * This class is an utility class that perform wilcard-patterns matching and
+ * isolation taken from Apache Cocoon.
+ *
+ * @version $Rev: 1209415 $ $Date: 2005-05-07 12:11:38 -0400 (Sat, 07 May 2005)
+ *          $
+ */
+public class WildcardHelper implements PatternMatcher<int[]> {
+    /**
+     * The int representing '*' in the pattern <code>int []</code>.
+     */
+    protected static final int MATCH_FILE = -1;
+
+    /**
+     * The int representing '**' in the pattern <code>int []</code>.
+     */
+    protected static final int MATCH_PATH = -2;
+
+    /**
+     * The int representing begin in the pattern <code>int []</code>.
+     */
+    protected static final int MATCH_BEGIN = -4;
+
+    /**
+     * The int representing end in pattern <code>int []</code>.
+     */
+    protected static final int MATCH_THEEND = -5;
+
+    /**
+     * The int value that terminates the pattern <code>int []</code>.
+     */
+    protected static final int MATCH_END = -3;
+
+    /**
+     * Determines if the pattern contains any * characters
+     *
+     * @param pattern The pattern
+     * @return True if no wildcards are found
+     */
+    public boolean isLiteral(String pattern) {
+        return (pattern == null || pattern.indexOf('*') == -1);
+    }
+
+    /**
+     * <p> Translate the given <code>String</code> into a <code>int []</code>
+     * representing the pattern matchable by this class. <br> This function
+     * translates a <code>String</code> into an int array converting the
+     * special '*' and '\' characters. <br> Here is how the conversion
+     * algorithm works:</p>
+     *
+     * <ul>
+     *
+     * <li>The '*' character is converted to MATCH_FILE, meaning that zero or
+     * more characters (excluding the path separator '/') are to be
+     * matched.</li>
+     *
+     * <li>The '**' sequence is converted to MATCH_PATH, meaning that zero or
+     * more characters (including the path separator '/') are to be
+     * matched.</li>
+     *
+     * <li>The '\' character is used as an escape sequence ('\*' is translated
+     * in '*', not in MATCH_FILE). If an exact '\' character is to be matched
+     * the source string must contain a '\\'. sequence.</li>
+     *
+     * </ul>
+     *
+     * <p>When more than two '*' characters, not separated by another
+     * character, are found their value is considered as '**' (MATCH_PATH).
+     * <br> The array is always terminated by a special value (MATCH_END).
+     * <br> All MATCH* values are less than zero, while normal characters are
+     * equal or greater.</p>
+     *
+     * @param data The string to translate.
+     * @return The encoded string as an int array, terminated by the MATCH_END
+     *         value (don't consider the array length).
+     * @throws NullPointerException If data is null.
+     */
+    public int[] compilePattern(String data) {
+        // Prepare the arrays
+        int[] expr = new int[data.length() + 2];
+        char[] buff = data.toCharArray();
+
+        // Prepare variables for the translation loop
+        int y = 0;
+        boolean slash = false;
+
+        // Must start from beginning
+        expr[y++] = MATCH_BEGIN;
+
+        if (buff.length > 0) {
+            if (buff[0] == '\\') {
+                slash = true;
+            } else if (buff[0] == '*') {
+                expr[y++] = MATCH_FILE;
+            } else {
+                expr[y++] = buff[0];
+            }
+
+            // Main translation loop
+            for (int x = 1; x < buff.length; x++) {
+                // If the previous char was '\' simply copy this char.
+                if (slash) {
+                    expr[y++] = buff[x];
+                    slash = false;
+
+                    // If the previous char was not '\' we have to do a bunch of
+                    // checks
+                } else {
+                    // If this char is '\' declare that and continue
+                    if (buff[x] == '\\') {
+                        slash = true;
+
+                        // If this char is '*' check the previous one
+                    } else if (buff[x] == '*') {
+                        // If the previous character als was '*' match a path
+                        if (expr[y - 1] <= MATCH_FILE) {
+                            expr[y - 1] = MATCH_PATH;
+                        } else {
+                            expr[y++] = MATCH_FILE;
+                        }
+                    } else {
+                        expr[y++] = buff[x];
+                    }
+                }
+            }
+        }
+
+        // Must match end at the end
+        expr[y] = MATCH_THEEND;
+
+        return expr;
+    }
+
+    /**
+     * Match a pattern agains a string and isolates wildcard replacement into
+     * a <code>Stack</code>.
+     *
+     * @param map  The map to store matched values
+     * @param data The string to match
+     * @param expr The compiled wildcard expression
+     * @return True if a match
+     * @throws NullPointerException If any parameters are null
+     */
+    public boolean match(Map<String, String> map, String data, int[] expr) {
+        if (map == null) {
+            throw new NullPointerException("No map provided");
+        }
+
+        if (data == null) {
+            throw new NullPointerException("No data provided");
+        }
+
+        if (expr == null) {
+            throw new NullPointerException("No pattern expression provided");
+        }
+
+        char[] buff = data.toCharArray();
+
+        // Allocate the result buffer
+        char[] rslt = new char[expr.length + buff.length];
+
+        // The previous and current position of the expression character
+        // (MATCH_*)
+        int charpos = 0;
+
+        // The position in the expression, input, translation and result arrays
+        int exprpos = 0;
+        int buffpos = 0;
+        int rsltpos = 0;
+        int offset = -1;
+
+        // The matching count
+        int mcount = 0;
+
+        // We want the complete data be in {0}
+        map.put(Integer.toString(mcount), data);
+
+        // First check for MATCH_BEGIN
+        boolean matchBegin = false;
+
+        if (expr[charpos] == MATCH_BEGIN) {
+            matchBegin = true;
+            exprpos = ++charpos;
+        }
+
+        // Search the fist expression character (except MATCH_BEGIN - already
+        // skipped)
+        while (expr[charpos] >= 0) {
+            charpos++;
+        }
+
+        // The expression charater (MATCH_*)
+        int exprchr = expr[charpos];
+
+        while (true) {
+            // Check if the data in the expression array before the current
+            // expression character matches the data in the input buffer
+            if (matchBegin) {
+                if (!matchArray(expr, exprpos, charpos, buff, buffpos)) {
+                    return (false);
+                }
+
+                matchBegin = false;
+            } else {
+                offset = indexOfArray(expr, exprpos, charpos, buff, buffpos);
+
+                if (offset < 0) {
+                    return (false);
+                }
+            }
+
+            // Check for MATCH_BEGIN
+            if (matchBegin) {
+                if (offset != 0) {
+                    return (false);
+                }
+
+                matchBegin = false;
+            }
+
+            // Advance buffpos
+            buffpos += (charpos - exprpos);
+
+            // Check for END's
+            if (exprchr == MATCH_END) {
+                if (rsltpos > 0) {
+                    map.put(Integer.toString(++mcount),
+                        new String(rslt, 0, rsltpos));
+                }
+
+                // Don't care about rest of input buffer
+                return (true);
+            } else if (exprchr == MATCH_THEEND) {
+                if (rsltpos > 0) {
+                    map.put(Integer.toString(++mcount),
+                        new String(rslt, 0, rsltpos));
+                }
+
+                // Check that we reach buffer's end
+                return (buffpos == buff.length);
+            }
+
+            // Search the next expression character
+            exprpos = ++charpos;
+
+            while (expr[charpos] >= 0) {
+                charpos++;
+            }
+
+            int prevchr = exprchr;
+
+            exprchr = expr[charpos];
+
+            // We have here prevchr == * or **.
+            offset =
+                (prevchr == MATCH_FILE)
+                ? indexOfArray(expr, exprpos, charpos, buff, buffpos)
+                : lastIndexOfArray(expr, exprpos, charpos, buff, buffpos);
+
+            if (offset < 0) {
+                return (false);
+            }
+
+            // Copy the data from the source buffer into the result buffer
+            // to substitute the expression character
+            if (prevchr == MATCH_PATH) {
+                while (buffpos < offset) {
+                    rslt[rsltpos++] = buff[buffpos++];
+                }
+            } else {
+                // Matching file, don't copy '/'
+                while (buffpos < offset) {
+                    if (buff[buffpos] == '/') {
+                        return (false);
+                    }
+
+                    rslt[rsltpos++] = buff[buffpos++];
+                }
+            }
+
+            map.put(Integer.toString(++mcount), new String(rslt, 0, rsltpos));
+            rsltpos = 0;
+        }
+    }
+
+    /**
+     * Get the offset of a part of an int array within a char array. <br> This
+     * method return the index in d of the first occurrence after dpos of that
+     * part of array specified by r, starting at rpos and terminating at
+     * rend.
+     *
+     * @param r    The array containing the data that need to be matched in
+     *             d.
+     * @param rpos The index of the first character in r to look for.
+     * @param rend The index of the last character in r to look for plus 1.
+     * @param d    The array of char that should contain a part of r.
+     * @param dpos The starting offset in d for the matching.
+     * @return The offset in d of the part of r matched in d or -1 if that was
+     *         not found.
+     */
+    protected int indexOfArray(int[] r, int rpos, int rend, char[] d, int dpos) {
+        // Check if pos and len are legal
+        if (rend < rpos) {
+            throw new IllegalArgumentException("rend < rpos");
+        }
+
+        // If we need to match a zero length string return current dpos
+        if (rend == rpos) {
+            return (d.length); //?? dpos?
+        }
+
+        // If we need to match a 1 char length string do it simply
+        if ((rend - rpos) == 1) {
+            // Search for the specified character
+            for (int x = dpos; x < d.length; x++) {
+                if (r[rpos] == d[x]) {
+                    return (x);
+                }
+            }
+        }
+
+        // Main string matching loop. It gets executed if the characters to
+        // match are less then the characters left in the d buffer
+        while (((dpos + rend) - rpos) <= d.length) {
+            // Set current startpoint in d
+            int y = dpos;
+
+            // Check every character in d for equity. If the string is matched
+            // return dpos
+            for (int x = rpos; x <= rend; x++) {
+                if (x == rend) {
+                    return (dpos);
+                }
+
+                if (r[x] != d[y++]) {
+                    break;
+                }
+            }
+
+            // Increase dpos to search for the same string at next offset
+            dpos++;
+        }
+
+        // The remaining chars in d buffer were not enough or the string
+        // wasn't matched
+        return (-1);
+    }
+
+    /**
+     * Get the offset of a last occurance of an int array within a char array.
+     * <br> This method return the index in d of the last occurrence after
+     * dpos of that part of array specified by r, starting at rpos and
+     * terminating at rend.
+     *
+     * @param r    The array containing the data that need to be matched in
+     *             d.
+     * @param rpos The index of the first character in r to look for.
+     * @param rend The index of the last character in r to look for plus 1.
+     * @param d    The array of char that should contain a part of r.
+     * @param dpos The starting offset in d for the matching.
+     * @return The offset in d of the last part of r matched in d or -1 if
+     *         that was not found.
+     */
+    protected int lastIndexOfArray(int[] r, int rpos, int rend, char[] d,
+        int dpos) {
+        // Check if pos and len are legal
+        if (rend < rpos) {
+            throw new IllegalArgumentException("rend < rpos");
+        }
+
+        // If we need to match a zero length string return current dpos
+        if (rend == rpos) {
+            return (d.length); //?? dpos?
+        }
+
+        // If we need to match a 1 char length string do it simply
+        if ((rend - rpos) == 1) {
+            // Search for the specified character
+            for (int x = d.length - 1; x > dpos; x--) {
+                if (r[rpos] == d[x]) {
+                    return (x);
+                }
+            }
+        }
+
+        // Main string matching loop. It gets executed if the characters to
+        // match are less then the characters left in the d buffer
+        int l = d.length - (rend - rpos);
+
+        while (l >= dpos) {
+            // Set current startpoint in d
+            int y = l;
+
+            // Check every character in d for equity. If the string is matched
+            // return dpos
+            for (int x = rpos; x <= rend; x++) {
+                if (x == rend) {
+                    return (l);
+                }
+
+                if (r[x] != d[y++]) {
+                    break;
+                }
+            }
+
+            // Decrease l to search for the same string at next offset
+            l--;
+        }
+
+        // The remaining chars in d buffer were not enough or the string
+        // wasn't matched
+        return (-1);
+    }
+
+    /**
+     * Matches elements of array r from rpos to rend with array d, starting
+     * from dpos. <br> This method return true if elements of array r from
+     * rpos to rend equals elements of array d starting from dpos to
+     * dpos+(rend-rpos).
+     *
+     * @param r    The array containing the data that need to be matched in
+     *             d.
+     * @param rpos The index of the first character in r to look for.
+     * @param rend The index of the last character in r to look for.
+     * @param d    The array of char that should start from a part of r.
+     * @param dpos The starting offset in d for the matching.
+     * @return true if array d starts from portion of array r.
+     */
+    protected boolean matchArray(int[] r, int rpos, int rend, char[] d, int dpos) {
+        if ((d.length - dpos) < (rend - rpos)) {
+            return (false);
+        }
+
+        for (int i = rpos; i < rend; i++) {
+            if (r[i] != d[dpos++]) {
+                return (false);
+            }
+        }
+
+        return (true);
+    }
+}

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/WildcardUtil.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/WildcardUtil.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/WildcardUtil.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/WildcardUtil.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2010 Yahoo, Inc.  All rights reserved.
+ *
+ * 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;
+
+import java.util.regex.Pattern;
+
+/**
+ * Helper class to convert wildcard expression to regular expression
+ */
+public class WildcardUtil {
+
+    /**
+     * Convert wildcard pattern to Pattern
+     * @param pattern String containing wildcard pattern
+     * @return compiled regular expression as a Pattern
+     */
+    public static Pattern compileWildcardPattern(String pattern) {
+        StringBuilder buf = new StringBuilder(pattern);
+
+        for (int i=buf.length()-1; i>=0; i--)
+        {
+            char c = buf.charAt(i);
+            if (c == '*' && (i == 0 || buf.charAt(i-1) != '\\'))
+            {
+                buf.insert(i+1, '?');
+                buf.insert(i, '.');
+            }
+            else if (c == '*')
+            {
+                i--;	// skip backslash, too
+            }
+            else if (needsBackslashToBeLiteralInRegex(c))
+            {
+                buf.insert(i, '\\');
+            }
+        }
+
+        return Pattern.compile(buf.toString());
+    }
+
+    /**
+     * @param c character to test
+     * @return true if the given character must be escaped to be a literal
+     * inside a regular expression.
+     */
+
+    private static final String theSpecialRegexCharList = ".[]\\?*+{}|()^$";
+
+    public static boolean needsBackslashToBeLiteralInRegex(
+        char c)
+    {
+        return (theSpecialRegexCharList.indexOf(c) >= 0);
+    }
+
+}

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/XWorkList.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/XWorkList.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/XWorkList.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/XWorkList.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2002-2006,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;
+
+import org.apache.struts2.xwork2.ActionContext;
+import org.apache.struts2.xwork2.ObjectFactory;
+import org.apache.struts2.xwork2.XWorkException;
+import org.apache.struts2.xwork2.conversion.impl.XWorkConverter;
+import org.apache.struts2.xwork2.util.logging.Logger;
+import org.apache.struts2.xwork2.util.logging.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+
+
+/**
+ * A simple list that guarantees that inserting and retrieving objects will always work regardless
+ * of the current size of the list.  Upon insertion, type conversion is also performed if necessary.
+ * Empty beans will be created to fill the gap between the current list size and the requested index
+ * using ObjectFactory's {@link ObjectFactory#buildBean(Class,java.util.Map) buildBean} method.
+ *
+ * @author Patrick Lightbody
+ */
+public class XWorkList extends ArrayList {
+    private static final Logger LOG = LoggerFactory.getLogger(XWorkConverter.class);
+
+    private Class clazz;
+    private XWorkConverter conv;
+
+    private ObjectFactory objectFactory;
+
+    public XWorkList(ObjectFactory fac, XWorkConverter conv, Class clazz) {
+        this.conv = conv;
+        this.clazz = clazz;
+        this.objectFactory = fac;
+    }
+
+    public XWorkList(ObjectFactory fac, XWorkConverter conv, Class clazz, int initialCapacity) {
+        super(initialCapacity);
+        this.clazz = clazz;
+        this.conv = conv;
+        this.objectFactory = fac;
+    }
+
+    /**
+     * Inserts the specified element at the specified position in this list. Shifts the element
+     * currently at that position (if any) and any subsequent elements to the right (adds one to
+     * their indices).
+     * <p/>
+     * This method is guaranteed to work since it will create empty beans to fill the gap between
+     * the current list size and the requested index to enable the element to be set.  This method
+     * also performs any necessary type conversion.
+     *
+     * @param index   index at which the specified element is to be inserted.
+     * @param element element to be inserted.
+     */
+    @Override
+    public void add(int index, Object element) {
+        if (index >= this.size()) {
+            get(index);
+        }
+
+        element = convert(element);
+
+        super.add(index, element);
+    }
+
+    /**
+     * Appends the specified element to the end of this list.
+     * <p/>
+     * This method performs any necessary type conversion.
+     *
+     * @param element element to be appended to this list.
+     * @return <tt>true</tt> (as per the general contract of Collection.add).
+     */
+    @Override
+    public boolean add(Object element) {
+        element = convert(element);
+
+        return super.add(element);
+    }
+
+    /**
+     * Appends all of the elements in the specified Collection to the end of this list, in the order
+     * that they are returned by the specified Collection's Iterator.  The behavior of this
+     * operation is undefined if the specified Collection is modified while the operation is in
+     * progress.  (This implies that the behavior of this call is undefined if the specified
+     * Collection is this list, and this list is nonempty.)
+     * <p/>
+     * This method performs any necessary type conversion.
+     *
+     * @param c the elements to be inserted into this list.
+     * @return <tt>true</tt> if this list changed as a result of the call.
+     * @throws NullPointerException if the specified collection is null.
+     */
+    @Override
+    public boolean addAll(Collection c) {
+        if (c == null) {
+            throw new NullPointerException("Collection to add is null");
+        }
+
+        for (Object aC : c) {
+            add(aC);
+        }
+
+        return true;
+    }
+
+    /**
+     * Inserts all of the elements in the specified Collection into this list, starting at the
+     * specified position.  Shifts the element currently at that position (if any) and any
+     * subsequent elements to the right (increases their indices).  The new elements will appear in
+     * the list in the order that they are returned by the specified Collection's iterator.
+     * <p/>
+     * This method is guaranteed to work since it will create empty beans to fill the gap between
+     * the current list size and the requested index to enable the element to be set.  This method
+     * also performs any necessary type conversion.
+     *
+     * @param index index at which to insert first element from the specified collection.
+     * @param c     elements to be inserted into this list.
+     * @return <tt>true</tt> if this list changed as a result of the call.
+     */
+    @Override
+    public boolean addAll(int index, Collection c) {
+        if (c == null) {
+            throw new NullPointerException("Collection to add is null");
+        }
+
+        boolean trim = false;
+
+        if (index >= this.size()) {
+            trim = true;
+        }
+
+        for (Iterator it = c.iterator(); it.hasNext(); index++) {
+            add(index, it.next());
+        }
+
+        if (trim) {
+            remove(this.size() - 1);
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns the element at the specified position in this list.
+     * <p/>
+     * An object is guaranteed to be returned since it will create empty beans to fill the gap
+     * between the current list size and the requested index.
+     *
+     * @param index index of element to return.
+     * @return the element at the specified position in this list.
+     */
+    @Override
+    public synchronized Object get(int index) {
+        while (index >= this.size()) {
+            try {
+                //todo
+                this.add(objectFactory.buildBean(clazz, null)); //ActionContext.getContext().getContextMap()));
+            } catch (Exception e) {
+                throw new XWorkException(e);
+            }
+        }
+
+        return super.get(index);
+    }
+
+    /**
+     * Replaces the element at the specified position in this list with the specified element.
+     * <p/>
+     * This method is guaranteed to work since it will create empty beans to fill the gap between
+     * the current list size and the requested index to enable the element to be set.  This method
+     * also performs any necessary type conversion.
+     *
+     * @param index   index of element to replace.
+     * @param element element to be stored at the specified position.
+     * @return the element previously at the specified position.
+     */
+    @Override
+    public Object set(int index, Object element) {
+        if (index >= this.size()) {
+            get(index);
+        }
+
+        element = convert(element);
+
+        return super.set(index, element);
+    }
+
+    private Object convert(Object element) {
+        if ((element != null) && !clazz.isAssignableFrom(element.getClass())) {
+            // convert to correct type
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("Converting from " + element.getClass().getName() + " to " + clazz.getName());
+            }
+
+            Map<String, Object> context = ActionContext.getContext().getContextMap();
+            element = conv.convertValue(context, null, null, null, element, clazz);
+        }
+
+        return element;
+    }
+
+    @Override
+    public boolean contains(Object element) {
+        element = convert(element);
+
+        return super.contains(element);
+    }
+}

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/XWorkTestCaseHelper.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/XWorkTestCaseHelper.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/XWorkTestCaseHelper.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/XWorkTestCaseHelper.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2002-2006,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;
+
+import org.apache.struts2.xwork2.ActionContext;
+import org.apache.struts2.xwork2.config.Configuration;
+import org.apache.struts2.xwork2.config.ConfigurationException;
+import org.apache.struts2.xwork2.config.providers.XWorkConfigurationProvider;
+import org.apache.struts2.xwork2.config.providers.XmlConfigurationProvider;
+import org.apache.struts2.xwork2.inject.Container;
+import org.apache.struts2.xwork2.inject.ContainerBuilder;
+import org.apache.struts2.xwork2.util.location.LocatableProperties;
+import org.apache.struts2.xwork2.config.ConfigurationManager;
+import org.apache.struts2.xwork2.config.ConfigurationProvider;
+import org.apache.struts2.xwork2.config.ContainerProvider;
+
+/**
+ * Generic test setup methods to be used with any unit testing framework. 
+ */
+public class XWorkTestCaseHelper {
+
+    public static ConfigurationManager setUp() throws Exception {
+        ConfigurationManager configurationManager = new ConfigurationManager();
+        configurationManager.addContainerProvider(new XWorkConfigurationProvider());
+        Configuration config = configurationManager.getConfiguration();
+        Container container = config.getContainer();
+        
+        // Reset the value stack
+        ValueStack stack = container.getInstance(ValueStackFactory.class).createValueStack();
+        stack.getContext().put(ActionContext.CONTAINER, container);
+        ActionContext.setContext(new ActionContext(stack.getContext()));
+    
+        // clear out localization
+        LocalizedTextUtil.reset();
+        
+    
+        //ObjectFactory.setObjectFactory(container.getInstance(ObjectFactory.class));
+        return configurationManager;
+    }
+
+    public static ConfigurationManager loadConfigurationProviders(ConfigurationManager configurationManager,
+            ConfigurationProvider... providers) {
+        try {
+            tearDown(configurationManager);
+        } catch (Exception e) {
+            throw new RuntimeException("Cannot clean old configuration", e);
+        }
+        configurationManager = new ConfigurationManager();
+        configurationManager.addContainerProvider(new ContainerProvider() {
+            public void destroy() {}
+            public void init(Configuration configuration) throws ConfigurationException {}
+            public boolean needsReload() { return false; }
+
+            public void register(ContainerBuilder builder,
+                    LocatableProperties props) throws ConfigurationException {
+                builder.setAllowDuplicates(true);
+            }
+            
+        });
+        configurationManager.addContainerProvider(new XWorkConfigurationProvider());
+        for (ConfigurationProvider prov : providers) {
+            if (prov instanceof XmlConfigurationProvider) {
+                ((XmlConfigurationProvider)prov).setThrowExceptionOnDuplicateBeans(false);
+            }
+            configurationManager.addContainerProvider(prov);
+        }
+        Container container = configurationManager.getConfiguration().getContainer();
+        
+        // Reset the value stack
+        ValueStack stack = container.getInstance(ValueStackFactory.class).createValueStack();
+        stack.getContext().put(ActionContext.CONTAINER, container);
+        ActionContext.setContext(new ActionContext(stack.getContext()));
+        
+        return configurationManager;
+    }
+
+    public static void tearDown(ConfigurationManager configurationManager) throws Exception {
+    
+        //  clear out configuration
+        if (configurationManager != null) {
+            configurationManager.destroyConfiguration();
+            configurationManager = null;
+        }
+        ActionContext.setContext(null);
+    }
+}
\ No newline at end of file

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/FileResourceStore.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/FileResourceStore.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/FileResourceStore.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/FileResourceStore.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2002-2006,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.classloader;
+
+import org.apache.struts2.xwork2.util.logging.Logger;
+import org.apache.struts2.xwork2.util.logging.LoggerFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * Reads a class from disk
+ *  class taken from Apache JCI
+ */
+public final class FileResourceStore implements ResourceStore {
+    private static final Logger LOG = LoggerFactory.getLogger(FileResourceStore.class);
+    private final File root;
+
+    public FileResourceStore(final File pFile) {
+        root = pFile;
+    }
+
+    public byte[] read(final String pResourceName) {
+        FileInputStream fis = null;
+        try {
+            File file = getFile(pResourceName);
+            byte[] data = new byte[(int) file.length()];
+            fis = new FileInputStream(file);
+            fis.read(data);
+
+            return data;
+        } catch (Exception e) {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Unable to read file [#0]", e, pResourceName);
+            return null;
+        } finally {
+            closeQuietly(fis);
+        }
+    }
+
+    public void write(final String pResourceName, final byte[] pData) {
+
+    }
+
+    private void closeQuietly(InputStream is) {
+        try {
+            if (is != null)
+                is.close();
+        } catch (IOException e) {
+            if (LOG.isErrorEnabled())
+                LOG.error("Unable to close file input stream", e);
+        }
+    }
+
+    private File getFile(final String pResourceName) {
+        final String fileName = pResourceName.replace('/', File.separatorChar);
+        return new File(root, fileName);
+    }
+
+    public String toString() {
+        return this.getClass().getName() + root.toString();
+    }
+}

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/JarResourceStore.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/JarResourceStore.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/JarResourceStore.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/util/classloader/JarResourceStore.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2002-2006,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.classloader;
+
+import org.apache.struts2.xwork2.util.logging.Logger;
+import org.apache.struts2.xwork2.util.logging.LoggerFactory;
+
+import java.io.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Read resources from a jar file
+ */
+public class JarResourceStore implements ResourceStore {
+    private static final Logger LOG = LoggerFactory.getLogger(JarResourceStore.class);
+
+    private final File file;
+
+    public JarResourceStore(File file) {
+        this.file = file;
+    }
+
+    public void write(String pResourceName, byte[] pResourceData) {
+    }
+
+    public byte[] read(String pResourceName) {
+        InputStream in = null;
+        try {
+            ZipFile jarFile = new ZipFile(file);
+            ZipEntry entry = jarFile.getEntry(pResourceName);
+
+            //read into byte array
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            in = jarFile.getInputStream(entry);
+            copy(in, out);
+
+            return out.toByteArray();
+        } catch (Exception e) {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Unable to read file [#0] from [#1]", e, pResourceName, file.getName());
+            return null;
+        } finally {
+            closeQuietly(in);
+        }
+    }
+
+    public static long copy(InputStream input, OutputStream output)
+            throws IOException {
+        byte[] buffer = new byte[1024 * 4];
+        long count = 0;
+        int n = 0;
+        while (-1 != (n = input.read(buffer))) {
+            output.write(buffer, 0, n);
+            count += n;
+        }
+        return count;
+    }
+
+    private void closeQuietly(InputStream is) {
+        try {
+            if (is != null)
+                is.close();
+        } catch (IOException e) {
+            if (LOG.isErrorEnabled())
+                LOG.error("Unable to close input stream", e);
+        }
+    }
+}