diff --git a/org.springframework.core/ivy.xml b/org.springframework.core/ivy.xml
index d6418d085a9..92e2dba0eec 100644
--- a/org.springframework.core/ivy.xml
+++ b/org.springframework.core/ivy.xml
@@ -21,11 +21,15 @@
name to the supplied value.
+ * If value is null, the attribute is {@link #removeAttribute removed}.
+ *
In general, users should take care to prevent overlaps with other
+ * metadata attributes by using fully-qualified names, perhaps using
+ * class or package names as prefix.
+ * @param name the unique attribute key
+ * @param value the attribute value to be attached
+ */
+ void setAttribute(String name, Object value);
+
+ /**
+ * Get the value of the attribute identified by name.
+ * Return null if the attribute doesn't exist.
+ * @param name the unique attribute key
+ * @return the current value of the attribute, if any
+ */
+ Object getAttribute(String name);
+
+ /**
+ * Remove the attribute identified by name and return its value.
+ * Return null if no attribute under name is found.
+ * @param name the unique attribute key
+ * @return the last value of the attribute, if any
+ */
+ Object removeAttribute(String name);
+
+ /**
+ * Return true if the attribute identified by name exists.
+ * Otherwise return false.
+ * @param name the unique attribute key
+ */
+ boolean hasAttribute(String name);
+
+ /**
+ * Return the names of all attributes.
+ */
+ String[] attributeNames();
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/AttributeAccessorSupport.java b/org.springframework.core/src/main/java/org/springframework/core/AttributeAccessorSupport.java
new file mode 100644
index 00000000000..ece4a05862b
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/AttributeAccessorSupport.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core;
+
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.util.Assert;
+
+/**
+ * Support class for {@link AttributeAccessor AttributeAccessors}, providing
+ * a base implementation of all methods. To be extended by subclasses.
+ *
+ *
{@link Serializable} if subclasses and all attribute values are {@link Serializable}. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + */ +public abstract class AttributeAccessorSupport implements AttributeAccessor, Serializable { + + /** Map with String keys and Object values */ + private final Map attributes = new LinkedHashMap(); + + + public void setAttribute(String name, Object value) { + Assert.notNull(name, "Name must not be null"); + if (value != null) { + this.attributes.put(name, value); + } + else { + removeAttribute(name); + } + } + + public Object getAttribute(String name) { + Assert.notNull(name, "Name must not be null"); + return this.attributes.get(name); + } + + public Object removeAttribute(String name) { + Assert.notNull(name, "Name must not be null"); + return this.attributes.remove(name); + } + + public boolean hasAttribute(String name) { + Assert.notNull(name, "Name must not be null"); + return this.attributes.containsKey(name); + } + + public String[] attributeNames() { + Set attributeNames = this.attributes.keySet(); + return (String[]) attributeNames.toArray(new String[attributeNames.size()]); + } + + + /** + * Copy the attributes from the supplied AttributeAccessor to this accessor. + * @param source the AttributeAccessor to copy from + */ + protected void copyAttributesFrom(AttributeAccessor source) { + Assert.notNull(source, "Source must not be null"); + String[] attributeNames = source.attributeNames(); + for (int i = 0; i < attributeNames.length; i++) { + String attributeName = attributeNames[i]; + setAttribute(attributeName, source.getAttribute(attributeName)); + } + } + + + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof AttributeAccessorSupport)) { + return false; + } + AttributeAccessorSupport that = (AttributeAccessorSupport) other; + return this.attributes.equals(that.attributes); + } + + public int hashCode() { + return this.attributes.hashCode(); + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/BridgeMethodResolver.java b/org.springframework.core/src/main/java/org/springframework/core/BridgeMethodResolver.java new file mode 100644 index 00000000000..a29a2f0d2c8 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/BridgeMethodResolver.java @@ -0,0 +1,206 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.core; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * Helper for resolving synthetic {@link Method#isBridge bridge Methods} to the + * {@link Method} being bridged. + * + *
Given a synthetic {@link Method#isBridge bridge Method} returns the {@link Method} + * being bridged. A bridge method may be created by the compiler when extending a + * parameterized type whose methods have parameterized arguments. During runtime + * invocation the bridge {@link Method} may be invoked and/or used via reflection. + * When attempting to locate annotations on {@link Method Methods}, it is wise to check + * for bridge {@link Method Methods} as appropriate and find the bridged {@link Method}. + * + *
See + * The Java Language Specification for more details on the use of bridge methods. + * + *
Only usable on JDK 1.5 and higher. Use an appropriate {@link JdkVersion} + * check before calling this class if a fallback for JDK 1.4 is desirable. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @since 2.0 + * @see JdkVersion + */ +public abstract class BridgeMethodResolver { + + /** + * Find the original method for the supplied {@link Method bridge Method}. + *
It is safe to call this method passing in a non-bridge {@link Method} instance.
+ * In such a case, the supplied {@link Method} instance is returned directly to the caller.
+ * Callers are not required to check for bridging before calling this method.
+ * @throws IllegalStateException if no bridged {@link Method} can be found
+ */
+ public static Method findBridgedMethod(Method bridgeMethod) {
+ if (bridgeMethod == null || !bridgeMethod.isBridge()) {
+ return bridgeMethod;
+ }
+
+ // Gather all methods with matching name and parameter size.
+ List candidateMethods = new ArrayList();
+ Method[] methods = ReflectionUtils.getAllDeclaredMethods(bridgeMethod.getDeclaringClass());
+ for (int i = 0; i < methods.length; i++) {
+ Method candidateMethod = methods[i];
+ if (isBridgedCandidateFor(candidateMethod, bridgeMethod)) {
+ candidateMethods.add(candidateMethod);
+ }
+ }
+
+ Method result;
+ // Now perform simple quick checks.
+ if (candidateMethods.size() == 1) {
+ result = (Method) candidateMethods.get(0);
+ }
+ else {
+ result = searchCandidates(candidateMethods, bridgeMethod);
+ }
+
+ if (result == null) {
+ throw new IllegalStateException(
+ "Unable to locate bridged method for bridge method '" + bridgeMethod + "'");
+ }
+
+ return result;
+ }
+
+ /**
+ * Searches for the bridged method in the given candidates.
+ * @param candidateMethods the List of candidate Methods
+ * @param bridgeMethod the bridge method
+ * @return the bridged method, or null if none found
+ */
+ private static Method searchCandidates(List candidateMethods, Method bridgeMethod) {
+ Map typeParameterMap = GenericTypeResolver.getTypeVariableMap(bridgeMethod.getDeclaringClass());
+ for (int i = 0; i < candidateMethods.size(); i++) {
+ Method candidateMethod = (Method) candidateMethods.get(i);
+ if (isBridgeMethodFor(bridgeMethod, candidateMethod, typeParameterMap)) {
+ return candidateMethod;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if the supplied 'candidateMethod' can be
+ * consider a validate candidate for the {@link Method} that is {@link Method#isBridge() bridged}
+ * by the supplied {@link Method bridge Method}. This method performs inexpensive
+ * checks and can be used quickly filter for a set of possible matches.
+ */
+ private static boolean isBridgedCandidateFor(Method candidateMethod, Method bridgeMethod) {
+ return (!candidateMethod.isBridge() && !candidateMethod.equals(bridgeMethod) &&
+ candidateMethod.getName().equals(bridgeMethod.getName()) &&
+ candidateMethod.getParameterTypes().length == bridgeMethod.getParameterTypes().length);
+ }
+
+ /**
+ * Determines whether or not the bridge {@link Method} is the bridge for the
+ * supplied candidate {@link Method}.
+ */
+ static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Map typeVariableMap) {
+ if (isResolvedTypeMatch(candidateMethod, bridgeMethod, typeVariableMap)) {
+ return true;
+ }
+ Method method = findGenericDeclaration(bridgeMethod);
+ return (method != null && isResolvedTypeMatch(method, candidateMethod, typeVariableMap));
+ }
+
+ /**
+ * Searches for the generic {@link Method} declaration whose erased signature
+ * matches that of the supplied bridge method.
+ * @throws IllegalStateException if the generic declaration cannot be found
+ */
+ private static Method findGenericDeclaration(Method bridgeMethod) {
+ // Search parent types for method that has same signature as bridge.
+ Class superclass = bridgeMethod.getDeclaringClass().getSuperclass();
+ while (!Object.class.equals(superclass)) {
+ Method method = searchForMatch(superclass, bridgeMethod);
+ if (method != null && !method.isBridge()) {
+ return method;
+ }
+ superclass = superclass.getSuperclass();
+ }
+
+ // Search interfaces.
+ Class[] interfaces = ClassUtils.getAllInterfacesForClass(bridgeMethod.getDeclaringClass());
+ for (int i = 0; i < interfaces.length; i++) {
+ Class anInterface = interfaces[i];
+ Method method = searchForMatch(anInterface, bridgeMethod);
+ if (method != null && !method.isBridge()) {
+ return method;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns true if the {@link Type} signature of both the supplied
+ * {@link Method#getGenericParameterTypes() generic Method} and concrete {@link Method}
+ * are equal after resolving all {@link TypeVariable TypeVariables} using the supplied
+ * TypeVariable Map, otherwise returns false.
+ */
+ private static boolean isResolvedTypeMatch(Method genericMethod, Method candidateMethod, Map typeVariableMap) {
+ Type[] genericParameters = genericMethod.getGenericParameterTypes();
+ Class[] candidateParameters = candidateMethod.getParameterTypes();
+ if (genericParameters.length != candidateParameters.length) {
+ return false;
+ }
+ for (int i = 0; i < genericParameters.length; i++) {
+ Type genericParameter = genericParameters[i];
+ Class candidateParameter = candidateParameters[i];
+ if (candidateParameter.isArray()) {
+ // An array type: compare the component type.
+ Type rawType = GenericTypeResolver.getRawType(genericParameter, typeVariableMap);
+ if (rawType instanceof GenericArrayType) {
+ if (!candidateParameter.getComponentType().equals(
+ GenericTypeResolver.resolveType(((GenericArrayType) rawType).getGenericComponentType(), typeVariableMap))) {
+ return false;
+ }
+ break;
+ }
+ }
+ // A non-array type: compare the type itself.
+ if (!candidateParameter.equals(GenericTypeResolver.resolveType(genericParameter, typeVariableMap))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * If the supplied {@link Class} has a declared {@link Method} whose signature matches
+ * that of the supplied {@link Method}, then this matching {@link Method} is returned,
+ * otherwise null is returned.
+ */
+ private static Method searchForMatch(Class type, Method bridgeMethod) {
+ return ReflectionUtils.findMethod(type, bridgeMethod.getName(), bridgeMethod.getParameterTypes());
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/CollectionFactory.java b/org.springframework.core/src/main/java/org/springframework/core/CollectionFactory.java
new file mode 100644
index 00000000000..f06cc2d8306
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/CollectionFactory.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.NavigableSet;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import org.apache.commons.collections.map.CaseInsensitiveMap;
+import org.apache.commons.collections.map.ListOrderedMap;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.util.ClassUtils;
+
+/**
+ * Factory for collections, being aware of Commons Collection 3.x's extended
+ * collections as well as of JDK 1.5+ concurrent collections and backport-concurrent
+ * collections. Mainly for internal use within the framework.
+ *
+ *
The goal of this class is to avoid runtime dependencies on JDK 1.5+ and
+ * Commons Collections 3.x, simply using the best collection implementation
+ * that is available at runtime. As of Spring 2.5, JDK 1.4 is required,
+ * so former adapter methods for JDK 1.3/1.4 always return the JDK 1.4
+ * collections now. The adapter methods are still kept for supporting
+ * Spring-based applications/frameworks which were built to support JDK 1.3.
+ *
+ * @author Juergen Hoeller
+ * @since 1.1.1
+ */
+public abstract class CollectionFactory {
+
+ private static final Log logger = LogFactory.getLog(CollectionFactory.class);
+
+ /** Whether the Commons Collections 3.x library is present on the classpath */
+ private static final boolean commonsCollections3Available =
+ ClassUtils.isPresent("org.apache.commons.collections.map.CaseInsensitiveMap",
+ CollectionFactory.class.getClassLoader());
+
+ /** Whether the backport-concurrent library is present on the classpath */
+ private static final boolean backportConcurrentAvailable =
+ ClassUtils.isPresent("edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap",
+ CollectionFactory.class.getClassLoader());
+
+
+ private static final Set approximableCollectionTypes = new HashSet(10);
+
+ private static final Set approximableMapTypes = new HashSet(6);
+
+ static {
+ approximableCollectionTypes.add(Collection.class);
+ approximableCollectionTypes.add(List.class);
+ approximableCollectionTypes.add(Set.class);
+ approximableCollectionTypes.add(SortedSet.class);
+ approximableMapTypes.add(Map.class);
+ approximableMapTypes.add(SortedMap.class);
+ if (JdkVersion.isAtLeastJava16()) {
+ approximableCollectionTypes.add(NavigableSet.class);
+ approximableMapTypes.add(NavigableMap.class);
+ }
+ approximableCollectionTypes.add(ArrayList.class);
+ approximableCollectionTypes.add(LinkedList.class);
+ approximableCollectionTypes.add(HashSet.class);
+ approximableCollectionTypes.add(LinkedHashSet.class);
+ approximableCollectionTypes.add(TreeSet.class);
+ approximableMapTypes.add(HashMap.class);
+ approximableMapTypes.add(LinkedHashMap.class);
+ approximableMapTypes.add(TreeMap.class);
+ }
+
+ /**
+ * Create a linked Set if possible: This implementation always
+ * creates a {@link java.util.LinkedHashSet}, since Spring 2.5
+ * requires JDK 1.4 anyway.
+ * @param initialCapacity the initial capacity of the Set
+ * @return the new Set instance
+ * @deprecated as of Spring 2.5, for usage on JDK 1.4 or higher
+ */
+ public static Set createLinkedSetIfPossible(int initialCapacity) {
+ return new LinkedHashSet(initialCapacity);
+ }
+
+ /**
+ * Create a copy-on-write Set (allowing for synchronization-less iteration),
+ * requiring JDK >= 1.5 or the backport-concurrent library on the classpath.
+ * Prefers a JDK 1.5+ CopyOnWriteArraySet to its backport-concurrent equivalent.
+ * Throws an IllegalStateException if no copy-on-write Set is available.
+ * @return the new Set instance
+ * @throws IllegalStateException if no copy-on-write Set is available
+ * @see java.util.concurrent.ConcurrentHashMap
+ * @see edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap
+ */
+ public static Set createCopyOnWriteSet() {
+ if (JdkVersion.isAtLeastJava15()) {
+ logger.trace("Creating [java.util.concurrent.CopyOnWriteArraySet]");
+ return JdkConcurrentCollectionFactory.createCopyOnWriteArraySet();
+ }
+ else if (backportConcurrentAvailable) {
+ logger.trace("Creating [edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArraySet]");
+ return BackportConcurrentCollectionFactory.createCopyOnWriteArraySet();
+ }
+ else {
+ throw new IllegalStateException("Cannot create CopyOnWriteArraySet - " +
+ "neither JDK 1.5 nor backport-concurrent available on the classpath");
+ }
+ }
+
+ /**
+ * Create a linked Map if possible: This implementation always
+ * creates a {@link java.util.LinkedHashMap}, since Spring 2.5
+ * requires JDK 1.4 anyway.
+ * @param initialCapacity the initial capacity of the Map
+ * @return the new Map instance
+ * @deprecated as of Spring 2.5, for usage on JDK 1.4 or higher
+ */
+ public static Map createLinkedMapIfPossible(int initialCapacity) {
+ return new LinkedHashMap(initialCapacity);
+ }
+
+ /**
+ * Create a linked case-insensitive Map if possible: if Commons Collections
+ * 3.x is available, a CaseInsensitiveMap with ListOrderedMap decorator will
+ * be created. Else, a JDK {@link java.util.LinkedHashMap} will be used.
+ * @param initialCapacity the initial capacity of the Map
+ * @return the new Map instance
+ * @see org.apache.commons.collections.map.CaseInsensitiveMap
+ * @see org.apache.commons.collections.map.ListOrderedMap
+ */
+ public static Map createLinkedCaseInsensitiveMapIfPossible(int initialCapacity) {
+ if (commonsCollections3Available) {
+ logger.trace("Creating [org.apache.commons.collections.map.ListOrderedMap/CaseInsensitiveMap]");
+ return CommonsCollectionFactory.createListOrderedCaseInsensitiveMap(initialCapacity);
+ }
+ else {
+ logger.debug("Falling back to [java.util.LinkedHashMap] for linked case-insensitive map");
+ return new LinkedHashMap(initialCapacity);
+ }
+ }
+
+ /**
+ * Create an identity Map if possible: This implementation always
+ * creates a {@link java.util.IdentityHashMap}, since Spring 2.5
+ * requires JDK 1.4 anyway.
+ * @param initialCapacity the initial capacity of the Map
+ * @return the new Map instance
+ * @deprecated as of Spring 2.5, for usage on JDK 1.4 or higher
+ */
+ public static Map createIdentityMapIfPossible(int initialCapacity) {
+ return new IdentityHashMap(initialCapacity);
+ }
+
+ /**
+ * Create a concurrent Map if possible: that is, if running on JDK >= 1.5
+ * or if the backport-concurrent library is available. Prefers a JDK 1.5+
+ * ConcurrentHashMap to its backport-concurrent equivalent. Falls back
+ * to a plain synchronized HashMap if no concurrent Map is available.
+ * @param initialCapacity the initial capacity of the Map
+ * @return the new Map instance
+ * @see java.util.concurrent.ConcurrentHashMap
+ * @see edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap
+ */
+ public static Map createConcurrentMapIfPossible(int initialCapacity) {
+ if (JdkVersion.isAtLeastJava15()) {
+ logger.trace("Creating [java.util.concurrent.ConcurrentHashMap]");
+ return JdkConcurrentCollectionFactory.createConcurrentHashMap(initialCapacity);
+ }
+ else if (backportConcurrentAvailable) {
+ logger.trace("Creating [edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap]");
+ return BackportConcurrentCollectionFactory.createConcurrentHashMap(initialCapacity);
+ }
+ else {
+ logger.debug("Falling back to plain synchronized [java.util.HashMap] for concurrent map");
+ return Collections.synchronizedMap(new HashMap(initialCapacity));
+ }
+ }
+
+ /**
+ * Create a concurrent Map with a dedicated {@link ConcurrentMap} interface,
+ * requiring JDK >= 1.5 or the backport-concurrent library on the classpath.
+ * Prefers a JDK 1.5+ ConcurrentHashMap to its backport-concurrent equivalent.
+ * Throws an IllegalStateException if no concurrent Map is available.
+ * @param initialCapacity the initial capacity of the Map
+ * @return the new ConcurrentMap instance
+ * @throws IllegalStateException if no concurrent Map is available
+ * @see java.util.concurrent.ConcurrentHashMap
+ * @see edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap
+ */
+ public static ConcurrentMap createConcurrentMap(int initialCapacity) {
+ if (JdkVersion.isAtLeastJava15()) {
+ logger.trace("Creating [java.util.concurrent.ConcurrentHashMap]");
+ return new JdkConcurrentHashMap(initialCapacity);
+ }
+ else if (backportConcurrentAvailable) {
+ logger.trace("Creating [edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap]");
+ return new BackportConcurrentHashMap(initialCapacity);
+ }
+ else {
+ throw new IllegalStateException("Cannot create ConcurrentHashMap - " +
+ "neither JDK 1.5 nor backport-concurrent available on the classpath");
+ }
+ }
+
+
+ /**
+ * Determine whether the given collection type is an approximable type,
+ * i.e. a type that {@link #createApproximateCollection} can approximate.
+ * @param collectionType the collection type to check
+ * @return true if the type is approximable,
+ * false if it is not
+ */
+ public static boolean isApproximableCollectionType(Class collectionType) {
+ return (collectionType != null && approximableCollectionTypes.contains(collectionType));
+ }
+
+ /**
+ * Create the most approximate collection for the given collection.
+ *
Creates an ArrayList, TreeSet or linked Set for a List, SortedSet
+ * or Set, respectively.
+ * @param collection the original collection object
+ * @param initialCapacity the initial capacity
+ * @return the new collection instance
+ * @see java.util.ArrayList
+ * @see java.util.TreeSet
+ * @see java.util.LinkedHashSet
+ */
+ public static Collection createApproximateCollection(Object collection, int initialCapacity) {
+ if (collection instanceof LinkedList) {
+ return new LinkedList();
+ }
+ else if (collection instanceof List) {
+ return new ArrayList(initialCapacity);
+ }
+ else if (collection instanceof SortedSet) {
+ return new TreeSet(((SortedSet) collection).comparator());
+ }
+ else {
+ return new LinkedHashSet(initialCapacity);
+ }
+ }
+
+ /**
+ * Determine whether the given map type is an approximable type,
+ * i.e. a type that {@link #createApproximateMap} can approximate.
+ * @param mapType the map type to check
+ * @return true if the type is approximable,
+ * false if it is not
+ */
+ public static boolean isApproximableMapType(Class mapType) {
+ return (mapType != null && approximableMapTypes.contains(mapType));
+ }
+
+ /**
+ * Create the most approximate map for the given map.
+ *
Creates a TreeMap or linked Map for a SortedMap or Map, respectively. + * @param map the original map object + * @param initialCapacity the initial capacity + * @return the new collection instance + * @see java.util.TreeMap + * @see java.util.LinkedHashMap + */ + public static Map createApproximateMap(Object map, int initialCapacity) { + if (map instanceof SortedMap) { + return new TreeMap(((SortedMap) map).comparator()); + } + else { + return new LinkedHashMap(initialCapacity); + } + } + + + /** + * Actual creation of Commons Collections. + * In separate inner class to avoid runtime dependency on Commons Collections 3.x. + */ + private static abstract class CommonsCollectionFactory { + + private static Map createListOrderedCaseInsensitiveMap(int initialCapacity) { + // Commons Collections does not support initial capacity of 0. + return ListOrderedMap.decorate(new CaseInsensitiveMap(initialCapacity == 0 ? 1 : initialCapacity)); + } + } + + + /** + * Actual creation of JDK 1.5+ concurrent Collections. + * In separate inner class to avoid runtime dependency on JDK 1.5. + */ + private static abstract class JdkConcurrentCollectionFactory { + + private static Set createCopyOnWriteArraySet() { + return new CopyOnWriteArraySet(); + } + + private static Map createConcurrentHashMap(int initialCapacity) { + return new ConcurrentHashMap(initialCapacity); + } + } + + + /** + * Actual creation of backport-concurrent Collections. + * In separate inner class to avoid runtime dependency on the backport-concurrent library. + */ + private static abstract class BackportConcurrentCollectionFactory { + + private static Set createCopyOnWriteArraySet() { + return new edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArraySet(); + } + + private static Map createConcurrentHashMap(int initialCapacity) { + return new edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap(initialCapacity); + } + } + + + /** + * ConcurrentMap adapter for the JDK ConcurrentHashMap class. + */ + private static class JdkConcurrentHashMap extends ConcurrentHashMap implements ConcurrentMap { + + public JdkConcurrentHashMap(int initialCapacity) { + super(initialCapacity); + } + } + + + /** + * ConcurrentMap adapter for the backport-concurrent ConcurrentHashMap class. + */ + private static class BackportConcurrentHashMap + extends edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap + implements ConcurrentMap { + + public BackportConcurrentHashMap(int initialCapacity) { + super(initialCapacity); + } + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/ConcurrentMap.java b/org.springframework.core/src/main/java/org/springframework/core/ConcurrentMap.java new file mode 100644 index 00000000000..9cdb50c702b --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/ConcurrentMap.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * 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.springframework.core; + +import java.util.Map; + +/** + * Common interface for a concurrent Map, as exposed by + * {@link CollectionFactory#createConcurrentMap}. Mirrors + * {@link java.util.concurrent.ConcurrentMap}, allowing to be backed by a + * JDK ConcurrentHashMap as well as a backport-concurrent ConcurrentHashMap. + * + *
Check out the {@link java.util.concurrent.ConcurrentMap ConcurrentMap javadoc} + * for details on the interface's methods. + * + * @author Juergen Hoeller + * @since 2.5 + */ +public interface ConcurrentMap extends Map { + + Object putIfAbsent(Object key, Object value); + + boolean remove(Object key, Object value); + + boolean replace(Object key, Object oldValue, Object newValue); + + Object replace(Object key, Object value); + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java b/org.springframework.core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java new file mode 100644 index 00000000000..30b5f0cc29c --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java @@ -0,0 +1,126 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * 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.springframework.core; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; +import java.lang.reflect.Proxy; + +import org.springframework.util.ClassUtils; + +/** + * Special ObjectInputStream subclass that resolves class names + * against a specific ClassLoader. Serves as base class for + * {@link org.springframework.remoting.rmi.CodebaseAwareObjectInputStream}. + * + * @author Juergen Hoeller + * @since 2.5.5 + */ +public class ConfigurableObjectInputStream extends ObjectInputStream { + + private final ClassLoader classLoader; + + + /** + * Create a new ConfigurableObjectInputStream for the given InputStream and ClassLoader. + * @param in the InputStream to read from + * @param classLoader the ClassLoader to use for loading local classes + * @see java.io.ObjectInputStream#ObjectInputStream(java.io.InputStream) + */ + public ConfigurableObjectInputStream(InputStream in, ClassLoader classLoader) throws IOException { + super(in); + this.classLoader = classLoader; + } + + + protected Class resolveClass(ObjectStreamClass classDesc) throws IOException, ClassNotFoundException { + try { + if (this.classLoader != null) { + // Use the specified ClassLoader to resolve local classes. + return ClassUtils.forName(classDesc.getName(), this.classLoader); + } + else { + // Use the default ClassLoader... + return super.resolveClass(classDesc); + } + } + catch (ClassNotFoundException ex) { + return resolveFallbackIfPossible(classDesc.getName(), ex); + } + } + + protected Class resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException { + if (this.classLoader != null) { + // Use the specified ClassLoader to resolve local proxy classes. + Class[] resolvedInterfaces = new Class[interfaces.length]; + for (int i = 0; i < interfaces.length; i++) { + try { + resolvedInterfaces[i] = ClassUtils.forName(interfaces[i], this.classLoader); + } + catch (ClassNotFoundException ex) { + resolvedInterfaces[i] = resolveFallbackIfPossible(interfaces[i], ex); + } + } + try { + return Proxy.getProxyClass(this.classLoader, resolvedInterfaces); + } + catch (IllegalArgumentException ex) { + throw new ClassNotFoundException(null, ex); + } + } + else { + // Use ObjectInputStream's default ClassLoader... + try { + return super.resolveProxyClass(interfaces); + } + catch (ClassNotFoundException ex) { + Class[] resolvedInterfaces = new Class[interfaces.length]; + for (int i = 0; i < interfaces.length; i++) { + resolvedInterfaces[i] = resolveFallbackIfPossible(interfaces[i], ex); + } + return Proxy.getProxyClass(getFallbackClassLoader(), resolvedInterfaces); + } + } + } + + + /** + * Resolve the given class name against a fallback class loader. + *
The default implementation simply rethrows the original exception,
+ * since there is no fallback available.
+ * @param className the class name to resolve
+ * @param ex the original exception thrown when attempting to load the class
+ * @return the newly resolved class (never null)
+ */
+ protected Class resolveFallbackIfPossible(String className, ClassNotFoundException ex)
+ throws IOException, ClassNotFoundException{
+
+ throw ex;
+ }
+
+ /**
+ * Return the fallback ClassLoader to use when no ClassLoader was specified
+ * and ObjectInputStream's own default ClassLoader failed.
+ *
The default implementation simply returns null.
+ */
+ protected ClassLoader getFallbackClassLoader() throws IOException {
+ return null;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/ConstantException.java b/org.springframework.core/src/main/java/org/springframework/core/ConstantException.java
new file mode 100644
index 00000000000..938667454bb
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/ConstantException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * 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.springframework.core;
+
+/**
+ * Exception thrown when the {@link Constants} class is asked for
+ * an invalid constant name.
+ *
+ * @author Rod Johnson
+ * @since 28.04.2003
+ * @see org.springframework.core.Constants
+ */
+public class ConstantException extends IllegalArgumentException {
+
+ /**
+ * Thrown when an invalid constant name is requested.
+ * @param className name of the class containing the constant definitions
+ * @param field invalid constant name
+ * @param message description of the problem
+ */
+ public ConstantException(String className, String field, String message) {
+ super("Field '" + field + "' " + message + " in class [" + className + "]");
+ }
+
+ /**
+ * Thrown when an invalid constant value is looked up.
+ * @param className name of the class containing the constant definitions
+ * @param namePrefix prefix of the searched constant names
+ * @param value the looked up constant value
+ */
+ public ConstantException(String className, String namePrefix, Object value) {
+ super("No '" + namePrefix + "' field with value '" + value + "' found in class [" + className + "]");
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/Constants.java b/org.springframework.core/src/main/java/org/springframework/core/Constants.java
new file mode 100644
index 00000000000..e0fb6d1310c
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/Constants.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * This class can be used to parse other classes containing constant definitions
+ * in public static final members. The asXXXX methods of this class
+ * allow these constant values to be accessed via their string names.
+ *
+ *
Consider class Foo containing public final static int CONSTANT1 = 66;
+ * An instance of this class wrapping Foo.class will return the constant value
+ * of 66 from its asNumber method given the argument "CONSTANT1".
+ *
+ *
This class is ideal for use in PropertyEditors, enabling them to + * recognize the same names as the constants themselves, and freeing them + * from maintaining their own mapping. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 16.03.2003 + */ +public class Constants { + + /** The name of the introspected class */ + private final String className; + + /** Map from String field name to object value */ + private final Map fieldCache = new HashMap(); + + + /** + * Create a new Constants converter class wrapping the given class. + *
All public static final variables will be exposed, whatever their type.
+ * @param clazz the class to analyze
+ * @throws IllegalArgumentException if the supplied clazz is null
+ */
+ public Constants(Class clazz) {
+ Assert.notNull(clazz);
+ this.className = clazz.getName();
+ Field[] fields = clazz.getFields();
+ for (int i = 0; i < fields.length; i++) {
+ Field field = fields[i];
+ if (ReflectionUtils.isPublicStaticFinal(field)) {
+ String name = field.getName();
+ try {
+ Object value = field.get(null);
+ this.fieldCache.put(name, value);
+ }
+ catch (IllegalAccessException ex) {
+ // just leave this field and continue
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Return the name of the analyzed class.
+ */
+ public final String getClassName() {
+ return this.className;
+ }
+
+ /**
+ * Return the number of constants exposed.
+ */
+ public final int getSize() {
+ return this.fieldCache.size();
+ }
+
+ /**
+ * Exposes the field cache to subclasses:
+ * a Map from String field name to object value.
+ */
+ protected final Map getFieldCache() {
+ return this.fieldCache;
+ }
+
+
+ /**
+ * Return a constant value cast to a Number.
+ * @param code the name of the field (never null)
+ * @return the Number value
+ * @see #asObject
+ * @throws ConstantException if the field name wasn't found
+ * or if the type wasn't compatible with Number
+ */
+ public Number asNumber(String code) throws ConstantException {
+ Object obj = asObject(code);
+ if (!(obj instanceof Number)) {
+ throw new ConstantException(this.className, code, "not a Number");
+ }
+ return (Number) obj;
+ }
+
+ /**
+ * Return a constant value as a String.
+ * @param code the name of the field (never null)
+ * @return the String value
+ * Works even if it's not a string (invokes toString()).
+ * @see #asObject
+ * @throws ConstantException if the field name wasn't found
+ */
+ public String asString(String code) throws ConstantException {
+ return asObject(code).toString();
+ }
+
+ /**
+ * Parse the given String (upper or lower case accepted) and return
+ * the appropriate value if it's the name of a constant field in the
+ * class that we're analysing.
+ * @param code the name of the field (never null)
+ * @return the Object value
+ * @throws ConstantException if there's no such field
+ */
+ public Object asObject(String code) throws ConstantException {
+ Assert.notNull(code, "Code must not be null");
+ String codeToUse = code.toUpperCase(Locale.ENGLISH);
+ Object val = this.fieldCache.get(codeToUse);
+ if (val == null) {
+ throw new ConstantException(this.className, codeToUse, "not found");
+ }
+ return val;
+ }
+
+
+ /**
+ * Return all names of the given group of constants.
+ *
Note that this method assumes that constants are named
+ * in accordance with the standard Java convention for constant
+ * values (i.e. all uppercase). The supplied namePrefix
+ * will be uppercased (in a locale-insensitive fashion) prior to
+ * the main logic of this method kicking in.
+ * @param namePrefix prefix of the constant names to search (may be null)
+ * @return the set of constant names
+ */
+ public Set getNames(String namePrefix) {
+ String prefixToUse = (namePrefix != null ? namePrefix.trim().toUpperCase(Locale.ENGLISH) : "");
+ Set names = new HashSet();
+ for (Iterator it = this.fieldCache.keySet().iterator(); it.hasNext();) {
+ String code = (String) it.next();
+ if (code.startsWith(prefixToUse)) {
+ names.add(code);
+ }
+ }
+ return names;
+ }
+
+ /**
+ * Return all names of the group of constants for the
+ * given bean property name.
+ * @param propertyName the name of the bean property
+ * @return the set of values
+ * @see #propertyToConstantNamePrefix
+ */
+ public Set getNamesForProperty(String propertyName) {
+ return getNames(propertyToConstantNamePrefix(propertyName));
+ }
+
+ /**
+ * Return all names of the given group of constants.
+ *
Note that this method assumes that constants are named
+ * in accordance with the standard Java convention for constant
+ * values (i.e. all uppercase). The supplied nameSuffix
+ * will be uppercased (in a locale-insensitive fashion) prior to
+ * the main logic of this method kicking in.
+ * @param nameSuffix suffix of the constant names to search (may be null)
+ * @return the set of constant names
+ */
+ public Set getNamesForSuffix(String nameSuffix) {
+ String suffixToUse = (nameSuffix != null ? nameSuffix.trim().toUpperCase(Locale.ENGLISH) : "");
+ Set names = new HashSet();
+ for (Iterator it = this.fieldCache.keySet().iterator(); it.hasNext();) {
+ String code = (String) it.next();
+ if (code.endsWith(suffixToUse)) {
+ names.add(code);
+ }
+ }
+ return names;
+ }
+
+
+ /**
+ * Return all values of the given group of constants.
+ *
Note that this method assumes that constants are named
+ * in accordance with the standard Java convention for constant
+ * values (i.e. all uppercase). The supplied namePrefix
+ * will be uppercased (in a locale-insensitive fashion) prior to
+ * the main logic of this method kicking in.
+ * @param namePrefix prefix of the constant names to search (may be null)
+ * @return the set of values
+ */
+ public Set getValues(String namePrefix) {
+ String prefixToUse = (namePrefix != null ? namePrefix.trim().toUpperCase(Locale.ENGLISH) : "");
+ Set values = new HashSet();
+ for (Iterator it = this.fieldCache.keySet().iterator(); it.hasNext();) {
+ String code = (String) it.next();
+ if (code.startsWith(prefixToUse)) {
+ values.add(this.fieldCache.get(code));
+ }
+ }
+ return values;
+ }
+
+ /**
+ * Return all values of the group of constants for the
+ * given bean property name.
+ * @param propertyName the name of the bean property
+ * @return the set of values
+ * @see #propertyToConstantNamePrefix
+ */
+ public Set getValuesForProperty(String propertyName) {
+ return getValues(propertyToConstantNamePrefix(propertyName));
+ }
+
+ /**
+ * Return all values of the given group of constants.
+ *
Note that this method assumes that constants are named
+ * in accordance with the standard Java convention for constant
+ * values (i.e. all uppercase). The supplied nameSuffix
+ * will be uppercased (in a locale-insensitive fashion) prior to
+ * the main logic of this method kicking in.
+ * @param nameSuffix suffix of the constant names to search (may be null)
+ * @return the set of values
+ */
+ public Set getValuesForSuffix(String nameSuffix) {
+ String suffixToUse = (nameSuffix != null ? nameSuffix.trim().toUpperCase(Locale.ENGLISH) : "");
+ Set values = new HashSet();
+ for (Iterator it = this.fieldCache.keySet().iterator(); it.hasNext();) {
+ String code = (String) it.next();
+ if (code.endsWith(suffixToUse)) {
+ values.add(this.fieldCache.get(code));
+ }
+ }
+ return values;
+ }
+
+
+ /**
+ * Look up the given value within the given group of constants.
+ *
Will return the first match.
+ * @param value constant value to look up
+ * @param namePrefix prefix of the constant names to search (may be null)
+ * @return the name of the constant field
+ * @throws ConstantException if the value wasn't found
+ */
+ public String toCode(Object value, String namePrefix) throws ConstantException {
+ String prefixToUse = (namePrefix != null ? namePrefix.trim().toUpperCase(Locale.ENGLISH) : null);
+ for (Iterator it = this.fieldCache.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ String key = (String) entry.getKey();
+ if (key.startsWith(prefixToUse) && entry.getValue().equals(value)) {
+ return key;
+ }
+ }
+ throw new ConstantException(this.className, prefixToUse, value);
+ }
+
+ /**
+ * Look up the given value within the group of constants for
+ * the given bean property name. Will return the first match.
+ * @param value constant value to look up
+ * @param propertyName the name of the bean property
+ * @return the name of the constant field
+ * @throws ConstantException if the value wasn't found
+ * @see #propertyToConstantNamePrefix
+ */
+ public String toCodeForProperty(Object value, String propertyName) throws ConstantException {
+ return toCode(value, propertyToConstantNamePrefix(propertyName));
+ }
+
+ /**
+ * Look up the given value within the given group of constants.
+ *
Will return the first match.
+ * @param value constant value to look up
+ * @param nameSuffix suffix of the constant names to search (may be null)
+ * @return the name of the constant field
+ * @throws ConstantException if the value wasn't found
+ */
+ public String toCodeForSuffix(Object value, String nameSuffix) throws ConstantException {
+ String suffixToUse = (nameSuffix != null ? nameSuffix.trim().toUpperCase(Locale.ENGLISH) : null);
+ for (Iterator it = this.fieldCache.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ String key = (String) entry.getKey();
+ if (key.endsWith(suffixToUse) && entry.getValue().equals(value)) {
+ return key;
+ }
+ }
+ throw new ConstantException(this.className, suffixToUse, value);
+ }
+
+
+ /**
+ * Convert the given bean property name to a constant name prefix.
+ *
Uses a common naming idiom: turning all lower case characters to + * upper case, and prepending upper case characters with an underscore. + *
Example: "imageSize" -> "IMAGE_SIZE"
+ * Example: "imagesize" -> "IMAGESIZE".
+ * Example: "ImageSize" -> "_IMAGE_SIZE".
+ * Example: "IMAGESIZE" -> "_I_M_A_G_E_S_I_Z_E"
+ * @param propertyName the name of the bean property
+ * @return the corresponding constant name prefix
+ * @see #getValuesForProperty
+ * @see #toCodeForProperty
+ */
+ public String propertyToConstantNamePrefix(String propertyName) {
+ StringBuffer parsedPrefix = new StringBuffer();
+ for(int i = 0; i < propertyName.length(); i++) {
+ char c = propertyName.charAt(i);
+ if (Character.isUpperCase(c)) {
+ parsedPrefix.append("_");
+ parsedPrefix.append(c);
+ }
+ else {
+ parsedPrefix.append(Character.toUpperCase(c));
+ }
+ }
+ return parsedPrefix.toString();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/ControlFlow.java b/org.springframework.core/src/main/java/org/springframework/core/ControlFlow.java
new file mode 100644
index 00000000000..4875f1c191f
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/ControlFlow.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * 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.springframework.core;
+
+/**
+ * Interface to be implemented by objects that can return information about
+ * the current call stack. Useful in AOP (as in AspectJ cflow concept)
+ * but not AOP-specific.
+ *
+ * @author Rod Johnson
+ * @since 02.02.2004
+ */
+public interface ControlFlow {
+
+ /**
+ * Detect whether we're under the given class,
+ * according to the current stack trace.
+ * @param clazz the clazz to look for
+ */
+ boolean under(Class clazz);
+
+ /**
+ * Detect whether we're under the given class and method,
+ * according to the current stack trace.
+ * @param clazz the clazz to look for
+ * @param methodName the name of the method to look for
+ */
+ boolean under(Class clazz, String methodName);
+
+ /**
+ * Detect whether the current stack trace contains the given token.
+ * @param token the token to look for
+ */
+ boolean underToken(String token);
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/ControlFlowFactory.java b/org.springframework.core/src/main/java/org/springframework/core/ControlFlowFactory.java
new file mode 100644
index 00000000000..ef10946ed6c
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/ControlFlowFactory.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.springframework.util.Assert;
+
+/**
+ * Static factory to conceal the automatic choice of the ControlFlow
+ * implementation class.
+ *
+ *
This implementation always uses the efficient Java 1.4 StackTraceElement + * mechanism for analyzing control flows. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @since 02.02.2004 + */ +public abstract class ControlFlowFactory { + + /** + * Return an appropriate {@link ControlFlow} instance. + */ + public static ControlFlow createControlFlow() { + return new Jdk14ControlFlow(); + } + + + /** + * Utilities for cflow-style pointcuts. Note that such pointcuts are + * 5-10 times more expensive to evaluate than other pointcuts, as they require + * analysis of the stack trace (through constructing a new throwable). + * However, they are useful in some cases. + *
This implementation uses the StackTraceElement class introduced in Java 1.4.
+ * @see java.lang.StackTraceElement
+ */
+ static class Jdk14ControlFlow implements ControlFlow {
+
+ private StackTraceElement[] stack;
+
+ public Jdk14ControlFlow() {
+ this.stack = new Throwable().getStackTrace();
+ }
+
+ /**
+ * Searches for class name match in a StackTraceElement.
+ */
+ public boolean under(Class clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ String className = clazz.getName();
+ for (int i = 0; i < stack.length; i++) {
+ if (this.stack[i].getClassName().equals(className)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Searches for class name match plus method name match
+ * in a StackTraceElement.
+ */
+ public boolean under(Class clazz, String methodName) {
+ Assert.notNull(clazz, "Class must not be null");
+ Assert.notNull(methodName, "Method name must not be null");
+ String className = clazz.getName();
+ for (int i = 0; i < this.stack.length; i++) {
+ if (this.stack[i].getClassName().equals(className) &&
+ this.stack[i].getMethodName().equals(methodName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Leave it up to the caller to decide what matches.
+ * Caller must understand stack trace format, so there's less abstraction.
+ */
+ public boolean underToken(String token) {
+ if (token == null) {
+ return false;
+ }
+ StringWriter sw = new StringWriter();
+ new Throwable().printStackTrace(new PrintWriter(sw));
+ String stackTrace = sw.toString();
+ return stackTrace.indexOf(token) != -1;
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer("Jdk14ControlFlow: ");
+ for (int i = 0; i < this.stack.length; i++) {
+ if (i > 0) {
+ sb.append("\n\t@");
+ }
+ sb.append(this.stack[i]);
+ }
+ return sb.toString();
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/Conventions.java b/org.springframework.core/src/main/java/org/springframework/core/Conventions.java
new file mode 100644
index 00000000000..dde279eaf67
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/Conventions.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core;
+
+import java.io.Externalizable;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Provides methods to support various naming and other conventions used
+ * throughout the framework. Mainly for internal use within the framework.
+ *
+ * @author Rob Harrop
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public abstract class Conventions {
+
+ /**
+ * Suffix added to names when using arrays.
+ */
+ private static final String PLURAL_SUFFIX = "List";
+
+
+ /**
+ * Set of interfaces that are supposed to be ignored
+ * when searching for the 'primary' interface of a proxy.
+ */
+ private static final Set ignoredInterfaces = new HashSet();
+
+ static {
+ ignoredInterfaces.add(Serializable.class);
+ ignoredInterfaces.add(Externalizable.class);
+ ignoredInterfaces.add(Cloneable.class);
+ ignoredInterfaces.add(Comparable.class);
+ }
+
+
+ /**
+ * Determine the conventional variable name for the supplied
+ * Object based on its concrete type. The convention
+ * used is to return the uncapitalized short name of the Class,
+ * according to JavaBeans property naming rules: So,
+ * com.myapp.Product becomes product;
+ * com.myapp.MyProduct becomes myProduct;
+ * com.myapp.UKProduct becomes UKProduct.
+ *
For arrays, we use the pluralized version of the array component type.
+ * For Collections we attempt to 'peek ahead' in the
+ * Collection to determine the component type and
+ * return the pluralized version of that component type.
+ * @param value the value to generate a variable name for
+ * @return the generated variable name
+ */
+ public static String getVariableName(Object value) {
+ Assert.notNull(value, "Value must not be null");
+ Class valueClass = null;
+ boolean pluralize = false;
+
+ if (value.getClass().isArray()) {
+ valueClass = value.getClass().getComponentType();
+ pluralize = true;
+ }
+ else if (value instanceof Collection) {
+ Collection collection = (Collection) value;
+ if (collection.isEmpty()) {
+ throw new IllegalArgumentException("Cannot generate variable name for an empty Collection");
+ }
+ Object valueToCheck = peekAhead(collection);
+ valueClass = getClassForValue(valueToCheck);
+ pluralize = true;
+ }
+ else {
+ valueClass = getClassForValue(value);
+ }
+
+ String name = ClassUtils.getShortNameAsProperty(valueClass);
+ return (pluralize ? pluralize(name) : name);
+ }
+
+ /**
+ * Determine the conventional variable name for the supplied parameter,
+ * taking the generic collection type (if any) into account.
+ * @param parameter the method or constructor parameter to generate a variable name for
+ * @return the generated variable name
+ */
+ public static String getVariableNameForParameter(MethodParameter parameter) {
+ Assert.notNull(parameter, "MethodParameter must not be null");
+ Class valueClass = null;
+ boolean pluralize = false;
+
+ if (parameter.getParameterType().isArray()) {
+ valueClass = parameter.getParameterType().getComponentType();
+ pluralize = true;
+ }
+ else if (Collection.class.isAssignableFrom(parameter.getParameterType())) {
+ if (JdkVersion.isAtLeastJava15()) {
+ valueClass = GenericCollectionTypeResolver.getCollectionParameterType(parameter);
+ }
+ if (valueClass == null) {
+ throw new IllegalArgumentException("Cannot generate variable name for non-typed Collection parameter type");
+ }
+ pluralize = true;
+ }
+ else {
+ valueClass = parameter.getParameterType();
+ }
+
+ String name = ClassUtils.getShortNameAsProperty(valueClass);
+ return (pluralize ? pluralize(name) : name);
+ }
+
+ /**
+ * Determine the conventional variable name for the return type of the supplied method,
+ * taking the generic collection type (if any) into account.
+ * @param method the method to generate a variable name for
+ * @return the generated variable name
+ */
+ public static String getVariableNameForReturnType(Method method) {
+ return getVariableNameForReturnType(method, method.getReturnType(), null);
+ }
+
+ /**
+ * Determine the conventional variable name for the return type of the supplied method,
+ * taking the generic collection type (if any) into account, falling back to the
+ * given return value if the method declaration is not specific enough (i.e. in case of
+ * the return type being declared as Object or as untyped collection).
+ * @param method the method to generate a variable name for
+ * @param value the return value (may be null if not available)
+ * @return the generated variable name
+ */
+ public static String getVariableNameForReturnType(Method method, Object value) {
+ return getVariableNameForReturnType(method, method.getReturnType(), value);
+ }
+
+ /**
+ * Determine the conventional variable name for the return type of the supplied method,
+ * taking the generic collection type (if any) into account, falling back to the
+ * given return value if the method declaration is not specific enough (i.e. in case of
+ * the return type being declared as Object or as untyped collection).
+ * @param method the method to generate a variable name for
+ * @param resolvedType the resolved return type of the method
+ * @param value the return value (may be null if not available)
+ * @return the generated variable name
+ */
+ public static String getVariableNameForReturnType(Method method, Class resolvedType, Object value) {
+ Assert.notNull(method, "Method must not be null");
+
+ if (Object.class.equals(resolvedType)) {
+ if (value == null) {
+ throw new IllegalArgumentException("Cannot generate variable name for an Object return type with null value");
+ }
+ return getVariableName(value);
+ }
+
+ Class valueClass = null;
+ boolean pluralize = false;
+
+ if (resolvedType.isArray()) {
+ valueClass = resolvedType.getComponentType();
+ pluralize = true;
+ }
+ else if (Collection.class.isAssignableFrom(resolvedType)) {
+ if (JdkVersion.isAtLeastJava15()) {
+ valueClass = GenericCollectionTypeResolver.getCollectionReturnType(method);
+ }
+ if (valueClass == null) {
+ if (!(value instanceof Collection)) {
+ throw new IllegalArgumentException(
+ "Cannot generate variable name for non-typed Collection return type and a non-Collection value");
+ }
+ Collection collection = (Collection) value;
+ if (collection.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Cannot generate variable name for non-typed Collection return type and an empty Collection value");
+ }
+ Object valueToCheck = peekAhead(collection);
+ valueClass = getClassForValue(valueToCheck);
+ }
+ pluralize = true;
+ }
+ else {
+ valueClass = resolvedType;
+ }
+
+ String name = ClassUtils.getShortNameAsProperty(valueClass);
+ return (pluralize ? pluralize(name) : name);
+ }
+
+ /**
+ * Convert Strings in attribute name format (lowercase, hyphens separating words)
+ * into property name format (camel-cased). For example, transaction-manager is
+ * converted into transactionManager.
+ */
+ public static String attributeNameToPropertyName(String attributeName) {
+ Assert.notNull(attributeName, "'attributeName' must not be null");
+ if (attributeName.indexOf("-") == -1) {
+ return attributeName;
+ }
+ char[] chars = attributeName.toCharArray();
+ char[] result = new char[chars.length -1]; // not completely accurate but good guess
+ int currPos = 0;
+ boolean upperCaseNext = false;
+ for (int i = 0; i < chars.length; i++) {
+ char c = chars[i];
+ if (c == '-') {
+ upperCaseNext = true;
+ }
+ else if (upperCaseNext) {
+ result[currPos++] = Character.toUpperCase(c);
+ upperCaseNext = false;
+ }
+ else {
+ result[currPos++] = c;
+ }
+ }
+ return new String(result, 0, currPos);
+ }
+
+ /**
+ * Return an attribute name qualified by the supplied enclosing {@link Class}. For example,
+ * the attribute name 'foo' qualified by {@link Class} 'com.myapp.SomeClass'
+ * would be 'com.myapp.SomeClass.foo'
+ */
+ public static String getQualifiedAttributeName(Class enclosingClass, String attributeName) {
+ Assert.notNull(enclosingClass, "'enclosingClass' must not be null");
+ Assert.notNull(attributeName, "'attributeName' must not be null");
+ return enclosingClass.getName() + "." + attributeName;
+ }
+
+
+ /**
+ * Determines the class to use for naming a variable that contains
+ * the given value.
+ *
Will return the class of the given value, except when
+ * encountering a JDK proxy, in which case it will determine
+ * the 'primary' interface implemented by that proxy.
+ * @param value the value to check
+ * @return the class to use for naming a variable
+ */
+ private static Class getClassForValue(Object value) {
+ Class valueClass = value.getClass();
+ if (Proxy.isProxyClass(valueClass)) {
+ Class[] ifcs = valueClass.getInterfaces();
+ for (int i = 0; i < ifcs.length; i++) {
+ Class ifc = ifcs[i];
+ if (!ignoredInterfaces.contains(ifc)) {
+ return ifc;
+ }
+ }
+ }
+ else if (valueClass.getName().lastIndexOf('$') != -1 && valueClass.getDeclaringClass() == null) {
+ // '$' in the class name but no inner class -
+ // assuming it's a special subclass (e.g. by OpenJPA)
+ valueClass = valueClass.getSuperclass();
+ }
+ return valueClass;
+ }
+
+ /**
+ * Pluralize the given name.
+ */
+ private static String pluralize(String name) {
+ return name + PLURAL_SUFFIX;
+ }
+
+ /**
+ * Retrieves the Class of an element in the Collection.
+ * The exact element for which the Class is retreived will depend
+ * on the concrete Collection implementation.
+ */
+ private static Object peekAhead(Collection collection) {
+ Iterator it = collection.iterator();
+ if (!it.hasNext()) {
+ throw new IllegalStateException(
+ "Unable to peek ahead in non-empty collection - no element found");
+ }
+ Object value = it.next();
+ if (value == null) {
+ throw new IllegalStateException(
+ "Unable to peek ahead in non-empty collection - only null element found");
+ }
+ return value;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/DecoratingClassLoader.java b/org.springframework.core/src/main/java/org/springframework/core/DecoratingClassLoader.java
new file mode 100644
index 00000000000..7ae436d4ba3
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/DecoratingClassLoader.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.springframework.util.Assert;
+
+/**
+ * Base class for decorating ClassLoaders such as {@link OverridingClassLoader}
+ * and {@link org.springframework.instrument.classloading.ShadowingClassLoader},
+ * providing common handling of excluded packages and classes.
+ *
+ * @author Juergen Hoeller
+ * @author Rod Johnson
+ * @since 2.5.2
+ */
+public abstract class DecoratingClassLoader extends ClassLoader {
+
+ private final Set excludedPackages = new HashSet();
+
+ private final Set excludedClasses = new HashSet();
+
+ private final Object exclusionMonitor = new Object();
+
+
+ /**
+ * Create a new DecoratingClassLoader with no parent ClassLoader.
+ */
+ public DecoratingClassLoader() {
+ }
+
+ /**
+ * Create a new DecoratingClassLoader using the given parent ClassLoader
+ * for delegation.
+ */
+ public DecoratingClassLoader(ClassLoader parent) {
+ super(parent);
+ }
+
+
+ /**
+ * Add a package name to exclude from decoration (e.g. overriding).
+ *
Any class whose fully-qualified name starts with the name registered + * here will be handled by the parent ClassLoader in the usual fashion. + * @param packageName the package name to exclude + */ + public void excludePackage(String packageName) { + Assert.notNull(packageName, "Package name must not be null"); + synchronized (this.exclusionMonitor) { + this.excludedPackages.add(packageName); + } + } + + /** + * Add a class name to exclude from decoration (e.g. overriding). + *
Any class name registered here will be handled by the parent + * ClassLoader in the usual fashion. + * @param className the class name to exclude + */ + public void excludeClass(String className) { + Assert.notNull(className, "Class name must not be null"); + synchronized (this.exclusionMonitor) { + this.excludedClasses.add(className); + } + } + + /** + * Determine whether the specified class is excluded from decoration + * by this class loader. + *
The default implementation checks against excluded packages and classes. + * @param className the class name to check + * @return whether the specified class is eligible + * @see #excludePackage + * @see #excludeClass + */ + protected boolean isExcluded(String className) { + synchronized (this.exclusionMonitor) { + if (this.excludedClasses.contains(className)) { + return true; + } + for (Iterator it = this.excludedPackages.iterator(); it.hasNext();) { + String packageName = (String) it.next(); + if (className.startsWith(packageName)) { + return true; + } + } + } + return false; + } + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/ErrorCoded.java b/org.springframework.core/src/main/java/org/springframework/core/ErrorCoded.java new file mode 100644 index 00000000000..6b75babe921 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/ErrorCoded.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * 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.springframework.core; + +/** + * Interface that can be implemented by exceptions etc that are error coded. + * The error code is a String, rather than a number, so it can be given + * user-readable values, such as "object.failureDescription". + * + *
An error code can be resolved by a MessageSource, for example.
+ *
+ * @author Rod Johnson
+ * @see org.springframework.context.MessageSource
+ */
+public interface ErrorCoded {
+
+ /**
+ * Return the error code associated with this failure.
+ * The GUI can render this any way it pleases, allowing for localization etc.
+ * @return a String error code associated with this failure,
+ * or null if not error-coded
+ */
+ String getErrorCode();
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java b/org.springframework.core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java
new file mode 100644
index 00000000000..51fcafe7d01
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java
@@ -0,0 +1,426 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Helper class for determining element types of collections and maps.
+ *
+ *
Mainly intended for usage within the framework, determining the + * target type of values to be added to a collection or map + * (to be able to attempt type conversion if appropriate). + * + *
Only usable on Java 5. Use an appropriate {@link JdkVersion} check
+ * before calling this class, if a fallback for JDK 1.4 is desirable.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see org.springframework.beans.BeanWrapper
+ */
+public abstract class GenericCollectionTypeResolver {
+
+ /**
+ * Determine the generic element type of the given Collection class
+ * (if it declares one through a generic superclass or generic interface).
+ * @param collectionClass the collection class to introspect
+ * @return the generic type, or null if none
+ */
+ public static Class getCollectionType(Class collectionClass) {
+ return extractTypeFromClass(collectionClass, Collection.class, 0);
+ }
+
+ /**
+ * Determine the generic key type of the given Map class
+ * (if it declares one through a generic superclass or generic interface).
+ * @param mapClass the map class to introspect
+ * @return the generic type, or null if none
+ */
+ public static Class getMapKeyType(Class mapClass) {
+ return extractTypeFromClass(mapClass, Map.class, 0);
+ }
+
+ /**
+ * Determine the generic value type of the given Map class
+ * (if it declares one through a generic superclass or generic interface).
+ * @param mapClass the map class to introspect
+ * @return the generic type, or null if none
+ */
+ public static Class getMapValueType(Class mapClass) {
+ return extractTypeFromClass(mapClass, Map.class, 1);
+ }
+
+ /**
+ * Determine the generic element type of the given Collection field.
+ * @param collectionField the collection field to introspect
+ * @return the generic type, or null if none
+ */
+ public static Class getCollectionFieldType(Field collectionField) {
+ return getGenericFieldType(collectionField, Collection.class, 0, 1);
+ }
+
+ /**
+ * Determine the generic element type of the given Collection field.
+ * @param collectionField the collection field to introspect
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ * @return the generic type, or null if none
+ */
+ public static Class getCollectionFieldType(Field collectionField, int nestingLevel) {
+ return getGenericFieldType(collectionField, Collection.class, 0, nestingLevel);
+ }
+
+ /**
+ * Determine the generic key type of the given Map field.
+ * @param mapField the map field to introspect
+ * @return the generic type, or null if none
+ */
+ public static Class getMapKeyFieldType(Field mapField) {
+ return getGenericFieldType(mapField, Map.class, 0, 1);
+ }
+
+ /**
+ * Determine the generic key type of the given Map field.
+ * @param mapField the map field to introspect
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ * @return the generic type, or null if none
+ */
+ public static Class getMapKeyFieldType(Field mapField, int nestingLevel) {
+ return getGenericFieldType(mapField, Map.class, 0, nestingLevel);
+ }
+
+ /**
+ * Determine the generic value type of the given Map field.
+ * @param mapField the map field to introspect
+ * @return the generic type, or null if none
+ */
+ public static Class getMapValueFieldType(Field mapField) {
+ return getGenericFieldType(mapField, Map.class, 1, 1);
+ }
+
+ /**
+ * Determine the generic value type of the given Map field.
+ * @param mapField the map field to introspect
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ * @return the generic type, or null if none
+ */
+ public static Class getMapValueFieldType(Field mapField, int nestingLevel) {
+ return getGenericFieldType(mapField, Map.class, 1, nestingLevel);
+ }
+
+ /**
+ * Determine the generic element type of the given Collection parameter.
+ * @param methodParam the method parameter specification
+ * @return the generic type, or null if none
+ */
+ public static Class getCollectionParameterType(MethodParameter methodParam) {
+ return getGenericParameterType(methodParam, Collection.class, 0);
+ }
+
+ /**
+ * Determine the generic key type of the given Map parameter.
+ * @param methodParam the method parameter specification
+ * @return the generic type, or null if none
+ */
+ public static Class getMapKeyParameterType(MethodParameter methodParam) {
+ return getGenericParameterType(methodParam, Map.class, 0);
+ }
+
+ /**
+ * Determine the generic value type of the given Map parameter.
+ * @param methodParam the method parameter specification
+ * @return the generic type, or null if none
+ */
+ public static Class getMapValueParameterType(MethodParameter methodParam) {
+ return getGenericParameterType(methodParam, Map.class, 1);
+ }
+
+ /**
+ * Determine the generic element type of the given Collection return type.
+ * @param method the method to check the return type for
+ * @return the generic type, or null if none
+ */
+ public static Class getCollectionReturnType(Method method) {
+ return getGenericReturnType(method, Collection.class, 0, 1);
+ }
+
+ /**
+ * Determine the generic element type of the given Collection return type.
+ *
If the specified nesting level is higher than 1, the element type of
+ * a nested Collection/Map will be analyzed.
+ * @param method the method to check the return type for
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ * @return the generic type, or null if none
+ */
+ public static Class getCollectionReturnType(Method method, int nestingLevel) {
+ return getGenericReturnType(method, Collection.class, 0, nestingLevel);
+ }
+
+ /**
+ * Determine the generic key type of the given Map return type.
+ * @param method the method to check the return type for
+ * @return the generic type, or null if none
+ */
+ public static Class getMapKeyReturnType(Method method) {
+ return getGenericReturnType(method, Map.class, 0, 1);
+ }
+
+ /**
+ * Determine the generic key type of the given Map return type.
+ * @param method the method to check the return type for
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ * @return the generic type, or null if none
+ */
+ public static Class getMapKeyReturnType(Method method, int nestingLevel) {
+ return getGenericReturnType(method, Map.class, 0, nestingLevel);
+ }
+
+ /**
+ * Determine the generic value type of the given Map return type.
+ * @param method the method to check the return type for
+ * @return the generic type, or null if none
+ */
+ public static Class getMapValueReturnType(Method method) {
+ return getGenericReturnType(method, Map.class, 1, 1);
+ }
+
+ /**
+ * Determine the generic value type of the given Map return type.
+ * @param method the method to check the return type for
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ * @return the generic type, or null if none
+ */
+ public static Class getMapValueReturnType(Method method, int nestingLevel) {
+ return getGenericReturnType(method, Map.class, 1, nestingLevel);
+ }
+
+
+ /**
+ * Extract the generic parameter type from the given method or constructor.
+ * @param methodParam the method parameter specification
+ * @param source the source class/interface defining the generic parameter types
+ * @param typeIndex the index of the type (e.g. 0 for Collections,
+ * 0 for Map keys, 1 for Map values)
+ * @return the generic type, or null if none
+ */
+ private static Class getGenericParameterType(MethodParameter methodParam, Class source, int typeIndex) {
+ return extractType(methodParam, GenericTypeResolver.getTargetType(methodParam),
+ source, typeIndex, methodParam.getNestingLevel(), 1);
+ }
+
+ /**
+ * Extract the generic type from the given field.
+ * @param field the field to check the type for
+ * @param source the source class/interface defining the generic parameter types
+ * @param typeIndex the index of the type (e.g. 0 for Collections,
+ * 0 for Map keys, 1 for Map values)
+ * @param nestingLevel the nesting level of the target type
+ * @return the generic type, or null if none
+ */
+ private static Class getGenericFieldType(Field field, Class source, int typeIndex, int nestingLevel) {
+ return extractType(null, field.getGenericType(), source, typeIndex, nestingLevel, 1);
+ }
+
+ /**
+ * Extract the generic return type from the given method.
+ * @param method the method to check the return type for
+ * @param source the source class/interface defining the generic parameter types
+ * @param typeIndex the index of the type (e.g. 0 for Collections,
+ * 0 for Map keys, 1 for Map values)
+ * @param nestingLevel the nesting level of the target type
+ * @return the generic type, or null if none
+ */
+ private static Class getGenericReturnType(Method method, Class source, int typeIndex, int nestingLevel) {
+ return extractType(null, method.getGenericReturnType(), source, typeIndex, nestingLevel, 1);
+ }
+
+ /**
+ * Extract the generic type from the given Type object.
+ * @param methodParam the method parameter specification
+ * @param type the Type to check
+ * @param source the source collection/map Class that we check
+ * @param typeIndex the index of the actual type argument
+ * @param nestingLevel the nesting level of the target type
+ * @param currentLevel the current nested level
+ * @return the generic type as Class, or null if none
+ */
+ private static Class extractType(
+ MethodParameter methodParam, Type type, Class source, int typeIndex, int nestingLevel, int currentLevel) {
+
+ Type resolvedType = type;
+ if (type instanceof TypeVariable && methodParam != null && methodParam.typeVariableMap != null) {
+ Type mappedType = (Type) methodParam.typeVariableMap.get(type);
+ if (mappedType != null) {
+ resolvedType = mappedType;
+ }
+ }
+ if (resolvedType instanceof ParameterizedType) {
+ return extractTypeFromParameterizedType(
+ methodParam, (ParameterizedType) resolvedType, source, typeIndex, nestingLevel, currentLevel);
+ }
+ else if (resolvedType instanceof Class) {
+ return extractTypeFromClass(methodParam, (Class) resolvedType, source, typeIndex, nestingLevel, currentLevel);
+ }
+ else {
+ return null;
+ }
+ }
+
+ /**
+ * Extract the generic type from the given ParameterizedType object.
+ * @param methodParam the method parameter specification
+ * @param ptype the ParameterizedType to check
+ * @param source the expected raw source type (can be null)
+ * @param typeIndex the index of the actual type argument
+ * @param nestingLevel the nesting level of the target type
+ * @param currentLevel the current nested level
+ * @return the generic type as Class, or null if none
+ */
+ private static Class extractTypeFromParameterizedType(MethodParameter methodParam,
+ ParameterizedType ptype, Class source, int typeIndex, int nestingLevel, int currentLevel) {
+
+ if (!(ptype.getRawType() instanceof Class)) {
+ return null;
+ }
+ Class rawType = (Class) ptype.getRawType();
+ Type[] paramTypes = ptype.getActualTypeArguments();
+ if (nestingLevel - currentLevel > 0) {
+ int nextLevel = currentLevel + 1;
+ Integer currentTypeIndex = (methodParam != null ? methodParam.getTypeIndexForLevel(nextLevel) : null);
+ // Default is last parameter type: Collection element or Map value.
+ int indexToUse = (currentTypeIndex != null ? currentTypeIndex.intValue() : paramTypes.length - 1);
+ Type paramType = paramTypes[indexToUse];
+ return extractType(methodParam, paramType, source, typeIndex, nestingLevel, nextLevel);
+ }
+ if (source != null && !source.isAssignableFrom(rawType)) {
+ return null;
+ }
+ Class fromSuperclassOrInterface =
+ extractTypeFromClass(methodParam, rawType, source, typeIndex, nestingLevel, currentLevel);
+ if (fromSuperclassOrInterface != null) {
+ return fromSuperclassOrInterface;
+ }
+ if (paramTypes == null || typeIndex >= paramTypes.length) {
+ return null;
+ }
+ Type paramType = paramTypes[typeIndex];
+ if (paramType instanceof TypeVariable && methodParam != null && methodParam.typeVariableMap != null) {
+ Type mappedType = (Type) methodParam.typeVariableMap.get(paramType);
+ if (mappedType != null) {
+ paramType = mappedType;
+ }
+ }
+ if (paramType instanceof WildcardType) {
+ Type[] lowerBounds = ((WildcardType) paramType).getLowerBounds();
+ if (lowerBounds != null && lowerBounds.length > 0) {
+ paramType = lowerBounds[0];
+ }
+ }
+ if (paramType instanceof ParameterizedType) {
+ paramType = ((ParameterizedType) paramType).getRawType();
+ }
+ if (paramType instanceof GenericArrayType) {
+ // A generic array type... Let's turn it into a straight array type if possible.
+ Type compType = ((GenericArrayType) paramType).getGenericComponentType();
+ if (compType instanceof Class) {
+ return Array.newInstance((Class) compType, 0).getClass();
+ }
+ }
+ else if (paramType instanceof Class) {
+ // We finally got a straight Class...
+ return (Class) paramType;
+ }
+ return null;
+ }
+
+ /**
+ * Extract the generic type from the given Class object.
+ * @param clazz the Class to check
+ * @param source the expected raw source type (can be null)
+ * @param typeIndex the index of the actual type argument
+ * @return the generic type as Class, or null if none
+ */
+ private static Class extractTypeFromClass(Class clazz, Class source, int typeIndex) {
+ return extractTypeFromClass(null, clazz, source, typeIndex, 1, 1);
+ }
+
+ /**
+ * Extract the generic type from the given Class object.
+ * @param methodParam the method parameter specification
+ * @param clazz the Class to check
+ * @param source the expected raw source type (can be null)
+ * @param typeIndex the index of the actual type argument
+ * @param nestingLevel the nesting level of the target type
+ * @param currentLevel the current nested level
+ * @return the generic type as Class, or null if none
+ */
+ private static Class extractTypeFromClass(
+ MethodParameter methodParam, Class clazz, Class source, int typeIndex, int nestingLevel, int currentLevel) {
+
+ if (clazz.getName().startsWith("java.util.")) {
+ return null;
+ }
+ if (clazz.getSuperclass() != null && isIntrospectionCandidate(clazz.getSuperclass())) {
+ return extractType(methodParam, clazz.getGenericSuperclass(), source, typeIndex, nestingLevel, currentLevel);
+ }
+ Type[] ifcs = clazz.getGenericInterfaces();
+ if (ifcs != null) {
+ for (int i = 0; i < ifcs.length; i++) {
+ Type ifc = ifcs[i];
+ Type rawType = ifc;
+ if (ifc instanceof ParameterizedType) {
+ rawType = ((ParameterizedType) ifc).getRawType();
+ }
+ if (rawType instanceof Class && isIntrospectionCandidate((Class) rawType)) {
+ return extractType(methodParam, ifc, source, typeIndex, nestingLevel, currentLevel);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Determine whether the given class is a potential candidate
+ * that defines generic collection or map types.
+ * @param clazz the class to check
+ * @return whether the given class is assignable to Collection or Map
+ */
+ private static boolean isIntrospectionCandidate(Class clazz) {
+ return (Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz));
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/GenericTypeResolver.java b/org.springframework.core/src/main/java/org/springframework/core/GenericTypeResolver.java
new file mode 100644
index 00000000000..6826571ad8d
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/GenericTypeResolver.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core;
+
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import org.springframework.util.Assert;
+
+/**
+ * Helper class for resolving generic types against type variables.
+ *
+ *
Mainly intended for usage within the framework, resolving method + * parameter types even when they are declared generically. + * + *
Only usable on Java 5. Use an appropriate JdkVersion check before
+ * calling this class, if a fallback for JDK 1.4 is desirable.
+ *
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @since 2.5.2
+ * @see GenericCollectionTypeResolver
+ * @see JdkVersion
+ */
+public abstract class GenericTypeResolver {
+
+ /** Cache from Class to TypeVariable Map */
+ private static final Map typeVariableCache = Collections.synchronizedMap(new WeakHashMap());
+
+
+ /**
+ * Determine the target type for the given parameter specification.
+ * @param methodParam the method parameter specification
+ * @return the corresponding generic parameter type
+ */
+ public static Type getTargetType(MethodParameter methodParam) {
+ Assert.notNull(methodParam, "MethodParameter must not be null");
+ if (methodParam.getConstructor() != null) {
+ return methodParam.getConstructor().getGenericParameterTypes()[methodParam.getParameterIndex()];
+ }
+ else {
+ if (methodParam.getParameterIndex() >= 0) {
+ return methodParam.getMethod().getGenericParameterTypes()[methodParam.getParameterIndex()];
+ }
+ else {
+ return methodParam.getMethod().getGenericReturnType();
+ }
+ }
+ }
+
+ /**
+ * Determine the target type for the given generic parameter type.
+ * @param methodParam the method parameter specification
+ * @param clazz the class to resolve type variables against
+ * @return the corresponding generic parameter or return type
+ */
+ public static Class resolveParameterType(MethodParameter methodParam, Class clazz) {
+ Type genericType = getTargetType(methodParam);
+ Assert.notNull(clazz, "Class must not be null");
+ Map typeVariableMap = getTypeVariableMap(clazz);
+ Type rawType = getRawType(genericType, typeVariableMap);
+ Class result = (rawType instanceof Class ? (Class) rawType : methodParam.getParameterType());
+ methodParam.setParameterType(result);
+ methodParam.typeVariableMap = typeVariableMap;
+ return result;
+ }
+
+ /**
+ * Determine the target type for the generic return type of the given method.
+ * @param method the method to introspect
+ * @param clazz the class to resolve type variables against
+ * @return the corresponding generic parameter or return type
+ */
+ public static Class resolveReturnType(Method method, Class clazz) {
+ Assert.notNull(method, "Method must not be null");
+ Type genericType = method.getGenericReturnType();
+ Assert.notNull(clazz, "Class must not be null");
+ Map typeVariableMap = getTypeVariableMap(clazz);
+ Type rawType = getRawType(genericType, typeVariableMap);
+ return (rawType instanceof Class ? (Class) rawType : method.getReturnType());
+ }
+
+
+ /**
+ * Resolve the specified generic type against the given TypeVariable map.
+ * @param genericType the generic type to resolve
+ * @param typeVariableMap the TypeVariable Map to resolved against
+ * @return the type if it resolves to a Class, or Object.class otherwise
+ */
+ static Class resolveType(Type genericType, Map typeVariableMap) {
+ Type rawType = getRawType(genericType, typeVariableMap);
+ return (rawType instanceof Class ? (Class) rawType : Object.class);
+ }
+
+ /**
+ * Determine the raw type for the given generic parameter type.
+ * @param genericType the generic type to resolve
+ * @param typeVariableMap the TypeVariable Map to resolved against
+ * @return the resolved raw type
+ */
+ static Type getRawType(Type genericType, Map typeVariableMap) {
+ Type resolvedType = genericType;
+ if (genericType instanceof TypeVariable) {
+ TypeVariable tv = (TypeVariable) genericType;
+ resolvedType = (Type) typeVariableMap.get(tv);
+ if (resolvedType == null) {
+ resolvedType = extractBoundForTypeVariable(tv);
+ }
+ }
+ if (resolvedType instanceof ParameterizedType) {
+ return ((ParameterizedType) resolvedType).getRawType();
+ }
+ else {
+ return resolvedType;
+ }
+ }
+
+ /**
+ * Build a mapping of {@link TypeVariable#getName TypeVariable names} to concrete
+ * {@link Class} for the specified {@link Class}. Searches all super types,
+ * enclosing types and interfaces.
+ */
+ static Map getTypeVariableMap(Class clazz) {
+ Map typeVariableMap = (Map) typeVariableCache.get(clazz);
+
+ if (typeVariableMap == null) {
+ typeVariableMap = new HashMap();
+
+ // interfaces
+ extractTypeVariablesFromGenericInterfaces(clazz.getGenericInterfaces(), typeVariableMap);
+
+ // super class
+ Type genericType = clazz.getGenericSuperclass();
+ Class type = clazz.getSuperclass();
+ while (type != null && !Object.class.equals(type)) {
+ if (genericType instanceof ParameterizedType) {
+ ParameterizedType pt = (ParameterizedType) genericType;
+ populateTypeMapFromParameterizedType(pt, typeVariableMap);
+ }
+ extractTypeVariablesFromGenericInterfaces(type.getGenericInterfaces(), typeVariableMap);
+ genericType = type.getGenericSuperclass();
+ type = type.getSuperclass();
+ }
+
+ // enclosing class
+ type = clazz;
+ while (type.isMemberClass()) {
+ genericType = type.getGenericSuperclass();
+ if (genericType instanceof ParameterizedType) {
+ ParameterizedType pt = (ParameterizedType) genericType;
+ populateTypeMapFromParameterizedType(pt, typeVariableMap);
+ }
+ type = type.getEnclosingClass();
+ }
+
+ typeVariableCache.put(clazz, typeVariableMap);
+ }
+
+ return typeVariableMap;
+ }
+
+ /**
+ * Extracts the bound Type for a given {@link TypeVariable}.
+ */
+ static Type extractBoundForTypeVariable(TypeVariable typeVariable) {
+ Type[] bounds = typeVariable.getBounds();
+ if (bounds.length == 0) {
+ return Object.class;
+ }
+ Type bound = bounds[0];
+ if (bound instanceof TypeVariable) {
+ bound = extractBoundForTypeVariable((TypeVariable) bound);
+ }
+ return bound;
+ }
+
+ private static void extractTypeVariablesFromGenericInterfaces(Type[] genericInterfaces, Map typeVariableMap) {
+ for (int i = 0; i < genericInterfaces.length; i++) {
+ Type genericInterface = genericInterfaces[i];
+ if (genericInterface instanceof ParameterizedType) {
+ ParameterizedType pt = (ParameterizedType) genericInterface;
+ populateTypeMapFromParameterizedType(pt, typeVariableMap);
+ if (pt.getRawType() instanceof Class) {
+ extractTypeVariablesFromGenericInterfaces(
+ ((Class) pt.getRawType()).getGenericInterfaces(), typeVariableMap);
+ }
+ }
+ else if (genericInterface instanceof Class) {
+ extractTypeVariablesFromGenericInterfaces(
+ ((Class) genericInterface).getGenericInterfaces(), typeVariableMap);
+ }
+ }
+ }
+
+ /**
+ * Read the {@link TypeVariable TypeVariables} from the supplied {@link ParameterizedType}
+ * and add mappings corresponding to the {@link TypeVariable#getName TypeVariable name} ->
+ * concrete type to the supplied {@link Map}.
+ *
Consider this case: + *
Such wrappers will automatically be unwrapped for key comparisons in
+ * {@link org.springframework.transaction.support.TransactionSynchronizationManager}.
+ *
+ * Only fully transparent proxies, e.g. for redirection or service lookups,
+ * are supposed to implement this interface. Proxies that decorate the target
+ * object with new behavior, such as AOP proxies, do not qualify here!
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.4
+ * @see org.springframework.transaction.support.TransactionSynchronizationManager
+ */
+public interface InfrastructureProxy {
+
+ /**
+ * Return the underlying resource (never null).
+ */
+ Object getWrappedObject();
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/JdkVersion.java b/org.springframework.core/src/main/java/org/springframework/core/JdkVersion.java
new file mode 100644
index 00000000000..a8b067625fc
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/JdkVersion.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core;
+
+/**
+ * Internal helper class used to find the Java/JDK version
+ * that Spring is operating on, to allow for automatically
+ * adapting to the present platform's capabilities.
+ *
+ *
Note that Spring requires JVM 1.4 or higher, as of Spring 2.5.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Rick Evans
+ */
+public abstract class JdkVersion {
+
+ /**
+ * Constant identifying the 1.3.x JVM (JDK 1.3).
+ */
+ public static final int JAVA_13 = 0;
+
+ /**
+ * Constant identifying the 1.4.x JVM (J2SE 1.4).
+ */
+ public static final int JAVA_14 = 1;
+
+ /**
+ * Constant identifying the 1.5 JVM (Java 5).
+ */
+ public static final int JAVA_15 = 2;
+
+ /**
+ * Constant identifying the 1.6 JVM (Java 6).
+ */
+ public static final int JAVA_16 = 3;
+
+ /**
+ * Constant identifying the 1.7 JVM (Java 7).
+ */
+ public static final int JAVA_17 = 4;
+
+
+ private static final String javaVersion;
+
+ private static final int majorJavaVersion;
+
+ static {
+ javaVersion = System.getProperty("java.version");
+ // version String should look like "1.4.2_10"
+ if (javaVersion.indexOf("1.7.") != -1) {
+ majorJavaVersion = JAVA_17;
+ }
+ else if (javaVersion.indexOf("1.6.") != -1) {
+ majorJavaVersion = JAVA_16;
+ }
+ else if (javaVersion.indexOf("1.5.") != -1) {
+ majorJavaVersion = JAVA_15;
+ }
+ else {
+ // else leave 1.4 as default (it's either 1.4 or unknown)
+ majorJavaVersion = JAVA_14;
+ }
+ }
+
+
+ /**
+ * Return the full Java version string, as returned by
+ * System.getProperty("java.version").
+ * @return the full Java version string
+ * @see System#getProperty(String)
+ */
+ public static String getJavaVersion() {
+ return javaVersion;
+ }
+
+ /**
+ * Get the major version code. This means we can do things like
+ * if (getMajorJavaVersion() < JAVA_14).
+ * @return a code comparable to the JAVA_XX codes in this class
+ * @see #JAVA_13
+ * @see #JAVA_14
+ * @see #JAVA_15
+ * @see #JAVA_16
+ * @see #JAVA_17
+ */
+ public static int getMajorJavaVersion() {
+ return majorJavaVersion;
+ }
+
+ /**
+ * Convenience method to determine if the current JVM is at least Java 1.4.
+ * @return true if the current JVM is at least Java 1.4
+ * @see #getMajorJavaVersion()
+ * @see #JAVA_14
+ * @see #JAVA_15
+ * @see #JAVA_16
+ * @see #JAVA_17
+ */
+ public static boolean isAtLeastJava14() {
+ return true;
+ }
+
+ /**
+ * Convenience method to determine if the current JVM is at least
+ * Java 1.5 (Java 5).
+ * @return true if the current JVM is at least Java 1.5
+ * @see #getMajorJavaVersion()
+ * @see #JAVA_15
+ * @see #JAVA_16
+ * @see #JAVA_17
+ */
+ public static boolean isAtLeastJava15() {
+ return getMajorJavaVersion() >= JAVA_15;
+ }
+
+ /**
+ * Convenience method to determine if the current JVM is at least
+ * Java 1.6 (Java 6).
+ * @return true if the current JVM is at least Java 1.6
+ * @see #getMajorJavaVersion()
+ * @see #JAVA_16
+ * @see #JAVA_17
+ */
+ public static boolean isAtLeastJava16() {
+ return getMajorJavaVersion() >= JAVA_16;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java b/org.springframework.core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java
new file mode 100644
index 00000000000..4a9033bc432
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.EmptyVisitor;
+
+import org.springframework.util.ClassUtils;
+
+/**
+ * Implementation of {@link ParameterNameDiscoverer} that uses the LocalVariableTable
+ * information in the method attributes to discover parameter names. Returns
+ * null if the class file was compiled without debug information.
+ *
+ *
Uses ObjectWeb's ASM library for analyzing class files. Each discoverer
+ * instance caches the ASM ClassReader for each introspected Class, in a
+ * thread-safe manner. It is recommended to reuse discoverer instances
+ * as far as possible.
+ *
+ * @author Adrian Colyer
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class LocalVariableTableParameterNameDiscoverer implements ParameterNameDiscoverer {
+
+ private static Log logger = LogFactory.getLog(LocalVariableTableParameterNameDiscoverer.class);
+
+ private final Map parameterNamesCache = CollectionFactory.createConcurrentMapIfPossible(16);
+
+ private final Map classReaderCache = new HashMap();
+
+
+ public String[] getParameterNames(Method method) {
+ String[] paramNames = (String[]) this.parameterNamesCache.get(method);
+ if (paramNames == null) {
+ try {
+ paramNames = visitMethod(method).getParameterNames();
+ if (paramNames != null) {
+ this.parameterNamesCache.put(method, paramNames);
+ }
+ }
+ catch (IOException ex) {
+ // We couldn't load the class file, which is not fatal as it
+ // simply means this method of discovering parameter names won't work.
+ if (logger.isDebugEnabled()) {
+ logger.debug("IOException whilst attempting to read '.class' file for class [" +
+ method.getDeclaringClass().getName() +
+ "] - unable to determine parameter names for method: " + method, ex);
+ }
+ }
+ }
+ return paramNames;
+ }
+
+ public String[] getParameterNames(Constructor ctor) {
+ String[] paramNames = (String[]) this.parameterNamesCache.get(ctor);
+ if (paramNames == null) {
+ try {
+ paramNames = visitConstructor(ctor).getParameterNames();
+ if (paramNames != null) {
+ this.parameterNamesCache.put(ctor, paramNames);
+ }
+ }
+ catch (IOException ex) {
+ // We couldn't load the class file, which is not fatal as it
+ // simply means this method of discovering parameter names won't work.
+ if (logger.isDebugEnabled()) {
+ logger.debug("IOException whilst attempting to read '.class' file for class [" +
+ ctor.getDeclaringClass().getName() +
+ "] - unable to determine parameter names for constructor: " + ctor, ex);
+ }
+ }
+ }
+ return paramNames;
+ }
+
+ /**
+ * Visit the given method and discover its parameter names.
+ */
+ private ParameterNameDiscoveringVisitor visitMethod(Method method) throws IOException {
+ ClassReader classReader = getClassReader(method.getDeclaringClass());
+ FindMethodParameterNamesClassVisitor classVisitor = new FindMethodParameterNamesClassVisitor(method);
+ classReader.accept(classVisitor, false);
+ return classVisitor;
+ }
+
+ /**
+ * Visit the given constructor and discover its parameter names.
+ */
+ private ParameterNameDiscoveringVisitor visitConstructor(Constructor ctor) throws IOException {
+ ClassReader classReader = getClassReader(ctor.getDeclaringClass());
+ FindConstructorParameterNamesClassVisitor classVisitor = new FindConstructorParameterNamesClassVisitor(ctor);
+ classReader.accept(classVisitor, false);
+ return classVisitor;
+ }
+
+ /**
+ * Obtain a (cached) ClassReader for the given class.
+ */
+ private ClassReader getClassReader(Class clazz) throws IOException {
+ synchronized (this.classReaderCache) {
+ ClassReader classReader = (ClassReader) this.classReaderCache.get(clazz);
+ if (classReader == null) {
+ InputStream is = clazz.getResourceAsStream(ClassUtils.getClassFileName(clazz));
+ if (is == null) {
+ throw new FileNotFoundException("Class file for class [" + clazz.getName() + "] not found");
+ }
+ try {
+ classReader = new ClassReader(is);
+ this.classReaderCache.put(clazz, classReader);
+ }
+ finally {
+ is.close();
+ }
+ }
+ return classReader;
+ }
+ }
+
+
+ /**
+ * Helper class that looks for a given member name and descriptor, and then
+ * attempts to find the parameter names for that member.
+ */
+ private static abstract class ParameterNameDiscoveringVisitor extends EmptyVisitor {
+
+ private String methodNameToMatch;
+
+ private String descriptorToMatch;
+
+ private int numParamsExpected;
+
+ /*
+ * The nth entry contains the slot index of the LVT table entry holding the
+ * argument name for the nth parameter.
+ */
+ private int[] lvtSlotIndex;
+
+ private String[] parameterNames;
+
+ public ParameterNameDiscoveringVisitor(String name, boolean isStatic, Class[] paramTypes) {
+ this.methodNameToMatch = name;
+ this.numParamsExpected = paramTypes.length;
+ computeLvtSlotIndices(isStatic, paramTypes);
+ }
+
+ public void setDescriptorToMatch(String descriptor) {
+ this.descriptorToMatch = descriptor;
+ }
+
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ if (name.equals(this.methodNameToMatch) && desc.equals(this.descriptorToMatch)) {
+ return new LocalVariableTableVisitor(this, isStatic(access));
+ }
+ else {
+ // Not interested in this method...
+ return null;
+ }
+ }
+
+ private boolean isStatic(int access) {
+ return ((access & Opcodes.ACC_STATIC) > 0);
+ }
+
+ public String[] getParameterNames() {
+ return this.parameterNames;
+ }
+
+ private void computeLvtSlotIndices(boolean isStatic, Class[] paramTypes) {
+ this.lvtSlotIndex = new int[paramTypes.length];
+ int nextIndex = (isStatic ? 0 : 1);
+ for (int i = 0; i < paramTypes.length; i++) {
+ this.lvtSlotIndex[i] = nextIndex;
+ if (isWideType(paramTypes[i])) {
+ nextIndex += 2;
+ }
+ else {
+ nextIndex++;
+ }
+ }
+ }
+
+ private boolean isWideType(Class aType) {
+ return (aType == Long.TYPE || aType == Double.TYPE);
+ }
+ }
+
+
+ private static class FindMethodParameterNamesClassVisitor extends ParameterNameDiscoveringVisitor {
+
+ public FindMethodParameterNamesClassVisitor(Method method) {
+ super(method.getName(), Modifier.isStatic(method.getModifiers()), method.getParameterTypes());
+ setDescriptorToMatch(Type.getMethodDescriptor(method));
+ }
+ }
+
+
+ private static class FindConstructorParameterNamesClassVisitor extends ParameterNameDiscoveringVisitor {
+
+ public FindConstructorParameterNamesClassVisitor(Constructor ctor) {
+ super("", false, ctor.getParameterTypes());
+ Type[] pTypes = new Type[ctor.getParameterTypes().length];
+ for (int i = 0; i < pTypes.length; i++) {
+ pTypes[i] = Type.getType(ctor.getParameterTypes()[i]);
+ }
+ setDescriptorToMatch(Type.getMethodDescriptor(Type.VOID_TYPE, pTypes));
+ }
+ }
+
+
+ private static class LocalVariableTableVisitor extends EmptyVisitor {
+
+ private final ParameterNameDiscoveringVisitor memberVisitor;
+
+ private final boolean isStatic;
+
+ private String[] parameterNames;
+
+ private boolean hasLvtInfo = false;
+
+ public LocalVariableTableVisitor(ParameterNameDiscoveringVisitor memberVisitor, boolean isStatic) {
+ this.memberVisitor = memberVisitor;
+ this.isStatic = isStatic;
+ this.parameterNames = new String[memberVisitor.numParamsExpected];
+ }
+
+ public void visitLocalVariable(
+ String name, String description, String signature, Label start, Label end, int index) {
+ this.hasLvtInfo = true;
+ int[] lvtSlotIndices = this.memberVisitor.lvtSlotIndex;
+ for (int i = 0; i < lvtSlotIndices.length; i++) {
+ if (lvtSlotIndices[i] == index) {
+ this.parameterNames[i] = name;
+ }
+ }
+ }
+
+ public void visitEnd() {
+ if (this.hasLvtInfo || (this.isStatic && this.parameterNames.length == 0)) {
+ // visitLocalVariable will never be called for static no args methods
+ // which doesn't use any local variables.
+ // This means that hasLvtInfo could be false for that kind of methods
+ // even if the class has local variable info.
+ this.memberVisitor.parameterNames = this.parameterNames;
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java b/org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java
new file mode 100644
index 00000000000..7a9ea3beb9d
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * Helper class that encapsulates the specification of a method parameter, i.e.
+ * a Method or Constructor plus a parameter index and a nested type index for
+ * a declared generic type. Useful as a specification object to pass along.
+ *
+ * Used by {@link GenericCollectionTypeResolver},
+ * {@link org.springframework.beans.BeanWrapperImpl} and
+ * {@link org.springframework.beans.factory.support.AbstractBeanFactory}.
+ *
+ *
Note that this class does not depend on JDK 1.5 API artifacts, in order
+ * to remain compatible with JDK 1.4. Concrete generic type resolution
+ * via JDK 1.5 API happens in {@link GenericCollectionTypeResolver} only.
+ *
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @since 2.0
+ * @see GenericCollectionTypeResolver
+ */
+public class MethodParameter {
+
+ private static final Method methodParameterAnnotationsMethod =
+ ClassUtils.getMethodIfAvailable(Method.class, "getParameterAnnotations", new Class[0]);
+
+ private static final Method constructorParameterAnnotationsMethod =
+ ClassUtils.getMethodIfAvailable(Constructor.class, "getParameterAnnotations", new Class[0]);
+
+
+ private Method method;
+
+ private Constructor constructor;
+
+ private final int parameterIndex;
+
+ private Class parameterType;
+
+ private Object[] parameterAnnotations;
+
+ private ParameterNameDiscoverer parameterNameDiscoverer;
+
+ private String parameterName;
+
+ private int nestingLevel = 1;
+
+ /** Map from Integer level to Integer type index */
+ private Map typeIndexesPerLevel;
+
+ Map typeVariableMap;
+
+
+ /**
+ * Create a new MethodParameter for the given method, with nesting level 1.
+ * @param method the Method to specify a parameter for
+ * @param parameterIndex the index of the parameter
+ */
+ public MethodParameter(Method method, int parameterIndex) {
+ this(method, parameterIndex, 1);
+ }
+
+ /**
+ * Create a new MethodParameter for the given method.
+ * @param method the Method to specify a parameter for
+ * @param parameterIndex the index of the parameter
+ * (-1 for the method return type; 0 for the first method parameter,
+ * 1 for the second method parameter, etc)
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ */
+ public MethodParameter(Method method, int parameterIndex, int nestingLevel) {
+ Assert.notNull(method, "Method must not be null");
+ this.method = method;
+ this.parameterIndex = parameterIndex;
+ this.nestingLevel = nestingLevel;
+ }
+
+ /**
+ * Create a new MethodParameter for the given constructor, with nesting level 1.
+ * @param constructor the Constructor to specify a parameter for
+ * @param parameterIndex the index of the parameter
+ */
+ public MethodParameter(Constructor constructor, int parameterIndex) {
+ this(constructor, parameterIndex, 1);
+ }
+
+ /**
+ * Create a new MethodParameter for the given constructor.
+ * @param constructor the Constructor to specify a parameter for
+ * @param parameterIndex the index of the parameter
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ */
+ public MethodParameter(Constructor constructor, int parameterIndex, int nestingLevel) {
+ Assert.notNull(constructor, "Constructor must not be null");
+ this.constructor = constructor;
+ this.parameterIndex = parameterIndex;
+ this.nestingLevel = nestingLevel;
+ }
+
+ /**
+ * Copy constructor, resulting in an independent MethodParameter object
+ * based on the same metadata and cache state that the original object was in.
+ * @param original the original MethodParameter object to copy from
+ */
+ public MethodParameter(MethodParameter original) {
+ Assert.notNull(original, "Original must not be null");
+ this.method = original.method;
+ this.constructor = original.constructor;
+ this.parameterIndex = original.parameterIndex;
+ this.parameterType = original.parameterType;
+ this.parameterAnnotations = original.parameterAnnotations;
+ this.typeVariableMap = original.typeVariableMap;
+ }
+
+
+ /**
+ * Return the wrapped Method, if any.
+ *
Note: Either Method or Constructor is available.
+ * @return the Method, or null if none
+ */
+ public Method getMethod() {
+ return this.method;
+ }
+
+ /**
+ * Return the wrapped Constructor, if any.
+ *
Note: Either Method or Constructor is available.
+ * @return the Constructor, or null if none
+ */
+ public Constructor getConstructor() {
+ return this.constructor;
+ }
+
+ /**
+ * Return the index of the method/constructor parameter.
+ * @return the parameter index (never negative)
+ */
+ public int getParameterIndex() {
+ return this.parameterIndex;
+ }
+
+ /**
+ * Set a resolved (generic) parameter type.
+ */
+ void setParameterType(Class parameterType) {
+ this.parameterType = parameterType;
+ }
+
+ /**
+ * Return the type of the method/constructor parameter.
+ * @return the parameter type (never null)
+ */
+ public Class getParameterType() {
+ if (this.parameterType == null) {
+ this.parameterType = (this.method != null ?
+ this.method.getParameterTypes()[this.parameterIndex] :
+ this.constructor.getParameterTypes()[this.parameterIndex]);
+ }
+ return this.parameterType;
+ }
+
+ /**
+ * Return the annotations associated with the method/constructor parameter.
+ * @return the parameter annotations, or null if there is
+ * no annotation support (on JDK < 1.5). The return value is an Object array
+ * instead of an Annotation array simply for compatibility with older JDKs;
+ * feel free to cast it to Annotation[] on JDK 1.5 or higher.
+ */
+ public Object[] getParameterAnnotations() {
+ if (this.parameterAnnotations != null) {
+ return this.parameterAnnotations;
+ }
+ if (methodParameterAnnotationsMethod == null) {
+ return null;
+ }
+ Object[][] annotationArray = (this.method != null ?
+ ((Object[][]) ReflectionUtils.invokeMethod(methodParameterAnnotationsMethod, this.method)) :
+ ((Object[][]) ReflectionUtils.invokeMethod(constructorParameterAnnotationsMethod, this.constructor)));
+ this.parameterAnnotations = annotationArray[this.parameterIndex];
+ return this.parameterAnnotations;
+ }
+
+ /**
+ * Initialize parameter name discovery for this method parameter.
+ *
This method does not actually try to retrieve the parameter name at
+ * this point; it just allows discovery to happen when the application calls
+ * {@link #getParameterName()} (if ever).
+ */
+ public void initParameterNameDiscovery(ParameterNameDiscoverer parameterNameDiscoverer) {
+ this.parameterNameDiscoverer = parameterNameDiscoverer;
+ }
+
+ /**
+ * Return the name of the method/constructor parameter.
+ * @return the parameter name (may be null if no
+ * parameter name metadata is contained in the class file or no
+ * {@link #initParameterNameDiscovery ParameterNameDiscoverer}
+ * has been set to begin with)
+ */
+ public String getParameterName() {
+ if (this.parameterNameDiscoverer != null) {
+ String[] parameterNames = (this.method != null ?
+ this.parameterNameDiscoverer.getParameterNames(this.method) :
+ this.parameterNameDiscoverer.getParameterNames(this.constructor));
+ if (parameterNames != null) {
+ this.parameterName = parameterNames[this.parameterIndex];
+ }
+ this.parameterNameDiscoverer = null;
+ }
+ return this.parameterName;
+ }
+
+ /**
+ * Increase this parameter's nesting level.
+ * @see #getNestingLevel()
+ */
+ public void increaseNestingLevel() {
+ this.nestingLevel++;
+ }
+
+ /**
+ * Decrease this parameter's nesting level.
+ * @see #getNestingLevel()
+ */
+ public void decreaseNestingLevel() {
+ getTypeIndexesPerLevel().remove(new Integer(this.nestingLevel));
+ this.nestingLevel--;
+ }
+
+ /**
+ * Return the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List).
+ */
+ public int getNestingLevel() {
+ return this.nestingLevel;
+ }
+
+ /**
+ * Set the type index for the current nesting level.
+ * @param typeIndex the corresponding type index
+ * (or null for the default type index)
+ * @see #getNestingLevel()
+ */
+ public void setTypeIndexForCurrentLevel(int typeIndex) {
+ getTypeIndexesPerLevel().put(new Integer(this.nestingLevel), new Integer(typeIndex));
+ }
+
+ /**
+ * Return the type index for the current nesting level.
+ * @return the corresponding type index, or null
+ * if none specified (indicating the default type index)
+ * @see #getNestingLevel()
+ */
+ public Integer getTypeIndexForCurrentLevel() {
+ return getTypeIndexForLevel(this.nestingLevel);
+ }
+
+ /**
+ * Return the type index for the specified nesting level.
+ * @param nestingLevel the nesting level to check
+ * @return the corresponding type index, or null
+ * if none specified (indicating the default type index)
+ */
+ public Integer getTypeIndexForLevel(int nestingLevel) {
+ return (Integer) getTypeIndexesPerLevel().get(new Integer(nestingLevel));
+ }
+
+ /**
+ * Obtain the (lazily constructed) type-indexes-per-level Map.
+ */
+ private Map getTypeIndexesPerLevel() {
+ if (this.typeIndexesPerLevel == null) {
+ this.typeIndexesPerLevel = new HashMap(4);
+ }
+ return this.typeIndexesPerLevel;
+ }
+
+
+ /**
+ * Create a new MethodParameter for the given method or constructor.
+ *
This is a convenience constructor for scenarios where a
+ * Method or Constructor reference is treated in a generic fashion.
+ * @param methodOrConstructor the Method or Constructor to specify a parameter for
+ * @param parameterIndex the index of the parameter
+ * @return the corresponding MethodParameter instance
+ */
+ public static MethodParameter forMethodOrConstructor(Object methodOrConstructor, int parameterIndex) {
+ if (methodOrConstructor instanceof Method) {
+ return new MethodParameter((Method) methodOrConstructor, parameterIndex);
+ }
+ else if (methodOrConstructor instanceof Constructor) {
+ return new MethodParameter((Constructor) methodOrConstructor, parameterIndex);
+ }
+ else {
+ throw new IllegalArgumentException(
+ "Given object [" + methodOrConstructor + "] is neither a Method nor a Constructor");
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/NamedInheritableThreadLocal.java b/org.springframework.core/src/main/java/org/springframework/core/NamedInheritableThreadLocal.java
new file mode 100644
index 00000000000..fa6c0d8bcfc
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/NamedInheritableThreadLocal.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core;
+
+import org.springframework.util.Assert;
+
+/**
+ * {@link InheritableThreadLocal} subclass that exposes a specified name
+ * as {@link #toString()} result (allowing for introspection).
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.2
+ * @see NamedThreadLocal
+ */
+public class NamedInheritableThreadLocal extends InheritableThreadLocal {
+
+ private final String name;
+
+
+ /**
+ * Create a new NamedInheritableThreadLocal with the given name.
+ * @param name a descriptive name for this ThreadLocal
+ */
+ public NamedInheritableThreadLocal(String name) {
+ Assert.hasText(name, "Name must not be empty");
+ this.name = name;
+ }
+
+ public String toString() {
+ return this.name;
+ }
+
+}
\ No newline at end of file
diff --git a/org.springframework.core/src/main/java/org/springframework/core/NamedThreadLocal.java b/org.springframework.core/src/main/java/org/springframework/core/NamedThreadLocal.java
new file mode 100644
index 00000000000..1b84aab8a4c
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/NamedThreadLocal.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core;
+
+import org.springframework.util.Assert;
+
+/**
+ * {@link ThreadLocal} subclass that exposes a specified name
+ * as {@link #toString()} result (allowing for introspection).
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.2
+ * @see NamedInheritableThreadLocal
+ */
+public class NamedThreadLocal extends ThreadLocal {
+
+ private final String name;
+
+
+ /**
+ * Create a new NamedThreadLocal with the given name.
+ * @param name a descriptive name for this ThreadLocal
+ */
+ public NamedThreadLocal(String name) {
+ Assert.hasText(name, "Name must not be empty");
+ this.name = name;
+ }
+
+ public String toString() {
+ return this.name;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/NestedCheckedException.java b/org.springframework.core/src/main/java/org/springframework/core/NestedCheckedException.java
new file mode 100644
index 00000000000..39361c6aad8
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/NestedCheckedException.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core;
+
+/**
+ * Handy class for wrapping checked Exceptions with a root cause.
+ *
+ *
This class is abstract to force the programmer to extend
+ * the class. getMessage will include nested exception
+ * information; printStackTrace and other like methods will
+ * delegate to the wrapped exception, if any.
+ *
+ *
The similarity between this class and the {@link NestedRuntimeException}
+ * class is unavoidable, as Java forces these two classes to have different
+ * superclasses (ah, the inflexibility of concrete inheritance!).
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @see #getMessage
+ * @see #printStackTrace
+ * @see NestedRuntimeException
+ */
+public abstract class NestedCheckedException extends Exception {
+
+ /** Use serialVersionUID from Spring 1.2 for interoperability */
+ private static final long serialVersionUID = 7100714597678207546L;
+
+
+ /**
+ * Construct a NestedCheckedException with the specified detail message.
+ * @param msg the detail message
+ */
+ public NestedCheckedException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Construct a NestedCheckedException with the specified detail message
+ * and nested exception.
+ * @param msg the detail message
+ * @param cause the nested exception
+ */
+ public NestedCheckedException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+
+ /**
+ * Return the detail message, including the message from the nested exception
+ * if there is one.
+ */
+ public String getMessage() {
+ return NestedExceptionUtils.buildMessage(super.getMessage(), getCause());
+ }
+
+
+ /**
+ * Retrieve the innermost cause of this exception, if any.
+ * @return the innermost exception, or null if none
+ */
+ public Throwable getRootCause() {
+ Throwable rootCause = null;
+ Throwable cause = getCause();
+ while (cause != null && cause != rootCause) {
+ rootCause = cause;
+ cause = cause.getCause();
+ }
+ return rootCause;
+ }
+
+ /**
+ * Retrieve the most specific cause of this exception, that is,
+ * either the innermost cause (root cause) or this exception itself.
+ *
Differs from {@link #getRootCause()} in that it falls back
+ * to the present exception if there is no root cause.
+ * @return the most specific cause (never null)
+ * @since 2.0.3
+ */
+ public Throwable getMostSpecificCause() {
+ Throwable rootCause = getRootCause();
+ return (rootCause != null ? rootCause : this);
+ }
+
+ /**
+ * Check whether this exception contains an exception of the given type:
+ * either it is of the given class itself or it contains a nested cause
+ * of the given type.
+ * @param exType the exception type to look for
+ * @return whether there is a nested exception of the specified type
+ */
+ public boolean contains(Class exType) {
+ if (exType == null) {
+ return false;
+ }
+ if (exType.isInstance(this)) {
+ return true;
+ }
+ Throwable cause = getCause();
+ if (cause == this) {
+ return false;
+ }
+ if (cause instanceof NestedCheckedException) {
+ return ((NestedCheckedException) cause).contains(exType);
+ }
+ else {
+ while (cause != null) {
+ if (exType.isInstance(cause)) {
+ return true;
+ }
+ if (cause.getCause() == cause) {
+ break;
+ }
+ cause = cause.getCause();
+ }
+ return false;
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/NestedExceptionUtils.java b/org.springframework.core/src/main/java/org/springframework/core/NestedExceptionUtils.java
new file mode 100644
index 00000000000..ed796acac6b
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/NestedExceptionUtils.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * 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.springframework.core;
+
+/**
+ * Helper class for implementing exception classes which are capable of
+ * holding nested exceptions. Necessary because we can't share a base
+ * class among different exception types.
+ *
+ *
Mainly for use within the framework.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see NestedRuntimeException
+ * @see NestedCheckedException
+ * @see NestedIOException
+ * @see org.springframework.web.util.NestedServletException
+ */
+public abstract class NestedExceptionUtils {
+
+ /**
+ * Build a message for the given base message and root cause.
+ * @param message the base message
+ * @param cause the root cause
+ * @return the full exception message
+ */
+ public static String buildMessage(String message, Throwable cause) {
+ if (cause != null) {
+ StringBuffer buf = new StringBuffer();
+ if (message != null) {
+ buf.append(message).append("; ");
+ }
+ buf.append("nested exception is ").append(cause);
+ return buf.toString();
+ }
+ else {
+ return message;
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/NestedIOException.java b/org.springframework.core/src/main/java/org/springframework/core/NestedIOException.java
new file mode 100644
index 00000000000..25536da2987
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/NestedIOException.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core;
+
+import java.io.IOException;
+
+/**
+ * Subclass of IOException that properly handles a root cause,
+ * exposing the root cause just like NestedChecked/RuntimeException does.
+ *
+ *
The similarity between this class and the NestedChecked/RuntimeException
+ * class is unavoidable, as this class needs to derive from IOException
+ * and cannot derive from NestedCheckedException.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see #getMessage
+ * @see #printStackTrace
+ * @see org.springframework.core.NestedCheckedException
+ * @see org.springframework.core.NestedRuntimeException
+ */
+public class NestedIOException extends IOException {
+
+ /**
+ * Construct a NestedIOException with the specified detail message.
+ * @param msg the detail message
+ */
+ public NestedIOException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Construct a NestedIOException with the specified detail message
+ * and nested exception.
+ * @param msg the detail message
+ * @param cause the nested exception
+ */
+ public NestedIOException(String msg, Throwable cause) {
+ super(msg);
+ initCause(cause);
+ }
+
+
+ /**
+ * Return the detail message, including the message from the nested exception
+ * if there is one.
+ */
+ public String getMessage() {
+ return NestedExceptionUtils.buildMessage(super.getMessage(), getCause());
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/NestedRuntimeException.java b/org.springframework.core/src/main/java/org/springframework/core/NestedRuntimeException.java
new file mode 100644
index 00000000000..fb62c33fbdd
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/NestedRuntimeException.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core;
+
+/**
+ * Handy class for wrapping runtime Exceptions with a root cause.
+ *
+ *
This class is abstract to force the programmer to extend
+ * the class. getMessage will include nested exception
+ * information; printStackTrace and other like methods will
+ * delegate to the wrapped exception, if any.
+ *
+ *
The similarity between this class and the {@link NestedCheckedException}
+ * class is unavoidable, as Java forces these two classes to have different
+ * superclasses (ah, the inflexibility of concrete inheritance!).
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @see #getMessage
+ * @see #printStackTrace
+ * @see NestedCheckedException
+ */
+public abstract class NestedRuntimeException extends RuntimeException {
+
+ /** Use serialVersionUID from Spring 1.2 for interoperability */
+ private static final long serialVersionUID = 5439915454935047936L;
+
+
+ /**
+ * Construct a NestedRuntimeException with the specified detail message.
+ * @param msg the detail message
+ */
+ public NestedRuntimeException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Construct a NestedRuntimeException with the specified detail message
+ * and nested exception.
+ * @param msg the detail message
+ * @param cause the nested exception
+ */
+ public NestedRuntimeException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+
+ /**
+ * Return the detail message, including the message from the nested exception
+ * if there is one.
+ */
+ public String getMessage() {
+ return NestedExceptionUtils.buildMessage(super.getMessage(), getCause());
+ }
+
+
+ /**
+ * Retrieve the innermost cause of this exception, if any.
+ * @return the innermost exception, or null if none
+ * @since 2.0
+ */
+ public Throwable getRootCause() {
+ Throwable rootCause = null;
+ Throwable cause = getCause();
+ while (cause != null && cause != rootCause) {
+ rootCause = cause;
+ cause = cause.getCause();
+ }
+ return rootCause;
+ }
+
+ /**
+ * Retrieve the most specific cause of this exception, that is,
+ * either the innermost cause (root cause) or this exception itself.
+ *
Differs from {@link #getRootCause()} in that it falls back
+ * to the present exception if there is no root cause.
+ * @return the most specific cause (never null)
+ * @since 2.0.3
+ */
+ public Throwable getMostSpecificCause() {
+ Throwable rootCause = getRootCause();
+ return (rootCause != null ? rootCause : this);
+ }
+
+ /**
+ * Check whether this exception contains an exception of the given type:
+ * either it is of the given class itself or it contains a nested cause
+ * of the given type.
+ * @param exType the exception type to look for
+ * @return whether there is a nested exception of the specified type
+ */
+ public boolean contains(Class exType) {
+ if (exType == null) {
+ return false;
+ }
+ if (exType.isInstance(this)) {
+ return true;
+ }
+ Throwable cause = getCause();
+ if (cause == this) {
+ return false;
+ }
+ if (cause instanceof NestedRuntimeException) {
+ return ((NestedRuntimeException) cause).contains(exType);
+ }
+ else {
+ while (cause != null) {
+ if (exType.isInstance(cause)) {
+ return true;
+ }
+ if (cause.getCause() == cause) {
+ break;
+ }
+ cause = cause.getCause();
+ }
+ return false;
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/OrderComparator.java b/org.springframework.core/src/main/java/org/springframework/core/OrderComparator.java
new file mode 100644
index 00000000000..338d262d0f0
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/OrderComparator.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core;
+
+import java.util.Comparator;
+
+/**
+ * {@link Comparator} implementation for {@link Ordered} objects,
+ * sorting by order value ascending (resp. by priority descending).
+ *
+ *
Non-Ordered objects are treated as greatest order
+ * values, thus ending up at the end of the list, in arbitrary order
+ * (just like same order values of Ordered objects).
+ *
+ * @author Juergen Hoeller
+ * @since 07.04.2003
+ * @see Ordered
+ * @see java.util.Collections#sort(java.util.List, java.util.Comparator)
+ * @see java.util.Arrays#sort(Object[], java.util.Comparator)
+ */
+public class OrderComparator implements Comparator {
+
+ public int compare(Object o1, Object o2) {
+ boolean p1 = (o1 instanceof PriorityOrdered);
+ boolean p2 = (o2 instanceof PriorityOrdered);
+ if (p1 && !p2) {
+ return -1;
+ }
+ else if (p2 && !p1) {
+ return 1;
+ }
+
+ // Direct evaluation instead of Integer.compareTo to avoid unnecessary object creation.
+ int i1 = getOrder(o1);
+ int i2 = getOrder(o2);
+ return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
+ }
+
+ /**
+ * Determine the order value for the given object.
+ *
The default implementation checks against the {@link Ordered}
+ * interface. Can be overridden in subclasses.
+ * @param obj the object to check
+ * @return the order value, or Ordered.LOWEST_PRECEDENCE as fallback
+ */
+ protected int getOrder(Object obj) {
+ return (obj instanceof Ordered ? ((Ordered) obj).getOrder() : Ordered.LOWEST_PRECEDENCE);
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/Ordered.java b/org.springframework.core/src/main/java/org/springframework/core/Ordered.java
new file mode 100644
index 00000000000..5a711f334e5
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/Ordered.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core;
+
+/**
+ * Interface that can be implemented by objects that should be
+ * orderable, for example in a Collection.
+ *
+ *
The actual order can be interpreted as prioritization, with
+ * the first object (with the lowest order value) having the highest
+ * priority.
+ *
+ *
Note that there is a 'priority' marker for this interface:
+ * {@link PriorityOrdered}. Order values expressed by PriorityOrdered
+ * objects always apply before order values of 'plain' Ordered values.
+ *
+ * @author Juergen Hoeller
+ * @since 07.04.2003
+ * @see OrderComparator
+ * @see org.springframework.core.annotation.Order
+ */
+public interface Ordered {
+
+ /**
+ * Useful constant for the highest precedence value.
+ * @see java.lang.Integer#MIN_VALUE
+ */
+ int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
+
+ /**
+ * Useful constant for the lowest precedence value.
+ * @see java.lang.Integer#MAX_VALUE
+ */
+ int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
+
+
+ /**
+ * Return the order value of this object, with a
+ * higher value meaning greater in terms of sorting.
+ *
Normally starting with 0 or 1, with {@link #LOWEST_PRECEDENCE}
+ * indicating greatest. Same order values will result in arbitrary
+ * positions for the affected objects.
+ *
Higher value can be interpreted as lower priority,
+ * consequently the first object has highest priority
+ * (somewhat analogous to Servlet "load-on-startup" values).
+ *
Note that order values below 0 are reserved for framework
+ * purposes. Application-specified values should always be 0 or
+ * greater, with only framework components (internal or third-party)
+ * supposed to use lower values.
+ * @return the order value
+ * @see #LOWEST_PRECEDENCE
+ */
+ int getOrder();
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/OverridingClassLoader.java b/org.springframework.core/src/main/java/org/springframework/core/OverridingClassLoader.java
new file mode 100644
index 00000000000..b1ef1191d33
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/OverridingClassLoader.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.springframework.util.FileCopyUtils;
+
+/**
+ * ClassLoader that does not always delegate to the
+ * parent loader, as normal class loaders do. This enables, for example,
+ * instrumentation to be forced in the overriding ClassLoader, or a
+ * "throwaway" class loading behavior, where selected classes are
+ * temporarily loaded in the overriding ClassLoader, in order to load
+ * an instrumented version of the class in the parent ClassLoader later on.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @since 2.0.1
+ */
+public class OverridingClassLoader extends DecoratingClassLoader {
+
+ /** Packages that are excluded by default */
+ public static final String[] DEFAULT_EXCLUDED_PACKAGES =
+ new String[] {"java.", "javax.", "sun.", "oracle."};
+
+ private static final String CLASS_FILE_SUFFIX = ".class";
+
+
+ /**
+ * Create a new OverridingClassLoader for the given class loader.
+ * @param parent the ClassLoader to build an overriding ClassLoader for
+ */
+ public OverridingClassLoader(ClassLoader parent) {
+ super(parent);
+ for (int i = 0; i < DEFAULT_EXCLUDED_PACKAGES.length; i++) {
+ excludePackage(DEFAULT_EXCLUDED_PACKAGES[i]);
+ }
+ }
+
+
+ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ Class result = null;
+ if (isEligibleForOverriding(name)) {
+ result = loadClassForOverriding(name);
+ }
+ if (result != null) {
+ if (resolve) {
+ resolveClass(result);
+ }
+ return result;
+ }
+ else {
+ return super.loadClass(name, resolve);
+ }
+ }
+
+ /**
+ * Determine whether the specified class is eligible for overriding
+ * by this class loader.
+ * @param className the class name to check
+ * @return whether the specified class is eligible
+ * @see #isExcluded
+ */
+ protected boolean isEligibleForOverriding(String className) {
+ return !isExcluded(className);
+ }
+
+ /**
+ * Load the specified class for overriding purposes in this ClassLoader.
+ *
The default implementation delegates to {@link #findLoadedClass},
+ * {@link #loadBytesForClass} and {@link #defineClass}.
+ * @param name the name of the class
+ * @return the Class object, or null if no class defined for that name
+ * @throws ClassNotFoundException if the class for the given name couldn't be loaded
+ */
+ protected Class loadClassForOverriding(String name) throws ClassNotFoundException {
+ Class result = findLoadedClass(name);
+ if (result == null) {
+ byte[] bytes = loadBytesForClass(name);
+ if (bytes != null) {
+ result = defineClass(name, bytes, 0, bytes.length);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Load the defining bytes for the given class,
+ * to be turned into a Class object through a {@link #defineClass} call.
+ *
The default implementation delegates to {@link #openStreamForClass}
+ * and {@link #transformIfNecessary}.
+ * @param name the name of the class
+ * @return the byte content (with transformers already applied),
+ * or null if no class defined for that name
+ * @throws ClassNotFoundException if the class for the given name couldn't be loaded
+ */
+ protected byte[] loadBytesForClass(String name) throws ClassNotFoundException {
+ InputStream is = openStreamForClass(name);
+ if (is == null) {
+ return null;
+ }
+ try {
+ // Load the raw bytes.
+ byte[] bytes = FileCopyUtils.copyToByteArray(is);
+ // Transform if necessary and use the potentially transformed bytes.
+ return transformIfNecessary(name, bytes);
+ }
+ catch (IOException ex) {
+ throw new ClassNotFoundException("Cannot load resource for class [" + name + "]", ex);
+ }
+ }
+
+ /**
+ * Open an InputStream for the specified class.
+ *
The default implementation loads a standard class file through
+ * the parent ClassLoader's getResourceAsStream method.
+ * @param name the name of the class
+ * @return the InputStream containing the byte code for the specified class
+ */
+ protected InputStream openStreamForClass(String name) {
+ String internalName = name.replace('.', '/') + CLASS_FILE_SUFFIX;
+ return getParent().getResourceAsStream(internalName);
+ }
+
+
+ /**
+ * Transformation hook to be implemented by subclasses.
+ *
The default implementation simply returns the given bytes as-is.
+ * @param name the fully-qualified name of the class being transformed
+ * @param bytes the raw bytes of the class
+ * @return the transformed bytes (never null;
+ * same as the input bytes if the transformation produced no changes)
+ */
+ protected byte[] transformIfNecessary(String name, byte[] bytes) {
+ return bytes;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/ParameterNameDiscoverer.java b/org.springframework.core/src/main/java/org/springframework/core/ParameterNameDiscoverer.java
new file mode 100644
index 00000000000..bd32dbdcdf7
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/ParameterNameDiscoverer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * 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.springframework.core;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/**
+ * Interface to discover parameter names for methods and constructors.
+ *
+ *
Parameter name discovery is not always possible, but various strategies are
+ * available to try, such as looking for debug information that may have been
+ * emitted at compile time, and looking for argname annotation values optionally
+ * accompanying AspectJ annotated methods.
+ *
+ * @author Rod Johnson
+ * @author Adrian Colyer
+ * @since 2.0
+ */
+public interface ParameterNameDiscoverer {
+
+ /**
+ * Return parameter names for this method,
+ * or null if they cannot be determined.
+ * @param method method to find parameter names for
+ * @return an array of parameter names if the names can be resolved,
+ * or null if they cannot
+ */
+ String[] getParameterNames(Method method);
+
+ /**
+ * Return parameter names for this constructor,
+ * or null if they cannot be determined.
+ * @param ctor constructor to find parameter names for
+ * @return an array of parameter names if the names can be resolved,
+ * or null if they cannot
+ */
+ String[] getParameterNames(Constructor ctor);
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/PrioritizedParameterNameDiscoverer.java b/org.springframework.core/src/main/java/org/springframework/core/PrioritizedParameterNameDiscoverer.java
new file mode 100644
index 00000000000..e3af49d9a66
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/PrioritizedParameterNameDiscoverer.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * ParameterNameDiscoverer implementation that tries several ParameterNameDiscoverers
+ * in succession. Those added first in the addDiscoverer method have
+ * highest priority. If one returns null, the next will be tried.
+ *
+ *
The default behavior is always to return null
+ * if no discoverer matches.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class PrioritizedParameterNameDiscoverer implements ParameterNameDiscoverer {
+
+ private final List parameterNameDiscoverers = new LinkedList();
+
+
+ /**
+ * Add a further ParameterNameDiscoverer to the list of discoverers
+ * that this PrioritizedParameterNameDiscoverer checks.
+ */
+ public void addDiscoverer(ParameterNameDiscoverer pnd) {
+ this.parameterNameDiscoverers.add(pnd);
+ }
+
+
+ public String[] getParameterNames(Method method) {
+ for (Iterator it = this.parameterNameDiscoverers.iterator(); it.hasNext(); ) {
+ ParameterNameDiscoverer pnd = (ParameterNameDiscoverer) it.next();
+ String[] result = pnd.getParameterNames(method);
+ if (result != null) {
+ return result;
+ }
+ }
+ return null;
+ }
+
+ public String[] getParameterNames(Constructor ctor) {
+ for (Iterator it = this.parameterNameDiscoverers.iterator(); it.hasNext(); ) {
+ ParameterNameDiscoverer pnd = (ParameterNameDiscoverer) it.next();
+ String[] result = pnd.getParameterNames(ctor);
+ if (result != null) {
+ return result;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/PriorityOrdered.java b/org.springframework.core/src/main/java/org/springframework/core/PriorityOrdered.java
new file mode 100644
index 00000000000..d2dc657cc3c
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/PriorityOrdered.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core;
+
+/**
+ * Extension of the {@link Ordered} interface, expressing a 'priority'
+ * ordering: Order values expressed by PriorityOrdered objects always
+ * apply before order values of 'plain' Ordered values.
+ *
+ *
This is primarily a special-purpose interface, used for objects
+ * where it is particularly important to determine 'prioritized'
+ * objects first, without even obtaining the remaining objects.
+ * A typical example: Prioritized post-processors in a Spring
+ * {@link org.springframework.context.ApplicationContext}.
+ *
+ *
Note: PriorityOrdered post-processor beans are initialized in
+ * a special phase, ahead of other post-postprocessor beans. This
+ * subtly affects their autowiring behavior: They will only be
+ * autowired against beans which do not require eager initialization
+ * for type matching.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see org.springframework.beans.factory.config.PropertyOverrideConfigurer
+ * @see org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
+ */
+public interface PriorityOrdered extends Ordered {
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/ReflectiveVisitorHelper.java b/org.springframework.core/src/main/java/org/springframework/core/ReflectiveVisitorHelper.java
new file mode 100644
index 00000000000..0d7c8a018a9
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/ReflectiveVisitorHelper.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.LinkedList;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.util.Assert;
+import org.springframework.util.CachingMapDecorator;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * Helper implementation for a reflective visitor.
+ * Mainly for internal use within the framework.
+ *
+ *
To use, call invokeVisit, passing a Visitor object
+ * and the data argument to accept (double-dispatch). For example:
+ *
+ *
+ * public String styleValue(Object value) {
+ * reflectiveVistorSupport.invokeVisit(this, value)
+ * }
+ *
+ * // visit call back will be invoked via reflection
+ * String visit(<valueType> arg) {
+ * // process argument of type <valueType>
+ * }
+ *
+ *
+ * See the {@link org.springframework.core.style.DefaultValueStyler} class
+ * for a concrete usage of this visitor helper.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ * @deprecated as of Spring 2.5, to be removed in Spring 3.0
+ */
+public class ReflectiveVisitorHelper {
+
+ private static final String VISIT_METHOD = "visit";
+
+ private static final String VISIT_NULL = "visitNull";
+
+ private static final Log logger = LogFactory.getLog(ReflectiveVisitorHelper.class);
+
+
+ private final CachingMapDecorator visitorClassVisitMethods = new CachingMapDecorator() {
+ public Object create(Object key) {
+ return new ClassVisitMethods((Class) key);
+ }
+ };
+
+
+ /**
+ * Use reflection to call the appropriate visit method
+ * on the provided visitor, passing in the specified argument.
+ * @param visitor the visitor encapsulating the logic to process the argument
+ * @param argument the argument to dispatch
+ * @throws IllegalArgumentException if the visitor parameter is null
+ */
+ public Object invokeVisit(Object visitor, Object argument) {
+ Assert.notNull(visitor, "The visitor to visit is required");
+ // Perform call back on the visitor through reflection.
+ Method method = getMethod(visitor.getClass(), argument);
+ if (method == null) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("No method found by reflection for visitor class [" + visitor.getClass().getName()
+ + "] and argument of type [" + (argument != null ? argument.getClass().getName() : "") + "]");
+ }
+ return null;
+ }
+ try {
+ Object[] args = null;
+ if (argument != null) {
+ args = new Object[] {argument};
+ }
+ if (!Modifier.isPublic(method.getModifiers())) {
+ method.setAccessible(true);
+ }
+ return method.invoke(visitor, args);
+ }
+ catch (Exception ex) {
+ ReflectionUtils.handleReflectionException(ex);
+ throw new IllegalStateException("Should never get here");
+ }
+ }
+
+ /**
+ * Determines the most appropriate visit method for the
+ * given visitor class and argument.
+ */
+ private Method getMethod(Class visitorClass, Object argument) {
+ ClassVisitMethods visitMethods = (ClassVisitMethods) this.visitorClassVisitMethods.get(visitorClass);
+ return visitMethods.getVisitMethod(argument != null ? argument.getClass() : null);
+ }
+
+
+ /**
+ * Internal class caching visitor methods by argument class.
+ */
+ private static class ClassVisitMethods {
+
+ private final Class visitorClass;
+
+ private final CachingMapDecorator visitMethodCache = new CachingMapDecorator() {
+ public Object create(Object argumentClazz) {
+ if (argumentClazz == null) {
+ return findNullVisitorMethod();
+ }
+ Method method = findVisitMethod((Class) argumentClazz);
+ if (method == null) {
+ method = findDefaultVisitMethod();
+ }
+ return method;
+ }
+ };
+
+ public ClassVisitMethods(Class visitorClass) {
+ this.visitorClass = visitorClass;
+ }
+
+ private Method findNullVisitorMethod() {
+ for (Class clazz = this.visitorClass; clazz != null; clazz = clazz.getSuperclass()) {
+ try {
+ return clazz.getDeclaredMethod(VISIT_NULL, (Class[]) null);
+ }
+ catch (NoSuchMethodException ex) {
+ }
+ }
+ return findDefaultVisitMethod();
+ }
+
+ private Method findDefaultVisitMethod() {
+ final Class[] args = {Object.class};
+ for (Class clazz = this.visitorClass; clazz != null; clazz = clazz.getSuperclass()) {
+ try {
+ return clazz.getDeclaredMethod(VISIT_METHOD, args);
+ }
+ catch (NoSuchMethodException ex) {
+ }
+ }
+ if (logger.isWarnEnabled()) {
+ logger.warn("No default '" + VISIT_METHOD + "' method found. Returning .");
+ }
+ return null;
+ }
+
+ /**
+ * Gets a cached visitor method for the specified argument type.
+ */
+ private Method getVisitMethod(Class argumentClass) {
+ return (Method) this.visitMethodCache.get(argumentClass);
+ }
+
+ /**
+ * Traverses class hierarchy looking for applicable visit() method.
+ */
+ private Method findVisitMethod(Class rootArgumentType) {
+ if (rootArgumentType == Object.class) {
+ return null;
+ }
+ LinkedList classQueue = new LinkedList();
+ classQueue.addFirst(rootArgumentType);
+
+ while (!classQueue.isEmpty()) {
+ Class argumentType = (Class) classQueue.removeLast();
+ // Check for a visit method on the visitor class matching this
+ // argument type.
+ try {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Looking for method " + VISIT_METHOD + "(" + argumentType + ")");
+ }
+ return findVisitMethod(this.visitorClass, argumentType);
+ }
+ catch (NoSuchMethodException e) {
+ // Queue up the argument super class if it's not of type Object.
+ if (!argumentType.isInterface() && (argumentType.getSuperclass() != Object.class)) {
+ classQueue.addFirst(argumentType.getSuperclass());
+ }
+ // Queue up argument's implemented interfaces.
+ Class[] interfaces = argumentType.getInterfaces();
+ for (int i = 0; i < interfaces.length; i++) {
+ classQueue.addFirst(interfaces[i]);
+ }
+ }
+ }
+ // No specific method found -> return the default.
+ return findDefaultVisitMethod();
+ }
+
+ private Method findVisitMethod(Class visitorClass, Class argumentType) throws NoSuchMethodException {
+ try {
+ return visitorClass.getDeclaredMethod(VISIT_METHOD, new Class[] {argumentType});
+ }
+ catch (NoSuchMethodException ex) {
+ // Try visitorClass superclasses.
+ if (visitorClass.getSuperclass() != Object.class) {
+ return findVisitMethod(visitorClass.getSuperclass(), argumentType);
+ }
+ else {
+ throw ex;
+ }
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/SimpleAliasRegistry.java b/org.springframework.core/src/main/java/org/springframework/core/SimpleAliasRegistry.java
new file mode 100644
index 00000000000..9abb79e1292
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/SimpleAliasRegistry.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+import org.springframework.util.StringValueResolver;
+
+/**
+ * Simple implementation of the {@link AliasRegistry} interface.
+ * Serves as base class for
+ * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}
+ * implementations.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.2
+ */
+public class SimpleAliasRegistry implements AliasRegistry {
+
+ /** Map from alias to canonical name */
+ private final Map aliasMap = CollectionFactory.createConcurrentMapIfPossible(16);
+
+
+ public void registerAlias(String name, String alias) {
+ Assert.hasText(name, "'name' must not be empty");
+ Assert.hasText(alias, "'alias' must not be empty");
+ if (alias.equals(name)) {
+ this.aliasMap.remove(alias);
+ }
+ else {
+ if (!allowAliasOverriding()) {
+ String registeredName = (String) this.aliasMap.get(alias);
+ if (registeredName != null && !registeredName.equals(name)) {
+ throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" +
+ name + "': It is already registered for name '" + registeredName + "'.");
+ }
+ }
+ this.aliasMap.put(alias, name);
+ }
+ }
+
+ /**
+ * Return whether alias overriding is allowed.
+ * Default is true.
+ */
+ protected boolean allowAliasOverriding() {
+ return true;
+ }
+
+ public void removeAlias(String alias) {
+ String name = (String) this.aliasMap.remove(alias);
+ if (name == null) {
+ throw new IllegalStateException("No alias '" + alias + "' registered");
+ }
+ }
+
+ public boolean isAlias(String name) {
+ return this.aliasMap.containsKey(name);
+ }
+
+ public String[] getAliases(String name) {
+ List aliases = new ArrayList();
+ synchronized (this.aliasMap) {
+ for (Iterator it = this.aliasMap.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ String registeredName = (String) entry.getValue();
+ if (registeredName.equals(name)) {
+ aliases.add(entry.getKey());
+ }
+ }
+ }
+ return StringUtils.toStringArray(aliases);
+ }
+
+ /**
+ * Resolve all alias target names and aliases registered in this
+ * factory, applying the given StringValueResolver to them.
+ * The value resolver may for example resolve placeholders
+ * in target bean names and even in alias names.
+ * @param valueResolver the StringValueResolver to apply
+ */
+ public void resolveAliases(StringValueResolver valueResolver) {
+ Assert.notNull(valueResolver, "StringValueResolver must not be null");
+ synchronized (this.aliasMap) {
+ Map aliasCopy = new HashMap(this.aliasMap);
+ for (Iterator it = aliasCopy.keySet().iterator(); it.hasNext();) {
+ String alias = (String) it.next();
+ String registeredName = (String) aliasCopy.get(alias);
+ String resolvedAlias = valueResolver.resolveStringValue(alias);
+ String resolvedName = valueResolver.resolveStringValue(registeredName);
+ if (!resolvedAlias.equals(alias)) {
+ String existingName = (String) this.aliasMap.get(resolvedAlias);
+ if (existingName != null && !existingName.equals(resolvedName)) {
+ throw new IllegalStateException("Cannot register resolved alias '" +
+ resolvedAlias + "' (original: '" + alias + "') for name '" + resolvedName +
+ "': It is already registered for name '" + registeredName + "'.");
+ }
+ this.aliasMap.put(resolvedAlias, resolvedName);
+ this.aliasMap.remove(alias);
+ }
+ else if (!registeredName.equals(resolvedName)) {
+ this.aliasMap.put(alias, resolvedName);
+ }
+ }
+ }
+ }
+
+ /**
+ * Determine the raw name, resolving aliases to canonical names.
+ * @param name the user-specified name
+ * @return the transformed name
+ */
+ public String canonicalName(String name) {
+ String canonicalName = name;
+ // Handle aliasing.
+ String resolvedName = null;
+ do {
+ resolvedName = (String) this.aliasMap.get(canonicalName);
+ if (resolvedName != null) {
+ canonicalName = resolvedName;
+ }
+ }
+ while (resolvedName != null);
+ return canonicalName;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/SmartClassLoader.java b/org.springframework.core/src/main/java/org/springframework/core/SmartClassLoader.java
new file mode 100644
index 00000000000..16f45d3c6aa
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/SmartClassLoader.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core;
+
+/**
+ * Interface to be implemented by a reloading-aware ClassLoader
+ * (e.g. a Groovy-based ClassLoader). Detected for example by
+ * Spring's CGLIB proxy factory for making a caching decision.
+ *
+ *
If a ClassLoader does not implement this interface,
+ * then all of the classes obtained from it should be considered
+ * as not reloadable (i.e. cacheable).
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.1
+ */
+public interface SmartClassLoader {
+
+ /**
+ * Determine whether the given class is reloadable (in this ClassLoader).
+ *
Typically used to check whether the result may be cached (for this
+ * ClassLoader) or whether it should be reobtained every time.
+ * @param clazz the class to check (usually loaded from this ClassLoader)
+ * @return whether the class should be expected to appear in a reloaded
+ * version (with a different Class object) later on
+ */
+ boolean isClassReloadable(Class clazz);
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/SpringVersion.java b/org.springframework.core/src/main/java/org/springframework/core/SpringVersion.java
new file mode 100644
index 00000000000..830e8efc818
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/SpringVersion.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * 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.springframework.core;
+
+/**
+ * Class that exposes the Spring version. Fetches the
+ * "Implementation-Version" manifest attribute from the jar file.
+ *
+ *
Note that some ClassLoaders do not expose the package metadata,
+ * hence this class might not be able to determine the Spring version
+ * in all environments. Consider using a reflection-based check instead:
+ * For example, checking for the presence of a specific Spring 2.0
+ * method that you intend to call.
+ *
+ * @author Juergen Hoeller
+ * @since 1.1
+ */
+public class SpringVersion {
+
+ /**
+ * Return the full version string of the present Spring codebase,
+ * or null if it cannot be determined.
+ * @see java.lang.Package#getImplementationVersion()
+ */
+ public static String getVersion() {
+ Package pkg = SpringVersion.class.getPackage();
+ return (pkg != null ? pkg.getImplementationVersion() : null);
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/enums/AbstractCachingLabeledEnumResolver.java b/org.springframework.core/src/main/java/org/springframework/core/enums/AbstractCachingLabeledEnumResolver.java
new file mode 100644
index 00000000000..40eba4d3223
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/enums/AbstractCachingLabeledEnumResolver.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core.enums;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.util.Assert;
+import org.springframework.util.CachingMapDecorator;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Abstract base class for {@link LabeledEnumResolver} implementations,
+ * caching all retrieved {@link LabeledEnum} instances.
+ *
+ *
Subclasses need to implement the template method
+ * {@link #findLabeledEnums(Class)}.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ * @see #findLabeledEnums(Class)
+ */
+public abstract class AbstractCachingLabeledEnumResolver implements LabeledEnumResolver {
+
+ protected transient final Log logger = LogFactory.getLog(getClass());
+
+
+ private final CachingMapDecorator labeledEnumCache = new CachingMapDecorator(true) {
+ protected Object create(Object key) {
+ Class enumType = (Class) key;
+ Set typeEnums = findLabeledEnums(enumType);
+ if (typeEnums == null || typeEnums.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Unsupported labeled enumeration type '" + key + "': " +
+ "make sure you've properly defined this enumeration! " +
+ "If it is static, are the class and its fields public/static/final?");
+ }
+ Map typeEnumMap = new HashMap(typeEnums.size());
+ for (Iterator it = typeEnums.iterator(); it.hasNext();) {
+ LabeledEnum labeledEnum = (LabeledEnum) it.next();
+ typeEnumMap.put(labeledEnum.getCode(), labeledEnum);
+ }
+ return Collections.unmodifiableMap(typeEnumMap);
+ }
+ protected boolean useWeakValue(Object key, Object value) {
+ Class enumType = (Class) key;
+ if (!ClassUtils.isCacheSafe(enumType, AbstractCachingLabeledEnumResolver.this.getClass().getClassLoader())) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Not strongly caching class [" + enumType.getName() + "] because it is not cache-safe");
+ }
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ };
+
+
+ public Set getLabeledEnumSet(Class type) throws IllegalArgumentException {
+ return new TreeSet(getLabeledEnumMap(type).values());
+ }
+
+ public Map getLabeledEnumMap(Class type) throws IllegalArgumentException {
+ Assert.notNull(type, "No type specified");
+ return (Map) this.labeledEnumCache.get(type);
+ }
+
+ public LabeledEnum getLabeledEnumByCode(Class type, Comparable code) throws IllegalArgumentException {
+ Assert.notNull(code, "No enum code specified");
+ Map typeEnums = getLabeledEnumMap(type);
+ LabeledEnum codedEnum = (LabeledEnum) typeEnums.get(code);
+ if (codedEnum == null) {
+ throw new IllegalArgumentException(
+ "No enumeration with code '" + code + "'" + " of type [" + type.getName() +
+ "] exists: this is likely a configuration error. " +
+ "Make sure the code value matches a valid instance's code property!");
+ }
+ return codedEnum;
+ }
+
+ public LabeledEnum getLabeledEnumByLabel(Class type, String label) throws IllegalArgumentException {
+ Map typeEnums = getLabeledEnumMap(type);
+ Iterator it = typeEnums.values().iterator();
+ while (it.hasNext()) {
+ LabeledEnum value = (LabeledEnum) it.next();
+ if (value.getLabel().equalsIgnoreCase(label)) {
+ return value;
+ }
+ }
+ throw new IllegalArgumentException(
+ "No enumeration with label '" + label + "' of type [" + type +
+ "] exists: this is likely a configuration error. " +
+ "Make sure the label string matches a valid instance's label property!");
+ }
+
+
+ /**
+ * Template method to be implemented by subclasses.
+ * Supposed to find all LabeledEnum instances for the given type.
+ * @param type the enum type
+ * @return the Set of LabeledEnum instances
+ * @see org.springframework.core.enums.LabeledEnum
+ */
+ protected abstract Set findLabeledEnums(Class type);
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/enums/AbstractGenericLabeledEnum.java b/org.springframework.core/src/main/java/org/springframework/core/enums/AbstractGenericLabeledEnum.java
new file mode 100644
index 00000000000..9cf452b02da
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/enums/AbstractGenericLabeledEnum.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * 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.springframework.core.enums;
+
+/**
+ * Base class for labeled enum instances that aren't static.
+ *
+ * @author Keith Donald
+ * @since 1.2.6
+ */
+public abstract class AbstractGenericLabeledEnum extends AbstractLabeledEnum {
+
+ /**
+ * A descriptive label for the enum.
+ */
+ private final String label;
+
+
+ /**
+ * Create a new StaticLabeledEnum instance.
+ * @param label the label; if null), the enum's code
+ * will be used as label
+ */
+ protected AbstractGenericLabeledEnum(String label) {
+ this.label = label;
+ }
+
+
+ public String getLabel() {
+ if (this.label != null) {
+ return label;
+ }
+ else {
+ return getCode().toString();
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/enums/AbstractLabeledEnum.java b/org.springframework.core/src/main/java/org/springframework/core/enums/AbstractLabeledEnum.java
new file mode 100644
index 00000000000..8081f8c3fe4
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/enums/AbstractLabeledEnum.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.enums;
+
+/**
+ * Abstract base superclass for LabeledEnum implementations.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @since 1.2.2
+ */
+public abstract class AbstractLabeledEnum implements LabeledEnum {
+
+ /**
+ * Create a new AbstractLabeledEnum instance.
+ */
+ protected AbstractLabeledEnum() {
+ }
+
+ public Class getType() {
+ // Could be coded as getClass().isAnonymousClass() on JDK 1.5
+ boolean isAnonymous = (getClass().getDeclaringClass() == null && getClass().getName().indexOf('$') != -1);
+ return (isAnonymous ? getClass().getSuperclass() : getClass());
+ }
+
+ public int compareTo(Object obj) {
+ if (!(obj instanceof LabeledEnum)) {
+ throw new ClassCastException("You may only compare LabeledEnums");
+ }
+ LabeledEnum that = (LabeledEnum) obj;
+ if (!this.getType().equals(that.getType())) {
+ throw new ClassCastException("You may only compare LabeledEnums of the same type");
+ }
+ return this.getCode().compareTo(that.getCode());
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof LabeledEnum)) {
+ return false;
+ }
+ LabeledEnum other = (LabeledEnum) obj;
+ return (this.getType().equals(other.getType()) && this.getCode().equals(other.getCode()));
+ }
+
+ public int hashCode() {
+ return (getType().hashCode() * 29 + getCode().hashCode());
+ }
+
+ public String toString() {
+ return getLabel();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/enums/LabeledEnum.java b/org.springframework.core/src/main/java/org/springframework/core/enums/LabeledEnum.java
new file mode 100644
index 00000000000..a84cf9c75bc
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/enums/LabeledEnum.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * 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.springframework.core.enums;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+import org.springframework.util.comparator.CompoundComparator;
+import org.springframework.util.comparator.NullSafeComparator;
+
+/**
+ * An interface for objects that represent a labeled enumeration.
+ * Each such enum instance has the following characteristics:
+ *
+ *
+ * - A type that identifies the enum's class.
+ * For example:
com.mycompany.util.FileFormat.
+ *
+ * - A code that uniquely identifies the enum within the context of its type.
+ * For example: "CSV". Different classes of codes are possible
+ * (e.g., Character, Integer, String).
+ *
+ * - A descriptive label. For example: "the CSV File Format".
+ *
+ *
+ * @author Keith Donald
+ * @since 1.2.2
+ */
+public interface LabeledEnum extends Comparable, Serializable {
+
+ /**
+ * Return this enumeration's type.
+ */
+ Class getType();
+
+ /**
+ * Return this enumeration's code.
+ * Each code should be unique within enumerations of the same type.
+ */
+ Comparable getCode();
+
+ /**
+ * Return a descriptive, optional label.
+ */
+ String getLabel();
+
+
+ // Constants for standard enum ordering (Comparator implementations)
+
+ /**
+ * Shared Comparator instance that sorts enumerations by CODE_ORDER.
+ */
+ Comparator CODE_ORDER = new Comparator() {
+ public int compare(Object o1, Object o2) {
+ Comparable c1 = ((LabeledEnum) o1).getCode();
+ Comparable c2 = ((LabeledEnum) o2).getCode();
+ return c1.compareTo(c2);
+ }
+ };
+
+ /**
+ * Shared Comparator instance that sorts enumerations by LABEL_ORDER.
+ */
+ Comparator LABEL_ORDER = new Comparator() {
+ public int compare(Object o1, Object o2) {
+ LabeledEnum e1 = (LabeledEnum) o1;
+ LabeledEnum e2 = (LabeledEnum) o2;
+ Comparator comp = new NullSafeComparator(String.CASE_INSENSITIVE_ORDER, true);
+ return comp.compare(e1.getLabel(), e2.getLabel());
+ }
+ };
+
+ /**
+ * Shared Comparator instance that sorts enumerations by LABEL_ORDER,
+ * then CODE_ORDER.
+ */
+ Comparator DEFAULT_ORDER =
+ new CompoundComparator(new Comparator[] { LABEL_ORDER, CODE_ORDER });
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/enums/LabeledEnumResolver.java b/org.springframework.core/src/main/java/org/springframework/core/enums/LabeledEnumResolver.java
new file mode 100644
index 00000000000..7a6ad90f556
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/enums/LabeledEnumResolver.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * 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.springframework.core.enums;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Interface for looking up LabeledEnum instances.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ */
+public interface LabeledEnumResolver {
+
+ /**
+ * Return a set of enumerations of a particular type. Each element in the
+ * set should be an instance of LabeledEnum.
+ * @param type the enum type
+ * @return a set of localized enumeration instances for the provided type
+ * @throws IllegalArgumentException if the type is not supported
+ */
+ public Set getLabeledEnumSet(Class type) throws IllegalArgumentException;
+
+ /**
+ * Return a map of enumerations of a particular type. Each element in the
+ * map should be a key/value pair, where the key is the enum code, and the
+ * value is the LabeledEnum instance.
+ * @param type the enum type
+ * @return a Map of localized enumeration instances,
+ * with enum code as key and LabeledEnum instance as value
+ * @throws IllegalArgumentException if the type is not supported
+ */
+ public Map getLabeledEnumMap(Class type) throws IllegalArgumentException;
+
+ /**
+ * Resolve a single LabeledEnum by its identifying code.
+ * @param type the enum type
+ * @param code the enum code
+ * @return the enum
+ * @throws IllegalArgumentException if the code did not map to a valid instance
+ */
+ public LabeledEnum getLabeledEnumByCode(Class type, Comparable code) throws IllegalArgumentException;
+
+ /**
+ * Resolve a single LabeledEnum by its identifying code.
+ * @param type the enum type
+ * @param label the enum label
+ * @return the enum
+ * @throws IllegalArgumentException if the label did not map to a valid instance
+ */
+ public LabeledEnum getLabeledEnumByLabel(Class type, String label) throws IllegalArgumentException;
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/enums/LetterCodedLabeledEnum.java b/org.springframework.core/src/main/java/org/springframework/core/enums/LetterCodedLabeledEnum.java
new file mode 100644
index 00000000000..88a0bf327b3
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/enums/LetterCodedLabeledEnum.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * 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.springframework.core.enums;
+
+import org.springframework.util.Assert;
+
+/**
+ * Implementation of LabeledEnum which uses a letter as the code type.
+ *
+ *
Should almost always be subclassed, but for some simple situations it may be
+ * used directly. Note that you will not be able to use unique type-based functionality
+ * like LabeledEnumResolver.getLabeledEnumSet(type) in this case.
+ *
+ * @author Keith Donald
+ * @since 1.2.2
+ */
+public class LetterCodedLabeledEnum extends AbstractGenericLabeledEnum {
+
+ /**
+ * The unique code of this enum.
+ */
+ private final Character code;
+
+
+ /**
+ * Create a new LetterCodedLabeledEnum instance.
+ * @param code the letter code
+ * @param label the label (can be null)
+ */
+ public LetterCodedLabeledEnum(char code, String label) {
+ super(label);
+ Assert.isTrue(Character.isLetter(code),
+ "The code '" + code + "' is invalid: it must be a letter");
+ this.code = new Character(code);
+ }
+
+
+ public Comparable getCode() {
+ return code;
+ }
+
+ /**
+ * Return the letter code of this LabeledEnum instance.
+ */
+ public char getLetterCode() {
+ return ((Character) getCode()).charValue();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/enums/ShortCodedLabeledEnum.java b/org.springframework.core/src/main/java/org/springframework/core/enums/ShortCodedLabeledEnum.java
new file mode 100644
index 00000000000..7db41f4f3d9
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/enums/ShortCodedLabeledEnum.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * 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.springframework.core.enums;
+
+/**
+ * Implementation of LabeledEnum which uses Short as the code type.
+ *
+ *
Should almost always be subclassed, but for some simple situations it may be
+ * used directly. Note that you will not be able to use unique type-based functionality
+ * like LabeledEnumResolver.getLabeledEnumSet(type) in this case.
+ *
+ * @author Keith Donald
+ * @since 1.2.2
+ */
+public class ShortCodedLabeledEnum extends AbstractGenericLabeledEnum {
+
+ /**
+ * The unique code of this enum.
+ */
+ private final Short code;
+
+
+ /**
+ * Create a new ShortCodedLabeledEnum instance.
+ * @param code the short code
+ * @param label the label (can be null)
+ */
+ public ShortCodedLabeledEnum(int code, String label) {
+ super(label);
+ this.code = new Short((short) code);
+ }
+
+
+ public Comparable getCode() {
+ return code;
+ }
+
+ /**
+ * Return the short code of this LabeledEnum instance.
+ */
+ public short getShortCode() {
+ return ((Short) getCode()).shortValue();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/enums/StaticLabeledEnum.java b/org.springframework.core/src/main/java/org/springframework/core/enums/StaticLabeledEnum.java
new file mode 100644
index 00000000000..95ef43b8680
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/enums/StaticLabeledEnum.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * 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.springframework.core.enums;
+
+/**
+ * Base class for static type-safe labeled enum instances.
+ *
+ * Usage example:
+ *
+ *
+ * public class FlowSessionStatus extends StaticLabeledEnum {
+ *
+ * // public static final instances!
+ * public static FlowSessionStatus CREATED = new FlowSessionStatus(0, "Created");
+ * public static FlowSessionStatus ACTIVE = new FlowSessionStatus(1, "Active");
+ * public static FlowSessionStatus PAUSED = new FlowSessionStatus(2, "Paused");
+ * public static FlowSessionStatus SUSPENDED = new FlowSessionStatus(3, "Suspended");
+ * public static FlowSessionStatus ENDED = new FlowSessionStatus(4, "Ended");
+ *
+ * // private constructor!
+ * private FlowSessionStatus(int code, String label) {
+ * super(code, label);
+ * }
+ *
+ * // custom behavior
+ * }
+ *
+ * @author Keith Donald
+ * @since 1.2.6
+ */
+public abstract class StaticLabeledEnum extends AbstractLabeledEnum {
+
+ /**
+ * The unique code of the enum.
+ */
+ private final Short code;
+
+ /**
+ * A descriptive label for the enum.
+ */
+ private final transient String label;
+
+
+ /**
+ * Create a new StaticLabeledEnum instance.
+ * @param code the short code
+ * @param label the label (can be null)
+ */
+ protected StaticLabeledEnum(int code, String label) {
+ this.code = new Short((short) code);
+ if (label != null) {
+ this.label = label;
+ }
+ else {
+ this.label = this.code.toString();
+ }
+ }
+
+ public Comparable getCode() {
+ return code;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ /**
+ * Return the code of this LabeledEnum instance as a short.
+ */
+ public short shortValue() {
+ return ((Number) getCode()).shortValue();
+ }
+
+
+ //---------------------------------------------------------------------
+ // Serialization support
+ //---------------------------------------------------------------------
+
+ /**
+ * Return the resolved type safe static enum instance.
+ */
+ protected Object readResolve() {
+ return StaticLabeledEnumResolver.instance().getLabeledEnumByCode(getType(), getCode());
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/enums/StaticLabeledEnumResolver.java b/org.springframework.core/src/main/java/org/springframework/core/enums/StaticLabeledEnumResolver.java
new file mode 100644
index 00000000000..a1ee63ef1a9
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/enums/StaticLabeledEnumResolver.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core.enums;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.springframework.util.Assert;
+
+/**
+ * {@link LabeledEnumResolver} that resolves statically defined enumerations.
+ * Static implies all enum instances were defined within Java code,
+ * implementing the type-safe enum pattern.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ */
+public class StaticLabeledEnumResolver extends AbstractCachingLabeledEnumResolver {
+
+ /**
+ * Shared StaticLabeledEnumResolver singleton instance.
+ */
+ private static final StaticLabeledEnumResolver INSTANCE = new StaticLabeledEnumResolver();
+
+
+ /**
+ * Return the shared StaticLabeledEnumResolver singleton instance.
+ * Mainly for resolving unique StaticLabeledEnum references on deserialization.
+ * @see StaticLabeledEnum
+ */
+ public static StaticLabeledEnumResolver instance() {
+ return INSTANCE;
+ }
+
+
+ protected Set findLabeledEnums(Class type) {
+ Set typeEnums = new TreeSet();
+ Field[] fields = type.getFields();
+ for (int i = 0; i < fields.length; i++) {
+ Field field = fields[i];
+ if (Modifier.isStatic(field.getModifiers()) && Modifier.isPublic(field.getModifiers())) {
+ if (type.isAssignableFrom(field.getType())) {
+ try {
+ Object value = field.get(null);
+ Assert.isTrue(value instanceof LabeledEnum, "Field value must be a LabeledEnum instance");
+ typeEnums.add(value);
+ }
+ catch (IllegalAccessException ex) {
+ logger.warn("Unable to access field value: " + field, ex);
+ }
+ }
+ }
+ }
+ return typeEnums;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/enums/StringCodedLabeledEnum.java b/org.springframework.core/src/main/java/org/springframework/core/enums/StringCodedLabeledEnum.java
new file mode 100644
index 00000000000..ffa9956f50e
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/enums/StringCodedLabeledEnum.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.enums;
+
+import org.springframework.util.Assert;
+
+/**
+ * Implementation of LabeledEnum which uses a String as the code type.
+ *
+ * Should almost always be subclassed, but for some simple situations it may be
+ * used directly. Note that you will not be able to use unique type-based
+ * functionality like LabeledEnumResolver.getLabeledEnumSet(type) in this case.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ * @see org.springframework.core.enums.LabeledEnumResolver#getLabeledEnumSet(Class)
+ */
+public class StringCodedLabeledEnum extends AbstractGenericLabeledEnum {
+
+ /**
+ * The unique code of this enum.
+ */
+ private final String code;
+
+
+ /**
+ * Create a new StringCodedLabeledEnum instance.
+ * @param code the String code
+ * @param label the label (can be null)
+ */
+ public StringCodedLabeledEnum(String code, String label) {
+ super(label);
+ Assert.notNull(code, "'code' must not be null");
+ this.code = code;
+ }
+
+
+ public Comparable getCode() {
+ return this.code;
+ }
+
+ /**
+ * Return the String code of this LabeledEnum instance.
+ */
+ public String getStringCode() {
+ return (String) getCode();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/enums/package.html b/org.springframework.core/src/main/java/org/springframework/core/enums/package.html
new file mode 100644
index 00000000000..be0ecc57e0d
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/enums/package.html
@@ -0,0 +1,8 @@
+
+
+
+Interfaces and classes for type-safe enum support on JDK >= 1.3.
+This enum abstraction support codes and labels.
+
+
+
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/AbstractResource.java b/org.springframework.core/src/main/java/org/springframework/core/io/AbstractResource.java
new file mode 100644
index 00000000000..21fa7604d4b
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/AbstractResource.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core.io;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import org.springframework.core.NestedIOException;
+import org.springframework.util.ResourceUtils;
+
+/**
+ * Convenience base class for {@link Resource} implementations,
+ * pre-implementing typical behavior.
+ *
+ * The "exists" method will check whether a File or InputStream can
+ * be opened; "isOpen" will always return false; "getURL" and "getFile"
+ * throw an exception; and "toString" will return the description.
+ *
+ * @author Juergen Hoeller
+ * @since 28.12.2003
+ */
+public abstract class AbstractResource implements Resource {
+
+ /**
+ * This implementation checks whether a File can be opened,
+ * falling back to whether an InputStream can be opened.
+ * This will cover both directories and content resources.
+ */
+ public boolean exists() {
+ // Try file existence: can we find the file in the file system?
+ try {
+ return getFile().exists();
+ }
+ catch (IOException ex) {
+ // Fall back to stream existence: can we open the stream?
+ try {
+ InputStream is = getInputStream();
+ is.close();
+ return true;
+ }
+ catch (Throwable isEx) {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * This implementation always returns true.
+ */
+ public boolean isReadable() {
+ return true;
+ }
+
+ /**
+ * This implementation always returns false.
+ */
+ public boolean isOpen() {
+ return false;
+ }
+
+ /**
+ * This implementation throws a FileNotFoundException, assuming
+ * that the resource cannot be resolved to a URL.
+ */
+ public URL getURL() throws IOException {
+ throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
+ }
+
+ /**
+ * This implementation builds a URI based on the URL returned
+ * by {@link #getURL()}.
+ */
+ public URI getURI() throws IOException {
+ URL url = getURL();
+ try {
+ return ResourceUtils.toURI(url);
+ }
+ catch (URISyntaxException ex) {
+ throw new NestedIOException("Invalid URI [" + url + "]", ex);
+ }
+ }
+
+ /**
+ * This implementation throws a FileNotFoundException, assuming
+ * that the resource cannot be resolved to an absolute file path.
+ */
+ public File getFile() throws IOException {
+ throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
+ }
+
+ /**
+ * This implementation checks the timestamp of the underlying File,
+ * if available.
+ * @see #getFileForLastModifiedCheck()
+ */
+ public long lastModified() throws IOException {
+ long lastModified = getFileForLastModifiedCheck().lastModified();
+ if (lastModified == 0L) {
+ throw new FileNotFoundException(getDescription() +
+ " cannot be resolved in the file system for resolving its last-modified timestamp");
+ }
+ return lastModified;
+ }
+
+ /**
+ * Determine the File to use for timestamp checking.
+ *
The default implementation delegates to {@link #getFile()}.
+ * @return the File to use for timestamp checking (never null)
+ * @throws IOException if the resource cannot be resolved as absolute
+ * file path, i.e. if the resource is not available in a file system
+ */
+ protected File getFileForLastModifiedCheck() throws IOException {
+ return getFile();
+ }
+
+ /**
+ * This implementation throws a FileNotFoundException, assuming
+ * that relative resources cannot be created for this resource.
+ */
+ public Resource createRelative(String relativePath) throws IOException {
+ throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
+ }
+
+ /**
+ * This implementation always throws IllegalStateException,
+ * assuming that the resource does not carry a filename.
+ */
+ public String getFilename() throws IllegalStateException {
+ throw new IllegalStateException(getDescription() + " does not carry a filename");
+ }
+
+
+ /**
+ * This implementation returns the description of this resource.
+ * @see #getDescription()
+ */
+ public String toString() {
+ return getDescription();
+ }
+
+ /**
+ * This implementation compares description strings.
+ * @see #getDescription()
+ */
+ public boolean equals(Object obj) {
+ return (obj == this ||
+ (obj instanceof Resource && ((Resource) obj).getDescription().equals(getDescription())));
+ }
+
+ /**
+ * This implementation returns the description's hash code.
+ * @see #getDescription()
+ */
+ public int hashCode() {
+ return getDescription().hashCode();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/ByteArrayResource.java b/org.springframework.core/src/main/java/org/springframework/core/io/ByteArrayResource.java
new file mode 100644
index 00000000000..48c5b81eabb
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/ByteArrayResource.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+/**
+ * {@link Resource} implementation for a given byte array.
+ * Creates a ByteArrayInputStreams for the given byte array.
+ *
+ *
Useful for loading content from any given byte array,
+ * without having to resort to a single-use {@link InputStreamResource}.
+ * Particularly useful for creating mail attachments from local content,
+ * where JavaMail needs to be able to read the stream multiple times.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2.3
+ * @see java.io.ByteArrayInputStream
+ * @see InputStreamResource
+ * @see org.springframework.mail.javamail.MimeMessageHelper#addAttachment(String, InputStreamSource)
+ */
+public class ByteArrayResource extends AbstractResource {
+
+ private final byte[] byteArray;
+
+ private final String description;
+
+
+ /**
+ * Create a new ByteArrayResource.
+ * @param byteArray the byte array to wrap
+ */
+ public ByteArrayResource(byte[] byteArray) {
+ this(byteArray, "resource loaded from byte array");
+ }
+
+ /**
+ * Create a new ByteArrayResource.
+ * @param byteArray the byte array to wrap
+ * @param description where the byte array comes from
+ */
+ public ByteArrayResource(byte[] byteArray, String description) {
+ if (byteArray == null) {
+ throw new IllegalArgumentException("Byte array must not be null");
+ }
+ this.byteArray = byteArray;
+ this.description = (description != null ? description : "");
+ }
+
+ /**
+ * Return the underlying byte array.
+ */
+ public final byte[] getByteArray() {
+ return this.byteArray;
+ }
+
+
+ /**
+ * This implementation always returns true.
+ */
+ public boolean exists() {
+ return true;
+ }
+
+ /**
+ * This implementation returns a ByteArrayInputStream for the
+ * underlying byte array.
+ * @see java.io.ByteArrayInputStream
+ */
+ public InputStream getInputStream() throws IOException {
+ return new ByteArrayInputStream(this.byteArray);
+ }
+
+ /**
+ * This implementation returns the passed-in description, if any.
+ */
+ public String getDescription() {
+ return this.description;
+ }
+
+
+ /**
+ * This implementation compares the underlying byte array.
+ * @see java.util.Arrays#equals(byte[], byte[])
+ */
+ public boolean equals(Object obj) {
+ return (obj == this ||
+ (obj instanceof ByteArrayResource && Arrays.equals(((ByteArrayResource) obj).byteArray, this.byteArray)));
+ }
+
+ /**
+ * This implementation returns the hash code based on the
+ * underlying byte array.
+ */
+ public int hashCode() {
+ return (byte[].class.hashCode() * 29 * this.byteArray.length);
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/ClassPathResource.java b/org.springframework.core/src/main/java/org/springframework/core/io/ClassPathResource.java
new file mode 100644
index 00000000000..b803ad77ed2
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/ClassPathResource.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core.io;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.ResourceUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link Resource} implementation for class path resources.
+ * Uses either a given ClassLoader or a given Class for loading resources.
+ *
+ *
Supports resolution as java.io.File if the class path
+ * resource resides in the file system, but not for resources in a JAR.
+ * Always supports resolution as URL.
+ *
+ * @author Juergen Hoeller
+ * @since 28.12.2003
+ * @see java.lang.ClassLoader#getResourceAsStream(String)
+ * @see java.lang.Class#getResourceAsStream(String)
+ */
+public class ClassPathResource extends AbstractResource {
+
+ private final String path;
+
+ private ClassLoader classLoader;
+
+ private Class clazz;
+
+
+ /**
+ * Create a new ClassPathResource for ClassLoader usage.
+ * A leading slash will be removed, as the ClassLoader
+ * resource access methods will not accept it.
+ *
The thread context class loader will be used for
+ * loading the resource.
+ * @param path the absolute path within the class path
+ * @see java.lang.ClassLoader#getResourceAsStream(String)
+ * @see org.springframework.util.ClassUtils#getDefaultClassLoader()
+ */
+ public ClassPathResource(String path) {
+ this(path, (ClassLoader) null);
+ }
+
+ /**
+ * Create a new ClassPathResource for ClassLoader usage.
+ * A leading slash will be removed, as the ClassLoader
+ * resource access methods will not accept it.
+ * @param path the absolute path within the classpath
+ * @param classLoader the class loader to load the resource with,
+ * or null for the thread context class loader
+ * @see java.lang.ClassLoader#getResourceAsStream(String)
+ */
+ public ClassPathResource(String path, ClassLoader classLoader) {
+ Assert.notNull(path, "Path must not be null");
+ String pathToUse = StringUtils.cleanPath(path);
+ if (pathToUse.startsWith("/")) {
+ pathToUse = pathToUse.substring(1);
+ }
+ this.path = pathToUse;
+ this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
+ }
+
+ /**
+ * Create a new ClassPathResource for Class usage.
+ * The path can be relative to the given class,
+ * or absolute within the classpath via a leading slash.
+ * @param path relative or absolute path within the class path
+ * @param clazz the class to load resources with
+ * @see java.lang.Class#getResourceAsStream
+ */
+ public ClassPathResource(String path, Class clazz) {
+ Assert.notNull(path, "Path must not be null");
+ this.path = StringUtils.cleanPath(path);
+ this.clazz = clazz;
+ }
+
+ /**
+ * Create a new ClassPathResource with optional ClassLoader and Class.
+ * Only for internal usage.
+ * @param path relative or absolute path within the classpath
+ * @param classLoader the class loader to load the resource with, if any
+ * @param clazz the class to load resources with, if any
+ */
+ protected ClassPathResource(String path, ClassLoader classLoader, Class clazz) {
+ this.path = StringUtils.cleanPath(path);
+ this.classLoader = classLoader;
+ this.clazz = clazz;
+ }
+
+
+ /**
+ * Return the path for this resource (as resource path within the class path).
+ */
+ public final String getPath() {
+ return this.path;
+ }
+
+ /**
+ * Return the ClassLoader that this resource will be obtained from.
+ */
+ public final ClassLoader getClassLoader() {
+ return (this.classLoader != null ? this.classLoader : this.clazz.getClassLoader());
+ }
+
+
+ /**
+ * This implementation opens an InputStream for the given class path resource.
+ * @see java.lang.ClassLoader#getResourceAsStream(String)
+ * @see java.lang.Class#getResourceAsStream(String)
+ */
+ public InputStream getInputStream() throws IOException {
+ InputStream is = null;
+ if (this.clazz != null) {
+ is = this.clazz.getResourceAsStream(this.path);
+ }
+ else {
+ is = this.classLoader.getResourceAsStream(this.path);
+ }
+ if (is == null) {
+ throw new FileNotFoundException(
+ getDescription() + " cannot be opened because it does not exist");
+ }
+ return is;
+ }
+
+ /**
+ * This implementation returns a URL for the underlying class path resource.
+ * @see java.lang.ClassLoader#getResource(String)
+ * @see java.lang.Class#getResource(String)
+ */
+ public URL getURL() throws IOException {
+ URL url = null;
+ if (this.clazz != null) {
+ url = this.clazz.getResource(this.path);
+ }
+ else {
+ url = this.classLoader.getResource(this.path);
+ }
+ if (url == null) {
+ throw new FileNotFoundException(
+ getDescription() + " cannot be resolved to URL because it does not exist");
+ }
+ return url;
+ }
+
+ /**
+ * This implementation returns a File reference for the underlying class path
+ * resource, provided that it refers to a file in the file system.
+ * @see org.springframework.util.ResourceUtils#getFile(java.net.URL, String)
+ */
+ public File getFile() throws IOException {
+ return ResourceUtils.getFile(getURL(), getDescription());
+ }
+
+ /**
+ * This implementation determines the underlying File
+ * (or jar file, in case of a resource in a jar/zip).
+ */
+ protected File getFileForLastModifiedCheck() throws IOException {
+ URL url = getURL();
+ if (ResourceUtils.isJarURL(url)) {
+ URL actualUrl = ResourceUtils.extractJarFileURL(url);
+ return ResourceUtils.getFile(actualUrl);
+ }
+ else {
+ return ResourceUtils.getFile(url, getDescription());
+ }
+ }
+
+ /**
+ * This implementation creates a ClassPathResource, applying the given path
+ * relative to the path of the underlying resource of this descriptor.
+ * @see org.springframework.util.StringUtils#applyRelativePath(String, String)
+ */
+ public Resource createRelative(String relativePath) {
+ String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
+ return new ClassPathResource(pathToUse, this.classLoader, this.clazz);
+ }
+
+ /**
+ * This implementation returns the name of the file that this class path
+ * resource refers to.
+ * @see org.springframework.util.StringUtils#getFilename(String)
+ */
+ public String getFilename() {
+ return StringUtils.getFilename(this.path);
+ }
+
+ /**
+ * This implementation returns a description that includes the class path location.
+ */
+ public String getDescription() {
+ return "class path resource [" + this.path + "]";
+ }
+
+
+ /**
+ * This implementation compares the underlying class path locations.
+ */
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof ClassPathResource) {
+ ClassPathResource otherRes = (ClassPathResource) obj;
+ return (this.path.equals(otherRes.path) &&
+ ObjectUtils.nullSafeEquals(this.classLoader, otherRes.classLoader) &&
+ ObjectUtils.nullSafeEquals(this.clazz, otherRes.clazz));
+ }
+ return false;
+ }
+
+ /**
+ * This implementation returns the hash code of the underlying
+ * class path location.
+ */
+ public int hashCode() {
+ return this.path.hashCode();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/ContextResource.java b/org.springframework.core/src/main/java/org/springframework/core/io/ContextResource.java
new file mode 100644
index 00000000000..0493cfa7a00
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/ContextResource.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.io;
+
+/**
+ * Extended interface for a resource that is loaded from an enclosing
+ * 'context', e.g. from a {@link javax.servlet.ServletContext} or a
+ * {@link javax.portlet.PortletContext} but also from plain classpath paths
+ * or relative file system paths (specified without an explicit prefix,
+ * hence applying relative to the local {@link ResourceLoader}'s context).
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see org.springframework.web.context.support.ServletContextResource
+ * @see org.springframework.web.portlet.context.PortletContextResource
+ */
+public interface ContextResource extends Resource {
+
+ /**
+ * Return the path within the enclosing 'context'.
+ *
This is typically path relative to a context-specific root directory,
+ * e.g. a ServletContext root or a PortletContext root.
+ */
+ String getPathWithinContext();
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java b/org.springframework.core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java
new file mode 100644
index 00000000000..6baf59498f1
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core.io;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Default implementation of the {@link ResourceLoader} interface.
+ * Used by {@link ResourceEditor}, and serves as base class for
+ * {@link org.springframework.context.support.AbstractApplicationContext}.
+ * Can also be used standalone.
+ *
+ *
Will return a {@link UrlResource} if the location value is a URL,
+ * and a {@link ClassPathResource} if it is a non-URL path or a
+ * "classpath:" pseudo-URL.
+ *
+ * @author Juergen Hoeller
+ * @since 10.03.2004
+ * @see FileSystemResourceLoader
+ * @see org.springframework.context.support.ClassPathXmlApplicationContext
+ */
+public class DefaultResourceLoader implements ResourceLoader {
+
+ private ClassLoader classLoader;
+
+
+ /**
+ * Create a new DefaultResourceLoader.
+ *
ClassLoader access will happen using the thread context class loader
+ * at the time of this ResourceLoader's initialization.
+ * @see java.lang.Thread#getContextClassLoader()
+ */
+ public DefaultResourceLoader() {
+ this.classLoader = ClassUtils.getDefaultClassLoader();
+ }
+
+ /**
+ * Create a new DefaultResourceLoader.
+ * @param classLoader the ClassLoader to load class path resources with, or null
+ * for using the thread context class loader at the time of actual resource access
+ */
+ public DefaultResourceLoader(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+
+ /**
+ * Specify the ClassLoader to load class path resources with, or null
+ * for using the thread context class loader at the time of actual resource access.
+ *
The default is that ClassLoader access will happen using the thread context
+ * class loader at the time of this ResourceLoader's initialization.
+ */
+ public void setClassLoader(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+ /**
+ * Return the ClassLoader to load class path resources with,
+ * or null if using the thread context class loader on actual access
+ * (applying to the thread that constructs the ClassPathResource object).
+ *
Will get passed to ClassPathResource's constructor for all
+ * ClassPathResource objects created by this resource loader.
+ * @see ClassPathResource
+ */
+ public ClassLoader getClassLoader() {
+ return this.classLoader;
+ }
+
+
+ public Resource getResource(String location) {
+ Assert.notNull(location, "Location must not be null");
+ if (location.startsWith(CLASSPATH_URL_PREFIX)) {
+ return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
+ }
+ else {
+ try {
+ // Try to parse the location as a URL...
+ URL url = new URL(location);
+ return new UrlResource(url);
+ }
+ catch (MalformedURLException ex) {
+ // No URL -> resolve as resource path.
+ return getResourceByPath(location);
+ }
+ }
+ }
+
+ /**
+ * Return a Resource handle for the resource at the given path.
+ *
The default implementation supports class path locations. This should
+ * be appropriate for standalone implementations but can be overridden,
+ * e.g. for implementations targeted at a Servlet container.
+ * @param path the path to the resource
+ * @return the corresponding Resource handle
+ * @see ClassPathResource
+ * @see org.springframework.context.support.FileSystemXmlApplicationContext#getResourceByPath
+ * @see org.springframework.web.context.support.XmlWebApplicationContext#getResourceByPath
+ */
+ protected Resource getResourceByPath(String path) {
+ return new ClassPathContextResource(path, getClassLoader());
+ }
+
+
+ /**
+ * ClassPathResource that explicitly expresses a context-relative path
+ * through implementing the ContextResource interface.
+ */
+ private static class ClassPathContextResource extends ClassPathResource implements ContextResource {
+
+ public ClassPathContextResource(String path, ClassLoader classLoader) {
+ super(path, classLoader);
+ }
+
+ public String getPathWithinContext() {
+ return getPath();
+ }
+
+ public Resource createRelative(String relativePath) {
+ String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
+ return new ClassPathContextResource(pathToUse, getClassLoader());
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/DescriptiveResource.java b/org.springframework.core/src/main/java/org/springframework/core/io/DescriptiveResource.java
new file mode 100644
index 00000000000..cab0c6a5d78
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/DescriptiveResource.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core.io;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Simple {@link Resource} implementation that holds a resource description
+ * but does not point to an actually readable resource.
+ *
+ *
To be used as placeholder if a Resource argument is
+ * expected by an API but not necessarily used for actual reading.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2.6
+ */
+public class DescriptiveResource extends AbstractResource {
+
+ private final String description;
+
+
+ /**
+ * Create a new DescriptiveResource.
+ * @param description the resource description
+ */
+ public DescriptiveResource(String description) {
+ this.description = (description != null ? description : "");
+ }
+
+
+ public boolean exists() {
+ return false;
+ }
+
+ public boolean isReadable() {
+ return false;
+ }
+
+ public InputStream getInputStream() throws IOException {
+ throw new FileNotFoundException(
+ getDescription() + " cannot be opened because it does not point to a readable resource");
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+
+ /**
+ * This implementation compares the underlying description String.
+ */
+ public boolean equals(Object obj) {
+ return (obj == this ||
+ (obj instanceof DescriptiveResource && ((DescriptiveResource) obj).description.equals(this.description)));
+ }
+
+ /**
+ * This implementation returns the hash code of the underlying description String.
+ */
+ public int hashCode() {
+ return this.description.hashCode();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/FileSystemResource.java b/org.springframework.core/src/main/java/org/springframework/core/io/FileSystemResource.java
new file mode 100644
index 00000000000..ef063870348
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/FileSystemResource.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core.io;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link Resource} implementation for java.io.File handles.
+ * Obviously supports resolution as File, and also as URL.
+ *
+ * @author Juergen Hoeller
+ * @since 28.12.2003
+ * @see java.io.File
+ */
+public class FileSystemResource extends AbstractResource {
+
+ private final File file;
+
+ private final String path;
+
+
+ /**
+ * Create a new FileSystemResource from a File handle.
+ *
Note: When building relative resources via {@link #createRelative},
+ * the relative path will apply at the same directory level:
+ * e.g. new File("C:/dir1"), relative path "dir2" -> "C:/dir2"!
+ * If you prefer to have relative paths built underneath the given root
+ * directory, use the {@link #FileSystemResource(String) constructor with a file path}
+ * to append a trailing slash to the root path: "C:/dir1/", which
+ * indicates this directory as root for all relative paths.
+ * @param file a File handle
+ */
+ public FileSystemResource(File file) {
+ Assert.notNull(file, "File must not be null");
+ this.file = file;
+ this.path = StringUtils.cleanPath(file.getPath());
+ }
+
+ /**
+ * Create a new FileSystemResource from a file path.
+ *
Note: When building relative resources via {@link #createRelative},
+ * it makes a difference whether the specified resource base path here
+ * ends with a slash or not. In the case of "C:/dir1/", relative paths
+ * will be built underneath that root: e.g. relative path "dir2" ->
+ * "C:/dir1/dir2". In the case of "C:/dir1", relative paths will apply
+ * at the same directory level: relative path "dir2" -> "C:/dir2".
+ * @param path a file path
+ */
+ public FileSystemResource(String path) {
+ Assert.notNull(path, "Path must not be null");
+ this.file = new File(path);
+ this.path = StringUtils.cleanPath(path);
+ }
+
+ /**
+ * Return the file path for this resource.
+ */
+ public final String getPath() {
+ return this.path;
+ }
+
+
+ /**
+ * This implementation returns whether the underlying file exists.
+ * @see java.io.File#exists()
+ */
+ public boolean exists() {
+ return this.file.exists();
+ }
+
+ /**
+ * This implementation checks whether the underlying file is marked as readable
+ * (and corresponds to an actual file with content, not to a directory).
+ * @see java.io.File#canRead()
+ * @see java.io.File#isDirectory()
+ */
+ public boolean isReadable() {
+ return (this.file.canRead() && !this.file.isDirectory());
+ }
+
+ /**
+ * This implementation opens a FileInputStream for the underlying file.
+ * @see java.io.FileInputStream
+ */
+ public InputStream getInputStream() throws IOException {
+ return new FileInputStream(this.file);
+ }
+
+ /**
+ * This implementation returns a URL for the underlying file.
+ * @see java.io.File#toURI()
+ */
+ public URL getURL() throws IOException {
+ return this.file.toURI().toURL();
+ }
+
+ /**
+ * This implementation returns a URI for the underlying file.
+ * @see java.io.File#toURI()
+ */
+ public URI getURI() throws IOException {
+ return this.file.toURI();
+ }
+
+ /**
+ * This implementation returns the underlying File reference.
+ */
+ public File getFile() {
+ return this.file;
+ }
+
+ /**
+ * This implementation creates a FileSystemResource, applying the given path
+ * relative to the path of the underlying file of this resource descriptor.
+ * @see org.springframework.util.StringUtils#applyRelativePath(String, String)
+ */
+ public Resource createRelative(String relativePath) {
+ String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
+ return new FileSystemResource(pathToUse);
+ }
+
+ /**
+ * This implementation returns the name of the file.
+ * @see java.io.File#getName()
+ */
+ public String getFilename() {
+ return this.file.getName();
+ }
+
+ /**
+ * This implementation returns a description that includes the absolute
+ * path of the file.
+ * @see java.io.File#getAbsolutePath()
+ */
+ public String getDescription() {
+ return "file [" + this.file.getAbsolutePath() + "]";
+ }
+
+
+ /**
+ * This implementation compares the underlying File references.
+ */
+ public boolean equals(Object obj) {
+ return (obj == this ||
+ (obj instanceof FileSystemResource && this.path.equals(((FileSystemResource) obj).path)));
+ }
+
+ /**
+ * This implementation returns the hash code of the underlying File reference.
+ */
+ public int hashCode() {
+ return this.path.hashCode();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/FileSystemResourceLoader.java b/org.springframework.core/src/main/java/org/springframework/core/io/FileSystemResourceLoader.java
new file mode 100644
index 00000000000..30891d18966
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/FileSystemResourceLoader.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.io;
+
+/**
+ * {@link ResourceLoader} implementation that resolves plain paths as
+ * file system resources rather than as class path resources
+ * (the latter is {@link DefaultResourceLoader}'s default strategy).
+ *
+ *
NOTE: Plain paths will always be interpreted as relative
+ * to the current VM working directory, even if they start with a slash.
+ * (This is consistent with the semantics in a Servlet container.)
+ * Use an explicit "file:" prefix to enforce an absolute file path.
+ *
+ *
{@link org.springframework.context.support.FileSystemXmlApplicationContext}
+ * is a full-fledged ApplicationContext implementation that provides
+ * the same resource path resolution strategy.
+ *
+ * @author Juergen Hoeller
+ * @since 1.1.3
+ * @see DefaultResourceLoader
+ * @see org.springframework.context.support.FileSystemXmlApplicationContext
+ */
+public class FileSystemResourceLoader extends DefaultResourceLoader {
+
+ /**
+ * Resolve resource paths as file system paths.
+ *
Note: Even if a given path starts with a slash, it will get
+ * interpreted as relative to the current VM working directory.
+ * @param path the path to the resource
+ * @return the corresponding Resource handle
+ * @see FileSystemResource
+ * @see org.springframework.web.context.support.ServletContextResourceLoader#getResourceByPath
+ */
+ protected Resource getResourceByPath(String path) {
+ if (path != null && path.startsWith("/")) {
+ path = path.substring(1);
+ }
+ return new FileSystemContextResource(path);
+ }
+
+
+ /**
+ * FileSystemResource that explicitly expresses a context-relative path
+ * through implementing the ContextResource interface.
+ */
+ private static class FileSystemContextResource extends FileSystemResource implements ContextResource {
+
+ public FileSystemContextResource(String path) {
+ super(path);
+ }
+
+ public String getPathWithinContext() {
+ return getPath();
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/InputStreamResource.java b/org.springframework.core/src/main/java/org/springframework/core/io/InputStreamResource.java
new file mode 100644
index 00000000000..e42edef6acf
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/InputStreamResource.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * 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.springframework.core.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * {@link Resource} implementation for a given InputStream. Should only
+ * be used if no specific Resource implementation is applicable.
+ * In particular, prefer {@link ByteArrayResource} or any of the
+ * file-based Resource implementations where possible.
+ *
+ *
In contrast to other Resource implementations, this is a descriptor
+ * for an already opened resource - therefore returning "true" from
+ * isOpen(). Do not use it if you need to keep the resource
+ * descriptor somewhere, or if you need to read a stream multiple times.
+ *
+ * @author Juergen Hoeller
+ * @since 28.12.2003
+ * @see ByteArrayResource
+ * @see ClassPathResource
+ * @see FileSystemResource
+ * @see UrlResource
+ */
+public class InputStreamResource extends AbstractResource {
+
+ private final InputStream inputStream;
+
+ private final String description;
+
+ private boolean read = false;
+
+
+ /**
+ * Create a new InputStreamResource.
+ * @param inputStream the InputStream to use
+ */
+ public InputStreamResource(InputStream inputStream) {
+ this(inputStream, "resource loaded through InputStream");
+ }
+
+ /**
+ * Create a new InputStreamResource.
+ * @param inputStream the InputStream to use
+ * @param description where the InputStream comes from
+ */
+ public InputStreamResource(InputStream inputStream, String description) {
+ if (inputStream == null) {
+ throw new IllegalArgumentException("InputStream must not be null");
+ }
+ this.inputStream = inputStream;
+ this.description = (description != null ? description : "");
+ }
+
+
+ /**
+ * This implementation always returns true.
+ */
+ public boolean exists() {
+ return true;
+ }
+
+ /**
+ * This implementation always returns true.
+ */
+ public boolean isOpen() {
+ return true;
+ }
+
+ /**
+ * This implementation throws IllegalStateException if attempting to
+ * read the underlying stream multiple times.
+ */
+ public InputStream getInputStream() throws IOException, IllegalStateException {
+ if (this.read) {
+ throw new IllegalStateException("InputStream has already been read - " +
+ "do not use InputStreamResource if a stream needs to be read multiple times");
+ }
+ this.read = true;
+ return this.inputStream;
+ }
+
+ /**
+ * This implementation returns the passed-in description, if any.
+ */
+ public String getDescription() {
+ return this.description;
+ }
+
+
+ /**
+ * This implementation compares the underlying InputStream.
+ */
+ public boolean equals(Object obj) {
+ return (obj == this ||
+ (obj instanceof InputStreamResource && ((InputStreamResource) obj).inputStream.equals(this.inputStream)));
+ }
+
+ /**
+ * This implementation returns the hash code of the underlying InputStream.
+ */
+ public int hashCode() {
+ return this.inputStream.hashCode();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/InputStreamSource.java b/org.springframework.core/src/main/java/org/springframework/core/io/InputStreamSource.java
new file mode 100644
index 00000000000..33bac479152
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/InputStreamSource.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Simple interface for objects that are sources for an {@link InputStream}.
+ *
+ *
This is the base interface for Spring's more extensive {@link Resource} interface.
+ *
+ *
For single-use streams, {@link InputStreamResource} can be used for any
+ * given InputStream. Spring's {@link ByteArrayResource} or any
+ * file-based Resource implementation can be used as a concrete
+ * instance, allowing one to read the underlying content stream multiple times.
+ * This makes this interface useful as an abstract content source for mail
+ * attachments, for example.
+ *
+ * @author Juergen Hoeller
+ * @since 20.01.2004
+ * @see java.io.InputStream
+ * @see Resource
+ * @see InputStreamResource
+ * @see ByteArrayResource
+ */
+public interface InputStreamSource {
+
+ /**
+ * Return an {@link InputStream}.
+ *
It is expected that each call creates a fresh stream.
+ *
This requirement is particularly important when you consider an API such
+ * as JavaMail, which needs to be able to read the stream multiple times when
+ * creating mail attachments. For such a use case, it is required
+ * that each getInputStream() call returns a fresh stream.
+ * @throws IOException if the stream could not be opened
+ * @see org.springframework.mail.javamail.MimeMessageHelper#addAttachment(String, InputStreamSource)
+ */
+ InputStream getInputStream() throws IOException;
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/Resource.java b/org.springframework.core/src/main/java/org/springframework/core/io/Resource.java
new file mode 100644
index 00000000000..bd75d8900c9
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/Resource.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+
+/**
+ * Interface for a resource descriptor that abstracts from the actual
+ * type of underlying resource, such as a file or class path resource.
+ *
+ *
An InputStream can be opened for every resource if it exists in
+ * physical form, but a URL or File handle can just be returned for
+ * certain resources. The actual behavior is implementation-specific.
+ *
+ * @author Juergen Hoeller
+ * @since 28.12.2003
+ * @see #getInputStream()
+ * @see #getURL()
+ * @see #getURI()
+ * @see #getFile()
+ * @see FileSystemResource
+ * @see ClassPathResource
+ * @see UrlResource
+ * @see ByteArrayResource
+ * @see InputStreamResource
+ * @see org.springframework.web.context.support.ServletContextResource
+ */
+public interface Resource extends InputStreamSource {
+
+ /**
+ * Return whether this resource actually exists in physical form.
+ *
This method performs a definitive existence check, whereas the
+ * existence of a Resource handle only guarantees a
+ * valid descriptor handle.
+ */
+ boolean exists();
+
+ /**
+ * Return whether the contents of this resource can be read,
+ * e.g. via {@link #getInputStream()} or {@link #getFile()}.
+ *
Will be true for typical resource descriptors;
+ * note that actual content reading may still fail when attempted.
+ * However, a value of false is a definitive indication
+ * that the resource content cannot be read.
+ */
+ boolean isReadable();
+
+ /**
+ * Return whether this resource represents a handle with an open
+ * stream. If true, the InputStream cannot be read multiple times,
+ * and must be read and closed to avoid resource leaks.
+ *
Will be false for typical resource descriptors.
+ */
+ boolean isOpen();
+
+ /**
+ * Return a URL handle for this resource.
+ * @throws IOException if the resource cannot be resolved as URL,
+ * i.e. if the resource is not available as descriptor
+ */
+ URL getURL() throws IOException;
+
+ /**
+ * Return a URI handle for this resource.
+ * @throws IOException if the resource cannot be resolved as URI,
+ * i.e. if the resource is not available as descriptor
+ */
+ URI getURI() throws IOException;
+
+ /**
+ * Return a File handle for this resource.
+ * @throws IOException if the resource cannot be resolved as absolute
+ * file path, i.e. if the resource is not available in a file system
+ */
+ File getFile() throws IOException;
+
+ /**
+ * Determine the last-modified timestamp for this resource.
+ * @throws IOException if the resource cannot be resolved
+ * (in the file system or as some other known physical resource type)
+ */
+ long lastModified() throws IOException;
+
+ /**
+ * Create a resource relative to this resource.
+ * @param relativePath the relative path (relative to this resource)
+ * @return the resource handle for the relative resource
+ * @throws IOException if the relative resource cannot be determined
+ */
+ Resource createRelative(String relativePath) throws IOException;
+
+ /**
+ * Return a filename for this resource, i.e. typically the last
+ * part of the path: for example, "myfile.txt".
+ */
+ String getFilename();
+
+ /**
+ * Return a description for this resource,
+ * to be used for error output when working with the resource.
+ *
Implementations are also encouraged to return this value
+ * from their toString method.
+ * @see java.lang.Object#toString()
+ */
+ String getDescription();
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/ResourceEditor.java b/org.springframework.core/src/main/java/org/springframework/core/io/ResourceEditor.java
new file mode 100644
index 00000000000..9d53130c02a
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/ResourceEditor.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.io;
+
+import java.beans.PropertyEditorSupport;
+import java.io.IOException;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+import org.springframework.util.SystemPropertyUtils;
+
+/**
+ * {@link java.beans.PropertyEditor Editor} for {@link Resource}
+ * descriptors, to automatically convert String locations
+ * e.g. "file:C:/myfile.txt" or
+ * "classpath:myfile.txt") to Resource
+ * properties instead of using a String location property.
+ *
+ *
The path may contain ${...} placeholders, to be resolved
+ * as system properties: e.g. ${user.dir}.
+ *
+ *
Delegates to a {@link ResourceLoader} to do the heavy lifting,
+ * by default using a {@link DefaultResourceLoader}.
+ *
+ * @author Juergen Hoeller
+ * @since 28.12.2003
+ * @see Resource
+ * @see ResourceLoader
+ * @see DefaultResourceLoader
+ * @see org.springframework.util.SystemPropertyUtils#resolvePlaceholders
+ * @see System#getProperty(String)
+ */
+public class ResourceEditor extends PropertyEditorSupport {
+
+ private final ResourceLoader resourceLoader;
+
+
+ /**
+ * Create a new instance of the {@link ResourceEditor} class
+ * using a {@link DefaultResourceLoader}.
+ */
+ public ResourceEditor() {
+ this(new DefaultResourceLoader());
+ }
+
+ /**
+ * Create a new instance of the {@link ResourceEditor} class
+ * using the given {@link ResourceLoader}.
+ * @param resourceLoader the ResourceLoader to use
+ */
+ public ResourceEditor(ResourceLoader resourceLoader) {
+ Assert.notNull(resourceLoader, "ResourceLoader must not be null");
+ this.resourceLoader = resourceLoader;
+ }
+
+
+ public void setAsText(String text) {
+ if (StringUtils.hasText(text)) {
+ String locationToUse = resolvePath(text).trim();
+ setValue(this.resourceLoader.getResource(locationToUse));
+ }
+ else {
+ setValue(null);
+ }
+ }
+
+ /**
+ * Resolve the given path, replacing placeholders with
+ * corresponding system property values if necessary.
+ * @param path the original file path
+ * @return the resolved file path
+ * @see org.springframework.util.SystemPropertyUtils#resolvePlaceholders
+ */
+ protected String resolvePath(String path) {
+ return SystemPropertyUtils.resolvePlaceholders(path);
+ }
+
+
+ public String getAsText() {
+ Resource value = (Resource) getValue();
+ try {
+ // Try to determine URL for resource.
+ return (value != null ? value.getURL().toExternalForm() : "");
+ }
+ catch (IOException ex) {
+ // Couldn't determine resource URL - return null to indicate
+ // that there is no appropriate text representation.
+ return null;
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/ResourceLoader.java b/org.springframework.core/src/main/java/org/springframework/core/io/ResourceLoader.java
new file mode 100644
index 00000000000..3efd0e5cf9d
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/ResourceLoader.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.io;
+
+import org.springframework.util.ResourceUtils;
+
+/**
+ * Strategy interface for loading resources (e.. class path or file system
+ * resources). An {@link org.springframework.context.ApplicationContext}
+ * is required to provide this functionality, plus extended
+ * {@link org.springframework.core.io.support.ResourcePatternResolver} support.
+ *
+ *
{@link DefaultResourceLoader} is a standalone implementation that is
+ * usable outside an ApplicationContext, also used by {@link ResourceEditor}.
+ *
+ *
Bean properties of type Resource and Resource array can be populated
+ * from Strings when running in an ApplicationContext, using the particular
+ * context's resource loading strategy.
+ *
+ * @author Juergen Hoeller
+ * @since 10.03.2004
+ * @see Resource
+ * @see org.springframework.core.io.support.ResourcePatternResolver
+ * @see org.springframework.context.ApplicationContext
+ * @see org.springframework.context.ResourceLoaderAware
+ */
+public interface ResourceLoader {
+
+ /** Pseudo URL prefix for loading from the class path: "classpath:" */
+ String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
+
+
+ /**
+ * Return a Resource handle for the specified resource.
+ * The handle should always be a reusable resource descriptor,
+ * allowing for multiple {@link Resource#getInputStream()} calls.
+ *
+ * - Must support fully qualified URLs, e.g. "file:C:/test.dat".
+ *
- Must support classpath pseudo-URLs, e.g. "classpath:test.dat".
+ *
- Should support relative file paths, e.g. "WEB-INF/test.dat".
+ * (This will be implementation-specific, typically provided by an
+ * ApplicationContext implementation.)
+ *
+ * Note that a Resource handle does not imply an existing resource;
+ * you need to invoke {@link Resource#exists} to check for existence.
+ * @param location the resource location
+ * @return a corresponding Resource handle
+ * @see #CLASSPATH_URL_PREFIX
+ * @see org.springframework.core.io.Resource#exists
+ * @see org.springframework.core.io.Resource#getInputStream
+ */
+ Resource getResource(String location);
+
+ /**
+ * Expose the ClassLoader used by this ResourceLoader.
+ *
Clients which need to access the ClassLoader directly can do so
+ * in a uniform manner with the ResourceLoader, rather than relying
+ * on the thread context ClassLoader.
+ * @return the ClassLoader (never null)
+ */
+ ClassLoader getClassLoader();
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/UrlResource.java b/org.springframework.core/src/main/java/org/springframework/core/io/UrlResource.java
new file mode 100644
index 00000000000..0627cb09ddf
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/UrlResource.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ResourceUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link Resource} implementation for java.net.URL locators.
+ * Obviously supports resolution as URL, and also as File in case of
+ * the "file:" protocol.
+ *
+ * @author Juergen Hoeller
+ * @since 28.12.2003
+ * @see java.net.URL
+ */
+public class UrlResource extends AbstractResource {
+
+ /**
+ * Original URL, used for actual access.
+ */
+ private final URL url;
+
+ /**
+ * Cleaned URL (with normalized path), used for comparisons.
+ */
+ private final URL cleanedUrl;
+
+ /**
+ * Original URI, if available; used for URI and File access.
+ */
+ private final URI uri;
+
+
+ /**
+ * Create a new UrlResource.
+ * @param url a URL
+ */
+ public UrlResource(URL url) {
+ Assert.notNull(url, "URL must not be null");
+ this.url = url;
+ this.cleanedUrl = getCleanedUrl(this.url, url.toString());
+ this.uri = null;
+ }
+
+ /**
+ * Create a new UrlResource.
+ * @param uri a URI
+ * @throws MalformedURLException if the given URL path is not valid
+ */
+ public UrlResource(URI uri) throws MalformedURLException {
+ Assert.notNull(uri, "URI must not be null");
+ this.url = uri.toURL();
+ this.cleanedUrl = getCleanedUrl(this.url, uri.toString());
+ this.uri = uri;
+ }
+
+ /**
+ * Create a new UrlResource.
+ * @param path a URL path
+ * @throws MalformedURLException if the given URL path is not valid
+ */
+ public UrlResource(String path) throws MalformedURLException {
+ Assert.notNull(path, "Path must not be null");
+ this.url = new URL(path);
+ this.cleanedUrl = getCleanedUrl(this.url, path);
+ this.uri = null;
+ }
+
+ /**
+ * Determine a cleaned URL for the given original URL.
+ * @param originalUrl the original URL
+ * @param originalPath the original URL path
+ * @return the cleaned URL
+ * @see org.springframework.util.StringUtils#cleanPath
+ */
+ private URL getCleanedUrl(URL originalUrl, String originalPath) {
+ try {
+ return new URL(StringUtils.cleanPath(originalPath));
+ }
+ catch (MalformedURLException ex) {
+ // Cleaned URL path cannot be converted to URL
+ // -> take original URL.
+ return originalUrl;
+ }
+ }
+
+
+ /**
+ * This implementation opens an InputStream for the given URL.
+ * It sets the "UseCaches" flag to false,
+ * mainly to avoid jar file locking on Windows.
+ * @see java.net.URL#openConnection()
+ * @see java.net.URLConnection#setUseCaches(boolean)
+ * @see java.net.URLConnection#getInputStream()
+ */
+ public InputStream getInputStream() throws IOException {
+ URLConnection con = this.url.openConnection();
+ con.setUseCaches(false);
+ return con.getInputStream();
+ }
+
+ /**
+ * This implementation returns the underlying URL reference.
+ */
+ public URL getURL() throws IOException {
+ return this.url;
+ }
+
+ /**
+ * This implementation returns the underlying URI directly,
+ * if possible.
+ */
+ public URI getURI() throws IOException {
+ if (this.uri != null) {
+ return this.uri;
+ }
+ else {
+ return super.getURI();
+ }
+ }
+
+ /**
+ * This implementation returns a File reference for the underlying URL/URI,
+ * provided that it refers to a file in the file system.
+ * @see org.springframework.util.ResourceUtils#getFile(java.net.URL, String)
+ */
+ public File getFile() throws IOException {
+ if (this.uri != null) {
+ return ResourceUtils.getFile(this.uri, getDescription());
+ }
+ else {
+ return ResourceUtils.getFile(this.url, getDescription());
+ }
+ }
+
+ /**
+ * This implementation determines the underlying File
+ * (or jar file, in case of a resource in a jar/zip).
+ */
+ protected File getFileForLastModifiedCheck() throws IOException {
+ if (ResourceUtils.isJarURL(this.url)) {
+ URL actualUrl = ResourceUtils.extractJarFileURL(this.url);
+ return ResourceUtils.getFile(actualUrl);
+ }
+ else {
+ return getFile();
+ }
+ }
+
+ /**
+ * This implementation creates a UrlResource, applying the given path
+ * relative to the path of the underlying URL of this resource descriptor.
+ * @see java.net.URL#URL(java.net.URL, String)
+ */
+ public Resource createRelative(String relativePath) throws MalformedURLException {
+ if (relativePath.startsWith("/")) {
+ relativePath = relativePath.substring(1);
+ }
+ return new UrlResource(new URL(this.url, relativePath));
+ }
+
+ /**
+ * This implementation returns the name of the file that this URL refers to.
+ * @see java.net.URL#getFile()
+ * @see java.io.File#getName()
+ */
+ public String getFilename() {
+ return new File(this.url.getFile()).getName();
+ }
+
+ /**
+ * This implementation returns a description that includes the URL.
+ */
+ public String getDescription() {
+ return "URL [" + this.url + "]";
+ }
+
+
+ /**
+ * This implementation compares the underlying URL references.
+ */
+ public boolean equals(Object obj) {
+ return (obj == this ||
+ (obj instanceof UrlResource && this.cleanedUrl.equals(((UrlResource) obj).cleanedUrl)));
+ }
+
+ /**
+ * This implementation returns the hash code of the underlying URL reference.
+ */
+ public int hashCode() {
+ return this.cleanedUrl.hashCode();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/package.html b/org.springframework.core/src/main/java/org/springframework/core/io/package.html
new file mode 100644
index 00000000000..b5c30df09b8
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/package.html
@@ -0,0 +1,7 @@
+
+
+
+Generic abstraction for (file-based) resources, used throughout the framework.
+
+
+
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/support/EncodedResource.java b/org.springframework.core/src/main/java/org/springframework/core/io/support/EncodedResource.java
new file mode 100644
index 00000000000..4e680d1193f
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/support/EncodedResource.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.io.support;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+import org.springframework.core.io.Resource;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Holder that combines a {@link org.springframework.core.io.Resource}
+ * with a specific encoding to be used for reading from the resource.
+ *
+ * Used as argument for operations that support to read content with
+ * a specific encoding (usually through a java.io.Reader.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2.6
+ * @see java.io.Reader
+ */
+public class EncodedResource {
+
+ private final Resource resource;
+
+ private final String encoding;
+
+
+ /**
+ * Create a new EncodedResource for the given Resource,
+ * not specifying a specific encoding.
+ * @param resource the Resource to hold
+ */
+ public EncodedResource(Resource resource) {
+ this(resource, null);
+ }
+
+ /**
+ * Create a new EncodedResource for the given Resource,
+ * using the specified encoding.
+ * @param resource the Resource to hold
+ * @param encoding the encoding to use for reading from the resource
+ */
+ public EncodedResource(Resource resource, String encoding) {
+ Assert.notNull(resource, "Resource must not be null");
+ this.resource = resource;
+ this.encoding = encoding;
+ }
+
+
+ /**
+ * Return the Resource held.
+ */
+ public final Resource getResource() {
+ return this.resource;
+ }
+
+ /**
+ * Return the encoding to use for reading from the resource,
+ * or null if none specified.
+ */
+ public final String getEncoding() {
+ return this.encoding;
+ }
+
+ /**
+ * Open a java.io.Reader for the specified resource,
+ * using the specified encoding (if any).
+ * @throws IOException if opening the Reader failed
+ */
+ public Reader getReader() throws IOException {
+ if (this.encoding != null) {
+ return new InputStreamReader(this.resource.getInputStream(), this.encoding);
+ }
+ else {
+ return new InputStreamReader(this.resource.getInputStream());
+ }
+ }
+
+
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof EncodedResource) {
+ EncodedResource otherRes = (EncodedResource) obj;
+ return (this.resource.equals(otherRes.resource) &&
+ ObjectUtils.nullSafeEquals(this.encoding, otherRes.encoding));
+ }
+ return false;
+ }
+
+ public int hashCode() {
+ return this.resource.hashCode();
+ }
+
+ public String toString() {
+ return this.resource.toString();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/support/LocalizedResourceHelper.java b/org.springframework.core/src/main/java/org/springframework/core/io/support/LocalizedResourceHelper.java
new file mode 100644
index 00000000000..1300f76f9ad
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/support/LocalizedResourceHelper.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * 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.springframework.core.io.support;
+
+import java.util.Locale;
+
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.util.Assert;
+
+/**
+ * Helper class for loading a localized resource,
+ * specified through name, extension and current locale.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2.5
+ */
+public class LocalizedResourceHelper {
+
+ /** The default separator to use inbetween file name parts: an underscore */
+ public static final String DEFAULT_SEPARATOR = "_";
+
+
+ private final ResourceLoader resourceLoader;
+
+ private String separator = DEFAULT_SEPARATOR;
+
+
+ /**
+ * Create a new LocalizedResourceHelper with a DefaultResourceLoader.
+ * @see org.springframework.core.io.DefaultResourceLoader
+ */
+ public LocalizedResourceHelper() {
+ this.resourceLoader = new DefaultResourceLoader();
+ }
+
+ /**
+ * Create a new LocalizedResourceHelper with the given ResourceLoader.
+ * @param resourceLoader the ResourceLoader to use
+ */
+ public LocalizedResourceHelper(ResourceLoader resourceLoader) {
+ Assert.notNull(resourceLoader, "ResourceLoader must not be null");
+ this.resourceLoader = resourceLoader;
+ }
+
+ /**
+ * Set the separator to use inbetween file name parts.
+ * Default is an underscore ("_").
+ */
+ public void setSeparator(String separator) {
+ this.separator = (separator != null ? separator : DEFAULT_SEPARATOR);
+ }
+
+
+ /**
+ * Find the most specific localized resource for the given name,
+ * extension and locale:
+ *
The file will be searched with locations in the following order,
+ * similar to java.util.ResourceBundle's search order:
+ *
+ * - [name]_[language]_[country]_[variant][extension]
+ *
- [name]_[language]_[country][extension]
+ *
- [name]_[language][extension]
+ *
- [name][extension]
+ *
+ * If none of the specific files can be found, a resource
+ * descriptor for the default location will be returned.
+ * @param name the name of the file, without localization part nor extension
+ * @param extension the file extension (e.g. ".xls")
+ * @param locale the current locale (may be null)
+ * @return the most specific localized resource found
+ * @see java.util.ResourceBundle
+ */
+ public Resource findLocalizedResource(String name, String extension, Locale locale) {
+ Assert.notNull(name, "Name must not be null");
+ Assert.notNull(extension, "Extension must not be null");
+
+ Resource resource = null;
+
+ if (locale != null) {
+ String lang = locale.getLanguage();
+ String country = locale.getCountry();
+ String variant = locale.getVariant();
+
+ // Check for file with language, country and variant localization.
+ if (variant.length() > 0) {
+ String location =
+ name + this.separator + lang + this.separator + country + this.separator + variant + extension;
+ resource = this.resourceLoader.getResource(location);
+ }
+
+ // Check for file with language and country localization.
+ if ((resource == null || !resource.exists()) && country.length() > 0) {
+ String location = name + this.separator + lang + this.separator + country + extension;
+ resource = this.resourceLoader.getResource(location);
+ }
+
+ // Check for document with language localization.
+ if ((resource == null || !resource.exists()) && lang.length() > 0) {
+ String location = name + this.separator + lang + extension;
+ resource = this.resourceLoader.getResource(location);
+ }
+ }
+
+ // Check for document without localization.
+ if (resource == null || !resource.exists()) {
+ String location = name + extension;
+ resource = this.resourceLoader.getResource(location);
+ }
+
+ return resource;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java b/org.springframework.core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java
new file mode 100644
index 00000000000..0c4ff5e7bd5
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java
@@ -0,0 +1,605 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core.io.support;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.JarURLConnection;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.io.UrlResource;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.Assert;
+import org.springframework.util.PathMatcher;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.ResourceUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * A {@link ResourcePatternResolver} implementation that is able to resolve a
+ * specified resource location path into one or more matching Resources.
+ * The source path may be a simple path which has a one-to-one mapping to a
+ * target {@link org.springframework.core.io.Resource}, or alternatively
+ * may contain the special "classpath*:" prefix and/or
+ * internal Ant-style regular expressions (matched using Spring's
+ * {@link org.springframework.util.AntPathMatcher} utility).
+ * Both of the latter are effectively wildcards.
+ *
+ *
No Wildcards:
+ *
+ *
In the simple case, if the specified location path does not start with the
+ * "classpath*:" prefix, and does not contain a PathMatcher pattern,
+ * this resolver will simply return a single resource via a
+ * getResource() call on the underlying ResourceLoader.
+ * Examples are real URLs such as "file:C:/context.xml", pseudo-URLs
+ * such as "classpath:/context.xml", and simple unprefixed paths
+ * such as "/WEB-INF/context.xml". The latter will resolve in a
+ * fashion specific to the underlying ResourceLoader (e.g.
+ * ServletContextResource for a WebApplicationContext).
+ *
+ *
Ant-style Patterns:
+ *
+ *
When the path location contains an Ant-style pattern, e.g.:
+ *
+ * /WEB-INF/*-context.xml
+ * com/mycompany/**/applicationContext.xml
+ * file:C:/some/path/*-context.xml
+ * classpath:com/mycompany/**/applicationContext.xml
+ * the resolver follows a more complex but defined procedure to try to resolve
+ * the wildcard. It produces a Resource for the path up to the last
+ * non-wildcard segment and obtains a URL from it. If this URL is
+ * not a "jar:" URL or container-specific variant (e.g.
+ * "zip:" in WebLogic, "wsjar" in WebSphere", etc.),
+ * then a java.io.File is obtained from it, and used to resolve the
+ * wildcard by walking the filesystem. In the case of a jar URL, the resolver
+ * either gets a java.net.JarURLConnection from it, or manually parse
+ * the jar URL, and then traverse the contents of the jar file, to resolve the
+ * wildcards.
+ *
+ * Implications on portability:
+ *
+ *
If the specified path is already a file URL (either explicitly, or
+ * implicitly because the base ResourceLoader is a filesystem one,
+ * then wildcarding is guaranteed to work in a completely portable fashion.
+ *
+ *
If the specified path is a classpath location, then the resolver must
+ * obtain the last non-wildcard path segment URL via a
+ * Classloader.getResource() call. Since this is just a
+ * node of the path (not the file at the end) it is actually undefined
+ * (in the ClassLoader Javadocs) exactly what sort of a URL is returned in
+ * this case. In practice, it is usually a java.io.File representing
+ * the directory, where the classpath resource resolves to a filesystem
+ * location, or a jar URL of some sort, where the classpath resource resolves
+ * to a jar location. Still, there is a portability concern on this operation.
+ *
+ *
If a jar URL is obtained for the last non-wildcard segment, the resolver
+ * must be able to get a java.net.JarURLConnection from it, or
+ * manually parse the jar URL, to be able to walk the contents of the jar,
+ * and resolve the wildcard. This will work in most environments, but will
+ * fail in others, and it is strongly recommended that the wildcard
+ * resolution of resources coming from jars be thoroughly tested in your
+ * specific environment before you rely on it.
+ *
+ *
classpath*: Prefix:
+ *
+ *
There is special support for retrieving multiple class path resources with
+ * the same name, via the "classpath*:" prefix. For example,
+ * "classpath*:META-INF/beans.xml" will find all "beans.xml"
+ * files in the class path, be it in "classes" directories or in JAR files.
+ * This is particularly useful for autodetecting config files of the same name
+ * at the same location within each jar file. Internally, this happens via a
+ * ClassLoader.getResources() call, and is completely portable.
+ *
+ *
The "classpath*:" prefix can also be combined with a PathMatcher pattern in
+ * the rest of the location path, for example "classpath*:META-INF/*-beans.xml".
+ * In this case, the resolution strategy is fairly simple: a
+ * ClassLoader.getResources() call is used on the last non-wildcard
+ * path segment to get all the matching resources in the class loader hierarchy,
+ * and then off each resource the same PathMatcher resolution strategy described
+ * above is used for the wildcard subpath.
+ *
+ *
Other notes:
+ *
+ *
WARNING: Note that "classpath*:" when combined with
+ * Ant-style patterns will only work reliably with at least one root directory
+ * before the pattern starts, unless the actual target files reside in the file
+ * system. This means that a pattern like "classpath*:*.xml" will
+ * not retrieve files from the root of jar files but rather only from the
+ * root of expanded directories. This originates from a limitation in the JDK's
+ * ClassLoader.getResources() method which only returns file system
+ * locations for a passed-in empty String (indicating potential roots to search).
+ *
+ *
WARNING: Ant-style patterns with "classpath:" resources are not
+ * guaranteed to find matching resources if the root package to search is available
+ * in multiple class path locations. This is because a resource such as
+ * com/mycompany/package1/service-context.xml
+ *
may be in only one location, but when a path such as
+ * classpath:com/mycompany/**/service-context.xml
+ *
is used to try to resolve it, the resolver will work off the (first) URL
+ * returned by getResource("com/mycompany");. If this base package
+ * node exists in multiple classloader locations, the actual end resource may
+ * not be underneath. Therefore, preferably, use "classpath*:" with the same
+ * Ant-style pattern in such a case, which will search all class path
+ * locations that contain the root package.
+ *
+ * @author Juergen Hoeller
+ * @author Colin Sampaleanu
+ * @since 1.0.2
+ * @see #CLASSPATH_ALL_URL_PREFIX
+ * @see org.springframework.util.AntPathMatcher
+ * @see org.springframework.core.io.ResourceLoader#getResource(String)
+ * @see java.lang.ClassLoader#getResources(String)
+ */
+public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
+
+ private static final Log logger = LogFactory.getLog(PathMatchingResourcePatternResolver.class);
+
+ private static Method equinoxResolveMethod;
+
+ static {
+ // Detect Equinox OSGi (e.g. on WebSphere 6.1)
+ try {
+ Class fileLocatorClass = PathMatchingResourcePatternResolver.class.getClassLoader().loadClass(
+ "org.eclipse.core.runtime.FileLocator");
+ equinoxResolveMethod = fileLocatorClass.getMethod("resolve", new Class[] {URL.class});
+ logger.debug("Found Equinox FileLocator for OSGi bundle URL resolution");
+ }
+ catch (Throwable ex) {
+ equinoxResolveMethod = null;
+ }
+ }
+
+
+ private final ResourceLoader resourceLoader;
+
+ private PathMatcher pathMatcher = new AntPathMatcher();
+
+
+ /**
+ * Create a new PathMatchingResourcePatternResolver with a DefaultResourceLoader.
+ * ClassLoader access will happen via the thread context class loader.
+ * @see org.springframework.core.io.DefaultResourceLoader
+ */
+ public PathMatchingResourcePatternResolver() {
+ this.resourceLoader = new DefaultResourceLoader();
+ }
+
+ /**
+ * Create a new PathMatchingResourcePatternResolver with a DefaultResourceLoader.
+ * @param classLoader the ClassLoader to load classpath resources with,
+ * or null for using the thread context class loader
+ * @see org.springframework.core.io.DefaultResourceLoader
+ */
+ public PathMatchingResourcePatternResolver(ClassLoader classLoader) {
+ this.resourceLoader = new DefaultResourceLoader(classLoader);
+ }
+
+ /**
+ * Create a new PathMatchingResourcePatternResolver.
+ *
ClassLoader access will happen via the thread context class loader.
+ * @param resourceLoader the ResourceLoader to load root directories and
+ * actual resources with
+ */
+ public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
+ Assert.notNull(resourceLoader, "ResourceLoader must not be null");
+ this.resourceLoader = resourceLoader;
+ }
+
+
+ /**
+ * Return the ResourceLoader that this pattern resolver works with.
+ */
+ public ResourceLoader getResourceLoader() {
+ return this.resourceLoader;
+ }
+
+ /**
+ * Return the ClassLoader that this pattern resolver works with
+ * (never null).
+ */
+ public ClassLoader getClassLoader() {
+ return getResourceLoader().getClassLoader();
+ }
+
+ /**
+ * Set the PathMatcher implementation to use for this
+ * resource pattern resolver. Default is AntPathMatcher.
+ * @see org.springframework.util.AntPathMatcher
+ */
+ public void setPathMatcher(PathMatcher pathMatcher) {
+ Assert.notNull(pathMatcher, "PathMatcher must not be null");
+ this.pathMatcher = pathMatcher;
+ }
+
+ /**
+ * Return the PathMatcher that this resource pattern resolver uses.
+ */
+ public PathMatcher getPathMatcher() {
+ return this.pathMatcher;
+ }
+
+
+ public Resource getResource(String location) {
+ return getResourceLoader().getResource(location);
+ }
+
+ public Resource[] getResources(String locationPattern) throws IOException {
+ Assert.notNull(locationPattern, "Location pattern must not be null");
+ if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
+ // a class path resource (multiple resources for same name possible)
+ if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
+ // a class path resource pattern
+ return findPathMatchingResources(locationPattern);
+ }
+ else {
+ // all class path resources with the given name
+ return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
+ }
+ }
+ else {
+ // Only look for a pattern after a prefix here
+ // (to not get fooled by a pattern symbol in a strange prefix).
+ int prefixEnd = locationPattern.indexOf(":") + 1;
+ if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
+ // a file pattern
+ return findPathMatchingResources(locationPattern);
+ }
+ else {
+ // a single resource with the given name
+ return new Resource[] {getResourceLoader().getResource(locationPattern)};
+ }
+ }
+ }
+
+
+ /**
+ * Find all class location resources with the given location via the ClassLoader.
+ * @param location the absolute path within the classpath
+ * @return the result as Resource array
+ * @throws IOException in case of I/O errors
+ * @see java.lang.ClassLoader#getResources
+ * @see #convertClassLoaderURL
+ */
+ protected Resource[] findAllClassPathResources(String location) throws IOException {
+ String path = location;
+ if (path.startsWith("/")) {
+ path = path.substring(1);
+ }
+ Enumeration resourceUrls = getClassLoader().getResources(path);
+ Set result = new LinkedHashSet(16);
+ while (resourceUrls.hasMoreElements()) {
+ URL url = (URL) resourceUrls.nextElement();
+ result.add(convertClassLoaderURL(url));
+ }
+ return (Resource[]) result.toArray(new Resource[result.size()]);
+ }
+
+ /**
+ * Convert the given URL as returned from the ClassLoader into a Resource object.
+ *
The default implementation simply creates a UrlResource instance.
+ * @param url a URL as returned from the ClassLoader
+ * @return the corresponding Resource object
+ * @see java.lang.ClassLoader#getResources
+ * @see org.springframework.core.io.Resource
+ */
+ protected Resource convertClassLoaderURL(URL url) {
+ return new UrlResource(url);
+ }
+
+ /**
+ * Find all resources that match the given location pattern via the
+ * Ant-style PathMatcher. Supports resources in jar files and zip files
+ * and in the file system.
+ * @param locationPattern the location pattern to match
+ * @return the result as Resource array
+ * @throws IOException in case of I/O errors
+ * @see #doFindPathMatchingJarResources
+ * @see #doFindPathMatchingFileResources
+ * @see org.springframework.util.PathMatcher
+ */
+ protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
+ String rootDirPath = determineRootDir(locationPattern);
+ String subPattern = locationPattern.substring(rootDirPath.length());
+ Resource[] rootDirResources = getResources(rootDirPath);
+ Set result = new LinkedHashSet(16);
+ for (int i = 0; i < rootDirResources.length; i++) {
+ Resource rootDirResource = resolveRootDirResource(rootDirResources[i]);
+ if (isJarResource(rootDirResource)) {
+ result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
+ }
+ else {
+ result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
+ }
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
+ }
+ return (Resource[]) result.toArray(new Resource[result.size()]);
+ }
+
+ /**
+ * Determine the root directory for the given location.
+ *
Used for determining the starting point for file matching,
+ * resolving the root directory location to a java.io.File
+ * and passing it into retrieveMatchingFiles, with the
+ * remainder of the location as pattern.
+ *
Will return "/WEB-INF" for the pattern "/WEB-INF/*.xml",
+ * for example.
+ * @param location the location to check
+ * @return the part of the location that denotes the root directory
+ * @see #retrieveMatchingFiles
+ */
+ protected String determineRootDir(String location) {
+ int prefixEnd = location.indexOf(":") + 1;
+ int rootDirEnd = location.length();
+ while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) {
+ rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1;
+ }
+ if (rootDirEnd == 0) {
+ rootDirEnd = prefixEnd;
+ }
+ return location.substring(0, rootDirEnd);
+ }
+
+ /**
+ * Resolve the specified resource for path matching.
+ *
The default implementation detects an Equinox OSGi "bundleresource:"
+ * / "bundleentry:" URL and resolves it into a standard jar file URL that
+ * can be traversed using Spring's standard jar file traversal algorithm.
+ * @param original the resource to resolfe
+ * @return the resolved resource (may be identical to the passed-in resource)
+ * @throws IOException in case of resolution failure
+ */
+ protected Resource resolveRootDirResource(Resource original) throws IOException {
+ if (equinoxResolveMethod != null) {
+ URL url = original.getURL();
+ if (url.getProtocol().startsWith("bundle")) {
+ return new UrlResource((URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, new Object[] {url}));
+ }
+ }
+ return original;
+ }
+
+ /**
+ * Return whether the given resource handle indicates a jar resource
+ * that the doFindPathMatchingJarResources method can handle.
+ *
The default implementation checks against the URL protocols
+ * "jar", "zip" and "wsjar" (the latter are used by BEA WebLogic Server
+ * and IBM WebSphere, respectively, but can be treated like jar files).
+ * @param resource the resource handle to check
+ * (usually the root directory to start path matching from)
+ * @see #doFindPathMatchingJarResources
+ * @see org.springframework.util.ResourceUtils#isJarURL
+ */
+ protected boolean isJarResource(Resource resource) throws IOException {
+ return ResourceUtils.isJarURL(resource.getURL());
+ }
+
+ /**
+ * Find all resources in jar files that match the given location pattern
+ * via the Ant-style PathMatcher.
+ * @param rootDirResource the root directory as Resource
+ * @param subPattern the sub pattern to match (below the root directory)
+ * @return the Set of matching Resource instances
+ * @throws IOException in case of I/O errors
+ * @see java.net.JarURLConnection
+ * @see org.springframework.util.PathMatcher
+ */
+ protected Set doFindPathMatchingJarResources(Resource rootDirResource, String subPattern) throws IOException {
+ URLConnection con = rootDirResource.getURL().openConnection();
+ JarFile jarFile = null;
+ String jarFileUrl = null;
+ String rootEntryPath = null;
+ boolean newJarFile = false;
+
+ if (con instanceof JarURLConnection) {
+ // Should usually be the case for traditional JAR files.
+ JarURLConnection jarCon = (JarURLConnection) con;
+ jarCon.setUseCaches(false);
+ jarFile = jarCon.getJarFile();
+ jarFileUrl = jarCon.getJarFileURL().toExternalForm();
+ JarEntry jarEntry = jarCon.getJarEntry();
+ rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
+ }
+ else {
+ // No JarURLConnection -> need to resort to URL file parsing.
+ // We'll assume URLs of the format "jar:path!/entry", with the protocol
+ // being arbitrary as long as following the entry format.
+ // We'll also handle paths with and without leading "file:" prefix.
+ String urlFile = rootDirResource.getURL().getFile();
+ int separatorIndex = urlFile.indexOf(ResourceUtils.JAR_URL_SEPARATOR);
+ if (separatorIndex != -1) {
+ jarFileUrl = urlFile.substring(0, separatorIndex);
+ rootEntryPath = urlFile.substring(separatorIndex + ResourceUtils.JAR_URL_SEPARATOR.length());
+ jarFile = getJarFile(jarFileUrl);
+ }
+ else {
+ jarFile = new JarFile(urlFile);
+ jarFileUrl = urlFile;
+ rootEntryPath = "";
+ }
+ newJarFile = true;
+ }
+
+ try {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Looking for matching resources in jar file [" + jarFileUrl + "]");
+ }
+ if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {
+ // Root entry path must end with slash to allow for proper matching.
+ // The Sun JRE does not return a slash here, but BEA JRockit does.
+ rootEntryPath = rootEntryPath + "/";
+ }
+ Set result = new LinkedHashSet(8);
+ for (Enumeration entries = jarFile.entries(); entries.hasMoreElements();) {
+ JarEntry entry = (JarEntry) entries.nextElement();
+ String entryPath = entry.getName();
+ if (entryPath.startsWith(rootEntryPath)) {
+ String relativePath = entryPath.substring(rootEntryPath.length());
+ if (getPathMatcher().match(subPattern, relativePath)) {
+ result.add(rootDirResource.createRelative(relativePath));
+ }
+ }
+ }
+ return result;
+ }
+ finally {
+ // Close jar file, but only if freshly obtained -
+ // not from JarURLConnection, which might cache the file reference.
+ if (newJarFile) {
+ jarFile.close();
+ }
+ }
+ }
+
+ /**
+ * Resolve the given jar file URL into a JarFile object.
+ */
+ protected JarFile getJarFile(String jarFileUrl) throws IOException {
+ if (jarFileUrl.startsWith(ResourceUtils.FILE_URL_PREFIX)) {
+ try {
+ return new JarFile(ResourceUtils.toURI(jarFileUrl).getSchemeSpecificPart());
+ }
+ catch (URISyntaxException ex) {
+ // Fallback for URLs that are not valid URIs (should hardly ever happen).
+ return new JarFile(jarFileUrl.substring(ResourceUtils.FILE_URL_PREFIX.length()));
+ }
+ }
+ else {
+ return new JarFile(jarFileUrl);
+ }
+ }
+
+ /**
+ * Find all resources in the file system that match the given location pattern
+ * via the Ant-style PathMatcher.
+ * @param rootDirResource the root directory as Resource
+ * @param subPattern the sub pattern to match (below the root directory)
+ * @return the Set of matching Resource instances
+ * @throws IOException in case of I/O errors
+ * @see #retrieveMatchingFiles
+ * @see org.springframework.util.PathMatcher
+ */
+ protected Set doFindPathMatchingFileResources(Resource rootDirResource, String subPattern) throws IOException {
+ File rootDir = null;
+ try {
+ rootDir = rootDirResource.getFile().getAbsoluteFile();
+ }
+ catch (IOException ex) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Cannot search for matching files underneath " + rootDirResource +
+ " because it does not correspond to a directory in the file system", ex);
+ }
+ return Collections.EMPTY_SET;
+ }
+ return doFindMatchingFileSystemResources(rootDir, subPattern);
+ }
+
+ /**
+ * Find all resources in the file system that match the given location pattern
+ * via the Ant-style PathMatcher.
+ * @param rootDir the root directory in the file system
+ * @param subPattern the sub pattern to match (below the root directory)
+ * @return the Set of matching Resource instances
+ * @throws IOException in case of I/O errors
+ * @see #retrieveMatchingFiles
+ * @see org.springframework.util.PathMatcher
+ */
+ protected Set doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
+ }
+ Set matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
+ Set result = new LinkedHashSet(matchingFiles.size());
+ for (Iterator it = matchingFiles.iterator(); it.hasNext();) {
+ File file = (File) it.next();
+ result.add(new FileSystemResource(file));
+ }
+ return result;
+ }
+
+ /**
+ * Retrieve files that match the given path pattern,
+ * checking the given directory and its subdirectories.
+ * @param rootDir the directory to start from
+ * @param pattern the pattern to match against,
+ * relative to the root directory
+ * @return the Set of matching File instances
+ * @throws IOException if directory contents could not be retrieved
+ */
+ protected Set retrieveMatchingFiles(File rootDir, String pattern) throws IOException {
+ if (!rootDir.isDirectory()) {
+ throw new IllegalArgumentException("Resource path [" + rootDir + "] does not denote a directory");
+ }
+ String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
+ if (!pattern.startsWith("/")) {
+ fullPattern += "/";
+ }
+ fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
+ Set result = new LinkedHashSet(8);
+ doRetrieveMatchingFiles(fullPattern, rootDir, result);
+ return result;
+ }
+
+ /**
+ * Recursively retrieve files that match the given pattern,
+ * adding them to the given result list.
+ * @param fullPattern the pattern to match against,
+ * with preprended root directory path
+ * @param dir the current directory
+ * @param result the Set of matching File instances to add to
+ * @throws IOException if directory contents could not be retrieved
+ */
+ protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set result) throws IOException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Searching directory [" + dir.getAbsolutePath() +
+ "] for files matching pattern [" + fullPattern + "]");
+ }
+ File[] dirContents = dir.listFiles();
+ if (dirContents == null) {
+ throw new IOException("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
+ }
+ for (int i = 0; i < dirContents.length; i++) {
+ File content = dirContents[i];
+ String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
+ if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
+ doRetrieveMatchingFiles(fullPattern, content, result);
+ }
+ if (getPathMatcher().match(fullPattern, currPath)) {
+ result.add(content);
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java b/org.springframework.core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java
new file mode 100644
index 00000000000..2a5e5facaab
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core.io.support;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Properties;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.core.io.Resource;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.DefaultPropertiesPersister;
+import org.springframework.util.PropertiesPersister;
+
+/**
+ * Base class for JavaBean-style components that need to load properties
+ * from one or more resources. Supports local properties as well, with
+ * configurable overriding.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ */
+public abstract class PropertiesLoaderSupport {
+
+ public static final String XML_FILE_EXTENSION = ".xml";
+
+
+ /** Logger available to subclasses */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ private Properties[] localProperties;
+
+ private Resource[] locations;
+
+ private boolean localOverride = false;
+
+ private boolean ignoreResourceNotFound = false;
+
+ private String fileEncoding;
+
+ private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister();
+
+
+ /**
+ * Set local properties, e.g. via the "props" tag in XML bean definitions.
+ * These can be considered defaults, to be overridden by properties
+ * loaded from files.
+ */
+ public void setProperties(Properties properties) {
+ this.localProperties = new Properties[] {properties};
+ }
+
+ /**
+ * Set local properties, e.g. via the "props" tag in XML bean definitions,
+ * allowing for merging multiple properties sets into one.
+ */
+ public void setPropertiesArray(Properties[] propertiesArray) {
+ this.localProperties = propertiesArray;
+ }
+
+ /**
+ * Set a location of a properties file to be loaded.
+ *
Can point to a classic properties file or to an XML file
+ * that follows JDK 1.5's properties XML format.
+ */
+ public void setLocation(Resource location) {
+ this.locations = new Resource[] {location};
+ }
+
+ /**
+ * Set locations of properties files to be loaded.
+ *
Can point to classic properties files or to XML files
+ * that follow JDK 1.5's properties XML format.
+ *
Note: Properties defined in later files will override
+ * properties defined earlier files, in case of overlapping keys.
+ * Hence, make sure that the most specific files are the last
+ * ones in the given list of locations.
+ */
+ public void setLocations(Resource[] locations) {
+ this.locations = locations;
+ }
+
+ /**
+ * Set whether local properties override properties from files.
+ *
Default is "false": Properties from files override local defaults.
+ * Can be switched to "true" to let local properties override defaults
+ * from files.
+ */
+ public void setLocalOverride(boolean localOverride) {
+ this.localOverride = localOverride;
+ }
+
+ /**
+ * Set if failure to find the property resource should be ignored.
+ *
"true" is appropriate if the properties file is completely optional.
+ * Default is "false".
+ */
+ public void setIgnoreResourceNotFound(boolean ignoreResourceNotFound) {
+ this.ignoreResourceNotFound = ignoreResourceNotFound;
+ }
+
+ /**
+ * Set the encoding to use for parsing properties files.
+ *
Default is none, using the java.util.Properties
+ * default encoding.
+ *
Only applies to classic properties files, not to XML files.
+ * @see org.springframework.util.PropertiesPersister#load
+ */
+ public void setFileEncoding(String encoding) {
+ this.fileEncoding = encoding;
+ }
+
+ /**
+ * Set the PropertiesPersister to use for parsing properties files.
+ * The default is DefaultPropertiesPersister.
+ * @see org.springframework.util.DefaultPropertiesPersister
+ */
+ public void setPropertiesPersister(PropertiesPersister propertiesPersister) {
+ this.propertiesPersister =
+ (propertiesPersister != null ? propertiesPersister : new DefaultPropertiesPersister());
+ }
+
+
+ /**
+ * Return a merged Properties instance containing both the
+ * loaded properties and properties set on this FactoryBean.
+ */
+ protected Properties mergeProperties() throws IOException {
+ Properties result = new Properties();
+
+ if (this.localOverride) {
+ // Load properties from file upfront, to let local properties override.
+ loadProperties(result);
+ }
+
+ if (this.localProperties != null) {
+ for (int i = 0; i < this.localProperties.length; i++) {
+ CollectionUtils.mergePropertiesIntoMap(this.localProperties[i], result);
+ }
+ }
+
+ if (!this.localOverride) {
+ // Load properties from file afterwards, to let those properties override.
+ loadProperties(result);
+ }
+
+ return result;
+ }
+
+ /**
+ * Load properties into the given instance.
+ * @param props the Properties instance to load into
+ * @throws java.io.IOException in case of I/O errors
+ * @see #setLocations
+ */
+ protected void loadProperties(Properties props) throws IOException {
+ if (this.locations != null) {
+ for (int i = 0; i < this.locations.length; i++) {
+ Resource location = this.locations[i];
+ if (logger.isInfoEnabled()) {
+ logger.info("Loading properties file from " + location);
+ }
+ InputStream is = null;
+ try {
+ is = location.getInputStream();
+ if (location.getFilename().endsWith(XML_FILE_EXTENSION)) {
+ this.propertiesPersister.loadFromXml(props, is);
+ }
+ else {
+ if (this.fileEncoding != null) {
+ this.propertiesPersister.load(props, new InputStreamReader(is, this.fileEncoding));
+ }
+ else {
+ this.propertiesPersister.load(props, is);
+ }
+ }
+ }
+ catch (IOException ex) {
+ if (this.ignoreResourceNotFound) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Could not load properties from " + location + ": " + ex.getMessage());
+ }
+ }
+ else {
+ throw ex;
+ }
+ }
+ finally {
+ if (is != null) {
+ is.close();
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/support/PropertiesLoaderUtils.java b/org.springframework.core/src/main/java/org/springframework/core/io/support/PropertiesLoaderUtils.java
new file mode 100644
index 00000000000..f52c0f04f7a
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/support/PropertiesLoaderUtils.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * 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.springframework.core.io.support;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Enumeration;
+import java.util.Properties;
+
+import org.springframework.core.io.Resource;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Convenient utility methods for loading of java.util.Properties,
+ * performing standard handling of input streams.
+ *
+ *
For more configurable properties loading, including the option of a
+ * customized encoding, consider using the PropertiesLoaderSupport class.
+ *
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @since 2.0
+ * @see PropertiesLoaderSupport
+ */
+public abstract class PropertiesLoaderUtils {
+
+ /**
+ * Load properties from the given resource.
+ * @param resource the resource to load from
+ * @return the populated Properties instance
+ * @throws IOException if loading failed
+ */
+ public static Properties loadProperties(Resource resource) throws IOException {
+ Properties props = new Properties();
+ fillProperties(props, resource);
+ return props;
+ }
+
+ /**
+ * Fill the given properties from the given resource.
+ * @param props the Properties instance to fill
+ * @param resource the resource to load from
+ * @throws IOException if loading failed
+ */
+ public static void fillProperties(Properties props, Resource resource) throws IOException {
+ InputStream is = resource.getInputStream();
+ try {
+ props.load(is);
+ }
+ finally {
+ is.close();
+ }
+ }
+
+ /**
+ * Load all properties from the given class path resource,
+ * using the default class loader.
+ *
Merges properties if more than one resource of the same name
+ * found in the class path.
+ * @param resourceName the name of the class path resource
+ * @return the populated Properties instance
+ * @throws IOException if loading failed
+ */
+ public static Properties loadAllProperties(String resourceName) throws IOException {
+ return loadAllProperties(resourceName, null);
+ }
+
+ /**
+ * Load all properties from the given class path resource,
+ * using the given class loader.
+ *
Merges properties if more than one resource of the same name
+ * found in the class path.
+ * @param resourceName the name of the class path resource
+ * @param classLoader the ClassLoader to use for loading
+ * (or null to use the default class loader)
+ * @return the populated Properties instance
+ * @throws IOException if loading failed
+ */
+ public static Properties loadAllProperties(String resourceName, ClassLoader classLoader) throws IOException {
+ Assert.notNull(resourceName, "Resource name must not be null");
+ ClassLoader clToUse = classLoader;
+ if (clToUse == null) {
+ clToUse = ClassUtils.getDefaultClassLoader();
+ }
+ Properties properties = new Properties();
+ Enumeration urls = clToUse.getResources(resourceName);
+ while (urls.hasMoreElements()) {
+ URL url = (URL) urls.nextElement();
+ InputStream is = null;
+ try {
+ URLConnection con = url.openConnection();
+ con.setUseCaches(false);
+ is = con.getInputStream();
+ properties.load(is);
+ }
+ finally {
+ if (is != null) {
+ is.close();
+ }
+ }
+ }
+ return properties;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/support/ResourceArrayPropertyEditor.java b/org.springframework.core/src/main/java/org/springframework/core/io/support/ResourceArrayPropertyEditor.java
new file mode 100644
index 00000000000..ac4b2c5596c
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/support/ResourceArrayPropertyEditor.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.io.support;
+
+import java.beans.PropertyEditorSupport;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.springframework.core.io.Resource;
+import org.springframework.util.SystemPropertyUtils;
+
+/**
+ * Editor for {@link org.springframework.core.io.Resource} arrays, to
+ * automatically convert String location patterns
+ * (e.g. "file:C:/my*.txt" or "classpath*:myfile.txt")
+ * to Resource array properties. Can also translate a collection
+ * or array of location patterns into a merged Resource array.
+ *
+ *
The path may contain ${...} placeholders, to be resolved
+ * as system properties: e.g. ${user.dir}.
+ *
+ *
Delegates to a {@link ResourcePatternResolver},
+ * by default using a {@link PathMatchingResourcePatternResolver}.
+ *
+ * @author Juergen Hoeller
+ * @since 1.1.2
+ * @see org.springframework.core.io.Resource
+ * @see ResourcePatternResolver
+ * @see PathMatchingResourcePatternResolver
+ * @see org.springframework.util.SystemPropertyUtils#resolvePlaceholders
+ * @see System#getProperty(String)
+ */
+public class ResourceArrayPropertyEditor extends PropertyEditorSupport {
+
+ private final ResourcePatternResolver resourcePatternResolver;
+
+
+ /**
+ * Create a new ResourceArrayPropertyEditor with a default
+ * PathMatchingResourcePatternResolver.
+ * @see PathMatchingResourcePatternResolver
+ */
+ public ResourceArrayPropertyEditor() {
+ this.resourcePatternResolver = new PathMatchingResourcePatternResolver();
+ }
+
+ /**
+ * Create a new ResourceArrayPropertyEditor with the given ResourcePatternResolver.
+ * @param resourcePatternResolver the ResourcePatternResolver to use
+ */
+ public ResourceArrayPropertyEditor(ResourcePatternResolver resourcePatternResolver) {
+ this.resourcePatternResolver = resourcePatternResolver;
+ }
+
+
+ /**
+ * Treat the given text as location pattern and convert it to a Resource array.
+ */
+ public void setAsText(String text) {
+ String pattern = resolvePath(text).trim();
+ try {
+ setValue(this.resourcePatternResolver.getResources(pattern));
+ }
+ catch (IOException ex) {
+ throw new IllegalArgumentException(
+ "Could not resolve resource location pattern [" + pattern + "]: " + ex.getMessage());
+ }
+ }
+
+ /**
+ * Treat the given value as collection or array and convert it to a Resource array.
+ * Considers String elements as location patterns, and takes Resource elements as-is.
+ */
+ public void setValue(Object value) throws IllegalArgumentException {
+ if (value instanceof Collection || (value instanceof Object[] && !(value instanceof Resource[]))) {
+ Collection input = (value instanceof Collection ? (Collection) value : Arrays.asList((Object[]) value));
+ List merged = new ArrayList();
+ for (Iterator it = input.iterator(); it.hasNext();) {
+ Object element = it.next();
+ if (element instanceof String) {
+ // A location pattern: resolve it into a Resource array.
+ // Might point to a single resource or to multiple resources.
+ String pattern = resolvePath((String) element).trim();
+ try {
+ Resource[] resources = this.resourcePatternResolver.getResources(pattern);
+ for (int i = 0; i < resources.length; i++) {
+ Resource resource = resources[i];
+ if (!merged.contains(resource)) {
+ merged.add(resource);
+ }
+ }
+ }
+ catch (IOException ex) {
+ throw new IllegalArgumentException(
+ "Could not resolve resource location pattern [" + pattern + "]: " + ex.getMessage());
+ }
+ }
+ else if (element instanceof Resource) {
+ // A Resource object: add it to the result.
+ if (!merged.contains(element)) {
+ merged.add(element);
+ }
+ }
+ else {
+ throw new IllegalArgumentException("Cannot convert element [" + element + "] to [" +
+ Resource.class.getName() + "]: only location String and Resource object supported");
+ }
+ }
+ super.setValue(merged.toArray(new Resource[merged.size()]));
+ }
+
+ else {
+ // An arbitrary value: probably a String or a Resource array.
+ // setAsText will be called for a String; a Resource array will be used as-is.
+ super.setValue(value);
+ }
+ }
+
+ /**
+ * Resolve the given path, replacing placeholders with
+ * corresponding system property values if necessary.
+ * @param path the original file path
+ * @return the resolved file path
+ * @see org.springframework.util.SystemPropertyUtils#resolvePlaceholders
+ */
+ protected String resolvePath(String path) {
+ return SystemPropertyUtils.resolvePlaceholders(path);
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/support/ResourcePatternResolver.java b/org.springframework.core/src/main/java/org/springframework/core/io/support/ResourcePatternResolver.java
new file mode 100644
index 00000000000..2bbe19cc884
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/support/ResourcePatternResolver.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.io.support;
+
+import java.io.IOException;
+
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+
+/**
+ * Strategy interface for resolving a location pattern (for example,
+ * an Ant-style path pattern) into Resource objects.
+ *
+ *
This is an extension to the {@link org.springframework.core.io.ResourceLoader}
+ * interface. A passed-in ResourceLoader (for example, an
+ * {@link org.springframework.context.ApplicationContext} passed in via
+ * {@link org.springframework.context.ResourceLoaderAware} when running in a context)
+ * can be checked whether it implements this extended interface too.
+ *
+ *
{@link PathMatchingResourcePatternResolver} is a standalone implementation
+ * that is usable outside an ApplicationContext, also used by
+ * {@link ResourceArrayPropertyEditor} for populating Resource array bean properties.
+ *
+ *
Can be used with any sort of location pattern (e.g. "/WEB-INF/*-context.xml"):
+ * Input patterns have to match the strategy implementation. This interface just
+ * specifies the conversion method rather than a specific pattern format.
+ *
+ *
This interface also suggests a new resource prefix "classpath*:" for all
+ * matching resources from the class path. Note that the resource location is
+ * expected to be a path without placeholders in this case (e.g. "/beans.xml");
+ * JAR files or classes directories can contain multiple files of the same name.
+ *
+ * @author Juergen Hoeller
+ * @since 1.0.2
+ * @see org.springframework.core.io.Resource
+ * @see org.springframework.core.io.ResourceLoader
+ * @see org.springframework.context.ApplicationContext
+ * @see org.springframework.context.ResourceLoaderAware
+ */
+public interface ResourcePatternResolver extends ResourceLoader {
+
+ /**
+ * Pseudo URL prefix for all matching resources from the class path: "classpath*:"
+ * This differs from ResourceLoader's classpath URL prefix in that it
+ * retrieves all matching resources for a given name (e.g. "/beans.xml"),
+ * for example in the root of all deployed JAR files.
+ * @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX
+ */
+ String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
+
+ /**
+ * Resolve the given location pattern into Resource objects.
+ *
Overlapping resource entries that point to the same physical
+ * resource should be avoided, as far as possible. The result should
+ * have set semantics.
+ * @param locationPattern the location pattern to resolve
+ * @return the corresponding Resource objects
+ * @throws IOException in case of I/O errors
+ */
+ Resource[] getResources(String locationPattern) throws IOException;
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/support/ResourcePatternUtils.java b/org.springframework.core/src/main/java/org/springframework/core/io/support/ResourcePatternUtils.java
new file mode 100644
index 00000000000..0ee5078986a
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/support/ResourcePatternUtils.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.io.support;
+
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.util.Assert;
+import org.springframework.util.ResourceUtils;
+
+/**
+ * Utility class for determining whether a given URL is a resource
+ * location that can be loaded via a ResourcePatternResolver.
+ *
+ *
Callers will usually assume that a location is a relative path
+ * if the {@link #isUrl(String)} method returns false.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2.3
+ */
+public abstract class ResourcePatternUtils {
+
+ /**
+ * Return whether the given resource location is a URL: either a
+ * special "classpath" or "classpath*" pseudo URL or a standard URL.
+ * @param resourceLocation the location String to check
+ * @return whether the location qualifies as a URL
+ * @see ResourcePatternResolver#CLASSPATH_ALL_URL_PREFIX
+ * @see org.springframework.util.ResourceUtils#CLASSPATH_URL_PREFIX
+ * @see org.springframework.util.ResourceUtils#isUrl(String)
+ * @see java.net.URL
+ */
+ public static boolean isUrl(String resourceLocation) {
+ return (resourceLocation != null &&
+ (resourceLocation.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX) ||
+ ResourceUtils.isUrl(resourceLocation)));
+ }
+
+ /**
+ * Return a default ResourcePatternResolver for the given ResourceLoader.
+ *
This might be the ResourceLoader itself, if it implements the
+ * ResourcePatternResolver extension, or a PathMatchingResourcePatternResolver
+ * built on the given ResourceLoader.
+ * @param resourceLoader the ResourceLoader to build a pattern resolver for
+ * (not null)
+ * @return the ResourcePatternResolver
+ * @see PathMatchingResourcePatternResolver
+ */
+ public static ResourcePatternResolver getResourcePatternResolver(ResourceLoader resourceLoader) {
+ Assert.notNull(resourceLoader, "ResourceLoader must not be null");
+ if (resourceLoader instanceof ResourcePatternResolver) {
+ return (ResourcePatternResolver) resourceLoader;
+ }
+ else {
+ return new PathMatchingResourcePatternResolver(resourceLoader);
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/io/support/package.html b/org.springframework.core/src/main/java/org/springframework/core/io/support/package.html
new file mode 100644
index 00000000000..622be20ee37
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/io/support/package.html
@@ -0,0 +1,8 @@
+
+
+
+Support classes for Spring's resource abstraction.
+Includes a ResourcePatternResolver mechanism.
+
+
+
diff --git a/org.springframework.core/src/main/java/org/springframework/core/package.html b/org.springframework.core/src/main/java/org/springframework/core/package.html
new file mode 100644
index 00000000000..5956e51c966
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/package.html
@@ -0,0 +1,8 @@
+
+
+
+Provides basic classes for exception handling and version detection,
+and other core helpers that are not specific to any part of the framework.
+
+
+
diff --git a/org.springframework.core/src/main/java/org/springframework/core/style/DefaultToStringStyler.java b/org.springframework.core/src/main/java/org/springframework/core/style/DefaultToStringStyler.java
new file mode 100644
index 00000000000..3eab22b408a
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/style/DefaultToStringStyler.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.style;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Spring's default toString() styler.
+ *
+ * This class is used by {@link ToStringCreator} to style toString()
+ * output in a consistent manner according to Spring conventions.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ */
+public class DefaultToStringStyler implements ToStringStyler {
+
+ private final ValueStyler valueStyler;
+
+
+ /**
+ * Create a new DefaultToStringStyler.
+ * @param valueStyler the ValueStyler to use
+ */
+ public DefaultToStringStyler(ValueStyler valueStyler) {
+ Assert.notNull(valueStyler, "ValueStyler must not be null");
+ this.valueStyler = valueStyler;
+ }
+
+ /**
+ * Return the ValueStyler used by this ToStringStyler.
+ */
+ protected final ValueStyler getValueStyler() {
+ return this.valueStyler;
+ }
+
+
+ public void styleStart(StringBuffer buffer, Object obj) {
+ if (!obj.getClass().isArray()) {
+ buffer.append('[').append(ClassUtils.getShortName(obj.getClass()));
+ styleIdentityHashCode(buffer, obj);
+ }
+ else {
+ buffer.append('[');
+ styleIdentityHashCode(buffer, obj);
+ buffer.append(' ');
+ styleValue(buffer, obj);
+ }
+ }
+
+ private void styleIdentityHashCode(StringBuffer buffer, Object obj) {
+ buffer.append('@');
+ buffer.append(ObjectUtils.getIdentityHexString(obj));
+ }
+
+ public void styleEnd(StringBuffer buffer, Object o) {
+ buffer.append(']');
+ }
+
+ public void styleField(StringBuffer buffer, String fieldName, Object value) {
+ styleFieldStart(buffer, fieldName);
+ styleValue(buffer, value);
+ styleFieldEnd(buffer, fieldName);
+ }
+
+ protected void styleFieldStart(StringBuffer buffer, String fieldName) {
+ buffer.append(' ').append(fieldName).append(" = ");
+ }
+
+ protected void styleFieldEnd(StringBuffer buffer, String fieldName) {
+ }
+
+ public void styleValue(StringBuffer buffer, Object value) {
+ buffer.append(this.valueStyler.style(value));
+ }
+
+ public void styleFieldSeparator(StringBuffer buffer) {
+ buffer.append(',');
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/style/DefaultValueStyler.java b/org.springframework.core/src/main/java/org/springframework/core/style/DefaultValueStyler.java
new file mode 100644
index 00000000000..0a5d3addde7
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/style/DefaultValueStyler.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.core.style;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Converts objects to String form, generally for debugging purposes,
+ * using Spring's toString styling conventions.
+ *
+ *
Uses the reflective visitor pattern underneath the hood to nicely
+ * encapsulate styling algorithms for each type of styled object.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ */
+public class DefaultValueStyler implements ValueStyler {
+
+ private static final String EMPTY = "[empty]";
+ private static final String NULL = "[null]";
+ private static final String COLLECTION = "collection";
+ private static final String SET = "set";
+ private static final String LIST = "list";
+ private static final String MAP = "map";
+ private static final String ARRAY = "array";
+
+
+ public String style(Object value) {
+ if (value == null) {
+ return NULL;
+ }
+ else if (value instanceof String) {
+ return "\'" + value + "\'";
+ }
+ else if (value instanceof Class) {
+ return ClassUtils.getShortName((Class) value);
+ }
+ else if (value instanceof Method) {
+ Method method = (Method) value;
+ return method.getName() + "@" + ClassUtils.getShortName(method.getDeclaringClass());
+ }
+ else if (value instanceof Map) {
+ return style((Map) value);
+ }
+ else if (value instanceof Map.Entry) {
+ return style((Map.Entry) value);
+ }
+ else if (value instanceof Collection) {
+ return style((Collection) value);
+ }
+ else if (value.getClass().isArray()) {
+ return styleArray(ObjectUtils.toObjectArray(value));
+ }
+ else {
+ return String.valueOf(value);
+ }
+ }
+
+ private String style(Map value) {
+ StringBuffer buffer = new StringBuffer(value.size() * 8 + 16);
+ buffer.append(MAP + "[");
+ for (Iterator it = value.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ buffer.append(style(entry));
+ if (it.hasNext()) {
+ buffer.append(',').append(' ');
+ }
+ }
+ if (value.isEmpty()) {
+ buffer.append(EMPTY);
+ }
+ buffer.append("]");
+ return buffer.toString();
+ }
+
+ private String style(Map.Entry value) {
+ return style(value.getKey()) + " -> " + style(value.getValue());
+ }
+
+ private String style(Collection value) {
+ StringBuffer buffer = new StringBuffer(value.size() * 8 + 16);
+ buffer.append(getCollectionTypeString(value)).append('[');
+ for (Iterator i = value.iterator(); i.hasNext();) {
+ buffer.append(style(i.next()));
+ if (i.hasNext()) {
+ buffer.append(',').append(' ');
+ }
+ }
+ if (value.isEmpty()) {
+ buffer.append(EMPTY);
+ }
+ buffer.append("]");
+ return buffer.toString();
+ }
+
+ private String getCollectionTypeString(Collection value) {
+ if (value instanceof List) {
+ return LIST;
+ }
+ else if (value instanceof Set) {
+ return SET;
+ }
+ else {
+ return COLLECTION;
+ }
+ }
+
+ private String styleArray(Object[] array) {
+ StringBuffer buffer = new StringBuffer(array.length * 8 + 16);
+ buffer.append(ARRAY + "<" + ClassUtils.getShortName(array.getClass().getComponentType()) + ">[");
+ for (int i = 0; i < array.length - 1; i++) {
+ buffer.append(style(array[i]));
+ buffer.append(',').append(' ');
+ }
+ if (array.length > 0) {
+ buffer.append(style(array[array.length - 1]));
+ }
+ else {
+ buffer.append(EMPTY);
+ }
+ buffer.append("]");
+ return buffer.toString();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/style/StylerUtils.java b/org.springframework.core/src/main/java/org/springframework/core/style/StylerUtils.java
new file mode 100644
index 00000000000..1457589bf19
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/style/StylerUtils.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.style;
+
+/**
+ * Simple utility class to allow for convenient access to value
+ * styling logic, mainly to support descriptive logging messages.
+ *
+ *
For more sophisticated needs, use the {@link ValueStyler} abstraction
+ * directly. This class simply uses a shared {@link DefaultValueStyler}
+ * instance underneath.
+ *
+ * @author Keith Donald
+ * @since 1.2.2
+ * @see ValueStyler
+ * @see DefaultValueStyler
+ */
+public abstract class StylerUtils {
+
+ /**
+ * Default ValueStyler instance used by the style method.
+ * Also available for the {@link ToStringCreator} class in this package.
+ */
+ static final ValueStyler DEFAULT_VALUE_STYLER = new DefaultValueStyler();
+
+ /**
+ * Style the specified value according to default conventions.
+ * @param value the Object value to style
+ * @return the styled String
+ * @see DefaultValueStyler
+ */
+ public static String style(Object value) {
+ return DEFAULT_VALUE_STYLER.style(value);
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/style/ToStringCreator.java b/org.springframework.core/src/main/java/org/springframework/core/style/ToStringCreator.java
new file mode 100644
index 00000000000..8d78888258e
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/style/ToStringCreator.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.style;
+
+import org.springframework.util.Assert;
+
+/**
+ * Utility class that builds pretty-printing toString() methods
+ * with pluggable styling conventions. By default, ToStringCreator adheres
+ * to Spring's toString() styling conventions.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ */
+public class ToStringCreator {
+
+ /**
+ * Default ToStringStyler instance used by this ToStringCreator.
+ */
+ private static final ToStringStyler DEFAULT_TO_STRING_STYLER =
+ new DefaultToStringStyler(StylerUtils.DEFAULT_VALUE_STYLER);
+
+
+ private StringBuffer buffer = new StringBuffer(512);
+
+ private ToStringStyler styler;
+
+ private Object object;
+
+ private boolean styledFirstField;
+
+
+ /**
+ * Create a ToStringCreator for the given object.
+ * @param obj the object to be stringified
+ */
+ public ToStringCreator(Object obj) {
+ this(obj, (ToStringStyler) null);
+ }
+
+ /**
+ * Create a ToStringCreator for the given object, using the provided style.
+ * @param obj the object to be stringified
+ * @param styler the ValueStyler encapsulating pretty-print instructions
+ */
+ public ToStringCreator(Object obj, ValueStyler styler) {
+ this(obj, new DefaultToStringStyler(styler != null ? styler : StylerUtils.DEFAULT_VALUE_STYLER));
+ }
+
+ /**
+ * Create a ToStringCreator for the given object, using the provided style.
+ * @param obj the object to be stringified
+ * @param styler the ToStringStyler encapsulating pretty-print instructions
+ */
+ public ToStringCreator(Object obj, ToStringStyler styler) {
+ Assert.notNull(obj, "The object to be styled must not be null");
+ this.object = obj;
+ this.styler = (styler != null ? styler : DEFAULT_TO_STRING_STYLER);
+ this.styler.styleStart(this.buffer, this.object);
+ }
+
+
+ /**
+ * Append a byte field value.
+ * @param fieldName the name of the field, usually the member variable name
+ * @param value the field value
+ * @return this, to support call-chaining
+ */
+ public ToStringCreator append(String fieldName, byte value) {
+ return append(fieldName, new Byte(value));
+ }
+
+ /**
+ * Append a short field value.
+ * @param fieldName the name of the field, usually the member variable name
+ * @param value the field value
+ * @return this, to support call-chaining
+ */
+ public ToStringCreator append(String fieldName, short value) {
+ return append(fieldName, new Short(value));
+ }
+
+ /**
+ * Append a integer field value.
+ * @param fieldName the name of the field, usually the member variable name
+ * @param value the field value
+ * @return this, to support call-chaining
+ */
+ public ToStringCreator append(String fieldName, int value) {
+ return append(fieldName, new Integer(value));
+ }
+
+ /**
+ * Append a float field value.
+ * @param fieldName the name of the field, usually the member variable name
+ * @param value the field value
+ * @return this, to support call-chaining
+ */
+ public ToStringCreator append(String fieldName, float value) {
+ return append(fieldName, new Float(value));
+ }
+
+ /**
+ * Append a double field value.
+ * @param fieldName the name of the field, usually the member variable name
+ * @param value the field value
+ * @return this, to support call-chaining
+ */
+ public ToStringCreator append(String fieldName, double value) {
+ return append(fieldName, new Double(value));
+ }
+
+ /**
+ * Append a long field value.
+ * @param fieldName the name of the field, usually the member variable name
+ * @param value the field value
+ * @return this, to support call-chaining
+ */
+ public ToStringCreator append(String fieldName, long value) {
+ return append(fieldName, new Long(value));
+ }
+
+ /**
+ * Append a boolean field value.
+ * @param fieldName the name of the field, usually the member variable name
+ * @param value the field value
+ * @return this, to support call-chaining
+ */
+ public ToStringCreator append(String fieldName, boolean value) {
+ return append(fieldName, value ? Boolean.TRUE : Boolean.FALSE);
+ }
+
+ /**
+ * Append a field value.
+ * @param fieldName the name of the field, usually the member variable name
+ * @param value the field value
+ * @return this, to support call-chaining
+ */
+ public ToStringCreator append(String fieldName, Object value) {
+ printFieldSeparatorIfNecessary();
+ this.styler.styleField(this.buffer, fieldName, value);
+ return this;
+ }
+
+ private void printFieldSeparatorIfNecessary() {
+ if (this.styledFirstField) {
+ this.styler.styleFieldSeparator(this.buffer);
+ }
+ else {
+ this.styledFirstField = true;
+ }
+ }
+
+ /**
+ * Append the provided value.
+ * @param value The value to append
+ * @return this, to support call-chaining.
+ */
+ public ToStringCreator append(Object value) {
+ this.styler.styleValue(this.buffer, value);
+ return this;
+ }
+
+
+ /**
+ * Return the String representation that this ToStringCreator built.
+ */
+ public String toString() {
+ this.styler.styleEnd(this.buffer, this.object);
+ return this.buffer.toString();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/style/ToStringStyler.java b/org.springframework.core/src/main/java/org/springframework/core/style/ToStringStyler.java
new file mode 100644
index 00000000000..461832938dd
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/style/ToStringStyler.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * 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.springframework.core.style;
+
+/**
+ * A strategy interface for pretty-printing toString() methods.
+ * Encapsulates the print algorithms; some other object such as a builder
+ * should provide the workflow.
+ *
+ * @author Keith Donald
+ * @since 1.2.2
+ */
+public interface ToStringStyler {
+
+ /**
+ * Style a toString()'ed object before its fields are styled.
+ * @param buffer the buffer to print to
+ * @param obj the object to style
+ */
+ void styleStart(StringBuffer buffer, Object obj);
+
+ /**
+ * Style a toString()'ed object after it's fields are styled.
+ * @param buffer the buffer to print to
+ * @param obj the object to style
+ */
+ void styleEnd(StringBuffer buffer, Object obj);
+
+ /**
+ * Style a field value as a string.
+ * @param buffer the buffer to print to
+ * @param fieldName the he name of the field
+ * @param value the field value
+ */
+ void styleField(StringBuffer buffer, String fieldName, Object value);
+
+ /**
+ * Style the given value.
+ * @param buffer the buffer to print to
+ * @param value the field value
+ */
+ void styleValue(StringBuffer buffer, Object value);
+
+ /**
+ * Style the field separator.
+ * @param buffer buffer to print to
+ */
+ void styleFieldSeparator(StringBuffer buffer);
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/style/ValueStyler.java b/org.springframework.core/src/main/java/org/springframework/core/style/ValueStyler.java
new file mode 100644
index 00000000000..731fb3aed3d
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/style/ValueStyler.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.style;
+
+/**
+ * Strategy that encapsulates value String styling algorithms
+ * according to Spring conventions.
+ *
+ * @author Keith Donald
+ * @since 1.2.2
+ */
+public interface ValueStyler {
+
+ /**
+ * Style the given value, returning a String representation.
+ * @param value the Object value to style
+ * @return the styled String
+ */
+ String style(Object value);
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/style/package.html b/org.springframework.core/src/main/java/org/springframework/core/style/package.html
new file mode 100644
index 00000000000..368554e3eda
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/style/package.html
@@ -0,0 +1,7 @@
+
+
+
+Support for styling values as Strings, with ToStringCreator as central class.
+
+
+
diff --git a/org.springframework.core/src/main/java/org/springframework/core/task/AsyncTaskExecutor.java b/org.springframework.core/src/main/java/org/springframework/core/task/AsyncTaskExecutor.java
new file mode 100644
index 00000000000..e8471c1b27d
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/task/AsyncTaskExecutor.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.task;
+
+/**
+ * Extended interface for asynchronous {@link TaskExecutor} implementations,
+ * offering an overloaded {@link #execute(Runnable, long)} variant with
+ * start timeout parameter.
+ *
+ * Implementing this interface also indicates that the {@link #execute(Runnable)}
+ * method will not execute its Runnable in the caller's thread but rather
+ * asynchronously in some other thread (at least usually).
+ *
+ * @author Juergen Hoeller
+ * @since 2.0.3
+ * @see SimpleAsyncTaskExecutor
+ * @see org.springframework.scheduling.SchedulingTaskExecutor
+ */
+public interface AsyncTaskExecutor extends TaskExecutor {
+
+ /** Constant that indicates immediate execution */
+ long TIMEOUT_IMMEDIATE = 0;
+
+ /** Constant that indicates no time limit */
+ long TIMEOUT_INDEFINITE = Long.MAX_VALUE;
+
+
+ /**
+ * Execute the given task.
+ * @param task the Runnable to execute (never null)
+ * @param startTimeout the time duration within which the task is supposed to start.
+ * This is intended as a hint to the executor, allowing for preferred handling
+ * of immediate tasks. Typical values are {@link #TIMEOUT_IMMEDIATE} or
+ * {@link #TIMEOUT_INDEFINITE} (the default as used by {@link #execute(Runnable)}).
+ * @throws TaskTimeoutException in case of the task being rejected because
+ * of the timeout (i.e. it cannot be started in time)
+ * @throws TaskRejectedException if the given task was not accepted
+ */
+ void execute(Runnable task, long startTimeout);
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java b/org.springframework.core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java
new file mode 100644
index 00000000000..a2a86328119
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.task;
+
+import java.io.Serializable;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ConcurrencyThrottleSupport;
+import org.springframework.util.CustomizableThreadCreator;
+
+/**
+ * TaskExecutor implementation that fires up a new Thread for each task,
+ * executing it asynchronously.
+ *
+ *
Supports limiting concurrent threads through the "concurrencyLimit"
+ * bean property. By default, the number of concurrent threads is unlimited.
+ *
+ *
NOTE: This implementation does not reuse threads! Consider a
+ * thread-pooling TaskExecutor implementation instead, in particular for
+ * executing a large number of short-lived tasks.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see #setConcurrencyLimit
+ * @see SyncTaskExecutor
+ * @see org.springframework.scheduling.timer.TimerTaskExecutor
+ * @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
+ * @see org.springframework.scheduling.commonj.WorkManagerTaskExecutor
+ */
+public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implements AsyncTaskExecutor, Serializable {
+
+ /**
+ * Default thread name prefix: "SimpleAsyncTaskExecutor-".
+ * @deprecated as of Spring 2.0.3, since the default thread name prefix
+ * is now taken from the concrete class (could be a subclass)
+ */
+ public static final String DEFAULT_THREAD_NAME_PREFIX =
+ ClassUtils.getShortName(SimpleAsyncTaskExecutor.class) + "-";
+
+ /**
+ * Permit any number of concurrent invocations: that is, don't throttle concurrency.
+ */
+ public static final int UNBOUNDED_CONCURRENCY = ConcurrencyThrottleSupport.UNBOUNDED_CONCURRENCY;
+
+ /**
+ * Switch concurrency 'off': that is, don't allow any concurrent invocations.
+ */
+ public static final int NO_CONCURRENCY = ConcurrencyThrottleSupport.NO_CONCURRENCY;
+
+
+ /**
+ * Internal concurrency throttle used by this executor.
+ */
+ private final ConcurrencyThrottleAdapter concurrencyThrottle = new ConcurrencyThrottleAdapter();
+
+
+ /**
+ * Create a new SimpleAsyncTaskExecutor with default thread name prefix.
+ */
+ public SimpleAsyncTaskExecutor() {
+ super();
+ }
+
+ /**
+ * Create a new SimpleAsyncTaskExecutor with the given thread name prefix.
+ * @param threadNamePrefix the prefix to use for the names of newly created threads
+ */
+ public SimpleAsyncTaskExecutor(String threadNamePrefix) {
+ super(threadNamePrefix);
+ }
+
+
+ /**
+ * Set the maximum number of parallel accesses allowed.
+ * -1 indicates no concurrency limit at all.
+ *
In principle, this limit can be changed at runtime,
+ * although it is generally designed as a config time setting.
+ * NOTE: Do not switch between -1 and any concrete limit at runtime,
+ * as this will lead to inconsistent concurrency counts: A limit
+ * of -1 effectively turns off concurrency counting completely.
+ * @see #UNBOUNDED_CONCURRENCY
+ */
+ public void setConcurrencyLimit(int concurrencyLimit) {
+ this.concurrencyThrottle.setConcurrencyLimit(concurrencyLimit);
+ }
+
+ /**
+ * Return the maximum number of parallel accesses allowed.
+ */
+ public int getConcurrencyLimit() {
+ return this.concurrencyThrottle.getConcurrencyLimit();
+ }
+
+ /**
+ * Return whether this throttle is currently active.
+ * @return true if the concurrency limit for this instance is active
+ * @see #getConcurrencyLimit()
+ * @see #setConcurrencyLimit
+ */
+ public boolean isThrottleActive() {
+ return this.concurrencyThrottle.isThrottleActive();
+ }
+
+
+ /**
+ * Executes the given task, within a concurrency throttle
+ * if configured (through the superclass's settings).
+ * @see #doExecute(Runnable)
+ */
+ public void execute(Runnable task) {
+ execute(task, TIMEOUT_INDEFINITE);
+ }
+
+ /**
+ * Executes the given task, within a concurrency throttle
+ * if configured (through the superclass's settings).
+ *
Executes urgent tasks (with 'immediate' timeout) directly,
+ * bypassing the concurrency throttle (if active). All other
+ * tasks are subject to throttling.
+ * @see #TIMEOUT_IMMEDIATE
+ * @see #doExecute(Runnable)
+ */
+ public void execute(Runnable task, long startTimeout) {
+ Assert.notNull(task, "Runnable must not be null");
+ if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
+ this.concurrencyThrottle.beforeAccess();
+ doExecute(new ConcurrencyThrottlingRunnable(task));
+ }
+ else {
+ doExecute(task);
+ }
+ }
+
+ /**
+ * Template method for the actual execution of a task.
+ *
The default implementation creates a new Thread and starts it.
+ * @param task the Runnable to execute
+ * @see #createThread
+ * @see java.lang.Thread#start()
+ */
+ protected void doExecute(Runnable task) {
+ createThread(task).start();
+ }
+
+
+ /**
+ * Subclass of the general ConcurrencyThrottleSupport class,
+ * making beforeAccess() and afterAccess()
+ * visible to the surrounding class.
+ */
+ private static class ConcurrencyThrottleAdapter extends ConcurrencyThrottleSupport {
+
+ protected void beforeAccess() {
+ super.beforeAccess();
+ }
+
+ protected void afterAccess() {
+ super.afterAccess();
+ }
+ }
+
+
+ /**
+ * This Runnable calls afterAccess() after the
+ * target Runnable has finished its execution.
+ */
+ private class ConcurrencyThrottlingRunnable implements Runnable {
+
+ private final Runnable target;
+
+ public ConcurrencyThrottlingRunnable(Runnable target) {
+ this.target = target;
+ }
+
+ public void run() {
+ try {
+ this.target.run();
+ }
+ finally {
+ concurrencyThrottle.afterAccess();
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/task/SyncTaskExecutor.java b/org.springframework.core/src/main/java/org/springframework/core/task/SyncTaskExecutor.java
new file mode 100644
index 00000000000..857edf1f449
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/task/SyncTaskExecutor.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * 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.springframework.core.task;
+
+import org.springframework.util.Assert;
+
+import java.io.Serializable;
+
+/**
+ * TaskExecutor implementation that executes each task
+ * synchronously in the calling thread.
+ *
+ *
Mainly intended for testing scenarios.
+ *
+ *
Execution in the calling thread does have the advantage of participating
+ * in it's thread context, for example the thread context class loader or the
+ * thread's current transaction association. That said, in many cases,
+ * asynchronous execution will be preferable: choose an asynchronous
+ * TaskExecutor instead for such scenarios.
+ *
+ * @author Juergen Hoeller
+ * @see SimpleAsyncTaskExecutor
+ * @see org.springframework.scheduling.timer.TimerTaskExecutor
+ * @since 2.0
+ */
+public class SyncTaskExecutor implements TaskExecutor, Serializable {
+
+ /**
+ * Executes the given task synchronously, through direct
+ * invocation of it's {@link Runnable#run() run()} method.
+ * @throws IllegalArgumentException if the given task is null
+ */
+ public void execute(Runnable task) {
+ Assert.notNull(task, "Runnable must not be null");
+ task.run();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/task/TaskExecutor.java b/org.springframework.core/src/main/java/org/springframework/core/task/TaskExecutor.java
new file mode 100644
index 00000000000..800806f74b7
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/task/TaskExecutor.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.task;
+
+/**
+ * Simple task executor interface that abstracts the execution
+ * of a {@link Runnable}.
+ *
+ *
Implementations can use all sorts of different execution strategies,
+ * such as: synchronous, asynchronous, using a thread pool, and more.
+ *
+ *
Equivalent to JDK 1.5's {@link java.util.concurrent.Executor}
+ * interface. Separate mainly for compatibility with JDK 1.4.
+ * Implementations can simply implement the JDK 1.5 Executor
+ * interface as well, as it defines the exact same method signature.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see java.util.concurrent.Executor
+ */
+public interface TaskExecutor {
+
+ /**
+ * Execute the given task.
+ *
The call might return immediately if the implementation uses
+ * an asynchronous execution strategy, or might block in the case
+ * of synchronous execution.
+ * @param task the Runnable to execute (never null)
+ * @throws TaskRejectedException if the given task was not accepted
+ */
+ void execute(Runnable task);
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/task/TaskRejectedException.java b/org.springframework.core/src/main/java/org/springframework/core/task/TaskRejectedException.java
new file mode 100644
index 00000000000..51d3e7c15d1
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/task/TaskRejectedException.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.task;
+
+import org.springframework.core.NestedRuntimeException;
+
+/**
+ * Exception thrown when a {@link TaskExecutor} rejects to accept
+ * a given task for execution.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0.1
+ * @see TaskExecutor#execute(Runnable)
+ * @see TaskTimeoutException
+ */
+public class TaskRejectedException extends NestedRuntimeException {
+
+ /**
+ * Create a new TaskRejectedException
+ * with the specified detail message and no root cause.
+ * @param msg the detail message
+ */
+ public TaskRejectedException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Create a new TaskRejectedException
+ * with the specified detail message and the given root cause.
+ * @param msg the detail message
+ * @param cause the root cause (usually from using an underlying
+ * API such as the java.util.concurrent package)
+ * @see java.util.concurrent.RejectedExecutionException
+ */
+ public TaskRejectedException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/task/TaskTimeoutException.java b/org.springframework.core/src/main/java/org/springframework/core/task/TaskTimeoutException.java
new file mode 100644
index 00000000000..6b31e0a659d
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/task/TaskTimeoutException.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.task;
+
+/**
+ * Exception thrown when a {@link AsyncTaskExecutor} rejects to accept
+ * a given task for execution because of the specified timeout.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0.3
+ * @see AsyncTaskExecutor#execute(Runnable, long)
+ * @see TaskRejectedException
+ */
+public class TaskTimeoutException extends TaskRejectedException {
+
+ /**
+ * Create a new TaskTimeoutException
+ * with the specified detail message and no root cause.
+ * @param msg the detail message
+ */
+ public TaskTimeoutException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Create a new TaskTimeoutException
+ * with the specified detail message and the given root cause.
+ * @param msg the detail message
+ * @param cause the root cause (usually from using an underlying
+ * API such as the java.util.concurrent package)
+ * @see java.util.concurrent.RejectedExecutionException
+ */
+ public TaskTimeoutException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/task/package.html b/org.springframework.core/src/main/java/org/springframework/core/task/package.html
new file mode 100644
index 00000000000..7c837cdb5e1
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/task/package.html
@@ -0,0 +1,8 @@
+
+
+
+This package defines Spring's core TaskExecutor abstraction,
+and provides SyncTaskExecutor and SimpleAsyncTaskExecutor implementations.
+
+
+
diff --git a/org.springframework.core/src/main/java/org/springframework/core/task/support/ConcurrentExecutorAdapter.java b/org.springframework.core/src/main/java/org/springframework/core/task/support/ConcurrentExecutorAdapter.java
new file mode 100644
index 00000000000..230e888c7a1
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/task/support/ConcurrentExecutorAdapter.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.core.task.support;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+import org.springframework.core.task.TaskExecutor;
+import org.springframework.core.task.TaskRejectedException;
+import org.springframework.util.Assert;
+
+/**
+ * Adapter that exposes the {@link java.util.concurrent.Executor java.util.concurrent.Executor}
+ * interface for any Spring {@link org.springframework.core.task.TaskExecutor}.
+ * Follows the JDK executor contract for exception handling.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see java.util.concurrent.Executor
+ * @see org.springframework.core.task.TaskExecutor
+ */
+public class ConcurrentExecutorAdapter implements Executor {
+
+ private final TaskExecutor taskExecutor;
+
+
+ /**
+ * Create a new ConcurrentExecutorAdapter for the given Spring TaskExecutor.
+ * @param taskExecutor the Spring TaskExecutor to wrap
+ */
+ public ConcurrentExecutorAdapter(TaskExecutor taskExecutor) {
+ Assert.notNull(taskExecutor, "TaskExecutor must not be null");
+ this.taskExecutor = taskExecutor;
+ }
+
+
+ public void execute(Runnable command) {
+ try {
+ this.taskExecutor.execute(command);
+ }
+ catch (TaskRejectedException ex) {
+ throw new RejectedExecutionException(ex.getMessage(), ex);
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/core/task/support/package.html b/org.springframework.core/src/main/java/org/springframework/core/task/support/package.html
new file mode 100644
index 00000000000..95e9c545b26
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/core/task/support/package.html
@@ -0,0 +1,8 @@
+
+
+
+Support classes for Spring's TaskExecutor abstraction.
+Includes an adapter for the JDK 1.5 Executor interface.
+
+
+
diff --git a/org.springframework.core/src/main/java/org/springframework/metadata/Attributes.java b/org.springframework.core/src/main/java/org/springframework/metadata/Attributes.java
new file mode 100644
index 00000000000..a1af10370d9
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/metadata/Attributes.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * 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.springframework.metadata;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collection;
+
+/**
+ * Interface for accessing attributes at runtime. This is a facade,
+ * which can accommodate any attributes API such as Jakarta Commons Attributes,
+ * or (possibly in future) a Spring attributes implementation.
+ *
+ * The purpose of using this interface is to decouple Spring code from any
+ * specific attributes implementation. Even once JSR-175 is available, there
+ * is still value in such a facade interface, as it allows for hierarchical
+ * attribute sources: for example, an XML file or properties file might override
+ * some attributes defined in source-level metadata with JSR-175 or another framework.
+ *
+ * @author Mark Pollack
+ * @author Rod Johnson
+ * @since 30.09.2003
+ * @see org.springframework.metadata.commons.CommonsAttributes
+ */
+public interface Attributes {
+
+ /**
+ * Return the class attributes of the target class.
+ * @param targetClass the class that contains attribute information
+ * @return a collection of attributes, possibly an empty collection, never null
+ */
+ Collection getAttributes(Class targetClass);
+
+ /**
+ * Return the class attributes of the target class of a given type.
+ *
The class attributes are filtered by providing a Class
+ * reference to indicate the type to filter on. This is useful if you know
+ * the type of the attribute you are looking for and don't want to sort
+ * through the unfiltered Collection yourself.
+ * @param targetClass the class that contains attribute information
+ * @param filter specify that only this type of class should be returned
+ * @return return only the Collection of attributes that are of the filter type
+ */
+ Collection getAttributes(Class targetClass, Class filter);
+
+ /**
+ * Return the method attributes of the target method.
+ * @param targetMethod the method that contains attribute information
+ * @return a Collection of attributes, possibly an empty Collection, never null
+ */
+ Collection getAttributes(Method targetMethod);
+
+ /**
+ * Return the method attributes of the target method of a given type.
+ *
The method attributes are filtered by providing a Class
+ * reference to indicate the type to filter on. This is useful if you know
+ * the type of the attribute you are looking for and don't want to sort
+ * through the unfiltered Collection yourself.
+ * @param targetMethod the method that contains attribute information
+ * @param filter specify that only this type of class should be returned
+ * @return a Collection of attributes, possibly an empty Collection, never null
+ */
+ Collection getAttributes(Method targetMethod, Class filter);
+
+ /**
+ * Return the field attributes of the target field.
+ * @param targetField the field that contains attribute information
+ * @return a Collection of attribute, possibly an empty Collection, never null
+ */
+ Collection getAttributes(Field targetField);
+
+ /**
+ * Return the field attributes of the target method of a given type.
+ *
The field attributes are filtered by providing a Class
+ * reference to indicate the type to filter on. This is useful if you know
+ * the type of the attribute you are looking for and don't want to sort
+ * through the unfiltered Collection yourself.
+ * @param targetField the field that contains attribute information
+ * @param filter specify that only this type of class should be returned
+ * @return a Collection of attributes, possibly an empty Collection, never null
+ */
+ Collection getAttributes(Field targetField, Class filter);
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/metadata/commons/CommonsAttributes.java b/org.springframework.core/src/main/java/org/springframework/metadata/commons/CommonsAttributes.java
new file mode 100644
index 00000000000..990a0443d0e
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/metadata/commons/CommonsAttributes.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * 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.springframework.metadata.commons;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collection;
+
+import org.springframework.metadata.Attributes;
+
+/**
+ * Implementation of the Spring Attributes facade for Commons Attributes.
+ *
+ *
Please see the
+ *
+ * Commons Attributes documentation for information on how to use the
+ * attribute compiler.
+ *
+ *
As of December 2003, follow the Javadocs to the AttributeCompiler class
+ * to see how the Ant task works. Note that you need to put the following jars
+ * in your $ANT_HOME/lib directory for the Common Attributes compiler to work:
+ *
+ * - Commons Attributes compiler jar
+ *
- the xjavadoc Jar (from XDoclet)
+ *
- commons-collection.jar (from Jakarta Commons)
+ *
+ *
+ * You need to perform the attribute compilation step before compiling your source.
+ *
+ *
See build.xml in the tests for package org.springframework.aop.autoproxy.metadata
+ * for an example of the required Ant scripting. The header of this build script
+ * includes some quick, and hopefully useful, hints on using Commons Attributes.
+ * The source files in the same package (TxClass and TxClassWithClassAttribute)
+ * illustrate attribute usage in source files.
+ *
+ *
The Spring Framework project does not provide support usage of specific
+ * attributes implementations. Please refer to the appropriate site and mailing
+ * list of the attributes implementation.
+ *
+ * @author Rod Johnson
+ */
+public class CommonsAttributes implements Attributes {
+
+ /*
+ * Commons Attributes caches attributes, so we don't need to cache here
+ * as well.
+ */
+
+ public Collection getAttributes(Class targetClass) {
+ return org.apache.commons.attributes.Attributes.getAttributes(targetClass);
+ }
+
+ public Collection getAttributes(Class targetClass, Class filter) {
+ return org.apache.commons.attributes.Attributes.getAttributes(targetClass, filter);
+ }
+
+ public Collection getAttributes(Method targetMethod) {
+ return org.apache.commons.attributes.Attributes.getAttributes(targetMethod);
+ }
+
+ public Collection getAttributes(Method targetMethod, Class filter) {
+ return org.apache.commons.attributes.Attributes.getAttributes(targetMethod, filter);
+ }
+
+ public Collection getAttributes(Field targetField) {
+ return org.apache.commons.attributes.Attributes.getAttributes(targetField);
+ }
+
+ public Collection getAttributes(Field targetField, Class filter) {
+ return org.apache.commons.attributes.Attributes.getAttributes(targetField, filter);
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/metadata/commons/package.html b/org.springframework.core/src/main/java/org/springframework/metadata/commons/package.html
new file mode 100644
index 00000000000..8b073e3b17d
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/metadata/commons/package.html
@@ -0,0 +1,8 @@
+
+
+
+Attributes wrapper for
+Commons Attributes.
+
+
+
diff --git a/org.springframework.core/src/main/java/org/springframework/metadata/package.html b/org.springframework.core/src/main/java/org/springframework/metadata/package.html
new file mode 100644
index 00000000000..12c4acf58f0
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/metadata/package.html
@@ -0,0 +1,8 @@
+
+
+
+Package defining a facade for accessing source-level
+metadata attributes at runtime.
+
+
+
diff --git a/org.springframework.core/src/main/java/org/springframework/util/AntPathMatcher.java b/org.springframework.core/src/main/java/org/springframework/util/AntPathMatcher.java
new file mode 100644
index 00000000000..7566153e31e
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/AntPathMatcher.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.util;
+
+/**
+ * PathMatcher implementation for Ant-style path patterns.
+ * Examples are provided below.
+ *
+ * Part of this mapping code has been kindly borrowed from
+ * Apache Ant.
+ *
+ *
The mapping matches URLs using the following rules:
+ *
+ * - ? matches one character
+ * - * matches zero or more characters
+ * - ** matches zero or more 'directories' in a path
+ *
+ *
+ * Some examples:
+ *
+ * com/t?st.jsp - matches com/test.jsp but also
+ * com/tast.jsp or com/txst.jsp
+ * com/*.jsp - matches all .jsp files in the
+ * com directory
+ * com/**/test.jsp - matches all test.jsp
+ * files underneath the com path
+ * org/springframework/**/*.jsp - matches all .jsp
+ * files underneath the org/springframework path
+ * org/**/servlet/bla.jsp - matches
+ * org/springframework/servlet/bla.jsp but also
+ * org/springframework/testing/servlet/bla.jsp and
+ * org/servlet/bla.jsp
+ *
+ *
+ * @author Alef Arendsen
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @since 16.07.2003
+ */
+public class AntPathMatcher implements PathMatcher {
+
+ /** Default path separator: "/" */
+ public static final String DEFAULT_PATH_SEPARATOR = "/";
+
+ private String pathSeparator = DEFAULT_PATH_SEPARATOR;
+
+
+ /**
+ * Set the path separator to use for pattern parsing.
+ * Default is "/", as in Ant.
+ */
+ public void setPathSeparator(String pathSeparator) {
+ this.pathSeparator = (pathSeparator != null ? pathSeparator : DEFAULT_PATH_SEPARATOR);
+ }
+
+
+ public boolean isPattern(String path) {
+ return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
+ }
+
+ public boolean match(String pattern, String path) {
+ return doMatch(pattern, path, true);
+ }
+
+ public boolean matchStart(String pattern, String path) {
+ return doMatch(pattern, path, false);
+ }
+
+
+ /**
+ * Actually match the given path against the given pattern.
+ * @param pattern the pattern to match against
+ * @param path the path String to test
+ * @param fullMatch whether a full pattern match is required
+ * (else a pattern match as far as the given base path goes is sufficient)
+ * @return true if the supplied path matched,
+ * false if it didn't
+ */
+ protected boolean doMatch(String pattern, String path, boolean fullMatch) {
+ if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
+ return false;
+ }
+
+ String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
+ String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator);
+
+ int pattIdxStart = 0;
+ int pattIdxEnd = pattDirs.length - 1;
+ int pathIdxStart = 0;
+ int pathIdxEnd = pathDirs.length - 1;
+
+ // Match all elements up to the first **
+ while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
+ String patDir = pattDirs[pattIdxStart];
+ if ("**".equals(patDir)) {
+ break;
+ }
+ if (!matchStrings(patDir, pathDirs[pathIdxStart])) {
+ return false;
+ }
+ pattIdxStart++;
+ pathIdxStart++;
+ }
+
+ if (pathIdxStart > pathIdxEnd) {
+ // Path is exhausted, only match if rest of pattern is * or **'s
+ if (pattIdxStart > pattIdxEnd) {
+ return (pattern.endsWith(this.pathSeparator) ?
+ path.endsWith(this.pathSeparator) : !path.endsWith(this.pathSeparator));
+ }
+ if (!fullMatch) {
+ return true;
+ }
+ if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") &&
+ path.endsWith(this.pathSeparator)) {
+ return true;
+ }
+ for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
+ if (!pattDirs[i].equals("**")) {
+ return false;
+ }
+ }
+ return true;
+ }
+ else if (pattIdxStart > pattIdxEnd) {
+ // String not exhausted, but pattern is. Failure.
+ return false;
+ }
+ else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
+ // Path start definitely matches due to "**" part in pattern.
+ return true;
+ }
+
+ // up to last '**'
+ while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
+ String patDir = pattDirs[pattIdxEnd];
+ if (patDir.equals("**")) {
+ break;
+ }
+ if (!matchStrings(patDir, pathDirs[pathIdxEnd])) {
+ return false;
+ }
+ pattIdxEnd--;
+ pathIdxEnd--;
+ }
+ if (pathIdxStart > pathIdxEnd) {
+ // String is exhausted
+ for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
+ if (!pattDirs[i].equals("**")) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
+ int patIdxTmp = -1;
+ for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
+ if (pattDirs[i].equals("**")) {
+ patIdxTmp = i;
+ break;
+ }
+ }
+ if (patIdxTmp == pattIdxStart + 1) {
+ // '**/**' situation, so skip one
+ pattIdxStart++;
+ continue;
+ }
+ // Find the pattern between padIdxStart & padIdxTmp in str between
+ // strIdxStart & strIdxEnd
+ int patLength = (patIdxTmp - pattIdxStart - 1);
+ int strLength = (pathIdxEnd - pathIdxStart + 1);
+ int foundIdx = -1;
+
+ strLoop:
+ for (int i = 0; i <= strLength - patLength; i++) {
+ for (int j = 0; j < patLength; j++) {
+ String subPat = (String) pattDirs[pattIdxStart + j + 1];
+ String subStr = (String) pathDirs[pathIdxStart + i + j];
+ if (!matchStrings(subPat, subStr)) {
+ continue strLoop;
+ }
+ }
+ foundIdx = pathIdxStart + i;
+ break;
+ }
+
+ if (foundIdx == -1) {
+ return false;
+ }
+
+ pattIdxStart = patIdxTmp;
+ pathIdxStart = foundIdx + patLength;
+ }
+
+ for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
+ if (!pattDirs[i].equals("**")) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Tests whether or not a string matches against a pattern.
+ * The pattern may contain two special characters:
+ * '*' means zero or more characters
+ * '?' means one and only one character
+ * @param pattern pattern to match against.
+ * Must not be null.
+ * @param str string which must be matched against the pattern.
+ * Must not be null.
+ * @return true if the string matches against the
+ * pattern, or false otherwise.
+ */
+ private boolean matchStrings(String pattern, String str) {
+ char[] patArr = pattern.toCharArray();
+ char[] strArr = str.toCharArray();
+ int patIdxStart = 0;
+ int patIdxEnd = patArr.length - 1;
+ int strIdxStart = 0;
+ int strIdxEnd = strArr.length - 1;
+ char ch;
+
+ boolean containsStar = false;
+ for (int i = 0; i < patArr.length; i++) {
+ if (patArr[i] == '*') {
+ containsStar = true;
+ break;
+ }
+ }
+
+ if (!containsStar) {
+ // No '*'s, so we make a shortcut
+ if (patIdxEnd != strIdxEnd) {
+ return false; // Pattern and string do not have the same size
+ }
+ for (int i = 0; i <= patIdxEnd; i++) {
+ ch = patArr[i];
+ if (ch != '?') {
+ if (ch != strArr[i]) {
+ return false;// Character mismatch
+ }
+ }
+ }
+ return true; // String matches against pattern
+ }
+
+
+ if (patIdxEnd == 0) {
+ return true; // Pattern contains only '*', which matches anything
+ }
+
+ // Process characters before first star
+ while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
+ if (ch != '?') {
+ if (ch != strArr[strIdxStart]) {
+ return false;// Character mismatch
+ }
+ }
+ patIdxStart++;
+ strIdxStart++;
+ }
+ if (strIdxStart > strIdxEnd) {
+ // All characters in the string are used. Check if only '*'s are
+ // left in the pattern. If so, we succeeded. Otherwise failure.
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (patArr[i] != '*') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Process characters after last star
+ while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
+ if (ch != '?') {
+ if (ch != strArr[strIdxEnd]) {
+ return false;// Character mismatch
+ }
+ }
+ patIdxEnd--;
+ strIdxEnd--;
+ }
+ if (strIdxStart > strIdxEnd) {
+ // All characters in the string are used. Check if only '*'s are
+ // left in the pattern. If so, we succeeded. Otherwise failure.
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (patArr[i] != '*') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // process pattern between stars. padIdxStart and patIdxEnd point
+ // always to a '*'.
+ while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
+ int patIdxTmp = -1;
+ for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
+ if (patArr[i] == '*') {
+ patIdxTmp = i;
+ break;
+ }
+ }
+ if (patIdxTmp == patIdxStart + 1) {
+ // Two stars next to each other, skip the first one.
+ patIdxStart++;
+ continue;
+ }
+ // Find the pattern between padIdxStart & padIdxTmp in str between
+ // strIdxStart & strIdxEnd
+ int patLength = (patIdxTmp - patIdxStart - 1);
+ int strLength = (strIdxEnd - strIdxStart + 1);
+ int foundIdx = -1;
+ strLoop:
+ for (int i = 0; i <= strLength - patLength; i++) {
+ for (int j = 0; j < patLength; j++) {
+ ch = patArr[patIdxStart + j + 1];
+ if (ch != '?') {
+ if (ch != strArr[strIdxStart + i + j]) {
+ continue strLoop;
+ }
+ }
+ }
+
+ foundIdx = strIdxStart + i;
+ break;
+ }
+
+ if (foundIdx == -1) {
+ return false;
+ }
+
+ patIdxStart = patIdxTmp;
+ strIdxStart = foundIdx + patLength;
+ }
+
+ // All characters in the string are used. Check if only '*'s are left
+ // in the pattern. If so, we succeeded. Otherwise failure.
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (patArr[i] != '*') {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Given a pattern and a full path, determine the pattern-mapped part.
+ * For example:
+ *
+ * - '
/docs/cvs/commit.html' and '/docs/cvs/commit.html -> ''
+ * - '
/docs/*' and '/docs/cvs/commit -> 'cvs/commit'
+ * - '
/docs/cvs/*.html' and '/docs/cvs/commit.html -> 'commit.html'
+ * - '
/docs/**' and '/docs/cvs/commit -> 'cvs/commit'
+ * - '
/docs/**\/*.html' and '/docs/cvs/commit.html -> 'cvs/commit.html'
+ * - '
/*.html' and '/docs/cvs/commit.html -> 'docs/cvs/commit.html'
+ * - '
*.html' and '/docs/cvs/commit.html -> '/docs/cvs/commit.html'
+ * - '
*' and '/docs/cvs/commit.html -> '/docs/cvs/commit.html'
+ *
+ * Assumes that {@link #match} returns true for 'pattern'
+ * and 'path', but does not enforce this.
+ */
+ public String extractPathWithinPattern(String pattern, String path) {
+ String[] patternParts = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
+ String[] pathParts = StringUtils.tokenizeToStringArray(path, this.pathSeparator);
+
+ StringBuffer buffer = new StringBuffer();
+
+ // Add any path parts that have a wildcarded pattern part.
+ int puts = 0;
+ for (int i = 0; i < patternParts.length; i++) {
+ String patternPart = patternParts[i];
+ if ((patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) && pathParts.length >= i + 1) {
+ if (puts > 0 || (i == 0 && !pattern.startsWith(this.pathSeparator))) {
+ buffer.append(this.pathSeparator);
+ }
+ buffer.append(pathParts[i]);
+ puts++;
+ }
+ }
+
+ // Append any trailing path parts.
+ for (int i = patternParts.length; i < pathParts.length; i++) {
+ if (puts > 0 || i > 0) {
+ buffer.append(this.pathSeparator);
+ }
+ buffer.append(pathParts[i]);
+ }
+
+ return buffer.toString();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/Assert.java b/org.springframework.core/src/main/java/org/springframework/util/Assert.java
new file mode 100644
index 00000000000..b78364a9f60
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/Assert.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Assertion utility class that assists in validating arguments.
+ * Useful for identifying programmer errors early and clearly at runtime.
+ *
+ *
For example, if the contract of a public method states it does not
+ * allow null arguments, Assert can be used to validate that
+ * contract. Doing this clearly indicates a contract violation when it
+ * occurs and protects the class's invariants.
+ *
+ *
Typically used to validate method arguments rather than configuration
+ * properties, to check for cases that are usually programmer errors rather than
+ * configuration errors. In contrast to config initialization code, there is
+ * usally no point in falling back to defaults in such methods.
+ *
+ *
This class is similar to JUnit's assertion library. If an argument value is
+ * deemed invalid, an {@link IllegalArgumentException} is thrown (typically).
+ * For example:
+ *
+ *
+ * Assert.notNull(clazz, "The class must not be null");
+ * Assert.isTrue(i > 0, "The value must be greater than zero");
+ *
+ * Mainly for internal use within the framework; consider Jakarta's Commons Lang
+ * >= 2.0 for a more comprehensive suite of assertion utilities.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @author Colin Sampaleanu
+ * @author Rob Harrop
+ * @since 1.1.2
+ */
+public abstract class Assert {
+
+ /**
+ * Assert a boolean expression, throwing IllegalArgumentException
+ * if the test result is false.
+ * Assert.isTrue(i > 0, "The value must be greater than zero");
+ * @param expression a boolean expression
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if expression is false
+ */
+ public static void isTrue(boolean expression, String message) {
+ if (!expression) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert a boolean expression, throwing IllegalArgumentException
+ * if the test result is false.
+ * Assert.isTrue(i > 0);
+ * @param expression a boolean expression
+ * @throws IllegalArgumentException if expression is false
+ */
+ public static void isTrue(boolean expression) {
+ isTrue(expression, "[Assertion failed] - this expression must be true");
+ }
+
+ /**
+ * Assert that an object is null .
+ * Assert.isNull(value, "The value must be null");
+ * @param object the object to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the object is not null
+ */
+ public static void isNull(Object object, String message) {
+ if (object != null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that an object is null .
+ * Assert.isNull(value);
+ * @param object the object to check
+ * @throws IllegalArgumentException if the object is not null
+ */
+ public static void isNull(Object object) {
+ isNull(object, "[Assertion failed] - the object argument must be null");
+ }
+
+ /**
+ * Assert that an object is not null .
+ * Assert.notNull(clazz, "The class must not be null");
+ * @param object the object to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the object is null
+ */
+ public static void notNull(Object object, String message) {
+ if (object == null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that an object is not null .
+ * Assert.notNull(clazz);
+ * @param object the object to check
+ * @throws IllegalArgumentException if the object is null
+ */
+ public static void notNull(Object object) {
+ notNull(object, "[Assertion failed] - this argument is required; it must not be null");
+ }
+
+ /**
+ * Assert that the given String is not empty; that is,
+ * it must not be null and not the empty String.
+ * Assert.hasLength(name, "Name must not be empty");
+ * @param text the String to check
+ * @param message the exception message to use if the assertion fails
+ * @see StringUtils#hasLength
+ */
+ public static void hasLength(String text, String message) {
+ if (!StringUtils.hasLength(text)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that the given String is not empty; that is,
+ * it must not be null and not the empty String.
+ * Assert.hasLength(name);
+ * @param text the String to check
+ * @see StringUtils#hasLength
+ */
+ public static void hasLength(String text) {
+ hasLength(text,
+ "[Assertion failed] - this String argument must have length; it must not be null or empty");
+ }
+
+ /**
+ * Assert that the given String has valid text content; that is, it must not
+ * be null and must contain at least one non-whitespace character.
+ * Assert.hasText(name, "'name' must not be empty");
+ * @param text the String to check
+ * @param message the exception message to use if the assertion fails
+ * @see StringUtils#hasText
+ */
+ public static void hasText(String text, String message) {
+ if (!StringUtils.hasText(text)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that the given String has valid text content; that is, it must not
+ * be null and must contain at least one non-whitespace character.
+ * Assert.hasText(name, "'name' must not be empty");
+ * @param text the String to check
+ * @see StringUtils#hasText
+ */
+ public static void hasText(String text) {
+ hasText(text,
+ "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");
+ }
+
+ /**
+ * Assert that the given text does not contain the given substring.
+ * Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");
+ * @param textToSearch the text to search
+ * @param substring the substring to find within the text
+ * @param message the exception message to use if the assertion fails
+ */
+ public static void doesNotContain(String textToSearch, String substring, String message) {
+ if (StringUtils.hasLength(textToSearch) && StringUtils.hasLength(substring) &&
+ textToSearch.indexOf(substring) != -1) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that the given text does not contain the given substring.
+ * Assert.doesNotContain(name, "rod");
+ * @param textToSearch the text to search
+ * @param substring the substring to find within the text
+ */
+ public static void doesNotContain(String textToSearch, String substring) {
+ doesNotContain(textToSearch, substring,
+ "[Assertion failed] - this String argument must not contain the substring [" + substring + "]");
+ }
+
+
+ /**
+ * Assert that an array has elements; that is, it must not be
+ * null and must have at least one element.
+ * Assert.notEmpty(array, "The array must have elements");
+ * @param array the array to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the object array is null or has no elements
+ */
+ public static void notEmpty(Object[] array, String message) {
+ if (ObjectUtils.isEmpty(array)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that an array has elements; that is, it must not be
+ * null and must have at least one element.
+ * Assert.notEmpty(array);
+ * @param array the array to check
+ * @throws IllegalArgumentException if the object array is null or has no elements
+ */
+ public static void notEmpty(Object[] array) {
+ notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element");
+ }
+
+ /**
+ * Assert that an array has no null elements.
+ * Note: Does not complain if the array is empty!
+ * Assert.noNullElements(array, "The array must have non-null elements");
+ * @param array the array to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the object array contains a null element
+ */
+ public static void noNullElements(Object[] array, String message) {
+ if (array != null) {
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+ }
+ }
+
+ /**
+ * Assert that an array has no null elements.
+ * Note: Does not complain if the array is empty!
+ * Assert.noNullElements(array);
+ * @param array the array to check
+ * @throws IllegalArgumentException if the object array contains a null element
+ */
+ public static void noNullElements(Object[] array) {
+ noNullElements(array, "[Assertion failed] - this array must not contain any null elements");
+ }
+
+ /**
+ * Assert that a collection has elements; that is, it must not be
+ * null and must have at least one element.
+ * Assert.notEmpty(collection, "Collection must have elements");
+ * @param collection the collection to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the collection is null or has no elements
+ */
+ public static void notEmpty(Collection collection, String message) {
+ if (CollectionUtils.isEmpty(collection)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that a collection has elements; that is, it must not be
+ * null and must have at least one element.
+ * Assert.notEmpty(collection, "Collection must have elements");
+ * @param collection the collection to check
+ * @throws IllegalArgumentException if the collection is null or has no elements
+ */
+ public static void notEmpty(Collection collection) {
+ notEmpty(collection,
+ "[Assertion failed] - this collection must not be empty: it must contain at least 1 element");
+ }
+
+ /**
+ * Assert that a Map has entries; that is, it must not be null
+ * and must have at least one entry.
+ * Assert.notEmpty(map, "Map must have entries");
+ * @param map the map to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the map is null or has no entries
+ */
+ public static void notEmpty(Map map, String message) {
+ if (CollectionUtils.isEmpty(map)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that a Map has entries; that is, it must not be null
+ * and must have at least one entry.
+ * Assert.notEmpty(map);
+ * @param map the map to check
+ * @throws IllegalArgumentException if the map is null or has no entries
+ */
+ public static void notEmpty(Map map) {
+ notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry");
+ }
+
+
+ /**
+ * Assert that the provided object is an instance of the provided class.
+ * Assert.instanceOf(Foo.class, foo);
+ * @param clazz the required class
+ * @param obj the object to check
+ * @throws IllegalArgumentException if the object is not an instance of clazz
+ * @see Class#isInstance
+ */
+ public static void isInstanceOf(Class clazz, Object obj) {
+ isInstanceOf(clazz, obj, "");
+ }
+
+ /**
+ * Assert that the provided object is an instance of the provided class.
+ * Assert.instanceOf(Foo.class, foo);
+ * @param type the type to check against
+ * @param obj the object to check
+ * @param message a message which will be prepended to the message produced by
+ * the function itself, and which may be used to provide context. It should
+ * normally end in a ": " or ". " so that the function generate message looks
+ * ok when prepended to it.
+ * @throws IllegalArgumentException if the object is not an instance of clazz
+ * @see Class#isInstance
+ */
+ public static void isInstanceOf(Class type, Object obj, String message) {
+ notNull(type, "Type to check against must not be null");
+ if (!type.isInstance(obj)) {
+ throw new IllegalArgumentException(message +
+ "Object of class [" + (obj != null ? obj.getClass().getName() : "null") +
+ "] must be an instance of " + type);
+ }
+ }
+
+ /**
+ * Assert that superType.isAssignableFrom(subType) is true.
+ * Assert.isAssignable(Number.class, myClass);
+ * @param superType the super type to check
+ * @param subType the sub type to check
+ * @throws IllegalArgumentException if the classes are not assignable
+ */
+ public static void isAssignable(Class superType, Class subType) {
+ isAssignable(superType, subType, "");
+ }
+
+ /**
+ * Assert that superType.isAssignableFrom(subType) is true.
+ * Assert.isAssignable(Number.class, myClass);
+ * @param superType the super type to check against
+ * @param subType the sub type to check
+ * @param message a message which will be prepended to the message produced by
+ * the function itself, and which may be used to provide context. It should
+ * normally end in a ": " or ". " so that the function generate message looks
+ * ok when prepended to it.
+ * @throws IllegalArgumentException if the classes are not assignable
+ */
+ public static void isAssignable(Class superType, Class subType, String message) {
+ notNull(superType, "Type to check against must not be null");
+ if (subType == null || !superType.isAssignableFrom(subType)) {
+ throw new IllegalArgumentException(message + subType + " is not assignable to " + superType);
+ }
+ }
+
+
+ /**
+ * Assert a boolean expression, throwing IllegalStateException
+ * if the test result is false. Call isTrue if you wish to
+ * throw IllegalArgumentException on an assertion failure.
+ * Assert.state(id == null, "The id property must not already be initialized");
+ * @param expression a boolean expression
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalStateException if expression is false
+ */
+ public static void state(boolean expression, String message) {
+ if (!expression) {
+ throw new IllegalStateException(message);
+ }
+ }
+
+ /**
+ * Assert a boolean expression, throwing {@link IllegalStateException}
+ * if the test result is false.
+ * Call {@link #isTrue(boolean)} if you wish to
+ * throw {@link IllegalArgumentException} on an assertion failure.
+ *
Assert.state(id == null);
+ * @param expression a boolean expression
+ * @throws IllegalStateException if the supplied expression is false
+ */
+ public static void state(boolean expression) {
+ state(expression, "[Assertion failed] - this state invariant must be true");
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/AutoPopulatingList.java b/org.springframework.core/src/main/java/org/springframework/util/AutoPopulatingList.java
new file mode 100644
index 00000000000..e7276f8c900
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/AutoPopulatingList.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.io.Serializable;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * Simple {@link List} wrapper class that allows for elements to be
+ * automatically populated as they are requested. This is particularly
+ * useful for data binding to {@link List Lists}, allowing for elements
+ * to be created and added to the {@link List} in a "just in time" fashion.
+ *
+ * Note: This class is not thread-safe. To create a thread-safe version,
+ * use the {@link java.util.Collections#synchronizedList} utility methods.
+ *
+ *
Inspired by LazyList from Commons Collections.
+ *
+ * @author Rob Harrop
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class AutoPopulatingList implements List, Serializable {
+
+ /**
+ * The {@link List} that all operations are eventually delegated to.
+ */
+ private final List backingList;
+
+ /**
+ * The {@link ElementFactory} to use to create new {@link List} elements
+ * on demand.
+ */
+ private final ElementFactory elementFactory;
+
+
+ /**
+ * Creates a new AutoPopulatingList that is backed by a standard
+ * {@link ArrayList} and adds new instances of the supplied {@link Class element Class}
+ * to the backing {@link List} on demand.
+ */
+ public AutoPopulatingList(Class elementClass) {
+ this(new ArrayList(), elementClass);
+ }
+
+ /**
+ * Creates a new AutoPopulatingList that is backed by the supplied {@link List}
+ * and adds new instances of the supplied {@link Class element Class} to the backing
+ * {@link List} on demand.
+ */
+ public AutoPopulatingList(List backingList, Class elementClass) {
+ this(backingList, new ReflectiveElementFactory(elementClass));
+ }
+
+ /**
+ * Creates a new AutoPopulatingList that is backed by a standard
+ * {@link ArrayList} and creates new elements on demand using the supplied {@link ElementFactory}.
+ */
+ public AutoPopulatingList(ElementFactory elementFactory) {
+ this(new ArrayList(), elementFactory);
+ }
+
+ /**
+ * Creates a new AutoPopulatingList that is backed by the supplied {@link List}
+ * and creates new elements on demand using the supplied {@link ElementFactory}.
+ */
+ public AutoPopulatingList(List backingList, ElementFactory elementFactory) {
+ Assert.notNull(backingList, "Backing List must not be null");
+ Assert.notNull(elementFactory, "Element factory must not be null");
+ this.backingList = backingList;
+ this.elementFactory = elementFactory;
+ }
+
+
+ public void add(int index, Object element) {
+ this.backingList.add(index, element);
+ }
+
+ public boolean add(Object o) {
+ return this.backingList.add(o);
+ }
+
+ public boolean addAll(Collection c) {
+ return this.backingList.addAll(c);
+ }
+
+ public boolean addAll(int index, Collection c) {
+ return this.backingList.addAll(index, c);
+ }
+
+ public void clear() {
+ this.backingList.clear();
+ }
+
+ public boolean contains(Object o) {
+ return this.backingList.contains(o);
+ }
+
+ public boolean containsAll(Collection c) {
+ return this.backingList.containsAll(c);
+ }
+
+ public boolean equals(Object o) {
+ return this.backingList.equals(o);
+ }
+
+ /**
+ * Get the element at the supplied index, creating it if there is
+ * no element at that index.
+ */
+ public Object get(int index) {
+ int backingListSize = this.backingList.size();
+
+ Object element = null;
+ if (index < backingListSize) {
+ element = this.backingList.get(index);
+ if (element == null) {
+ element = this.elementFactory.createElement(index);
+ this.backingList.set(index, element);
+ }
+ }
+ else {
+ for (int x = backingListSize; x < index; x++) {
+ this.backingList.add(null);
+ }
+ element = this.elementFactory.createElement(index);
+ this.backingList.add(element);
+ }
+ return element;
+ }
+
+ public int hashCode() {
+ return this.backingList.hashCode();
+ }
+
+ public int indexOf(Object o) {
+ return this.backingList.indexOf(o);
+ }
+
+ public boolean isEmpty() {
+ return this.backingList.isEmpty();
+ }
+
+ public Iterator iterator() {
+ return this.backingList.iterator();
+ }
+
+ public int lastIndexOf(Object o) {
+ return this.backingList.lastIndexOf(o);
+ }
+
+ public ListIterator listIterator() {
+ return this.backingList.listIterator();
+ }
+
+ public ListIterator listIterator(int index) {
+ return this.backingList.listIterator(index);
+ }
+
+ public Object remove(int index) {
+ return this.backingList.remove(index);
+ }
+
+ public boolean remove(Object o) {
+ return this.backingList.remove(o);
+ }
+
+ public boolean removeAll(Collection c) {
+ return this.backingList.removeAll(c);
+ }
+
+ public boolean retainAll(Collection c) {
+ return this.backingList.retainAll(c);
+ }
+
+ public Object set(int index, Object element) {
+ return this.backingList.set(index, element);
+ }
+
+ public int size() {
+ return this.backingList.size();
+ }
+
+ public List subList(int fromIndex, int toIndex) {
+ return this.backingList.subList(fromIndex, toIndex);
+ }
+
+ public Object[] toArray() {
+ return this.backingList.toArray();
+ }
+
+ public Object[] toArray(Object[] a) {
+ return this.backingList.toArray(a);
+ }
+
+
+ /**
+ * Factory interface for creating elements for an index-based access
+ * data structure such as a {@link java.util.List}.
+ */
+ public interface ElementFactory {
+
+ /**
+ * Create the element for the supplied index.
+ * @return the element object
+ * @throws ElementInstantiationException if the instantiation process failed
+ * (any exception thrown by a target constructor should be propagated as-is)
+ */
+ Object createElement(int index) throws ElementInstantiationException;
+ }
+
+
+ /**
+ * Exception to be thrown from ElementFactory.
+ */
+ public static class ElementInstantiationException extends RuntimeException {
+
+ public ElementInstantiationException(String msg) {
+ super(msg);
+ }
+ }
+
+
+ /**
+ * Reflective implementation of the ElementFactory interface,
+ * using Class.newInstance() on a given element class.
+ * @see java.lang.Class#newInstance()
+ */
+ private static class ReflectiveElementFactory implements ElementFactory, Serializable {
+
+ private final Class elementClass;
+
+ public ReflectiveElementFactory(Class elementClass) {
+ Assert.notNull(elementClass, "Element clas must not be null");
+ Assert.isTrue(!elementClass.isInterface(), "Element class must not be an interface type");
+ Assert.isTrue(!Modifier.isAbstract(elementClass.getModifiers()), "Element class cannot be an abstract class");
+ this.elementClass = elementClass;
+ }
+
+ public Object createElement(int index) {
+ try {
+ return this.elementClass.newInstance();
+ }
+ catch (InstantiationException ex) {
+ throw new ElementInstantiationException("Unable to instantiate element class [" +
+ this.elementClass.getName() + "]. Root cause is " + ex);
+ }
+ catch (IllegalAccessException ex) {
+ throw new ElementInstantiationException("Cannot access element class [" +
+ this.elementClass.getName() + "]. Root cause is " + ex);
+ }
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/CachingMapDecorator.java b/org.springframework.core/src/main/java/org/springframework/util/CachingMapDecorator.java
new file mode 100644
index 00000000000..c6f87c57a8d
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/CachingMapDecorator.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.io.Serializable;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+/**
+ * A simple decorator for a Map, encapsulating the workflow for caching
+ * expensive values in a target Map. Supports caching weak or strong keys.
+ *
+ *
This class is also an abstract template. Caching Map implementations
+ * should subclass and override the create(key) method which
+ * encapsulates expensive creation of a new object.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ */
+public class CachingMapDecorator implements Map, Serializable {
+
+ protected static Object NULL_VALUE = new Object();
+
+
+ private final Map targetMap;
+
+ private final boolean synchronize;
+
+ private final boolean weak;
+
+
+ /**
+ * Create a CachingMapDecorator with strong keys,
+ * using an underlying synchronized Map.
+ */
+ public CachingMapDecorator() {
+ this(false);
+ }
+
+ /**
+ * Create a CachingMapDecorator,
+ * using an underlying synchronized Map.
+ * @param weak whether to use weak references for keys and values
+ */
+ public CachingMapDecorator(boolean weak) {
+ Map internalMap = weak ? (Map) new WeakHashMap() : new HashMap();
+ this.targetMap = Collections.synchronizedMap(internalMap);
+ this.synchronize = true;
+ this.weak = weak;
+ }
+
+ /**
+ * Create a CachingMapDecorator with initial size,
+ * using an underlying synchronized Map.
+ * @param weak whether to use weak references for keys and values
+ * @param size the initial cache size
+ */
+ public CachingMapDecorator(boolean weak, int size) {
+ Map internalMap = weak ? (Map) new WeakHashMap(size) : new HashMap(size);
+ this.targetMap = Collections.synchronizedMap(internalMap);
+ this.synchronize = true;
+ this.weak = weak;
+ }
+
+ /**
+ * Create a CachingMapDecorator for the given Map.
+ *
The passed-in Map won't get synchronized explicitly,
+ * so make sure to pass in a properly synchronized Map, if desired.
+ * @param targetMap the Map to decorate
+ */
+ public CachingMapDecorator(Map targetMap) {
+ this(targetMap, false, false);
+ }
+
+ /**
+ * Create a CachingMapDecorator for the given Map.
+ *
The passed-in Map won't get synchronized explicitly unless
+ * you specify "synchronize" as "true".
+ * @param targetMap the Map to decorate
+ * @param synchronize whether to synchronize on the given Map
+ * @param weak whether to use weak references for values
+ */
+ public CachingMapDecorator(Map targetMap, boolean synchronize, boolean weak) {
+ Assert.notNull(targetMap, "Target Map is required");
+ this.targetMap = (synchronize ? Collections.synchronizedMap(targetMap) : targetMap);
+ this.synchronize = synchronize;
+ this.weak = weak;
+ }
+
+
+ public int size() {
+ return this.targetMap.size();
+ }
+
+ public boolean isEmpty() {
+ return this.targetMap.isEmpty();
+ }
+
+ public boolean containsKey(Object key) {
+ return this.targetMap.containsKey(key);
+ }
+
+ public boolean containsValue(Object value) {
+ Object valueToCheck = value;
+ if (valueToCheck == null) {
+ valueToCheck = NULL_VALUE;
+ }
+ if (this.synchronize) {
+ synchronized (this.targetMap) {
+ return containsValueOrReference(valueToCheck);
+ }
+ }
+ else {
+ return containsValueOrReference(valueToCheck);
+ }
+ }
+
+ private boolean containsValueOrReference(Object value) {
+ if (this.targetMap.containsValue(value)) {
+ return true;
+ }
+ for (Iterator it = this.targetMap.values().iterator(); it.hasNext();) {
+ Object mapVal = it.next();
+ if (mapVal instanceof Reference && value.equals(((Reference) mapVal).get())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public Object remove(Object key) {
+ return this.targetMap.remove(key);
+ }
+
+ public void putAll(Map map) {
+ this.targetMap.putAll(map);
+ }
+
+ public void clear() {
+ this.targetMap.clear();
+ }
+
+ public Set keySet() {
+ if (this.synchronize) {
+ synchronized (this.targetMap) {
+ return new LinkedHashSet(this.targetMap.keySet());
+ }
+ }
+ else {
+ return new LinkedHashSet(this.targetMap.keySet());
+ }
+ }
+
+ public Collection values() {
+ if (this.synchronize) {
+ synchronized (this.targetMap) {
+ return valuesCopy();
+ }
+ }
+ else {
+ return valuesCopy();
+ }
+ }
+
+ private Collection valuesCopy() {
+ LinkedList values = new LinkedList();
+ for (Iterator it = this.targetMap.values().iterator(); it.hasNext();) {
+ Object value = it.next();
+ values.add(value instanceof Reference ? ((Reference) value).get() : value);
+ }
+ return values;
+ }
+
+ public Set entrySet() {
+ if (this.synchronize) {
+ synchronized (this.targetMap) {
+ return new LinkedHashSet(this.targetMap.entrySet());
+ }
+ }
+ else {
+ return new LinkedHashSet(this.targetMap.entrySet());
+ }
+ }
+
+
+ /**
+ * Put an object into the cache, possibly wrapping it with a weak
+ * reference.
+ * @see #useWeakValue(Object, Object)
+ */
+ public Object put(Object key, Object value) {
+ Object newValue = value;
+ if (newValue == null) {
+ newValue = NULL_VALUE;
+ }
+ if (useWeakValue(key, newValue)) {
+ newValue = new WeakReference(newValue);
+ }
+ return this.targetMap.put(key, newValue);
+ }
+
+ /**
+ * Decide whether use a weak reference for the value of
+ * the given key-value pair.
+ * @param key the candidate key
+ * @param value the candidate value
+ * @return true in order to use a weak reference;
+ * false otherwise.
+ */
+ protected boolean useWeakValue(Object key, Object value) {
+ return this.weak;
+ }
+
+ /**
+ * Get value for key.
+ * Creates and caches value if it doesn't already exist in the cache.
+ *
This implementation is not synchronized: This is highly
+ * concurrent but does not guarantee unique instances in the cache,
+ * as multiple values for the same key could get created in parallel.
+ * Consider overriding this method to synchronize it, if desired.
+ * @see #create(Object)
+ */
+ public Object get(Object key) {
+ Object value = this.targetMap.get(key);
+ if (value instanceof Reference) {
+ value = ((Reference) value).get();
+ }
+ if (value == null) {
+ value = create(key);
+ if (value != null) {
+ put(key, value);
+ }
+ }
+ return (value == NULL_VALUE ? null : value);
+ }
+
+ /**
+ * Create a value to cache for the given key.
+ * Called by get if there is no value cached already.
+ * @param key the cache key
+ * @see #get(Object)
+ */
+ protected Object create(Object key) {
+ return null;
+ }
+
+
+ public String toString() {
+ return "CachingMapDecorator [" + getClass().getName() + "]:" + this.targetMap;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/ClassLoaderUtils.java b/org.springframework.core/src/main/java/org/springframework/util/ClassLoaderUtils.java
new file mode 100644
index 00000000000..a6fbbe9b99b
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/ClassLoaderUtils.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util;
+
+/**
+ * Utility class for diagnostic purposes, to analyze the
+ * ClassLoader hierarchy for any given object or class loader.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @since 02 April 2001
+ * @deprecated as of Spring 2.5, to be removed in Spring 3.0
+ * @see java.lang.ClassLoader
+ */
+public abstract class ClassLoaderUtils {
+
+ /**
+ * Show the class loader hierarchy for this class.
+ * Uses default line break and tab text characters.
+ * @param obj object to analyze loader hierarchy for
+ * @param role a description of the role of this class in the application
+ * (e.g., "servlet" or "EJB reference")
+ * @return a String showing the class loader hierarchy for this class
+ */
+ public static String showClassLoaderHierarchy(Object obj, String role) {
+ return showClassLoaderHierarchy(obj, role, "\n", "\t");
+ }
+
+ /**
+ * Show the class loader hierarchy for this class.
+ * @param obj object to analyze loader hierarchy for
+ * @param role a description of the role of this class in the application
+ * (e.g., "servlet" or "EJB reference")
+ * @param lineBreak line break
+ * @param tabText text to use to set tabs
+ * @return a String showing the class loader hierarchy for this class
+ */
+ public static String showClassLoaderHierarchy(Object obj, String role, String lineBreak, String tabText) {
+ String s = "object of " + obj.getClass() + ": role is " + role + lineBreak;
+ return s + showClassLoaderHierarchy(obj.getClass().getClassLoader(), lineBreak, tabText, 0);
+ }
+
+ /**
+ * Show the class loader hierarchy for the given class loader.
+ * Uses default line break and tab text characters.
+ * @param cl class loader to analyze hierarchy for
+ * @return a String showing the class loader hierarchy for this class
+ */
+ public static String showClassLoaderHierarchy(ClassLoader cl) {
+ return showClassLoaderHierarchy(cl, "\n", "\t");
+ }
+
+ /**
+ * Show the class loader hierarchy for the given class loader.
+ * @param cl class loader to analyze hierarchy for
+ * @param lineBreak line break
+ * @param tabText text to use to set tabs
+ * @return a String showing the class loader hierarchy for this class
+ */
+ public static String showClassLoaderHierarchy(ClassLoader cl, String lineBreak, String tabText) {
+ return showClassLoaderHierarchy(cl, lineBreak, tabText, 0);
+ }
+
+ /**
+ * Show the class loader hierarchy for the given class loader.
+ * @param cl class loader to analyze hierarchy for
+ * @param lineBreak line break
+ * @param tabText text to use to set tabs
+ * @param indent nesting level (from 0) of this loader; used in pretty printing
+ * @return a String showing the class loader hierarchy for this class
+ */
+ private static String showClassLoaderHierarchy(ClassLoader cl, String lineBreak, String tabText, int indent) {
+ if (cl == null) {
+ ClassLoader ccl = Thread.currentThread().getContextClassLoader();
+ return "context class loader=[" + ccl + "] hashCode=" + ccl.hashCode();
+ }
+ StringBuffer buf = new StringBuffer();
+ for (int i = 0; i < indent; i++) {
+ buf.append(tabText);
+ }
+ buf.append("[").append(cl).append("] hashCode=").append(cl.hashCode()).append(lineBreak);
+ ClassLoader parent = cl.getParent();
+ return buf.toString() + showClassLoaderHierarchy(parent, lineBreak, tabText, indent + 1);
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/ClassUtils.java b/org.springframework.core/src/main/java/org/springframework/util/ClassUtils.java
new file mode 100644
index 00000000000..e1f79d1e8eb
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/ClassUtils.java
@@ -0,0 +1,982 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.beans.Introspector;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Miscellaneous class utility methods. Mainly for internal use within the
+ * framework; consider Jakarta's Commons Lang for a more comprehensive suite
+ * of class utilities.
+ *
+ * @author Keith Donald
+ * @author Rob Harrop
+ * @author Juergen Hoeller
+ * @since 1.1
+ * @see TypeUtils
+ * @see ReflectionUtils
+ */
+public abstract class ClassUtils {
+
+ /** Suffix for array class names: "[]" */
+ public static final String ARRAY_SUFFIX = "[]";
+
+ /** Prefix for internal array class names: "[L" */
+ private static final String INTERNAL_ARRAY_PREFIX = "[L";
+
+ /** The package separator character '.' */
+ private static final char PACKAGE_SEPARATOR = '.';
+
+ /** The inner class separator character '$' */
+ private static final char INNER_CLASS_SEPARATOR = '$';
+
+ /** The CGLIB class separator character "$$" */
+ public static final String CGLIB_CLASS_SEPARATOR = "$$";
+
+ /** The ".class" file suffix */
+ public static final String CLASS_FILE_SUFFIX = ".class";
+
+
+ /**
+ * Map with primitive wrapper type as key and corresponding primitive
+ * type as value, for example: Integer.class -> int.class.
+ */
+ private static final Map primitiveWrapperTypeMap = new HashMap(8);
+
+ /**
+ * Map with primitive type name as key and corresponding primitive
+ * type as value, for example: "int" -> "int.class".
+ */
+ private static final Map primitiveTypeNameMap = new HashMap(16);
+
+
+ static {
+ primitiveWrapperTypeMap.put(Boolean.class, boolean.class);
+ primitiveWrapperTypeMap.put(Byte.class, byte.class);
+ primitiveWrapperTypeMap.put(Character.class, char.class);
+ primitiveWrapperTypeMap.put(Double.class, double.class);
+ primitiveWrapperTypeMap.put(Float.class, float.class);
+ primitiveWrapperTypeMap.put(Integer.class, int.class);
+ primitiveWrapperTypeMap.put(Long.class, long.class);
+ primitiveWrapperTypeMap.put(Short.class, short.class);
+
+ Set primitiveTypeNames = new HashSet(16);
+ primitiveTypeNames.addAll(primitiveWrapperTypeMap.values());
+ primitiveTypeNames.addAll(Arrays.asList(new Class[] {
+ boolean[].class, byte[].class, char[].class, double[].class,
+ float[].class, int[].class, long[].class, short[].class}));
+ for (Iterator it = primitiveTypeNames.iterator(); it.hasNext();) {
+ Class primitiveClass = (Class) it.next();
+ primitiveTypeNameMap.put(primitiveClass.getName(), primitiveClass);
+ }
+ }
+
+
+ /**
+ * Return the default ClassLoader to use: typically the thread context
+ * ClassLoader, if available; the ClassLoader that loaded the ClassUtils
+ * class will be used as fallback.
+ *
Call this method if you intend to use the thread context ClassLoader
+ * in a scenario where you absolutely need a non-null ClassLoader reference:
+ * for example, for class path resource loading (but not necessarily for
+ * Class.forName, which accepts a null ClassLoader
+ * reference as well).
+ * @return the default ClassLoader (never null)
+ * @see java.lang.Thread#getContextClassLoader()
+ */
+ public static ClassLoader getDefaultClassLoader() {
+ ClassLoader cl = null;
+ try {
+ cl = Thread.currentThread().getContextClassLoader();
+ }
+ catch (Throwable ex) {
+ // Cannot access thread context ClassLoader - falling back to system class loader...
+ }
+ if (cl == null) {
+ // No thread context class loader -> use class loader of this class.
+ cl = ClassUtils.class.getClassLoader();
+ }
+ return cl;
+ }
+
+ /**
+ * Override the thread context ClassLoader with the environment's bean ClassLoader
+ * if necessary, i.e. if the bean ClassLoader is not equivalent to the thread
+ * context ClassLoader already.
+ * @param classLoaderToUse the actual ClassLoader to use for the thread context
+ * @return the original thread context ClassLoader, or null if not overridden
+ */
+ public static ClassLoader overrideThreadContextClassLoader(ClassLoader classLoaderToUse) {
+ Thread currentThread = Thread.currentThread();
+ ClassLoader threadContextClassLoader = currentThread.getContextClassLoader();
+ if (classLoaderToUse != null && !classLoaderToUse.equals(threadContextClassLoader)) {
+ currentThread.setContextClassLoader(classLoaderToUse);
+ return threadContextClassLoader;
+ }
+ else {
+ return null;
+ }
+ }
+
+ /**
+ * Replacement for Class.forName() that also returns Class instances
+ * for primitives (like "int") and array class names (like "String[]").
+ *
Always uses the default class loader: that is, preferably the thread context
+ * class loader, or the ClassLoader that loaded the ClassUtils class as fallback.
+ * @param name the name of the Class
+ * @return Class instance for the supplied name
+ * @throws ClassNotFoundException if the class was not found
+ * @throws LinkageError if the class file could not be loaded
+ * @see Class#forName(String, boolean, ClassLoader)
+ * @see #getDefaultClassLoader()
+ */
+ public static Class forName(String name) throws ClassNotFoundException, LinkageError {
+ return forName(name, getDefaultClassLoader());
+ }
+
+ /**
+ * Replacement for Class.forName() that also returns Class instances
+ * for primitives (like "int") and array class names (like "String[]").
+ * @param name the name of the Class
+ * @param classLoader the class loader to use
+ * (may be null, which indicates the default class loader)
+ * @return Class instance for the supplied name
+ * @throws ClassNotFoundException if the class was not found
+ * @throws LinkageError if the class file could not be loaded
+ * @see Class#forName(String, boolean, ClassLoader)
+ */
+ public static Class forName(String name, ClassLoader classLoader) throws ClassNotFoundException, LinkageError {
+ Assert.notNull(name, "Name must not be null");
+
+ Class clazz = resolvePrimitiveClassName(name);
+ if (clazz != null) {
+ return clazz;
+ }
+
+ // "java.lang.String[]" style arrays
+ if (name.endsWith(ARRAY_SUFFIX)) {
+ String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length());
+ Class elementClass = forName(elementClassName, classLoader);
+ return Array.newInstance(elementClass, 0).getClass();
+ }
+
+ // "[Ljava.lang.String;" style arrays
+ int internalArrayMarker = name.indexOf(INTERNAL_ARRAY_PREFIX);
+ if (internalArrayMarker != -1 && name.endsWith(";")) {
+ String elementClassName = null;
+ if (internalArrayMarker == 0) {
+ elementClassName = name.substring(INTERNAL_ARRAY_PREFIX.length(), name.length() - 1);
+ }
+ else if (name.startsWith("[")) {
+ elementClassName = name.substring(1);
+ }
+ Class elementClass = forName(elementClassName, classLoader);
+ return Array.newInstance(elementClass, 0).getClass();
+ }
+
+ ClassLoader classLoaderToUse = classLoader;
+ if (classLoaderToUse == null) {
+ classLoaderToUse = getDefaultClassLoader();
+ }
+ return classLoaderToUse.loadClass(name);
+ }
+
+ /**
+ * Resolve the given class name into a Class instance. Supports
+ * primitives (like "int") and array class names (like "String[]").
+ *
This is effectively equivalent to the forName
+ * method with the same arguments, with the only difference being
+ * the exceptions thrown in case of class loading failure.
+ * @param className the name of the Class
+ * @param classLoader the class loader to use
+ * (may be null, which indicates the default class loader)
+ * @return Class instance for the supplied name
+ * @throws IllegalArgumentException if the class name was not resolvable
+ * (that is, the class could not be found or the class file could not be loaded)
+ * @see #forName(String, ClassLoader)
+ */
+ public static Class resolveClassName(String className, ClassLoader classLoader) throws IllegalArgumentException {
+ try {
+ return forName(className, classLoader);
+ }
+ catch (ClassNotFoundException ex) {
+ IllegalArgumentException iae = new IllegalArgumentException("Cannot find class [" + className + "]");
+ iae.initCause(ex);
+ throw iae;
+ }
+ catch (LinkageError ex) {
+ IllegalArgumentException iae = new IllegalArgumentException(
+ "Error loading class [" + className + "]: problem with class file or dependent class.");
+ iae.initCause(ex);
+ throw iae;
+ }
+ }
+
+ /**
+ * Resolve the given class name as primitive class, if appropriate,
+ * according to the JVM's naming rules for primitive classes.
+ *
Also supports the JVM's internal class names for primitive arrays.
+ * Does not support the "[]" suffix notation for primitive arrays;
+ * this is only supported by {@link #forName}.
+ * @param name the name of the potentially primitive class
+ * @return the primitive class, or null if the name does not denote
+ * a primitive class or primitive array class
+ */
+ public static Class resolvePrimitiveClassName(String name) {
+ Class result = null;
+ // Most class names will be quite long, considering that they
+ // SHOULD sit in a package, so a length check is worthwhile.
+ if (name != null && name.length() <= 8) {
+ // Could be a primitive - likely.
+ result = (Class) primitiveTypeNameMap.get(name);
+ }
+ return result;
+ }
+
+ /**
+ * Determine whether the {@link Class} identified by the supplied name is present
+ * and can be loaded. Will return false if either the class or
+ * one of its dependencies is not present or cannot be loaded.
+ * @param className the name of the class to check
+ * @return whether the specified class is present
+ * @deprecated as of Spring 2.5, in favor of {@link #isPresent(String, ClassLoader)}
+ */
+ public static boolean isPresent(String className) {
+ return isPresent(className, getDefaultClassLoader());
+ }
+
+ /**
+ * Determine whether the {@link Class} identified by the supplied name is present
+ * and can be loaded. Will return false if either the class or
+ * one of its dependencies is not present or cannot be loaded.
+ * @param className the name of the class to check
+ * @param classLoader the class loader to use
+ * (may be null, which indicates the default class loader)
+ * @return whether the specified class is present
+ */
+ public static boolean isPresent(String className, ClassLoader classLoader) {
+ try {
+ forName(className, classLoader);
+ return true;
+ }
+ catch (Throwable ex) {
+ // Class or one of its dependencies is not present...
+ return false;
+ }
+ }
+
+ /**
+ * Return the user-defined class for the given instance: usually simply
+ * the class of the given instance, but the original class in case of a
+ * CGLIB-generated subclass.
+ * @param instance the instance to check
+ * @return the user-defined class
+ */
+ public static Class getUserClass(Object instance) {
+ Assert.notNull(instance, "Instance must not be null");
+ return getUserClass(instance.getClass());
+ }
+
+ /**
+ * Return the user-defined class for the given class: usually simply the given
+ * class, but the original class in case of a CGLIB-generated subclass.
+ * @param clazz the class to check
+ * @return the user-defined class
+ */
+ public static Class getUserClass(Class clazz) {
+ return (clazz != null && clazz.getName().indexOf(CGLIB_CLASS_SEPARATOR) != -1 ?
+ clazz.getSuperclass() : clazz);
+ }
+
+ /**
+ * Check whether the given class is cache-safe in the given context,
+ * i.e. whether it is loaded by the given ClassLoader or a parent of it.
+ * @param clazz the class to analyze
+ * @param classLoader the ClassLoader to potentially cache metadata in
+ */
+ public static boolean isCacheSafe(Class clazz, ClassLoader classLoader) {
+ Assert.notNull(clazz, "Class must not be null");
+ ClassLoader target = clazz.getClassLoader();
+ if (target == null) {
+ return false;
+ }
+ ClassLoader cur = classLoader;
+ if (cur == target) {
+ return true;
+ }
+ while (cur != null) {
+ cur = cur.getParent();
+ if (cur == target) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ /**
+ * Get the class name without the qualified package name.
+ * @param className the className to get the short name for
+ * @return the class name of the class without the package name
+ * @throws IllegalArgumentException if the className is empty
+ */
+ public static String getShortName(String className) {
+ Assert.hasLength(className, "Class name must not be empty");
+ int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR);
+ int nameEndIndex = className.indexOf(CGLIB_CLASS_SEPARATOR);
+ if (nameEndIndex == -1) {
+ nameEndIndex = className.length();
+ }
+ String shortName = className.substring(lastDotIndex + 1, nameEndIndex);
+ shortName = shortName.replace(INNER_CLASS_SEPARATOR, PACKAGE_SEPARATOR);
+ return shortName;
+ }
+
+ /**
+ * Get the class name without the qualified package name.
+ * @param clazz the class to get the short name for
+ * @return the class name of the class without the package name
+ */
+ public static String getShortName(Class clazz) {
+ return getShortName(getQualifiedName(clazz));
+ }
+
+ /**
+ * Return the short string name of a Java class in decapitalized JavaBeans
+ * property format. Strips the outer class name in case of an inner class.
+ * @param clazz the class
+ * @return the short name rendered in a standard JavaBeans property format
+ * @see java.beans.Introspector#decapitalize(String)
+ */
+ public static String getShortNameAsProperty(Class clazz) {
+ String shortName = ClassUtils.getShortName(clazz);
+ int dotIndex = shortName.lastIndexOf('.');
+ shortName = (dotIndex != -1 ? shortName.substring(dotIndex + 1) : shortName);
+ return Introspector.decapitalize(shortName);
+ }
+
+ /**
+ * Determine the name of the class file, relative to the containing
+ * package: e.g. "String.class"
+ * @param clazz the class
+ * @return the file name of the ".class" file
+ */
+ public static String getClassFileName(Class clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ String className = clazz.getName();
+ int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR);
+ return className.substring(lastDotIndex + 1) + CLASS_FILE_SUFFIX;
+ }
+
+ /**
+ * Determine the name of the package of the given class:
+ * e.g. "java.lang" for the java.lang.String class.
+ * @param clazz the class
+ * @return the package name, or the empty String if the class
+ * is defined in the default package
+ */
+ public static String getPackageName(Class clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ String className = clazz.getName();
+ int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR);
+ return (lastDotIndex != -1 ? className.substring(0, lastDotIndex) : "");
+ }
+
+ /**
+ * Return the qualified name of the given class: usually simply
+ * the class name, but component type class name + "[]" for arrays.
+ * @param clazz the class
+ * @return the qualified name of the class
+ */
+ public static String getQualifiedName(Class clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ if (clazz.isArray()) {
+ return getQualifiedNameForArray(clazz);
+ }
+ else {
+ return clazz.getName();
+ }
+ }
+
+ /**
+ * Build a nice qualified name for an array:
+ * component type class name + "[]".
+ * @param clazz the array class
+ * @return a qualified name for the array class
+ */
+ private static String getQualifiedNameForArray(Class clazz) {
+ StringBuffer buffer = new StringBuffer();
+ while (clazz.isArray()) {
+ clazz = clazz.getComponentType();
+ buffer.append(ClassUtils.ARRAY_SUFFIX);
+ }
+ buffer.insert(0, clazz.getName());
+ return buffer.toString();
+ }
+
+ /**
+ * Return the qualified name of the given method, consisting of
+ * fully qualified interface/class name + "." + method name.
+ * @param method the method
+ * @return the qualified name of the method
+ */
+ public static String getQualifiedMethodName(Method method) {
+ Assert.notNull(method, "Method must not be null");
+ return method.getDeclaringClass().getName() + "." + method.getName();
+ }
+
+ /**
+ * Return a descriptive name for the given object's type: usually simply
+ * the class name, but component type class name + "[]" for arrays,
+ * and an appended list of implemented interfaces for JDK proxies.
+ * @param value the value to introspect
+ * @return the qualified name of the class
+ */
+ public static String getDescriptiveType(Object value) {
+ if (value == null) {
+ return null;
+ }
+ Class clazz = value.getClass();
+ if (Proxy.isProxyClass(clazz)) {
+ StringBuffer buf = new StringBuffer(clazz.getName());
+ buf.append(" implementing ");
+ Class[] ifcs = clazz.getInterfaces();
+ for (int i = 0; i < ifcs.length; i++) {
+ buf.append(ifcs[i].getName());
+ if (i < ifcs.length - 1) {
+ buf.append(',');
+ }
+ }
+ return buf.toString();
+ }
+ else if (clazz.isArray()) {
+ return getQualifiedNameForArray(clazz);
+ }
+ else {
+ return clazz.getName();
+ }
+ }
+
+
+ /**
+ * Determine whether the given class has a constructor with the given signature.
+ *
Essentially translates NoSuchMethodException to "false".
+ * @param clazz the clazz to analyze
+ * @param paramTypes the parameter types of the method
+ * @return whether the class has a corresponding constructor
+ * @see java.lang.Class#getMethod
+ */
+ public static boolean hasConstructor(Class clazz, Class[] paramTypes) {
+ return (getConstructorIfAvailable(clazz, paramTypes) != null);
+ }
+
+ /**
+ * Determine whether the given class has a constructor with the given signature,
+ * and return it if available (else return null).
+ *
Essentially translates NoSuchMethodException to null.
+ * @param clazz the clazz to analyze
+ * @param paramTypes the parameter types of the method
+ * @return the constructor, or null if not found
+ * @see java.lang.Class#getConstructor
+ */
+ public static Constructor getConstructorIfAvailable(Class clazz, Class[] paramTypes) {
+ Assert.notNull(clazz, "Class must not be null");
+ try {
+ return clazz.getConstructor(paramTypes);
+ }
+ catch (NoSuchMethodException ex) {
+ return null;
+ }
+ }
+
+ /**
+ * Determine whether the given class has a method with the given signature.
+ *
Essentially translates NoSuchMethodException to "false".
+ * @param clazz the clazz to analyze
+ * @param methodName the name of the method
+ * @param paramTypes the parameter types of the method
+ * @return whether the class has a corresponding method
+ * @see java.lang.Class#getMethod
+ */
+ public static boolean hasMethod(Class clazz, String methodName, Class[] paramTypes) {
+ return (getMethodIfAvailable(clazz, methodName, paramTypes) != null);
+ }
+
+ /**
+ * Determine whether the given class has a method with the given signature,
+ * and return it if available (else return null).
+ *
Essentially translates NoSuchMethodException to null.
+ * @param clazz the clazz to analyze
+ * @param methodName the name of the method
+ * @param paramTypes the parameter types of the method
+ * @return the method, or null if not found
+ * @see java.lang.Class#getMethod
+ */
+ public static Method getMethodIfAvailable(Class clazz, String methodName, Class[] paramTypes) {
+ Assert.notNull(clazz, "Class must not be null");
+ Assert.notNull(methodName, "Method name must not be null");
+ try {
+ return clazz.getMethod(methodName, paramTypes);
+ }
+ catch (NoSuchMethodException ex) {
+ return null;
+ }
+ }
+
+ /**
+ * Return the number of methods with a given name (with any argument types),
+ * for the given class and/or its superclasses. Includes non-public methods.
+ * @param clazz the clazz to check
+ * @param methodName the name of the method
+ * @return the number of methods with the given name
+ */
+ public static int getMethodCountForName(Class clazz, String methodName) {
+ Assert.notNull(clazz, "Class must not be null");
+ Assert.notNull(methodName, "Method name must not be null");
+ int count = 0;
+ Method[] declaredMethods = clazz.getDeclaredMethods();
+ for (int i = 0; i < declaredMethods.length; i++) {
+ Method method = declaredMethods[i];
+ if (methodName.equals(method.getName())) {
+ count++;
+ }
+ }
+ Class[] ifcs = clazz.getInterfaces();
+ for (int i = 0; i < ifcs.length; i++) {
+ count += getMethodCountForName(ifcs[i], methodName);
+ }
+ if (clazz.getSuperclass() != null) {
+ count += getMethodCountForName(clazz.getSuperclass(), methodName);
+ }
+ return count;
+ }
+
+ /**
+ * Does the given class and/or its superclasses at least have one or more
+ * methods (with any argument types)? Includes non-public methods.
+ * @param clazz the clazz to check
+ * @param methodName the name of the method
+ * @return whether there is at least one method with the given name
+ */
+ public static boolean hasAtLeastOneMethodWithName(Class clazz, String methodName) {
+ Assert.notNull(clazz, "Class must not be null");
+ Assert.notNull(methodName, "Method name must not be null");
+ Method[] declaredMethods = clazz.getDeclaredMethods();
+ for (int i = 0; i < declaredMethods.length; i++) {
+ Method method = declaredMethods[i];
+ if (method.getName().equals(methodName)) {
+ return true;
+ }
+ }
+ Class[] ifcs = clazz.getInterfaces();
+ for (int i = 0; i < ifcs.length; i++) {
+ if (hasAtLeastOneMethodWithName(ifcs[i], methodName)) {
+ return true;
+ }
+ }
+ return (clazz.getSuperclass() != null && hasAtLeastOneMethodWithName(clazz.getSuperclass(), methodName));
+ }
+
+ /**
+ * Given a method, which may come from an interface, and a target class used
+ * in the current reflective invocation, find the corresponding target method
+ * if there is one. E.g. the method may be IFoo.bar() and the
+ * target class may be DefaultFoo. In this case, the method may be
+ * DefaultFoo.bar(). This enables attributes on that method to be found.
+ *
NOTE: In contrast to {@link org.springframework.aop.support.AopUtils#getMostSpecificMethod},
+ * this method does not resolve Java 5 bridge methods automatically.
+ * Call {@link org.springframework.core.BridgeMethodResolver#findBridgedMethod}
+ * if bridge method resolution is desirable (e.g. for obtaining metadata from
+ * the original method definition).
+ * @param method the method to be invoked, which may come from an interface
+ * @param targetClass the target class for the current invocation.
+ * May be null or may not even implement the method.
+ * @return the specific target method, or the original method if the
+ * targetClass doesn't implement it or is null
+ * @see org.springframework.aop.support.AopUtils#getMostSpecificMethod
+ */
+ public static Method getMostSpecificMethod(Method method, Class targetClass) {
+ if (method != null && targetClass != null && !targetClass.equals(method.getDeclaringClass())) {
+ try {
+ method = targetClass.getMethod(method.getName(), method.getParameterTypes());
+ }
+ catch (NoSuchMethodException ex) {
+ // Perhaps the target class doesn't implement this method:
+ // that's fine, just use the original method.
+ }
+ }
+ return method;
+ }
+
+ /**
+ * Return a static method of a class.
+ * @param methodName the static method name
+ * @param clazz the class which defines the method
+ * @param args the parameter types to the method
+ * @return the static method, or null if no static method was found
+ * @throws IllegalArgumentException if the method name is blank or the clazz is null
+ */
+ public static Method getStaticMethod(Class clazz, String methodName, Class[] args) {
+ Assert.notNull(clazz, "Class must not be null");
+ Assert.notNull(methodName, "Method name must not be null");
+ try {
+ Method method = clazz.getDeclaredMethod(methodName, args);
+ if ((method.getModifiers() & Modifier.STATIC) != 0) {
+ return method;
+ }
+ }
+ catch (NoSuchMethodException ex) {
+ }
+ return null;
+ }
+
+
+ /**
+ * Check if the given class represents a primitive wrapper,
+ * i.e. Boolean, Byte, Character, Short, Integer, Long, Float, or Double.
+ * @param clazz the class to check
+ * @return whether the given class is a primitive wrapper class
+ */
+ public static boolean isPrimitiveWrapper(Class clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ return primitiveWrapperTypeMap.containsKey(clazz);
+ }
+
+ /**
+ * Check if the given class represents a primitive (i.e. boolean, byte,
+ * char, short, int, long, float, or double) or a primitive wrapper
+ * (i.e. Boolean, Byte, Character, Short, Integer, Long, Float, or Double).
+ * @param clazz the class to check
+ * @return whether the given class is a primitive or primitive wrapper class
+ */
+ public static boolean isPrimitiveOrWrapper(Class clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ return (clazz.isPrimitive() || isPrimitiveWrapper(clazz));
+ }
+
+ /**
+ * Check if the given class represents an array of primitives,
+ * i.e. boolean, byte, char, short, int, long, float, or double.
+ * @param clazz the class to check
+ * @return whether the given class is a primitive array class
+ */
+ public static boolean isPrimitiveArray(Class clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ return (clazz.isArray() && clazz.getComponentType().isPrimitive());
+ }
+
+ /**
+ * Check if the given class represents an array of primitive wrappers,
+ * i.e. Boolean, Byte, Character, Short, Integer, Long, Float, or Double.
+ * @param clazz the class to check
+ * @return whether the given class is a primitive wrapper array class
+ */
+ public static boolean isPrimitiveWrapperArray(Class clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ return (clazz.isArray() && isPrimitiveWrapper(clazz.getComponentType()));
+ }
+
+ /**
+ * Check if the right-hand side type may be assigned to the left-hand side
+ * type, assuming setting by reflection. Considers primitive wrapper
+ * classes as assignable to the corresponding primitive types.
+ * @param lhsType the target type
+ * @param rhsType the value type that should be assigned to the target type
+ * @return if the target type is assignable from the value type
+ * @see TypeUtils#isAssignable
+ */
+ public static boolean isAssignable(Class lhsType, Class rhsType) {
+ Assert.notNull(lhsType, "Left-hand side type must not be null");
+ Assert.notNull(rhsType, "Right-hand side type must not be null");
+ return (lhsType.isAssignableFrom(rhsType) ||
+ lhsType.equals(primitiveWrapperTypeMap.get(rhsType)));
+ }
+
+ /**
+ * Determine if the given type is assignable from the given value,
+ * assuming setting by reflection. Considers primitive wrapper classes
+ * as assignable to the corresponding primitive types.
+ * @param type the target type
+ * @param value the value that should be assigned to the type
+ * @return if the type is assignable from the value
+ */
+ public static boolean isAssignableValue(Class type, Object value) {
+ Assert.notNull(type, "Type must not be null");
+ return (value != null ? isAssignable(type, value.getClass()) : !type.isPrimitive());
+ }
+
+
+ /**
+ * Convert a "/"-based resource path to a "."-based fully qualified class name.
+ * @param resourcePath the resource path pointing to a class
+ * @return the corresponding fully qualified class name
+ */
+ public static String convertResourcePathToClassName(String resourcePath) {
+ return resourcePath.replace('/', '.');
+ }
+
+ /**
+ * Convert a "."-based fully qualified class name to a "/"-based resource path.
+ * @param className the fully qualified class name
+ * @return the corresponding resource path, pointing to the class
+ */
+ public static String convertClassNameToResourcePath(String className) {
+ return className.replace('.', '/');
+ }
+
+ /**
+ * Return a path suitable for use with ClassLoader.getResource
+ * (also suitable for use with Class.getResource by prepending a
+ * slash ('/') to the return value. Built by taking the package of the specified
+ * class file, converting all dots ('.') to slashes ('/'), adding a trailing slash
+ * if necesssary, and concatenating the specified resource name to this.
+ *
As such, this function may be used to build a path suitable for
+ * loading a resource file that is in the same package as a class file,
+ * although {@link org.springframework.core.io.ClassPathResource} is usually
+ * even more convenient.
+ * @param clazz the Class whose package will be used as the base
+ * @param resourceName the resource name to append. A leading slash is optional.
+ * @return the built-up resource path
+ * @see java.lang.ClassLoader#getResource
+ * @see java.lang.Class#getResource
+ */
+ public static String addResourcePathToPackagePath(Class clazz, String resourceName) {
+ Assert.notNull(resourceName, "Resource name must not be null");
+ if (!resourceName.startsWith("/")) {
+ return classPackageAsResourcePath(clazz) + "/" + resourceName;
+ }
+ return classPackageAsResourcePath(clazz) + resourceName;
+ }
+
+ /**
+ * Given an input class object, return a string which consists of the
+ * class's package name as a pathname, i.e., all dots ('.') are replaced by
+ * slashes ('/'). Neither a leading nor trailing slash is added. The result
+ * could be concatenated with a slash and the name of a resource, and fed
+ * directly to ClassLoader.getResource(). For it to be fed to
+ * Class.getResource instead, a leading slash would also have
+ * to be prepended to the returned value.
+ * @param clazz the input class. A null value or the default
+ * (empty) package will result in an empty string ("") being returned.
+ * @return a path which represents the package name
+ * @see ClassLoader#getResource
+ * @see Class#getResource
+ */
+ public static String classPackageAsResourcePath(Class clazz) {
+ if (clazz == null) {
+ return "";
+ }
+ String className = clazz.getName();
+ int packageEndIndex = className.lastIndexOf('.');
+ if (packageEndIndex == -1) {
+ return "";
+ }
+ String packageName = className.substring(0, packageEndIndex);
+ return packageName.replace('.', '/');
+ }
+
+ /**
+ * Build a String that consists of the names of the classes/interfaces
+ * in the given array.
+ *
Basically like AbstractCollection.toString(), but stripping
+ * the "class "/"interface " prefix before every class name.
+ * @param classes a Collection of Class objects (may be null)
+ * @return a String of form "[com.foo.Bar, com.foo.Baz]"
+ * @see java.util.AbstractCollection#toString()
+ */
+ public static String classNamesToString(Class[] classes) {
+ return classNamesToString(Arrays.asList(classes));
+ }
+
+ /**
+ * Build a String that consists of the names of the classes/interfaces
+ * in the given collection.
+ *
Basically like AbstractCollection.toString(), but stripping
+ * the "class "/"interface " prefix before every class name.
+ * @param classes a Collection of Class objects (may be null)
+ * @return a String of form "[com.foo.Bar, com.foo.Baz]"
+ * @see java.util.AbstractCollection#toString()
+ */
+ public static String classNamesToString(Collection classes) {
+ if (CollectionUtils.isEmpty(classes)) {
+ return "[]";
+ }
+ StringBuffer sb = new StringBuffer("[");
+ for (Iterator it = classes.iterator(); it.hasNext(); ) {
+ Class clazz = (Class) it.next();
+ sb.append(clazz.getName());
+ if (it.hasNext()) {
+ sb.append(", ");
+ }
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+
+ /**
+ * Return all interfaces that the given instance implements as array,
+ * including ones implemented by superclasses.
+ * @param instance the instance to analyse for interfaces
+ * @return all interfaces that the given instance implements as array
+ */
+ public static Class[] getAllInterfaces(Object instance) {
+ Assert.notNull(instance, "Instance must not be null");
+ return getAllInterfacesForClass(instance.getClass());
+ }
+
+ /**
+ * Return all interfaces that the given class implements as array,
+ * including ones implemented by superclasses.
+ *
If the class itself is an interface, it gets returned as sole interface.
+ * @param clazz the class to analyse for interfaces
+ * @return all interfaces that the given object implements as array
+ */
+ public static Class[] getAllInterfacesForClass(Class clazz) {
+ return getAllInterfacesForClass(clazz, null);
+ }
+
+ /**
+ * Return all interfaces that the given class implements as array,
+ * including ones implemented by superclasses.
+ *
If the class itself is an interface, it gets returned as sole interface.
+ * @param clazz the class to analyse for interfaces
+ * @param classLoader the ClassLoader that the interfaces need to be visible in
+ * (may be null when accepting all declared interfaces)
+ * @return all interfaces that the given object implements as array
+ */
+ public static Class[] getAllInterfacesForClass(Class clazz, ClassLoader classLoader) {
+ Assert.notNull(clazz, "Class must not be null");
+ if (clazz.isInterface()) {
+ return new Class[] {clazz};
+ }
+ List interfaces = new ArrayList();
+ while (clazz != null) {
+ for (int i = 0; i < clazz.getInterfaces().length; i++) {
+ Class ifc = clazz.getInterfaces()[i];
+ if (!interfaces.contains(ifc) &&
+ (classLoader == null || isVisible(ifc, classLoader))) {
+ interfaces.add(ifc);
+ }
+ }
+ clazz = clazz.getSuperclass();
+ }
+ return (Class[]) interfaces.toArray(new Class[interfaces.size()]);
+ }
+
+ /**
+ * Return all interfaces that the given instance implements as Set,
+ * including ones implemented by superclasses.
+ * @param instance the instance to analyse for interfaces
+ * @return all interfaces that the given instance implements as Set
+ */
+ public static Set getAllInterfacesAsSet(Object instance) {
+ Assert.notNull(instance, "Instance must not be null");
+ return getAllInterfacesForClassAsSet(instance.getClass());
+ }
+
+ /**
+ * Return all interfaces that the given class implements as Set,
+ * including ones implemented by superclasses.
+ *
If the class itself is an interface, it gets returned as sole interface.
+ * @param clazz the class to analyse for interfaces
+ * @return all interfaces that the given object implements as Set
+ */
+ public static Set getAllInterfacesForClassAsSet(Class clazz) {
+ return getAllInterfacesForClassAsSet(clazz, null);
+ }
+
+ /**
+ * Return all interfaces that the given class implements as Set,
+ * including ones implemented by superclasses.
+ *
If the class itself is an interface, it gets returned as sole interface.
+ * @param clazz the class to analyse for interfaces
+ * @param classLoader the ClassLoader that the interfaces need to be visible in
+ * (may be null when accepting all declared interfaces)
+ * @return all interfaces that the given object implements as Set
+ */
+ public static Set getAllInterfacesForClassAsSet(Class clazz, ClassLoader classLoader) {
+ Assert.notNull(clazz, "Class must not be null");
+ if (clazz.isInterface()) {
+ return Collections.singleton(clazz);
+ }
+ Set interfaces = new LinkedHashSet();
+ while (clazz != null) {
+ for (int i = 0; i < clazz.getInterfaces().length; i++) {
+ Class ifc = clazz.getInterfaces()[i];
+ if (classLoader == null || isVisible(ifc, classLoader)) {
+ interfaces.add(ifc);
+ }
+ }
+ clazz = clazz.getSuperclass();
+ }
+ return interfaces;
+ }
+
+ /**
+ * Create a composite interface Class for the given interfaces,
+ * implementing the given interfaces in one single Class.
+ *
This implementation builds a JDK proxy class for the given interfaces.
+ * @param interfaces the interfaces to merge
+ * @param classLoader the ClassLoader to create the composite Class in
+ * @return the merged interface as Class
+ * @see java.lang.reflect.Proxy#getProxyClass
+ */
+ public static Class createCompositeInterface(Class[] interfaces, ClassLoader classLoader) {
+ Assert.notEmpty(interfaces, "Interfaces must not be empty");
+ Assert.notNull(classLoader, "ClassLoader must not be null");
+ return Proxy.getProxyClass(classLoader, interfaces);
+ }
+
+ /**
+ * Check whether the given class is visible in the given ClassLoader.
+ * @param clazz the class to check (typically an interface)
+ * @param classLoader the ClassLoader to check against (may be null,
+ * in which case this method will always return true)
+ */
+ public static boolean isVisible(Class clazz, ClassLoader classLoader) {
+ if (classLoader == null) {
+ return true;
+ }
+ try {
+ Class actualClass = classLoader.loadClass(clazz.getName());
+ return (clazz == actualClass);
+ // Else: different interface class found...
+ }
+ catch (ClassNotFoundException ex) {
+ // No interface class found...
+ return false;
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/CollectionUtils.java b/org.springframework.core/src/main/java/org/springframework/util/CollectionUtils.java
new file mode 100644
index 00000000000..66b362e86e2
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/CollectionUtils.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Miscellaneous collection utility methods.
+ * Mainly for internal use within the framework.
+ *
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @since 1.1.3
+ */
+public abstract class CollectionUtils {
+
+ /**
+ * Return true if the supplied Collection is null
+ * or empty. Otherwise, return false.
+ * @param collection the Collection to check
+ * @return whether the given Collection is empty
+ */
+ public static boolean isEmpty(Collection collection) {
+ return (collection == null || collection.isEmpty());
+ }
+
+ /**
+ * Return true if the supplied Map is null
+ * or empty. Otherwise, return false.
+ * @param map the Map to check
+ * @return whether the given Map is empty
+ */
+ public static boolean isEmpty(Map map) {
+ return (map == null || map.isEmpty());
+ }
+
+ /**
+ * Convert the supplied array into a List. A primitive array gets
+ * converted into a List of the appropriate wrapper type.
+ *
A null source value will be converted to an
+ * empty List.
+ * @param source the (potentially primitive) array
+ * @return the converted List result
+ * @see ObjectUtils#toObjectArray(Object)
+ */
+ public static List arrayToList(Object source) {
+ return Arrays.asList(ObjectUtils.toObjectArray(source));
+ }
+
+ /**
+ * Merge the given array into the given Collection.
+ * @param array the array to merge (may be null)
+ * @param collection the target Collection to merge the array into
+ */
+ public static void mergeArrayIntoCollection(Object array, Collection collection) {
+ if (collection == null) {
+ throw new IllegalArgumentException("Collection must not be null");
+ }
+ Object[] arr = ObjectUtils.toObjectArray(array);
+ for (int i = 0; i < arr.length; i++) {
+ collection.add(arr[i]);
+ }
+ }
+
+ /**
+ * Merge the given Properties instance into the given Map,
+ * copying all properties (key-value pairs) over.
+ *
Uses Properties.propertyNames() to even catch
+ * default properties linked into the original Properties instance.
+ * @param props the Properties instance to merge (may be null)
+ * @param map the target Map to merge the properties into
+ */
+ public static void mergePropertiesIntoMap(Properties props, Map map) {
+ if (map == null) {
+ throw new IllegalArgumentException("Map must not be null");
+ }
+ if (props != null) {
+ for (Enumeration en = props.propertyNames(); en.hasMoreElements();) {
+ String key = (String) en.nextElement();
+ map.put(key, props.getProperty(key));
+ }
+ }
+ }
+
+
+ /**
+ * Check whether the given Iterator contains the given element.
+ * @param iterator the Iterator to check
+ * @param element the element to look for
+ * @return true if found, false else
+ */
+ public static boolean contains(Iterator iterator, Object element) {
+ if (iterator != null) {
+ while (iterator.hasNext()) {
+ Object candidate = iterator.next();
+ if (ObjectUtils.nullSafeEquals(candidate, element)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the given Enumeration contains the given element.
+ * @param enumeration the Enumeration to check
+ * @param element the element to look for
+ * @return true if found, false else
+ */
+ public static boolean contains(Enumeration enumeration, Object element) {
+ if (enumeration != null) {
+ while (enumeration.hasMoreElements()) {
+ Object candidate = enumeration.nextElement();
+ if (ObjectUtils.nullSafeEquals(candidate, element)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the given Collection contains the given element instance.
+ *
Enforces the given instance to be present, rather than returning
+ * true for an equal element as well.
+ * @param collection the Collection to check
+ * @param element the element to look for
+ * @return true if found, false else
+ */
+ public static boolean containsInstance(Collection collection, Object element) {
+ if (collection != null) {
+ for (Iterator it = collection.iterator(); it.hasNext();) {
+ Object candidate = it.next();
+ if (candidate == element) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return true if any element in 'candidates' is
+ * contained in 'source'; otherwise returns false.
+ * @param source the source Collection
+ * @param candidates the candidates to search for
+ * @return whether any of the candidates has been found
+ */
+ public static boolean containsAny(Collection source, Collection candidates) {
+ if (isEmpty(source) || isEmpty(candidates)) {
+ return false;
+ }
+ for (Iterator it = candidates.iterator(); it.hasNext();) {
+ if (source.contains(it.next())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return the first element in 'candidates' that is contained in
+ * 'source'. If no element in 'candidates' is present in
+ * 'source' returns null. Iteration order is
+ * {@link Collection} implementation specific.
+ * @param source the source Collection
+ * @param candidates the candidates to search for
+ * @return the first present object, or null if not found
+ */
+ public static Object findFirstMatch(Collection source, Collection candidates) {
+ if (isEmpty(source) || isEmpty(candidates)) {
+ return null;
+ }
+ for (Iterator it = candidates.iterator(); it.hasNext();) {
+ Object candidate = it.next();
+ if (source.contains(candidate)) {
+ return candidate;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Find a single value of the given type in the given Collection.
+ * @param collection the Collection to search
+ * @param type the type to look for
+ * @return a value of the given type found if there is a clear match,
+ * or null if none or more than one such value found
+ */
+ public static Object findValueOfType(Collection collection, Class type) {
+ if (isEmpty(collection)) {
+ return null;
+ }
+ Object value = null;
+ for (Iterator it = collection.iterator(); it.hasNext();) {
+ Object obj = it.next();
+ if (type == null || type.isInstance(obj)) {
+ if (value != null) {
+ // More than one value found... no clear single value.
+ return null;
+ }
+ value = obj;
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Find a single value of one of the given types in the given Collection:
+ * searching the Collection for a value of the first type, then
+ * searching for a value of the second type, etc.
+ * @param collection the collection to search
+ * @param types the types to look for, in prioritized order
+ * @return a value of one of the given types found if there is a clear match,
+ * or null if none or more than one such value found
+ */
+ public static Object findValueOfType(Collection collection, Class[] types) {
+ if (isEmpty(collection) || ObjectUtils.isEmpty(types)) {
+ return null;
+ }
+ for (int i = 0; i < types.length; i++) {
+ Object value = findValueOfType(collection, types[i]);
+ if (value != null) {
+ return value;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Determine whether the given Collection only contains a single unique object.
+ * @param collection the Collection to check
+ * @return true if the collection contains a single reference or
+ * multiple references to the same instance, false else
+ */
+ public static boolean hasUniqueObject(Collection collection) {
+ if (isEmpty(collection)) {
+ return false;
+ }
+ boolean hasCandidate = false;
+ Object candidate = null;
+ for (Iterator it = collection.iterator(); it.hasNext();) {
+ Object elem = it.next();
+ if (!hasCandidate) {
+ hasCandidate = true;
+ candidate = elem;
+ }
+ else if (candidate != elem) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/CommonsLogWriter.java b/org.springframework.core/src/main/java/org/springframework/util/CommonsLogWriter.java
new file mode 100644
index 00000000000..9db1c876601
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/CommonsLogWriter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.io.Writer;
+
+import org.apache.commons.logging.Log;
+
+/**
+ * java.io.Writer adapter for a Commons Logging Log.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.1
+ */
+public class CommonsLogWriter extends Writer {
+
+ private final Log logger;
+
+ private final StringBuffer buffer = new StringBuffer();
+
+
+ /**
+ * Create a new CommonsLogWriter for the given Commons Logging logger.
+ * @param logger the Commons Logging logger to write to
+ */
+ public CommonsLogWriter(Log logger) {
+ Assert.notNull(logger, "Logger must not be null");
+ this.logger = logger;
+ }
+
+
+ public void write(char ch) {
+ if (ch == '\n' && this.buffer.length() > 0) {
+ this.logger.debug(this.buffer.toString());
+ this.buffer.setLength(0);
+ }
+ else {
+ this.buffer.append((char) ch);
+ }
+ }
+
+ public void write(char[] buffer, int offset, int length) {
+ for (int i = 0; i < length; i++) {
+ char ch = buffer[offset + i];
+ if (ch == '\n' && this.buffer.length() > 0) {
+ this.logger.debug(this.buffer.toString());
+ this.buffer.setLength(0);
+ }
+ else {
+ this.buffer.append((char) ch);
+ }
+ }
+ }
+
+ public void flush() {
+ }
+
+ public void close() {
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/ConcurrencyThrottleSupport.java b/org.springframework.core/src/main/java/org/springframework/util/ConcurrencyThrottleSupport.java
new file mode 100644
index 00000000000..172359c351e
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/ConcurrencyThrottleSupport.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Support class for throttling concurrent access to a specific resource.
+ *
+ *
Designed for use as a base class, with the subclass invoking
+ * the {@link #beforeAccess()} and {@link #afterAccess()} methods at
+ * appropriate points of its workflow. Note that afterAccess
+ * should usually be called in a finally block!
+ *
+ *
The default concurrency limit of this support class is -1
+ * ("unbounded concurrency"). Subclasses may override this default;
+ * check the javadoc of the concrete class that you're using.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2.5
+ * @see #setConcurrencyLimit
+ * @see #beforeAccess()
+ * @see #afterAccess()
+ * @see org.springframework.aop.interceptor.ConcurrencyThrottleInterceptor
+ * @see java.io.Serializable
+ */
+public abstract class ConcurrencyThrottleSupport implements Serializable {
+
+ /**
+ * Permit any number of concurrent invocations: that is, don't throttle concurrency.
+ */
+ public static final int UNBOUNDED_CONCURRENCY = -1;
+
+ /**
+ * Switch concurrency 'off': that is, don't allow any concurrent invocations.
+ */
+ public static final int NO_CONCURRENCY = 0;
+
+
+ /** Transient to optimize serialization */
+ protected transient Log logger = LogFactory.getLog(getClass());
+
+ private transient Object monitor = new Object();
+
+ private int concurrencyLimit = UNBOUNDED_CONCURRENCY;
+
+ private int concurrencyCount = 0;
+
+
+ /**
+ * Set the maximum number of concurrent access attempts allowed.
+ * -1 indicates unbounded concurrency.
+ *
In principle, this limit can be changed at runtime,
+ * although it is generally designed as a config time setting.
+ *
NOTE: Do not switch between -1 and any concrete limit at runtime,
+ * as this will lead to inconsistent concurrency counts: A limit
+ * of -1 effectively turns off concurrency counting completely.
+ */
+ public void setConcurrencyLimit(int concurrencyLimit) {
+ this.concurrencyLimit = concurrencyLimit;
+ }
+
+ /**
+ * Return the maximum number of concurrent access attempts allowed.
+ */
+ public int getConcurrencyLimit() {
+ return this.concurrencyLimit;
+ }
+
+ /**
+ * Return whether this throttle is currently active.
+ * @return true if the concurrency limit for this instance is active
+ * @see #getConcurrencyLimit()
+ */
+ public boolean isThrottleActive() {
+ return (this.concurrencyLimit > 0);
+ }
+
+
+ /**
+ * To be invoked before the main execution logic of concrete subclasses.
+ *
This implementation applies the concurrency throttle.
+ * @see #afterAccess()
+ */
+ protected void beforeAccess() {
+ if (this.concurrencyLimit == NO_CONCURRENCY) {
+ throw new IllegalStateException(
+ "Currently no invocations allowed - concurrency limit set to NO_CONCURRENCY");
+ }
+ if (this.concurrencyLimit > 0) {
+ boolean debug = logger.isDebugEnabled();
+ synchronized (this.monitor) {
+ boolean interrupted = false;
+ while (this.concurrencyCount >= this.concurrencyLimit) {
+ if (interrupted) {
+ throw new IllegalStateException("Thread was interrupted while waiting for invocation access, " +
+ "but concurrency limit still does not allow for entering");
+ }
+ if (debug) {
+ logger.debug("Concurrency count " + this.concurrencyCount +
+ " has reached limit " + this.concurrencyLimit + " - blocking");
+ }
+ try {
+ this.monitor.wait();
+ }
+ catch (InterruptedException ex) {
+ // Re-interrupt current thread, to allow other threads to react.
+ Thread.currentThread().interrupt();
+ interrupted = true;
+ }
+ }
+ if (debug) {
+ logger.debug("Entering throttle at concurrency count " + this.concurrencyCount);
+ }
+ this.concurrencyCount++;
+ }
+ }
+ }
+
+ /**
+ * To be invoked after the main execution logic of concrete subclasses.
+ * @see #beforeAccess()
+ */
+ protected void afterAccess() {
+ if (this.concurrencyLimit >= 0) {
+ synchronized (this.monitor) {
+ this.concurrencyCount--;
+ if (logger.isDebugEnabled()) {
+ logger.debug("Returning from throttle at concurrency count " + this.concurrencyCount);
+ }
+ this.monitor.notify();
+ }
+ }
+ }
+
+
+ //---------------------------------------------------------------------
+ // Serialization support
+ //---------------------------------------------------------------------
+
+ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
+ // Rely on default serialization, just initialize state after deserialization.
+ ois.defaultReadObject();
+
+ // Initialize transient fields.
+ this.logger = LogFactory.getLog(getClass());
+ this.monitor = new Object();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/CustomizableThreadCreator.java b/org.springframework.core/src/main/java/org/springframework/util/CustomizableThreadCreator.java
new file mode 100644
index 00000000000..0713b9842ed
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/CustomizableThreadCreator.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util;
+
+/**
+ * Simple customizable helper class for creating threads. Provides various
+ * bean properties, such as thread name prefix, thread priority, etc.
+ *
+ *
Serves as base class for thread factories such as
+ * {@link org.springframework.scheduling.concurrent.CustomizableThreadFactory}.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0.3
+ * @see org.springframework.scheduling.concurrent.CustomizableThreadFactory
+ */
+public class CustomizableThreadCreator {
+
+ private String threadNamePrefix;
+
+ private int threadPriority = Thread.NORM_PRIORITY;
+
+ private boolean daemon = false;
+
+ private ThreadGroup threadGroup;
+
+ private int threadCount = 0;
+
+ private final Object threadCountMonitor = new Object();
+
+
+ /**
+ * Create a new CustomizableThreadCreator with default thread name prefix.
+ */
+ public CustomizableThreadCreator() {
+ this.threadNamePrefix = getDefaultThreadNamePrefix();
+ }
+
+ /**
+ * Create a new CustomizableThreadCreator with the given thread name prefix.
+ * @param threadNamePrefix the prefix to use for the names of newly created threads
+ */
+ public CustomizableThreadCreator(String threadNamePrefix) {
+ this.threadNamePrefix = (threadNamePrefix != null ? threadNamePrefix : getDefaultThreadNamePrefix());
+ }
+
+
+ /**
+ * Specify the prefix to use for the names of newly created threads.
+ * Default is "SimpleAsyncTaskExecutor-".
+ */
+ public void setThreadNamePrefix(String threadNamePrefix) {
+ this.threadNamePrefix = (threadNamePrefix != null ? threadNamePrefix : getDefaultThreadNamePrefix());
+ }
+
+ /**
+ * Return the thread name prefix to use for the names of newly
+ * created threads.
+ */
+ public String getThreadNamePrefix() {
+ return this.threadNamePrefix;
+ }
+
+ /**
+ * Set the priority of the threads that this factory creates.
+ * Default is 5.
+ * @see java.lang.Thread#NORM_PRIORITY
+ */
+ public void setThreadPriority(int threadPriority) {
+ this.threadPriority = threadPriority;
+ }
+
+ /**
+ * Return the priority of the threads that this factory creates.
+ */
+ public int getThreadPriority() {
+ return this.threadPriority;
+ }
+
+ /**
+ * Set whether this factory is supposed to create daemon threads,
+ * just executing as long as the application itself is running.
+ *
Default is "false": Concrete factories usually support explicit
+ * cancelling. Hence, if the application shuts down, Runnables will
+ * by default finish their execution.
+ *
Specify "true" for eager shutdown of threads which still
+ * actively execute a Runnable.
+ * @see java.lang.Thread#setDaemon
+ */
+ public void setDaemon(boolean daemon) {
+ this.daemon = daemon;
+ }
+
+ /**
+ * Return whether this factory should create daemon threads.
+ */
+ public boolean isDaemon() {
+ return this.daemon;
+ }
+
+ /**
+ * Specify the name of the thread group that threads should be created in.
+ * @see #setThreadGroup
+ */
+ public void setThreadGroupName(String name) {
+ this.threadGroup = new ThreadGroup(name);
+ }
+
+ /**
+ * Specify the thread group that threads should be created in.
+ * @see #setThreadGroupName
+ */
+ public void setThreadGroup(ThreadGroup threadGroup) {
+ this.threadGroup = threadGroup;
+ }
+
+ /**
+ * Return the thread group that threads should be created in
+ * (or null) for the default group.
+ */
+ public ThreadGroup getThreadGroup() {
+ return this.threadGroup;
+ }
+
+
+ /**
+ * Template method for the creation of a Thread.
+ *
Default implementation creates a new Thread for the given
+ * Runnable, applying an appropriate thread name.
+ * @param runnable the Runnable to execute
+ * @see #nextThreadName()
+ */
+ public Thread createThread(Runnable runnable) {
+ Thread thread = new Thread(getThreadGroup(), runnable, nextThreadName());
+ thread.setPriority(getThreadPriority());
+ thread.setDaemon(isDaemon());
+ return thread;
+ }
+
+ /**
+ * Return the thread name to use for a newly created thread.
+ *
Default implementation returns the specified thread name prefix
+ * with an increasing thread count appended: for example,
+ * "SimpleAsyncTaskExecutor-0".
+ * @see #getThreadNamePrefix()
+ */
+ protected String nextThreadName() {
+ int threadNumber = 0;
+ synchronized (this.threadCountMonitor) {
+ this.threadCount++;
+ threadNumber = this.threadCount;
+ }
+ return getThreadNamePrefix() + threadNumber;
+ }
+
+ /**
+ * Build the default thread name prefix for this factory.
+ * @return the default thread name prefix (never null)
+ */
+ protected String getDefaultThreadNamePrefix() {
+ return ClassUtils.getShortName(getClass()) + "-";
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/DefaultPropertiesPersister.java b/org.springframework.core/src/main/java/org/springframework/util/DefaultPropertiesPersister.java
new file mode 100644
index 00000000000..bedb9bb1e82
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/DefaultPropertiesPersister.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Properties;
+
+/**
+ * Default implementation of the {@link PropertiesPersister} interface.
+ * Follows the native parsing of java.util.Properties.
+ *
+ *
Allows for reading from any Reader and writing to any Writer, for example
+ * to specify a charset for a properties file. This is a capability that standard
+ * java.util.Properties unfortunately lacks up until JDK 1.5:
+ * You can only load files using the ISO-8859-1 charset there.
+ *
+ *
Loading from and storing to a stream delegates to Properties.load
+ * and Properties.store, respectively, to be fully compatible with
+ * the Unicode conversion as implemented by the JDK Properties class. On JDK 1.6,
+ * Properties.load/store will also be used for readers/writers,
+ * effectively turning this class into a plain backwards compatibility adapter.
+ *
+ *
The persistence code that works with Reader/Writer follows the JDK's parsing
+ * strategy but does not implement Unicode conversion, because the Reader/Writer
+ * should already apply proper decoding/encoding of characters. If you use prefer
+ * to escape unicode characters in your properties files, do not specify
+ * an encoding for a Reader/Writer (like ReloadableResourceBundleMessageSource's
+ * "defaultEncoding" and "fileEncodings" properties).
+ *
+ *
As of Spring 1.2.2, this implementation also supports properties XML files,
+ * through the loadFromXml and storeToXml methods.
+ * The default implementations delegate to JDK 1.5's corresponding methods,
+ * throwing an exception if running on an older JDK. Those implementations
+ * could be subclassed to apply custom XML handling on JDK 1.4, for example.
+ *
+ * @author Juergen Hoeller
+ * @since 10.03.2004
+ * @see java.util.Properties
+ * @see java.util.Properties#load
+ * @see java.util.Properties#store
+ * @see org.springframework.context.support.ReloadableResourceBundleMessageSource#setPropertiesPersister
+ * @see org.springframework.context.support.ReloadableResourceBundleMessageSource#setDefaultEncoding
+ * @see org.springframework.context.support.ReloadableResourceBundleMessageSource#setFileEncodings
+ */
+public class DefaultPropertiesPersister implements PropertiesPersister {
+
+ // Determine whether Properties.load(Reader) is available (on JDK 1.6+)
+ private static final boolean loadFromReaderAvailable =
+ ClassUtils.hasMethod(Properties.class, "load", new Class[] {Reader.class});
+
+ // Determine whether Properties.store(Writer, String) is available (on JDK 1.6+)
+ private static final boolean storeToWriterAvailable =
+ ClassUtils.hasMethod(Properties.class, "store", new Class[] {Writer.class, String.class});
+
+
+ public void load(Properties props, InputStream is) throws IOException {
+ props.load(is);
+ }
+
+ public void load(Properties props, Reader reader) throws IOException {
+ if (loadFromReaderAvailable) {
+ // On JDK 1.6+
+ props.load(reader);
+ }
+ else {
+ // Fall back to manual parsing.
+ doLoad(props, reader);
+ }
+ }
+
+ protected void doLoad(Properties props, Reader reader) throws IOException {
+ BufferedReader in = new BufferedReader(reader);
+ while (true) {
+ String line = in.readLine();
+ if (line == null) {
+ return;
+ }
+ line = StringUtils.trimLeadingWhitespace(line);
+ if (line.length() > 0) {
+ char firstChar = line.charAt(0);
+ if (firstChar != '#' && firstChar != '!') {
+ while (endsWithContinuationMarker(line)) {
+ String nextLine = in.readLine();
+ line = line.substring(0, line.length() - 1);
+ if (nextLine != null) {
+ line += StringUtils.trimLeadingWhitespace(nextLine);
+ }
+ }
+ int separatorIndex = line.indexOf("=");
+ if (separatorIndex == -1) {
+ separatorIndex = line.indexOf(":");
+ }
+ String key = (separatorIndex != -1 ? line.substring(0, separatorIndex) : line);
+ String value = (separatorIndex != -1) ? line.substring(separatorIndex + 1) : "";
+ key = StringUtils.trimTrailingWhitespace(key);
+ value = StringUtils.trimLeadingWhitespace(value);
+ props.put(unescape(key), unescape(value));
+ }
+ }
+ }
+ }
+
+ protected boolean endsWithContinuationMarker(String line) {
+ boolean evenSlashCount = true;
+ int index = line.length() - 1;
+ while (index >= 0 && line.charAt(index) == '\\') {
+ evenSlashCount = !evenSlashCount;
+ index--;
+ }
+ return !evenSlashCount;
+ }
+
+ protected String unescape(String str) {
+ StringBuffer outBuffer = new StringBuffer(str.length());
+ for (int index = 0; index < str.length();) {
+ char c = str.charAt(index++);
+ if (c == '\\') {
+ c = str.charAt(index++);
+ if (c == 't') {
+ c = '\t';
+ }
+ else if (c == 'r') {
+ c = '\r';
+ }
+ else if (c == 'n') {
+ c = '\n';
+ }
+ else if (c == 'f') {
+ c = '\f';
+ }
+ }
+ outBuffer.append(c);
+ }
+ return outBuffer.toString();
+ }
+
+
+ public void store(Properties props, OutputStream os, String header) throws IOException {
+ props.store(os, header);
+ }
+
+ public void store(Properties props, Writer writer, String header) throws IOException {
+ if (storeToWriterAvailable) {
+ // On JDK 1.6+
+ props.store(writer, header);
+ }
+ else {
+ // Fall back to manual parsing.
+ doStore(props, writer, header);
+ }
+ }
+
+ protected void doStore(Properties props, Writer writer, String header) throws IOException {
+ BufferedWriter out = new BufferedWriter(writer);
+ if (header != null) {
+ out.write("#" + header);
+ out.newLine();
+ }
+ out.write("#" + new Date());
+ out.newLine();
+ for (Enumeration keys = props.keys(); keys.hasMoreElements();) {
+ String key = (String) keys.nextElement();
+ String val = props.getProperty(key);
+ out.write(escape(key, true) + "=" + escape(val, false));
+ out.newLine();
+ }
+ out.flush();
+ }
+
+ protected String escape(String str, boolean isKey) {
+ int len = str.length();
+ StringBuffer outBuffer = new StringBuffer(len * 2);
+ for (int index = 0; index < len; index++) {
+ char c = str.charAt(index);
+ switch (c) {
+ case ' ':
+ if (index == 0 || isKey) {
+ outBuffer.append('\\');
+ }
+ outBuffer.append(' ');
+ break;
+ case '\\':
+ outBuffer.append("\\\\");
+ break;
+ case '\t':
+ outBuffer.append("\\t");
+ break;
+ case '\n':
+ outBuffer.append("\\n");
+ break;
+ case '\r':
+ outBuffer.append("\\r");
+ break;
+ case '\f':
+ outBuffer.append("\\f");
+ break;
+ default:
+ if ("=: \t\r\n\f#!".indexOf(c) != -1) {
+ outBuffer.append('\\');
+ }
+ outBuffer.append(c);
+ }
+ }
+ return outBuffer.toString();
+ }
+
+
+ public void loadFromXml(Properties props, InputStream is) throws IOException {
+ try {
+ props.loadFromXML(is);
+ }
+ catch (NoSuchMethodError err) {
+ throw new IOException("Cannot load properties XML file - not running on JDK 1.5+: " + err.getMessage());
+ }
+ }
+
+ public void storeToXml(Properties props, OutputStream os, String header) throws IOException {
+ try {
+ props.storeToXML(os, header);
+ }
+ catch (NoSuchMethodError err) {
+ throw new IOException("Cannot store properties XML file - not running on JDK 1.5+: " + err.getMessage());
+ }
+ }
+
+ public void storeToXml(Properties props, OutputStream os, String header, String encoding) throws IOException {
+ try {
+ props.storeToXML(os, header, encoding);
+ }
+ catch (NoSuchMethodError err) {
+ throw new IOException("Cannot store properties XML file - not running on JDK 1.5+: " + err.getMessage());
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/FileCopyUtils.java b/org.springframework.core/src/main/java/org/springframework/util/FileCopyUtils.java
new file mode 100644
index 00000000000..1153c475fa2
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/FileCopyUtils.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+
+/**
+ * Simple utility methods for file and stream copying.
+ * All copy methods use a block size of 4096 bytes,
+ * and close all affected streams when done.
+ *
+ *
Mainly for use within the framework,
+ * but also useful for application code.
+ *
+ * @author Juergen Hoeller
+ * @since 06.10.2003
+ */
+public abstract class FileCopyUtils {
+
+ public static final int BUFFER_SIZE = 4096;
+
+
+ //---------------------------------------------------------------------
+ // Copy methods for java.io.File
+ //---------------------------------------------------------------------
+
+ /**
+ * Copy the contents of the given input File to the given output File.
+ * @param in the file to copy from
+ * @param out the file to copy to
+ * @return the number of bytes copied
+ * @throws IOException in case of I/O errors
+ */
+ public static int copy(File in, File out) throws IOException {
+ Assert.notNull(in, "No input File specified");
+ Assert.notNull(out, "No output File specified");
+ return copy(new BufferedInputStream(new FileInputStream(in)),
+ new BufferedOutputStream(new FileOutputStream(out)));
+ }
+
+ /**
+ * Copy the contents of the given byte array to the given output File.
+ * @param in the byte array to copy from
+ * @param out the file to copy to
+ * @throws IOException in case of I/O errors
+ */
+ public static void copy(byte[] in, File out) throws IOException {
+ Assert.notNull(in, "No input byte array specified");
+ Assert.notNull(out, "No output File specified");
+ ByteArrayInputStream inStream = new ByteArrayInputStream(in);
+ OutputStream outStream = new BufferedOutputStream(new FileOutputStream(out));
+ copy(inStream, outStream);
+ }
+
+ /**
+ * Copy the contents of the given input File into a new byte array.
+ * @param in the file to copy from
+ * @return the new byte array that has been copied to
+ * @throws IOException in case of I/O errors
+ */
+ public static byte[] copyToByteArray(File in) throws IOException {
+ Assert.notNull(in, "No input File specified");
+ return copyToByteArray(new BufferedInputStream(new FileInputStream(in)));
+ }
+
+
+ //---------------------------------------------------------------------
+ // Copy methods for java.io.InputStream / java.io.OutputStream
+ //---------------------------------------------------------------------
+
+ /**
+ * Copy the contents of the given InputStream to the given OutputStream.
+ * Closes both streams when done.
+ * @param in the stream to copy from
+ * @param out the stream to copy to
+ * @return the number of bytes copied
+ * @throws IOException in case of I/O errors
+ */
+ public static int copy(InputStream in, OutputStream out) throws IOException {
+ Assert.notNull(in, "No InputStream specified");
+ Assert.notNull(out, "No OutputStream specified");
+ try {
+ int byteCount = 0;
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int bytesRead = -1;
+ while ((bytesRead = in.read(buffer)) != -1) {
+ out.write(buffer, 0, bytesRead);
+ byteCount += bytesRead;
+ }
+ out.flush();
+ return byteCount;
+ }
+ finally {
+ try {
+ in.close();
+ }
+ catch (IOException ex) {
+ }
+ try {
+ out.close();
+ }
+ catch (IOException ex) {
+ }
+ }
+ }
+
+ /**
+ * Copy the contents of the given byte array to the given OutputStream.
+ * Closes the stream when done.
+ * @param in the byte array to copy from
+ * @param out the OutputStream to copy to
+ * @throws IOException in case of I/O errors
+ */
+ public static void copy(byte[] in, OutputStream out) throws IOException {
+ Assert.notNull(in, "No input byte array specified");
+ Assert.notNull(out, "No OutputStream specified");
+ try {
+ out.write(in);
+ }
+ finally {
+ try {
+ out.close();
+ }
+ catch (IOException ex) {
+ }
+ }
+ }
+
+ /**
+ * Copy the contents of the given InputStream into a new byte array.
+ * Closes the stream when done.
+ * @param in the stream to copy from
+ * @return the new byte array that has been copied to
+ * @throws IOException in case of I/O errors
+ */
+ public static byte[] copyToByteArray(InputStream in) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE);
+ copy(in, out);
+ return out.toByteArray();
+ }
+
+
+ //---------------------------------------------------------------------
+ // Copy methods for java.io.Reader / java.io.Writer
+ //---------------------------------------------------------------------
+
+ /**
+ * Copy the contents of the given Reader to the given Writer.
+ * Closes both when done.
+ * @param in the Reader to copy from
+ * @param out the Writer to copy to
+ * @return the number of characters copied
+ * @throws IOException in case of I/O errors
+ */
+ public static int copy(Reader in, Writer out) throws IOException {
+ Assert.notNull(in, "No Reader specified");
+ Assert.notNull(out, "No Writer specified");
+ try {
+ int byteCount = 0;
+ char[] buffer = new char[BUFFER_SIZE];
+ int bytesRead = -1;
+ while ((bytesRead = in.read(buffer)) != -1) {
+ out.write(buffer, 0, bytesRead);
+ byteCount += bytesRead;
+ }
+ out.flush();
+ return byteCount;
+ }
+ finally {
+ try {
+ in.close();
+ }
+ catch (IOException ex) {
+ }
+ try {
+ out.close();
+ }
+ catch (IOException ex) {
+ }
+ }
+ }
+
+ /**
+ * Copy the contents of the given String to the given output Writer.
+ * Closes the write when done.
+ * @param in the String to copy from
+ * @param out the Writer to copy to
+ * @throws IOException in case of I/O errors
+ */
+ public static void copy(String in, Writer out) throws IOException {
+ Assert.notNull(in, "No input String specified");
+ Assert.notNull(out, "No Writer specified");
+ try {
+ out.write(in);
+ }
+ finally {
+ try {
+ out.close();
+ }
+ catch (IOException ex) {
+ }
+ }
+ }
+
+ /**
+ * Copy the contents of the given Reader into a String.
+ * Closes the reader when done.
+ * @param in the reader to copy from
+ * @return the String that has been copied to
+ * @throws IOException in case of I/O errors
+ */
+ public static String copyToString(Reader in) throws IOException {
+ StringWriter out = new StringWriter();
+ copy(in, out);
+ return out.toString();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/FileSystemUtils.java b/org.springframework.core/src/main/java/org/springframework/util/FileSystemUtils.java
new file mode 100644
index 00000000000..02261f3226a
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/FileSystemUtils.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Utility methods for working with the file system.
+ *
+ * @author Rob Harrop
+ * @author Juergen Hoeller
+ * @since 2.5.3
+ */
+public abstract class FileSystemUtils {
+
+ /**
+ * Delete the supplied {@link File} - for directories,
+ * recursively delete any nested directories or files as well.
+ * @param root the root File to delete
+ * @return true if the File was deleted,
+ * otherwise false
+ */
+ public static boolean deleteRecursively(File root) {
+ if (root != null && root.exists()) {
+ if (root.isDirectory()) {
+ File[] children = root.listFiles();
+ if (children != null) {
+ for (int i = 0; i < children.length; i++) {
+ deleteRecursively(children[i]);
+ }
+ }
+ }
+ return root.delete();
+ }
+ return false;
+ }
+
+ /**
+ * Recursively copy the contents of the src file/directory
+ * to the dest file/directory.
+ * @param src the source directory
+ * @param dest the destination directory
+ * @throws IOException in the case of I/O errors
+ */
+ public static void copyRecursively(File src, File dest) throws IOException {
+ Assert.isTrue(src != null && (src.isDirectory() || src.isFile()), "Source File must denote a directory or file");
+ Assert.notNull(dest, "Destination File must not be null");
+ doCopyRecursively(src, dest);
+ }
+
+ /**
+ * Actually copy the contents of the src file/directory
+ * to the dest file/directory.
+ * @param src the source directory
+ * @param dest the destination directory
+ * @throws IOException in the case of I/O errors
+ */
+ private static void doCopyRecursively(File src, File dest) throws IOException {
+ if (src.isDirectory()) {
+ dest.mkdir();
+ File[] entries = src.listFiles();
+ if (entries == null) {
+ throw new IOException("Could not list files in directory: " + src);
+ }
+ for (int i = 0; i < entries.length; i++) {
+ File file = entries[i];
+ doCopyRecursively(file, new File(dest, file.getName()));
+ }
+ }
+ else if (src.isFile()) {
+ try {
+ dest.createNewFile();
+ }
+ catch (IOException ex) {
+ IOException ioex = new IOException("Failed to create file: " + dest);
+ ioex.initCause(ex);
+ throw ioex;
+ }
+ FileCopyUtils.copy(src, dest);
+ }
+ else {
+ // Special File handle: neither a file not a directory.
+ // Simply skip it when contained in nested directory...
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/Log4jConfigurer.java b/org.springframework.core/src/main/java/org/springframework/util/Log4jConfigurer.java
new file mode 100644
index 00000000000..3cf6043ac0d
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/Log4jConfigurer.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.net.URL;
+
+import org.apache.log4j.LogManager;
+import org.apache.log4j.PropertyConfigurator;
+import org.apache.log4j.xml.DOMConfigurator;
+
+/**
+ * Convenience class that features simple methods for custom log4j configuration.
+ *
+ *
Only needed for non-default log4j initialization, for example with a custom
+ * config location or a refresh interval. By default, log4j will simply read its
+ * configuration from a "log4j.properties" or "log4j.xml" file in the root of
+ * the classpath.
+ *
+ *
For web environments, the analogous Log4jWebConfigurer class can be found
+ * in the web package, reading in its configuration from context-params in
+ * web.xml. In a J2EE web application, log4j is usually set up
+ * via Log4jConfigListener or Log4jConfigServlet, delegating to
+ * Log4jWebConfigurer underneath.
+ *
+ * @author Juergen Hoeller
+ * @since 13.03.2003
+ * @see org.springframework.web.util.Log4jWebConfigurer
+ * @see org.springframework.web.util.Log4jConfigListener
+ * @see org.springframework.web.util.Log4jConfigServlet
+ */
+public abstract class Log4jConfigurer {
+
+ /** Pseudo URL prefix for loading from the class path: "classpath:" */
+ public static final String CLASSPATH_URL_PREFIX = "classpath:";
+
+ /** Extension that indicates a log4j XML config file: ".xml" */
+ public static final String XML_FILE_EXTENSION = ".xml";
+
+
+ /**
+ * Initialize log4j from the given file location, with no config file refreshing.
+ * Assumes an XML file in case of a ".xml" file extension, and a properties file
+ * otherwise.
+ * @param location the location of the config file: either a "classpath:" location
+ * (e.g. "classpath:myLog4j.properties"), an absolute file URL
+ * (e.g. "file:C:/log4j.properties), or a plain absolute path in the file system
+ * (e.g. "C:/log4j.properties")
+ * @throws FileNotFoundException if the location specifies an invalid file path
+ */
+ public static void initLogging(String location) throws FileNotFoundException {
+ String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(location);
+ URL url = ResourceUtils.getURL(resolvedLocation);
+ if (resolvedLocation.toLowerCase().endsWith(XML_FILE_EXTENSION)) {
+ DOMConfigurator.configure(url);
+ }
+ else {
+ PropertyConfigurator.configure(url);
+ }
+ }
+
+ /**
+ * Initialize log4j from the given location, with the given refresh interval
+ * for the config file. Assumes an XML file in case of a ".xml" file extension,
+ * and a properties file otherwise.
+ *
Log4j's watchdog thread will asynchronously check whether the timestamp
+ * of the config file has changed, using the given interval between checks.
+ * A refresh interval of 1000 milliseconds (one second), which allows to
+ * do on-demand log level changes with immediate effect, is not unfeasible.
+ *
WARNING: Log4j's watchdog thread does not terminate until VM shutdown;
+ * in particular, it does not terminate on LogManager shutdown. Therefore, it is
+ * recommended to not use config file refreshing in a production J2EE
+ * environment; the watchdog thread would not stop on application shutdown there.
+ * @param location the location of the config file: either a "classpath:" location
+ * (e.g. "classpath:myLog4j.properties"), an absolute file URL
+ * (e.g. "file:C:/log4j.properties), or a plain absolute path in the file system
+ * (e.g. "C:/log4j.properties")
+ * @param refreshInterval interval between config file refresh checks, in milliseconds
+ * @throws FileNotFoundException if the location specifies an invalid file path
+ */
+ public static void initLogging(String location, long refreshInterval) throws FileNotFoundException {
+ String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(location);
+ File file = ResourceUtils.getFile(resolvedLocation);
+ if (!file.exists()) {
+ throw new FileNotFoundException("Log4j config file [" + resolvedLocation + "] not found");
+ }
+ if (resolvedLocation.toLowerCase().endsWith(XML_FILE_EXTENSION)) {
+ DOMConfigurator.configureAndWatch(file.getAbsolutePath(), refreshInterval);
+ }
+ else {
+ PropertyConfigurator.configureAndWatch(file.getAbsolutePath(), refreshInterval);
+ }
+ }
+
+ /**
+ * Shut down log4j, properly releasing all file locks.
+ *
This isn't strictly necessary, but recommended for shutting down
+ * log4j in a scenario where the host VM stays alive (for example, when
+ * shutting down an application in a J2EE environment).
+ */
+ public static void shutdownLogging() {
+ LogManager.shutdown();
+ }
+
+ /**
+ * Set the specified system property to the current working directory.
+ *
This can be used e.g. for test environments, for applications that leverage
+ * Log4jWebConfigurer's "webAppRootKey" support in a web environment.
+ * @param key system property key to use, as expected in Log4j configuration
+ * (for example: "demo.root", used as "${demo.root}/WEB-INF/demo.log")
+ * @see org.springframework.web.util.Log4jWebConfigurer
+ */
+ public static void setWorkingDirSystemProperty(String key) {
+ System.setProperty(key, new File("").getAbsolutePath());
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/MethodInvoker.java b/org.springframework.core/src/main/java/org/springframework/util/MethodInvoker.java
new file mode 100644
index 00000000000..e13cdbdecba
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/MethodInvoker.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Helper class that allows for specifying a method to invoke in a declarative
+ * fashion, be it static or non-static.
+ *
+ *
Usage: Specify "targetClass"/"targetMethod" or "targetObject"/"targetMethod",
+ * optionally specify arguments, prepare the invoker. Afterwards, you may
+ * invoke the method any number of times, obtaining the invocation result.
+ *
+ *
Typically not used directly but via its subclasses
+ * {@link org.springframework.beans.factory.config.MethodInvokingFactoryBean} and
+ * {@link org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean}.
+ *
+ * @author Colin Sampaleanu
+ * @author Juergen Hoeller
+ * @since 19.02.2004
+ * @see #prepare
+ * @see #invoke
+ * @see org.springframework.beans.factory.config.MethodInvokingFactoryBean
+ * @see org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean
+ */
+public class MethodInvoker {
+
+ private Class targetClass;
+
+ private Object targetObject;
+
+ private String targetMethod;
+
+ private String staticMethod;
+
+ private Object[] arguments = new Object[0];
+
+ /** The method we will call */
+ private Method methodObject;
+
+
+ /**
+ * Set the target class on which to call the target method.
+ * Only necessary when the target method is static; else,
+ * a target object needs to be specified anyway.
+ * @see #setTargetObject
+ * @see #setTargetMethod
+ */
+ public void setTargetClass(Class targetClass) {
+ this.targetClass = targetClass;
+ }
+
+ /**
+ * Return the target class on which to call the target method.
+ */
+ public Class getTargetClass() {
+ return this.targetClass;
+ }
+
+ /**
+ * Set the target object on which to call the target method.
+ * Only necessary when the target method is not static;
+ * else, a target class is sufficient.
+ * @see #setTargetClass
+ * @see #setTargetMethod
+ */
+ public void setTargetObject(Object targetObject) {
+ this.targetObject = targetObject;
+ if (targetObject != null) {
+ this.targetClass = targetObject.getClass();
+ }
+ }
+
+ /**
+ * Return the target object on which to call the target method.
+ */
+ public Object getTargetObject() {
+ return this.targetObject;
+ }
+
+ /**
+ * Set the name of the method to be invoked.
+ * Refers to either a static method or a non-static method,
+ * depending on a target object being set.
+ * @see #setTargetClass
+ * @see #setTargetObject
+ */
+ public void setTargetMethod(String targetMethod) {
+ this.targetMethod = targetMethod;
+ }
+
+ /**
+ * Return the name of the method to be invoked.
+ */
+ public String getTargetMethod() {
+ return this.targetMethod;
+ }
+
+ /**
+ * Set a fully qualified static method name to invoke,
+ * e.g. "example.MyExampleClass.myExampleMethod".
+ * Convenient alternative to specifying targetClass and targetMethod.
+ * @see #setTargetClass
+ * @see #setTargetMethod
+ */
+ public void setStaticMethod(String staticMethod) {
+ this.staticMethod = staticMethod;
+ }
+
+ /**
+ * Set arguments for the method invocation. If this property is not set,
+ * or the Object array is of length 0, a method with no arguments is assumed.
+ */
+ public void setArguments(Object[] arguments) {
+ this.arguments = (arguments != null ? arguments : new Object[0]);
+ }
+
+ /**
+ * Return the arguments for the method invocation.
+ */
+ public Object[] getArguments() {
+ return this.arguments;
+ }
+
+
+ /**
+ * Prepare the specified method.
+ * The method can be invoked any number of times afterwards.
+ * @see #getPreparedMethod
+ * @see #invoke
+ */
+ public void prepare() throws ClassNotFoundException, NoSuchMethodException {
+ if (this.staticMethod != null) {
+ int lastDotIndex = this.staticMethod.lastIndexOf('.');
+ if (lastDotIndex == -1 || lastDotIndex == this.staticMethod.length()) {
+ throw new IllegalArgumentException(
+ "staticMethod must be a fully qualified class plus method name: " +
+ "e.g. 'example.MyExampleClass.myExampleMethod'");
+ }
+ String className = this.staticMethod.substring(0, lastDotIndex);
+ String methodName = this.staticMethod.substring(lastDotIndex + 1);
+ this.targetClass = resolveClassName(className);
+ this.targetMethod = methodName;
+ }
+
+ Class targetClass = getTargetClass();
+ String targetMethod = getTargetMethod();
+ if (targetClass == null) {
+ throw new IllegalArgumentException("Either 'targetClass' or 'targetObject' is required");
+ }
+ if (targetMethod == null) {
+ throw new IllegalArgumentException("Property 'targetMethod' is required");
+ }
+
+ Object[] arguments = getArguments();
+ Class[] argTypes = new Class[arguments.length];
+ for (int i = 0; i < arguments.length; ++i) {
+ argTypes[i] = (arguments[i] != null ? arguments[i].getClass() : Object.class);
+ }
+
+ // Try to get the exact method first.
+ try {
+ this.methodObject = targetClass.getMethod(targetMethod, argTypes);
+ }
+ catch (NoSuchMethodException ex) {
+ // Just rethrow exception if we can't get any match.
+ this.methodObject = findMatchingMethod();
+ if (this.methodObject == null) {
+ throw ex;
+ }
+ }
+ }
+
+ /**
+ * Resolve the given class name into a Class.
+ *
The default implementations uses ClassUtils.forName,
+ * using the thread context class loader.
+ * @param className the class name to resolve
+ * @return the resolved Class
+ * @throws ClassNotFoundException if the class name was invalid
+ */
+ protected Class resolveClassName(String className) throws ClassNotFoundException {
+ return ClassUtils.forName(className);
+ }
+
+ /**
+ * Find a matching method with the specified name for the specified arguments.
+ * @return a matching method, or null if none
+ * @see #getTargetClass()
+ * @see #getTargetMethod()
+ * @see #getArguments()
+ */
+ protected Method findMatchingMethod() {
+ String targetMethod = getTargetMethod();
+ Object[] arguments = getArguments();
+ int argCount = arguments.length;
+
+ Method[] candidates = ReflectionUtils.getAllDeclaredMethods(getTargetClass());
+ int minTypeDiffWeight = Integer.MAX_VALUE;
+ Method matchingMethod = null;
+
+ for (int i = 0; i < candidates.length; i++) {
+ Method candidate = candidates[i];
+ if (candidate.getName().equals(targetMethod)) {
+ Class[] paramTypes = candidate.getParameterTypes();
+ if (paramTypes.length == argCount) {
+ int typeDiffWeight = getTypeDifferenceWeight(paramTypes, arguments);
+ if (typeDiffWeight < minTypeDiffWeight) {
+ minTypeDiffWeight = typeDiffWeight;
+ matchingMethod = candidate;
+ }
+ }
+ }
+ }
+
+ return matchingMethod;
+ }
+
+ /**
+ * Return the prepared Method object that will be invoked.
+ *
Can for example be used to determine the return type.
+ * @return the prepared Method object (never null)
+ * @throws IllegalStateException if the invoker hasn't been prepared yet
+ * @see #prepare
+ * @see #invoke
+ */
+ public Method getPreparedMethod() throws IllegalStateException {
+ if (this.methodObject == null) {
+ throw new IllegalStateException("prepare() must be called prior to invoke() on MethodInvoker");
+ }
+ return this.methodObject;
+ }
+
+ /**
+ * Return whether this invoker has been prepared already,
+ * i.e. whether it allows access to {@link #getPreparedMethod()} already.
+ */
+ public boolean isPrepared() {
+ return (this.methodObject != null);
+ }
+
+ /**
+ * Invoke the specified method.
+ *
The invoker needs to have been prepared before.
+ * @return the object (possibly null) returned by the method invocation,
+ * or null if the method has a void return type
+ * @throws InvocationTargetException if the target method threw an exception
+ * @throws IllegalAccessException if the target method couldn't be accessed
+ * @see #prepare
+ */
+ public Object invoke() throws InvocationTargetException, IllegalAccessException {
+ // In the static case, target will simply be null.
+ Object targetObject = getTargetObject();
+ Method preparedMethod = getPreparedMethod();
+ if (targetObject == null && !Modifier.isStatic(preparedMethod.getModifiers())) {
+ throw new IllegalArgumentException("Target method must not be non-static without a target");
+ }
+ ReflectionUtils.makeAccessible(preparedMethod);
+ return preparedMethod.invoke(targetObject, getArguments());
+ }
+
+
+ /**
+ * Algorithm that judges the match between the declared parameter types of a candidate method
+ * and a specific list of arguments that this method is supposed to be invoked with.
+ *
Determines a weight that represents the class hierarchy difference between types and
+ * arguments. A direct match, i.e. type Integer -> arg of class Integer, does not increase
+ * the result - all direct matches means weight 0. A match between type Object and arg of
+ * class Integer would increase the weight by 2, due to the superclass 2 steps up in the
+ * hierarchy (i.e. Object) being the last one that still matches the required type Object.
+ * Type Number and class Integer would increase the weight by 1 accordingly, due to the
+ * superclass 1 step up the hierarchy (i.e. Number) still matching the required type Number.
+ * Therefore, with an arg of type Integer, a constructor (Integer) would be preferred to a
+ * constructor (Number) which would in turn be preferred to a constructor (Object).
+ * All argument weights get accumulated.
+ * @param paramTypes the parameter types to match
+ * @param args the arguments to match
+ * @return the accumulated weight for all arguments
+ */
+ public static int getTypeDifferenceWeight(Class[] paramTypes, Object[] args) {
+ int result = 0;
+ for (int i = 0; i < paramTypes.length; i++) {
+ if (!ClassUtils.isAssignableValue(paramTypes[i], args[i])) {
+ return Integer.MAX_VALUE;
+ }
+ if (args[i] != null) {
+ Class paramType = paramTypes[i];
+ Class superClass = args[i].getClass().getSuperclass();
+ while (superClass != null) {
+ if (paramType.equals(superClass)) {
+ result = result + 2;
+ superClass = null;
+ }
+ else if (ClassUtils.isAssignable(paramType, superClass)) {
+ result = result + 2;
+ superClass = superClass.getSuperclass();
+ }
+ else {
+ superClass = null;
+ }
+ }
+ if (paramType.isInterface()) {
+ result = result + 1;
+ }
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/NumberUtils.java b/org.springframework.core/src/main/java/org/springframework/util/NumberUtils.java
new file mode 100644
index 00000000000..7dbeed7beff
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/NumberUtils.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.text.ParseException;
+
+/**
+ * Miscellaneous utility methods for number conversion and parsing.
+ * Mainly for internal use within the framework; consider Jakarta's
+ * Commons Lang for a more comprehensive suite of string utilities.
+ *
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @since 1.1.2
+ */
+public abstract class NumberUtils {
+
+ private static final boolean decimalFormatSupportsBigDecimal =
+ ClassUtils.hasMethod(DecimalFormat.class, "setParseBigDecimal", new Class[] {boolean.class});
+
+
+ /**
+ * Convert the given number into an instance of the given target class.
+ * @param number the number to convert
+ * @param targetClass the target class to convert to
+ * @return the converted number
+ * @throws IllegalArgumentException if the target class is not supported
+ * (i.e. not a standard Number subclass as included in the JDK)
+ * @see java.lang.Byte
+ * @see java.lang.Short
+ * @see java.lang.Integer
+ * @see java.lang.Long
+ * @see java.math.BigInteger
+ * @see java.lang.Float
+ * @see java.lang.Double
+ * @see java.math.BigDecimal
+ */
+ public static Number convertNumberToTargetClass(Number number, Class targetClass)
+ throws IllegalArgumentException {
+
+ Assert.notNull(number, "Number must not be null");
+ Assert.notNull(targetClass, "Target class must not be null");
+
+ if (targetClass.isInstance(number)) {
+ return number;
+ }
+ else if (targetClass.equals(Byte.class)) {
+ long value = number.longValue();
+ if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
+ raiseOverflowException(number, targetClass);
+ }
+ return new Byte(number.byteValue());
+ }
+ else if (targetClass.equals(Short.class)) {
+ long value = number.longValue();
+ if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
+ raiseOverflowException(number, targetClass);
+ }
+ return new Short(number.shortValue());
+ }
+ else if (targetClass.equals(Integer.class)) {
+ long value = number.longValue();
+ if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
+ raiseOverflowException(number, targetClass);
+ }
+ return new Integer(number.intValue());
+ }
+ else if (targetClass.equals(Long.class)) {
+ return new Long(number.longValue());
+ }
+ else if (targetClass.equals(BigInteger.class)) {
+ if (number instanceof BigDecimal) {
+ // do not lose precision - use BigDecimal's own conversion
+ return ((BigDecimal) number).toBigInteger();
+ }
+ else {
+ // original value is not a Big* number - use standard long conversion
+ return BigInteger.valueOf(number.longValue());
+ }
+ }
+ else if (targetClass.equals(Float.class)) {
+ return new Float(number.floatValue());
+ }
+ else if (targetClass.equals(Double.class)) {
+ return new Double(number.doubleValue());
+ }
+ else if (targetClass.equals(BigDecimal.class)) {
+ // always use BigDecimal(String) here to avoid unpredictability of BigDecimal(double)
+ // (see BigDecimal javadoc for details)
+ return new BigDecimal(number.toString());
+ }
+ else {
+ throw new IllegalArgumentException("Could not convert number [" + number + "] of type [" +
+ number.getClass().getName() + "] to unknown target class [" + targetClass.getName() + "]");
+ }
+ }
+
+ /**
+ * Raise an overflow exception for the given number and target class.
+ * @param number the number we tried to convert
+ * @param targetClass the target class we tried to convert to
+ */
+ private static void raiseOverflowException(Number number, Class targetClass) {
+ throw new IllegalArgumentException("Could not convert number [" + number + "] of type [" +
+ number.getClass().getName() + "] to target class [" + targetClass.getName() + "]: overflow");
+ }
+
+ /**
+ * Parse the given text into a number instance of the given target class,
+ * using the corresponding decode / valueOf methods.
+ *
Trims the input String before attempting to parse the number.
+ * Supports numbers in hex format (with leading "0x", "0X" or "#") as well.
+ * @param text the text to convert
+ * @param targetClass the target class to parse into
+ * @return the parsed number
+ * @throws IllegalArgumentException if the target class is not supported
+ * (i.e. not a standard Number subclass as included in the JDK)
+ * @see java.lang.Byte#decode
+ * @see java.lang.Short#decode
+ * @see java.lang.Integer#decode
+ * @see java.lang.Long#decode
+ * @see #decodeBigInteger(String)
+ * @see java.lang.Float#valueOf
+ * @see java.lang.Double#valueOf
+ * @see java.math.BigDecimal#BigDecimal(String)
+ */
+ public static Number parseNumber(String text, Class targetClass) {
+ Assert.notNull(text, "Text must not be null");
+ Assert.notNull(targetClass, "Target class must not be null");
+ String trimmed = StringUtils.trimAllWhitespace(text);
+
+ if (targetClass.equals(Byte.class)) {
+ return (isHexNumber(trimmed) ? Byte.decode(trimmed) : Byte.valueOf(trimmed));
+ }
+ else if (targetClass.equals(Short.class)) {
+ return (isHexNumber(trimmed) ? Short.decode(trimmed) : Short.valueOf(trimmed));
+ }
+ else if (targetClass.equals(Integer.class)) {
+ return (isHexNumber(trimmed) ? Integer.decode(trimmed) : Integer.valueOf(trimmed));
+ }
+ else if (targetClass.equals(Long.class)) {
+ return (isHexNumber(trimmed) ? Long.decode(trimmed) : Long.valueOf(trimmed));
+ }
+ else if (targetClass.equals(BigInteger.class)) {
+ return (isHexNumber(trimmed) ? decodeBigInteger(trimmed) : new BigInteger(trimmed));
+ }
+ else if (targetClass.equals(Float.class)) {
+ return Float.valueOf(trimmed);
+ }
+ else if (targetClass.equals(Double.class)) {
+ return Double.valueOf(trimmed);
+ }
+ else if (targetClass.equals(BigDecimal.class) || targetClass.equals(Number.class)) {
+ return new BigDecimal(trimmed);
+ }
+ else {
+ throw new IllegalArgumentException(
+ "Cannot convert String [" + text + "] to target class [" + targetClass.getName() + "]");
+ }
+ }
+
+ /**
+ * Parse the given text into a number instance of the given target class,
+ * using the given NumberFormat. Trims the input String
+ * before attempting to parse the number.
+ * @param text the text to convert
+ * @param targetClass the target class to parse into
+ * @param numberFormat the NumberFormat to use for parsing (if null,
+ * this method falls back to parseNumber(String, Class))
+ * @return the parsed number
+ * @throws IllegalArgumentException if the target class is not supported
+ * (i.e. not a standard Number subclass as included in the JDK)
+ * @see java.text.NumberFormat#parse
+ * @see #convertNumberToTargetClass
+ * @see #parseNumber(String, Class)
+ */
+ public static Number parseNumber(String text, Class targetClass, NumberFormat numberFormat) {
+ if (numberFormat != null) {
+ Assert.notNull(text, "Text must not be null");
+ Assert.notNull(targetClass, "Target class must not be null");
+ DecimalFormat decimalFormat = null;
+ boolean resetBigDecimal = false;
+ if (numberFormat instanceof DecimalFormat) {
+ decimalFormat = (DecimalFormat) numberFormat;
+ if (BigDecimal.class.equals(targetClass) && decimalFormatSupportsBigDecimal &&
+ !decimalFormat.isParseBigDecimal()) {
+ decimalFormat.setParseBigDecimal(true);
+ resetBigDecimal = true;
+ }
+ }
+ try {
+ Number number = numberFormat.parse(StringUtils.trimAllWhitespace(text));
+ return convertNumberToTargetClass(number, targetClass);
+ }
+ catch (ParseException ex) {
+ IllegalArgumentException iae =
+ new IllegalArgumentException("Could not parse number: " + ex.getMessage());
+ iae.initCause(ex);
+ throw iae;
+ }
+ finally {
+ if (resetBigDecimal) {
+ decimalFormat.setParseBigDecimal(false);
+ }
+ }
+ }
+ else {
+ return parseNumber(text, targetClass);
+ }
+ }
+
+ /**
+ * Determine whether the given value String indicates a hex number, i.e. needs to be
+ * passed into Integer.decode instead of Integer.valueOf (etc).
+ */
+ private static boolean isHexNumber(String value) {
+ int index = (value.startsWith("-") ? 1 : 0);
+ return (value.startsWith("0x", index) || value.startsWith("0X", index) || value.startsWith("#", index));
+ }
+
+ /**
+ * Decode a {@link java.math.BigInteger} from a {@link String} value.
+ * Supports decimal, hex and octal notation.
+ * @see BigInteger#BigInteger(String, int)
+ */
+ private static BigInteger decodeBigInteger(String value) {
+ int radix = 10;
+ int index = 0;
+ boolean negative = false;
+
+ // Handle minus sign, if present.
+ if (value.startsWith("-")) {
+ negative = true;
+ index++;
+ }
+
+ // Handle radix specifier, if present.
+ if (value.startsWith("0x", index) || value.startsWith("0X", index)) {
+ index += 2;
+ radix = 16;
+ }
+ else if (value.startsWith("#", index)) {
+ index++;
+ radix = 16;
+ }
+ else if (value.startsWith("0", index) && value.length() > 1 + index) {
+ index++;
+ radix = 8;
+ }
+
+ BigInteger result = new BigInteger(value.substring(index), radix);
+ return (negative ? result.negate() : result);
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/ObjectUtils.java b/org.springframework.core/src/main/java/org/springframework/util/ObjectUtils.java
new file mode 100644
index 00000000000..3237160df45
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/ObjectUtils.java
@@ -0,0 +1,833 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+
+/**
+ * Miscellaneous object utility methods. Mainly for internal use within the
+ * framework; consider Jakarta's Commons Lang for a more comprehensive suite
+ * of object utilities.
+ *
+ * @author Juergen Hoeller
+ * @author Keith Donald
+ * @author Rod Johnson
+ * @author Rob Harrop
+ * @author Alex Ruiz
+ * @since 19.03.2004
+ * @see org.apache.commons.lang.ObjectUtils
+ */
+public abstract class ObjectUtils {
+
+ private static final int INITIAL_HASH = 7;
+ private static final int MULTIPLIER = 31;
+
+ private static final String EMPTY_STRING = "";
+ private static final String NULL_STRING = "null";
+ private static final String ARRAY_START = "{";
+ private static final String ARRAY_END = "}";
+ private static final String EMPTY_ARRAY = ARRAY_START + ARRAY_END;
+ private static final String ARRAY_ELEMENT_SEPARATOR = ", ";
+
+
+ /**
+ * Return whether the given throwable is a checked exception:
+ * that is, neither a RuntimeException nor an Error.
+ * @param ex the throwable to check
+ * @return whether the throwable is a checked exception
+ * @see java.lang.Exception
+ * @see java.lang.RuntimeException
+ * @see java.lang.Error
+ */
+ public static boolean isCheckedException(Throwable ex) {
+ return !(ex instanceof RuntimeException || ex instanceof Error);
+ }
+
+ /**
+ * Check whether the given exception is compatible with the exceptions
+ * declared in a throws clause.
+ * @param ex the exception to checked
+ * @param declaredExceptions the exceptions declared in the throws clause
+ * @return whether the given exception is compatible
+ */
+ public static boolean isCompatibleWithThrowsClause(Throwable ex, Class[] declaredExceptions) {
+ if (!isCheckedException(ex)) {
+ return true;
+ }
+ if (declaredExceptions != null) {
+ for (int i = 0; i < declaredExceptions.length; i++) {
+ if (declaredExceptions[i].isAssignableFrom(ex.getClass())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return whether the given array is empty: that is, null
+ * or of zero length.
+ * @param array the array to check
+ * @return whether the given array is empty
+ */
+ public static boolean isEmpty(Object[] array) {
+ return (array == null || array.length == 0);
+ }
+
+ /**
+ * Check whether the given array contains the given element.
+ * @param array the array to check (may be null,
+ * in which case the return value will always be false)
+ * @param element the element to check for
+ * @return whether the element has been found in the given array
+ */
+ public static boolean containsElement(Object[] array, Object element) {
+ if (array == null) {
+ return false;
+ }
+ for (int i = 0; i < array.length; i++) {
+ if (nullSafeEquals(array[i], element)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Append the given Object to the given array, returning a new array
+ * consisting of the input array contents plus the given Object.
+ * @param array the array to append to (can be null)
+ * @param obj the Object to append
+ * @return the new array (of the same component type; never null)
+ */
+ public static Object[] addObjectToArray(Object[] array, Object obj) {
+ Class compType = Object.class;
+ if (array != null) {
+ compType = array.getClass().getComponentType();
+ }
+ else if (obj != null) {
+ compType = obj.getClass();
+ }
+ int newArrLength = (array != null ? array.length + 1 : 1);
+ Object[] newArr = (Object[]) Array.newInstance(compType, newArrLength);
+ if (array != null) {
+ System.arraycopy(array, 0, newArr, 0, array.length);
+ }
+ newArr[newArr.length - 1] = obj;
+ return newArr;
+ }
+
+ /**
+ * Convert the given array (which may be a primitive array) to an
+ * object array (if necessary of primitive wrapper objects).
+ *
A null source value will be converted to an
+ * empty Object array.
+ * @param source the (potentially primitive) array
+ * @return the corresponding object array (never null)
+ * @throws IllegalArgumentException if the parameter is not an array
+ */
+ public static Object[] toObjectArray(Object source) {
+ if (source instanceof Object[]) {
+ return (Object[]) source;
+ }
+ if (source == null) {
+ return new Object[0];
+ }
+ if (!source.getClass().isArray()) {
+ throw new IllegalArgumentException("Source is not an array: " + source);
+ }
+ int length = Array.getLength(source);
+ if (length == 0) {
+ return new Object[0];
+ }
+ Class wrapperType = Array.get(source, 0).getClass();
+ Object[] newArray = (Object[]) Array.newInstance(wrapperType, length);
+ for (int i = 0; i < length; i++) {
+ newArray[i] = Array.get(source, i);
+ }
+ return newArray;
+ }
+
+
+ //---------------------------------------------------------------------
+ // Convenience methods for content-based equality/hash-code handling
+ //---------------------------------------------------------------------
+
+ /**
+ * Determine if the given objects are equal, returning true
+ * if both are null or false if only one is
+ * null.
+ *
Compares arrays with Arrays.equals, performing an equality
+ * check based on the array elements rather than the array reference.
+ * @param o1 first Object to compare
+ * @param o2 second Object to compare
+ * @return whether the given objects are equal
+ * @see java.util.Arrays#equals
+ */
+ public static boolean nullSafeEquals(Object o1, Object o2) {
+ if (o1 == o2) {
+ return true;
+ }
+ if (o1 == null || o2 == null) {
+ return false;
+ }
+ if (o1.equals(o2)) {
+ return true;
+ }
+ if (o1.getClass().isArray() && o2.getClass().isArray()) {
+ if (o1 instanceof Object[] && o2 instanceof Object[]) {
+ return Arrays.equals((Object[]) o1, (Object[]) o2);
+ }
+ if (o1 instanceof boolean[] && o2 instanceof boolean[]) {
+ return Arrays.equals((boolean[]) o1, (boolean[]) o2);
+ }
+ if (o1 instanceof byte[] && o2 instanceof byte[]) {
+ return Arrays.equals((byte[]) o1, (byte[]) o2);
+ }
+ if (o1 instanceof char[] && o2 instanceof char[]) {
+ return Arrays.equals((char[]) o1, (char[]) o2);
+ }
+ if (o1 instanceof double[] && o2 instanceof double[]) {
+ return Arrays.equals((double[]) o1, (double[]) o2);
+ }
+ if (o1 instanceof float[] && o2 instanceof float[]) {
+ return Arrays.equals((float[]) o1, (float[]) o2);
+ }
+ if (o1 instanceof int[] && o2 instanceof int[]) {
+ return Arrays.equals((int[]) o1, (int[]) o2);
+ }
+ if (o1 instanceof long[] && o2 instanceof long[]) {
+ return Arrays.equals((long[]) o1, (long[]) o2);
+ }
+ if (o1 instanceof short[] && o2 instanceof short[]) {
+ return Arrays.equals((short[]) o1, (short[]) o2);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return as hash code for the given object; typically the value of
+ * {@link Object#hashCode()}. If the object is an array,
+ * this method will delegate to any of the nullSafeHashCode
+ * methods for arrays in this class. If the object is null,
+ * this method returns 0.
+ * @see #nullSafeHashCode(Object[])
+ * @see #nullSafeHashCode(boolean[])
+ * @see #nullSafeHashCode(byte[])
+ * @see #nullSafeHashCode(char[])
+ * @see #nullSafeHashCode(double[])
+ * @see #nullSafeHashCode(float[])
+ * @see #nullSafeHashCode(int[])
+ * @see #nullSafeHashCode(long[])
+ * @see #nullSafeHashCode(short[])
+ */
+ public static int nullSafeHashCode(Object obj) {
+ if (obj == null) {
+ return 0;
+ }
+ if (obj.getClass().isArray()) {
+ if (obj instanceof Object[]) {
+ return nullSafeHashCode((Object[]) obj);
+ }
+ if (obj instanceof boolean[]) {
+ return nullSafeHashCode((boolean[]) obj);
+ }
+ if (obj instanceof byte[]) {
+ return nullSafeHashCode((byte[]) obj);
+ }
+ if (obj instanceof char[]) {
+ return nullSafeHashCode((char[]) obj);
+ }
+ if (obj instanceof double[]) {
+ return nullSafeHashCode((double[]) obj);
+ }
+ if (obj instanceof float[]) {
+ return nullSafeHashCode((float[]) obj);
+ }
+ if (obj instanceof int[]) {
+ return nullSafeHashCode((int[]) obj);
+ }
+ if (obj instanceof long[]) {
+ return nullSafeHashCode((long[]) obj);
+ }
+ if (obj instanceof short[]) {
+ return nullSafeHashCode((short[]) obj);
+ }
+ }
+ return obj.hashCode();
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If array is null, this method returns 0.
+ */
+ public static int nullSafeHashCode(Object[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ int arraySize = array.length;
+ for (int i = 0; i < arraySize; i++) {
+ hash = MULTIPLIER * hash + nullSafeHashCode(array[i]);
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If array is null, this method returns 0.
+ */
+ public static int nullSafeHashCode(boolean[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ int arraySize = array.length;
+ for (int i = 0; i < arraySize; i++) {
+ hash = MULTIPLIER * hash + hashCode(array[i]);
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If array is null, this method returns 0.
+ */
+ public static int nullSafeHashCode(byte[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ int arraySize = array.length;
+ for (int i = 0; i < arraySize; i++) {
+ hash = MULTIPLIER * hash + array[i];
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If array is null, this method returns 0.
+ */
+ public static int nullSafeHashCode(char[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ int arraySize = array.length;
+ for (int i = 0; i < arraySize; i++) {
+ hash = MULTIPLIER * hash + array[i];
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If array is null, this method returns 0.
+ */
+ public static int nullSafeHashCode(double[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ int arraySize = array.length;
+ for (int i = 0; i < arraySize; i++) {
+ hash = MULTIPLIER * hash + hashCode(array[i]);
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If array is null, this method returns 0.
+ */
+ public static int nullSafeHashCode(float[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ int arraySize = array.length;
+ for (int i = 0; i < arraySize; i++) {
+ hash = MULTIPLIER * hash + hashCode(array[i]);
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If array is null, this method returns 0.
+ */
+ public static int nullSafeHashCode(int[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ int arraySize = array.length;
+ for (int i = 0; i < arraySize; i++) {
+ hash = MULTIPLIER * hash + array[i];
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If array is null, this method returns 0.
+ */
+ public static int nullSafeHashCode(long[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ int arraySize = array.length;
+ for (int i = 0; i < arraySize; i++) {
+ hash = MULTIPLIER * hash + hashCode(array[i]);
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If array is null, this method returns 0.
+ */
+ public static int nullSafeHashCode(short[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ int arraySize = array.length;
+ for (int i = 0; i < arraySize; i++) {
+ hash = MULTIPLIER * hash + array[i];
+ }
+ return hash;
+ }
+
+ /**
+ * Return the same value as {@link Boolean#hashCode()}.
+ * @see Boolean#hashCode()
+ */
+ public static int hashCode(boolean bool) {
+ return bool ? 1231 : 1237;
+ }
+
+ /**
+ * Return the same value as {@link Double#hashCode()}.
+ * @see Double#hashCode()
+ */
+ public static int hashCode(double dbl) {
+ long bits = Double.doubleToLongBits(dbl);
+ return hashCode(bits);
+ }
+
+ /**
+ * Return the same value as {@link Float#hashCode()}.
+ * @see Float#hashCode()
+ */
+ public static int hashCode(float flt) {
+ return Float.floatToIntBits(flt);
+ }
+
+ /**
+ * Return the same value as {@link Long#hashCode()}.
+ * @see Long#hashCode()
+ */
+ public static int hashCode(long lng) {
+ return (int) (lng ^ (lng >>> 32));
+ }
+
+
+ //---------------------------------------------------------------------
+ // Convenience methods for toString output
+ //---------------------------------------------------------------------
+
+ /**
+ * Return a String representation of an object's overall identity.
+ * @param obj the object (may be null)
+ * @return the object's identity as String representation,
+ * or an empty String if the object was null
+ */
+ public static String identityToString(Object obj) {
+ if (obj == null) {
+ return EMPTY_STRING;
+ }
+ return obj.getClass().getName() + "@" + getIdentityHexString(obj);
+ }
+
+ /**
+ * Return a hex String form of an object's identity hash code.
+ * @param obj the object
+ * @return the object's identity code in hex notation
+ */
+ public static String getIdentityHexString(Object obj) {
+ return Integer.toHexString(System.identityHashCode(obj));
+ }
+
+ /**
+ * Return a content-based String representation if obj is
+ * not null; otherwise returns an empty String.
+ *
Differs from {@link #nullSafeToString(Object)} in that it returns
+ * an empty String rather than "null" for a null value.
+ * @param obj the object to build a display String for
+ * @return a display String representation of obj
+ * @see #nullSafeToString(Object)
+ */
+ public static String getDisplayString(Object obj) {
+ if (obj == null) {
+ return EMPTY_STRING;
+ }
+ return nullSafeToString(obj);
+ }
+
+ /**
+ * Determine the class name for the given object.
+ *
Returns "null" if obj is null.
+ * @param obj the object to introspect (may be null)
+ * @return the corresponding class name
+ */
+ public static String nullSafeClassName(Object obj) {
+ return (obj != null ? obj.getClass().getName() : NULL_STRING);
+ }
+
+ /**
+ * Return a String representation of the specified Object.
+ *
Builds a String representation of the contents in case of an array.
+ * Returns "null" if obj is null.
+ * @param obj the object to build a String representation for
+ * @return a String representation of obj
+ */
+ public static String nullSafeToString(Object obj) {
+ if (obj == null) {
+ return NULL_STRING;
+ }
+ if (obj instanceof String) {
+ return (String) obj;
+ }
+ if (obj instanceof Object[]) {
+ return nullSafeToString((Object[]) obj);
+ }
+ if (obj instanceof boolean[]) {
+ return nullSafeToString((boolean[]) obj);
+ }
+ if (obj instanceof byte[]) {
+ return nullSafeToString((byte[]) obj);
+ }
+ if (obj instanceof char[]) {
+ return nullSafeToString((char[]) obj);
+ }
+ if (obj instanceof double[]) {
+ return nullSafeToString((double[]) obj);
+ }
+ if (obj instanceof float[]) {
+ return nullSafeToString((float[]) obj);
+ }
+ if (obj instanceof int[]) {
+ return nullSafeToString((int[]) obj);
+ }
+ if (obj instanceof long[]) {
+ return nullSafeToString((long[]) obj);
+ }
+ if (obj instanceof short[]) {
+ return nullSafeToString((short[]) obj);
+ }
+ String str = obj.toString();
+ return (str != null ? str : EMPTY_STRING);
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ *
The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ("{}"). Adjacent elements are separated
+ * by the characters ", " (a comma followed by a space). Returns
+ * "null" if array is null.
+ * @param array the array to build a String representation for
+ * @return a String representation of array
+ */
+ public static String nullSafeToString(Object[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ buffer.append(ARRAY_START);
+ }
+ else {
+ buffer.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+ buffer.append(String.valueOf(array[i]));
+ }
+ buffer.append(ARRAY_END);
+ return buffer.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ *
The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ("{}"). Adjacent elements are separated
+ * by the characters ", " (a comma followed by a space). Returns
+ * "null" if array is null.
+ * @param array the array to build a String representation for
+ * @return a String representation of array
+ */
+ public static String nullSafeToString(boolean[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ buffer.append(ARRAY_START);
+ }
+ else {
+ buffer.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+
+ buffer.append(array[i]);
+ }
+ buffer.append(ARRAY_END);
+ return buffer.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ *
The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ("{}"). Adjacent elements are separated
+ * by the characters ", " (a comma followed by a space). Returns
+ * "null" if array is null.
+ * @param array the array to build a String representation for
+ * @return a String representation of array
+ */
+ public static String nullSafeToString(byte[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ buffer.append(ARRAY_START);
+ }
+ else {
+ buffer.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+ buffer.append(array[i]);
+ }
+ buffer.append(ARRAY_END);
+ return buffer.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ *
The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ("{}"). Adjacent elements are separated
+ * by the characters ", " (a comma followed by a space). Returns
+ * "null" if array is null.
+ * @param array the array to build a String representation for
+ * @return a String representation of array
+ */
+ public static String nullSafeToString(char[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ buffer.append(ARRAY_START);
+ }
+ else {
+ buffer.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+ buffer.append("'").append(array[i]).append("'");
+ }
+ buffer.append(ARRAY_END);
+ return buffer.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ *
The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ("{}"). Adjacent elements are separated
+ * by the characters ", " (a comma followed by a space). Returns
+ * "null" if array is null.
+ * @param array the array to build a String representation for
+ * @return a String representation of array
+ */
+ public static String nullSafeToString(double[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ buffer.append(ARRAY_START);
+ }
+ else {
+ buffer.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+
+ buffer.append(array[i]);
+ }
+ buffer.append(ARRAY_END);
+ return buffer.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ *
The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ("{}"). Adjacent elements are separated
+ * by the characters ", " (a comma followed by a space). Returns
+ * "null" if array is null.
+ * @param array the array to build a String representation for
+ * @return a String representation of array
+ */
+ public static String nullSafeToString(float[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ buffer.append(ARRAY_START);
+ }
+ else {
+ buffer.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+
+ buffer.append(array[i]);
+ }
+ buffer.append(ARRAY_END);
+ return buffer.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ *
The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ("{}"). Adjacent elements are separated
+ * by the characters ", " (a comma followed by a space). Returns
+ * "null" if array is null.
+ * @param array the array to build a String representation for
+ * @return a String representation of array
+ */
+ public static String nullSafeToString(int[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ buffer.append(ARRAY_START);
+ }
+ else {
+ buffer.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+ buffer.append(array[i]);
+ }
+ buffer.append(ARRAY_END);
+ return buffer.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ *
The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ("{}"). Adjacent elements are separated
+ * by the characters ", " (a comma followed by a space). Returns
+ * "null" if array is null.
+ * @param array the array to build a String representation for
+ * @return a String representation of array
+ */
+ public static String nullSafeToString(long[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ buffer.append(ARRAY_START);
+ }
+ else {
+ buffer.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+ buffer.append(array[i]);
+ }
+ buffer.append(ARRAY_END);
+ return buffer.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ *
The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ("{}"). Adjacent elements are separated
+ * by the characters ", " (a comma followed by a space). Returns
+ * "null" if array is null.
+ * @param array the array to build a String representation for
+ * @return a String representation of array
+ */
+ public static String nullSafeToString(short[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ buffer.append(ARRAY_START);
+ }
+ else {
+ buffer.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+ buffer.append(array[i]);
+ }
+ buffer.append(ARRAY_END);
+ return buffer.toString();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/PathMatcher.java b/org.springframework.core/src/main/java/org/springframework/util/PathMatcher.java
new file mode 100644
index 00000000000..b7671d2f872
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/PathMatcher.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.util;
+
+/**
+ * Strategy interface for String-based path matching.
+ *
+ *
Used by {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver},
+ * {@link org.springframework.web.servlet.handler.AbstractUrlHandlerMapping},
+ * {@link org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver},
+ * and {@link org.springframework.web.servlet.mvc.WebContentInterceptor}.
+ *
+ *
The default implementation is {@link AntPathMatcher}, supporting the
+ * Ant-style pattern syntax.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see AntPathMatcher
+ */
+public interface PathMatcher {
+
+ /**
+ * Does the given path represent a pattern that can be matched
+ * by an implementation of this interface?
+ *
If the return value is false, then the {@link #match}
+ * method does not have to be used because direct equality comparisons
+ * on the static path Strings will lead to the same result.
+ * @param path the path String to check
+ * @return true if the given path represents a pattern
+ */
+ boolean isPattern(String path);
+
+ /**
+ * Match the given path against the given pattern,
+ * according to this PathMatcher's matching strategy.
+ * @param pattern the pattern to match against
+ * @param path the path String to test
+ * @return true if the supplied path matched,
+ * false if it didn't
+ */
+ boolean match(String pattern, String path);
+
+ /**
+ * Match the given path against the corresponding part of the given
+ * pattern, according to this PathMatcher's matching strategy.
+ *
Determines whether the pattern at least matches as far as the given base
+ * path goes, assuming that a full path may then match as well.
+ * @param pattern the pattern to match against
+ * @param path the path String to test
+ * @return true if the supplied path matched,
+ * false if it didn't
+ */
+ boolean matchStart(String pattern, String path);
+
+ /**
+ * Given a pattern and a full path, determine the pattern-mapped part.
+ *
This method is supposed to find out which part of the path is matched
+ * dynamically through an actual pattern, that is, it strips off a statically
+ * defined leading path from the given full path, returning only the actually
+ * pattern-matched part of the path.
+ *
For example: For "myroot/*.html" as pattern and "myroot/myfile.html"
+ * as full path, this method should return "myfile.html". The detailed
+ * determination rules are specified to this PathMatcher's matching strategy.
+ *
A simple implementation may return the given full path as-is in case
+ * of an actual pattern, and the empty String in case of the pattern not
+ * containing any dynamic parts (i.e. the pattern parameter being
+ * a static path that wouldn't qualify as an actual {@link #isPattern pattern}).
+ * A sophisticated implementation will differentiate between the static parts
+ * and the dynamic parts of the given path pattern.
+ * @param pattern the path pattern
+ * @param path the full path to introspect
+ * @return the pattern-mapped part of the given path
+ * (never null)
+ */
+ String extractPathWithinPattern(String pattern, String path);
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/PatternMatchUtils.java b/org.springframework.core/src/main/java/org/springframework/util/PatternMatchUtils.java
new file mode 100644
index 00000000000..0b7c80a47e1
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/PatternMatchUtils.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.util;
+
+/**
+ * Utility methods for simple pattern matching, in particular for
+ * Spring's typical "xxx*", "*xxx" and "*xxx*" pattern styles.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public abstract class PatternMatchUtils {
+
+ /**
+ * Match a String against the given pattern, supporting the following simple
+ * pattern styles: "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an
+ * arbitrary number of pattern parts), as well as direct equality.
+ * @param pattern the pattern to match against
+ * @param str the String to match
+ * @return whether the String matches the given pattern
+ */
+ public static boolean simpleMatch(String pattern, String str) {
+ if (pattern == null || str == null) {
+ return false;
+ }
+ int firstIndex = pattern.indexOf('*');
+ if (firstIndex == -1) {
+ return pattern.equals(str);
+ }
+ if (firstIndex == 0) {
+ if (pattern.length() == 1) {
+ return true;
+ }
+ int nextIndex = pattern.indexOf('*', firstIndex + 1);
+ if (nextIndex == -1) {
+ return str.endsWith(pattern.substring(1));
+ }
+ String part = pattern.substring(1, nextIndex);
+ int partIndex = str.indexOf(part);
+ while (partIndex != -1) {
+ if (simpleMatch(pattern.substring(nextIndex), str.substring(partIndex + part.length()))) {
+ return true;
+ }
+ partIndex = str.indexOf(part, partIndex + 1);
+ }
+ return false;
+ }
+ return (str.length() >= firstIndex &&
+ pattern.substring(0, firstIndex).equals(str.substring(0, firstIndex)) &&
+ simpleMatch(pattern.substring(firstIndex), str.substring(firstIndex)));
+ }
+
+ /**
+ * Match a String against the given patterns, supporting the following simple
+ * pattern styles: "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an
+ * arbitrary number of pattern parts), as well as direct equality.
+ * @param patterns the patterns to match against
+ * @param str the String to match
+ * @return whether the String matches any of the given patterns
+ */
+ public static boolean simpleMatch(String[] patterns, String str) {
+ if (patterns != null) {
+ for (int i = 0; i < patterns.length; i++) {
+ if (simpleMatch(patterns[i], str)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/PropertiesPersister.java b/org.springframework.core/src/main/java/org/springframework/util/PropertiesPersister.java
new file mode 100644
index 00000000000..9fb5c755a40
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/PropertiesPersister.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Properties;
+
+/**
+ * Strategy interface for persisting java.util.Properties,
+ * allowing for pluggable parsing strategies.
+ *
+ *
The default implementation is DefaultPropertiesPersister,
+ * providing the native parsing of java.util.Properties,
+ * but allowing for reading from any Reader and writing to any Writer
+ * (which allows to specify an encoding for a properties file).
+ *
+ *
As of Spring 1.2.2, this interface also supports properties XML files,
+ * through the loadFromXml and storeToXml methods.
+ * The default implementations delegate to JDK 1.5's corresponding methods.
+ *
+ * @author Juergen Hoeller
+ * @since 10.03.2004
+ * @see DefaultPropertiesPersister
+ * @see java.util.Properties
+ */
+public interface PropertiesPersister {
+
+ /**
+ * Load properties from the given InputStream into the given
+ * Properties object.
+ * @param props the Properties object to load into
+ * @param is the InputStream to load from
+ * @throws IOException in case of I/O errors
+ * @see java.util.Properties#load
+ */
+ void load(Properties props, InputStream is) throws IOException;
+
+ /**
+ * Load properties from the given Reader into the given
+ * Properties object.
+ * @param props the Properties object to load into
+ * @param reader the Reader to load from
+ * @throws IOException in case of I/O errors
+ */
+ void load(Properties props, Reader reader) throws IOException;
+
+
+ /**
+ * Write the contents of the given Properties object to the
+ * given OutputStream.
+ * @param props the Properties object to store
+ * @param os the OutputStream to write to
+ * @param header the description of the property list
+ * @throws IOException in case of I/O errors
+ * @see java.util.Properties#store
+ */
+ void store(Properties props, OutputStream os, String header) throws IOException;
+
+ /**
+ * Write the contents of the given Properties object to the
+ * given Writer.
+ * @param props the Properties object to store
+ * @param writer the Writer to write to
+ * @param header the description of the property list
+ * @throws IOException in case of I/O errors
+ */
+ void store(Properties props, Writer writer, String header) throws IOException;
+
+
+ /**
+ * Load properties from the given XML InputStream into the
+ * given Properties object.
+ * @param props the Properties object to load into
+ * @param is the InputStream to load from
+ * @throws IOException in case of I/O errors
+ * @see java.util.Properties#loadFromXML(java.io.InputStream)
+ */
+ void loadFromXml(Properties props, InputStream is) throws IOException;
+
+ /**
+ * Write the contents of the given Properties object to the
+ * given XML OutputStream.
+ * @param props the Properties object to store
+ * @param os the OutputStream to write to
+ * @param header the description of the property list
+ * @throws IOException in case of I/O errors
+ * @see java.util.Properties#storeToXML(java.io.OutputStream, String)
+ */
+ void storeToXml(Properties props, OutputStream os, String header) throws IOException;
+
+ /**
+ * Write the contents of the given Properties object to the
+ * given XML OutputStream.
+ * @param props the Properties object to store
+ * @param os the OutputStream to write to
+ * @param encoding the encoding to use
+ * @param header the description of the property list
+ * @throws IOException in case of I/O errors
+ * @see java.util.Properties#storeToXML(java.io.OutputStream, String, String)
+ */
+ void storeToXml(Properties props, OutputStream os, String header, String encoding) throws IOException;
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/ReflectionUtils.java b/org.springframework.core/src/main/java/org/springframework/util/ReflectionUtils.java
new file mode 100644
index 00000000000..694cb70242a
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/ReflectionUtils.java
@@ -0,0 +1,624 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Simple utility class for working with the reflection API and handling
+ * reflection exceptions.
+ *
+ *
Only intended for internal use.
+ *
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @author Rod Johnson
+ * @author Costin Leau
+ * @author Sam Brannen
+ * @since 1.2.2
+ */
+public abstract class ReflectionUtils {
+
+ /**
+ * Attempt to find a {@link Field field} on the supplied {@link Class} with
+ * the supplied name. Searches all superclasses up to {@link Object}.
+ * @param clazz the class to introspect
+ * @param name the name of the field
+ * @return the corresponding Field object, or null if not found
+ */
+ public static Field findField(Class clazz, String name) {
+ return findField(clazz, name, null);
+ }
+
+ /**
+ * Attempt to find a {@link Field field} on the supplied {@link Class} with
+ * the supplied name and/or {@link Class type}. Searches all
+ * superclasses up to {@link Object}.
+ * @param clazz the class to introspect
+ * @param name the name of the field (may be null if type is specified)
+ * @param type the type of the field (may be null if name is specified)
+ * @return the corresponding Field object, or null if not found
+ */
+ public static Field findField(Class clazz, String name, Class type) {
+ Assert.notNull(clazz, "Class must not be null");
+ Assert.isTrue(name != null || type != null, "Either name or type of the field must be specified");
+ Class searchType = clazz;
+ while (!Object.class.equals(searchType) && searchType != null) {
+ Field[] fields = searchType.getDeclaredFields();
+ for (int i = 0; i < fields.length; i++) {
+ Field field = fields[i];
+ if ((name == null || name.equals(field.getName()))
+ && (type == null || type.equals(field.getType()))) {
+ return field;
+ }
+ }
+ searchType = searchType.getSuperclass();
+ }
+ return null;
+ }
+
+ /**
+ * Set the field represented by the supplied {@link Field field object} on
+ * the specified {@link Object target object} to the specified
+ * value. In accordance with
+ * {@link Field#set(Object, Object)} semantics, the new value is
+ * automatically unwrapped if the underlying field has a primitive type.
+ *
Thrown exceptions are handled via a call to
+ * {@link #handleReflectionException(Exception)}.
+ * @param field the field to set
+ * @param target the target object on which to set the field
+ * @param value the value to set; may be null
+ */
+ public static void setField(Field field, Object target, Object value) {
+ try {
+ field.set(target, value);
+ }
+ catch (IllegalAccessException ex) {
+ handleReflectionException(ex);
+ throw new IllegalStateException(
+ "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage());
+ }
+ }
+
+ /**
+ * Get the field represented by the supplied {@link Field field object} on
+ * the specified {@link Object target object}. In accordance with
+ * {@link Field#get(Object)} semantics, the returned value is
+ * automatically wrapped if the underlying field has a primitive type.
+ *
Thrown exceptions are handled via a call to
+ * {@link #handleReflectionException(Exception)}.
+ * @param field the field to get
+ * @param target the target object from which to get the field
+ * @return the field's current value
+ */
+ public static Object getField(Field field, Object target) {
+ try {
+ return field.get(target);
+ }
+ catch (IllegalAccessException ex) {
+ handleReflectionException(ex);
+ throw new IllegalStateException(
+ "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage());
+ }
+ }
+
+ /**
+ * Attempt to find a {@link Method} on the supplied class with the supplied name
+ * and no parameters. Searches all superclasses up to Object.
+ *
Returns null if no {@link Method} can be found.
+ * @param clazz the class to introspect
+ * @param name the name of the method
+ * @return the Method object, or null if none found
+ */
+ public static Method findMethod(Class clazz, String name) {
+ return findMethod(clazz, name, new Class[0]);
+ }
+
+ /**
+ * Attempt to find a {@link Method} on the supplied class with the supplied name
+ * and parameter types. Searches all superclasses up to Object.
+ *
Returns null if no {@link Method} can be found.
+ * @param clazz the class to introspect
+ * @param name the name of the method
+ * @param paramTypes the parameter types of the method
+ * (may be null to indicate any signature)
+ * @return the Method object, or null if none found
+ */
+ public static Method findMethod(Class clazz, String name, Class[] paramTypes) {
+ Assert.notNull(clazz, "Class must not be null");
+ Assert.notNull(name, "Method name must not be null");
+ Class searchType = clazz;
+ while (!Object.class.equals(searchType) && searchType != null) {
+ Method[] methods = (searchType.isInterface() ? searchType.getMethods() : searchType.getDeclaredMethods());
+ for (int i = 0; i < methods.length; i++) {
+ Method method = methods[i];
+ if (name.equals(method.getName()) &&
+ (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) {
+ return method;
+ }
+ }
+ searchType = searchType.getSuperclass();
+ }
+ return null;
+ }
+
+ /**
+ * Invoke the specified {@link Method} against the supplied target object
+ * with no arguments. The target object can be null when
+ * invoking a static {@link Method}.
+ *
Thrown exceptions are handled via a call to {@link #handleReflectionException}.
+ * @param method the method to invoke
+ * @param target the target object to invoke the method on
+ * @return the invocation result, if any
+ * @see #invokeMethod(java.lang.reflect.Method, Object, Object[])
+ */
+ public static Object invokeMethod(Method method, Object target) {
+ return invokeMethod(method, target, null);
+ }
+
+ /**
+ * Invoke the specified {@link Method} against the supplied target object
+ * with the supplied arguments. The target object can be null
+ * when invoking a static {@link Method}.
+ *
Thrown exceptions are handled via a call to {@link #handleReflectionException}.
+ * @param method the method to invoke
+ * @param target the target object to invoke the method on
+ * @param args the invocation arguments (may be null)
+ * @return the invocation result, if any
+ */
+ public static Object invokeMethod(Method method, Object target, Object[] args) {
+ try {
+ return method.invoke(target, args);
+ }
+ catch (Exception ex) {
+ handleReflectionException(ex);
+ }
+ throw new IllegalStateException("Should never get here");
+ }
+
+ /**
+ * Invoke the specified JDBC API {@link Method} against the supplied
+ * target object with no arguments.
+ * @param method the method to invoke
+ * @param target the target object to invoke the method on
+ * @return the invocation result, if any
+ * @throws SQLException the JDBC API SQLException to rethrow (if any)
+ * @see #invokeJdbcMethod(java.lang.reflect.Method, Object, Object[])
+ */
+ public static Object invokeJdbcMethod(Method method, Object target) throws SQLException {
+ return invokeJdbcMethod(method, target, null);
+ }
+
+ /**
+ * Invoke the specified JDBC API {@link Method} against the supplied
+ * target object with the supplied arguments.
+ * @param method the method to invoke
+ * @param target the target object to invoke the method on
+ * @param args the invocation arguments (may be null)
+ * @return the invocation result, if any
+ * @throws SQLException the JDBC API SQLException to rethrow (if any)
+ * @see #invokeMethod(java.lang.reflect.Method, Object, Object[])
+ */
+ public static Object invokeJdbcMethod(Method method, Object target, Object[] args) throws SQLException {
+ try {
+ return method.invoke(target, args);
+ }
+ catch (IllegalAccessException ex) {
+ handleReflectionException(ex);
+ }
+ catch (InvocationTargetException ex) {
+ if (ex.getTargetException() instanceof SQLException) {
+ throw (SQLException) ex.getTargetException();
+ }
+ handleInvocationTargetException(ex);
+ }
+ throw new IllegalStateException("Should never get here");
+ }
+
+ /**
+ * Handle the given reflection exception. Should only be called if
+ * no checked exception is expected to be thrown by the target method.
+ *
Throws the underlying RuntimeException or Error in case of an
+ * InvocationTargetException with such a root cause. Throws an
+ * IllegalStateException with an appropriate message else.
+ * @param ex the reflection exception to handle
+ */
+ public static void handleReflectionException(Exception ex) {
+ if (ex instanceof NoSuchMethodException) {
+ throw new IllegalStateException("Method not found: " + ex.getMessage());
+ }
+ if (ex instanceof IllegalAccessException) {
+ throw new IllegalStateException("Could not access method: " + ex.getMessage());
+ }
+ if (ex instanceof InvocationTargetException) {
+ handleInvocationTargetException((InvocationTargetException) ex);
+ }
+ if (ex instanceof RuntimeException) {
+ throw (RuntimeException) ex;
+ }
+ handleUnexpectedException(ex);
+ }
+
+ /**
+ * Handle the given invocation target exception. Should only be called if
+ * no checked exception is expected to be thrown by the target method.
+ *
Throws the underlying RuntimeException or Error in case of such
+ * a root cause. Throws an IllegalStateException else.
+ * @param ex the invocation target exception to handle
+ */
+ public static void handleInvocationTargetException(InvocationTargetException ex) {
+ rethrowRuntimeException(ex.getTargetException());
+ }
+
+ /**
+ * Rethrow the given {@link Throwable exception}, which is presumably the
+ * target exception of an {@link InvocationTargetException}.
+ * Should only be called if no checked exception is expected to be thrown by
+ * the target method.
+ *
Rethrows the underlying exception cast to an {@link RuntimeException}
+ * or {@link Error} if appropriate; otherwise, throws an
+ * {@link IllegalStateException}.
+ * @param ex the exception to rethrow
+ * @throws RuntimeException the rethrown exception
+ */
+ public static void rethrowRuntimeException(Throwable ex) {
+ if (ex instanceof RuntimeException) {
+ throw (RuntimeException) ex;
+ }
+ if (ex instanceof Error) {
+ throw (Error) ex;
+ }
+ handleUnexpectedException(ex);
+ }
+
+ /**
+ * Rethrow the given {@link Throwable exception}, which is presumably the
+ * target exception of an {@link InvocationTargetException}.
+ * Should only be called if no checked exception is expected to be thrown by
+ * the target method.
+ *
Rethrows the underlying exception cast to an {@link Exception} or
+ * {@link Error} if appropriate; otherwise, throws an
+ * {@link IllegalStateException}.
+ * @param ex the exception to rethrow
+ * @throws Exception the rethrown exception (in case of a checked exception)
+ */
+ public static void rethrowException(Throwable ex) throws Exception {
+ if (ex instanceof Exception) {
+ throw (Exception) ex;
+ }
+ if (ex instanceof Error) {
+ throw (Error) ex;
+ }
+ handleUnexpectedException(ex);
+ }
+
+ /**
+ * Throws an IllegalStateException with the given exception as root cause.
+ * @param ex the unexpected exception
+ */
+ private static void handleUnexpectedException(Throwable ex) {
+ // Needs to avoid the chained constructor for JDK 1.4 compatibility.
+ IllegalStateException isex = new IllegalStateException("Unexpected exception thrown");
+ isex.initCause(ex);
+ throw isex;
+ }
+
+ /**
+ * Determine whether the given method explicitly declares the given exception
+ * or one of its superclasses, which means that an exception of that type
+ * can be propagated as-is within a reflective invocation.
+ * @param method the declaring method
+ * @param exceptionType the exception to throw
+ * @return true if the exception can be thrown as-is;
+ * false if it needs to be wrapped
+ */
+ public static boolean declaresException(Method method, Class exceptionType) {
+ Assert.notNull(method, "Method must not be null");
+ Class[] declaredExceptions = method.getExceptionTypes();
+ for (int i = 0; i < declaredExceptions.length; i++) {
+ Class declaredException = declaredExceptions[i];
+ if (declaredException.isAssignableFrom(exceptionType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ /**
+ * Determine whether the given field is a "public static final" constant.
+ * @param field the field to check
+ */
+ public static boolean isPublicStaticFinal(Field field) {
+ int modifiers = field.getModifiers();
+ return (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers));
+ }
+
+ /**
+ * Determine whether the given method is an "equals" method.
+ * @see java.lang.Object#equals
+ */
+ public static boolean isEqualsMethod(Method method) {
+ if (method == null || !method.getName().equals("equals")) {
+ return false;
+ }
+ Class[] paramTypes = method.getParameterTypes();
+ return (paramTypes.length == 1 && paramTypes[0] == Object.class);
+ }
+
+ /**
+ * Determine whether the given method is a "hashCode" method.
+ * @see java.lang.Object#hashCode
+ */
+ public static boolean isHashCodeMethod(Method method) {
+ return (method != null && method.getName().equals("hashCode") &&
+ method.getParameterTypes().length == 0);
+ }
+
+ /**
+ * Determine whether the given method is a "toString" method.
+ * @see java.lang.Object#toString()
+ */
+ public static boolean isToStringMethod(Method method) {
+ return (method != null && method.getName().equals("toString") &&
+ method.getParameterTypes().length == 0);
+ }
+
+
+ /**
+ * Make the given field accessible, explicitly setting it accessible if necessary.
+ * The setAccessible(true) method is only called when actually necessary,
+ * to avoid unnecessary conflicts with a JVM SecurityManager (if active).
+ * @param field the field to make accessible
+ * @see java.lang.reflect.Field#setAccessible
+ */
+ public static void makeAccessible(Field field) {
+ if (!Modifier.isPublic(field.getModifiers()) ||
+ !Modifier.isPublic(field.getDeclaringClass().getModifiers())) {
+ field.setAccessible(true);
+ }
+ }
+
+ /**
+ * Make the given method accessible, explicitly setting it accessible if necessary.
+ * The setAccessible(true) method is only called when actually necessary,
+ * to avoid unnecessary conflicts with a JVM SecurityManager (if active).
+ * @param method the method to make accessible
+ * @see java.lang.reflect.Method#setAccessible
+ */
+ public static void makeAccessible(Method method) {
+ if (!Modifier.isPublic(method.getModifiers()) ||
+ !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
+ method.setAccessible(true);
+ }
+ }
+
+ /**
+ * Make the given constructor accessible, explicitly setting it accessible if necessary.
+ * The setAccessible(true) method is only called when actually necessary,
+ * to avoid unnecessary conflicts with a JVM SecurityManager (if active).
+ * @param ctor the constructor to make accessible
+ * @see java.lang.reflect.Constructor#setAccessible
+ */
+ public static void makeAccessible(Constructor ctor) {
+ if (!Modifier.isPublic(ctor.getModifiers()) ||
+ !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) {
+ ctor.setAccessible(true);
+ }
+ }
+
+
+ /**
+ * Perform the given callback operation on all matching methods of the
+ * given class and superclasses.
+ *
The same named method occurring on subclass and superclass will
+ * appear twice, unless excluded by a {@link MethodFilter}.
+ * @param targetClass class to start looking at
+ * @param mc the callback to invoke for each method
+ * @see #doWithMethods(Class, MethodCallback, MethodFilter)
+ */
+ public static void doWithMethods(Class targetClass, MethodCallback mc) throws IllegalArgumentException {
+ doWithMethods(targetClass, mc, null);
+ }
+
+ /**
+ * Perform the given callback operation on all matching methods of the
+ * given class and superclasses.
+ *
The same named method occurring on subclass and superclass will
+ * appear twice, unless excluded by the specified {@link MethodFilter}.
+ * @param targetClass class to start looking at
+ * @param mc the callback to invoke for each method
+ * @param mf the filter that determines the methods to apply the callback to
+ */
+ public static void doWithMethods(Class targetClass, MethodCallback mc, MethodFilter mf)
+ throws IllegalArgumentException {
+
+ // Keep backing up the inheritance hierarchy.
+ do {
+ Method[] methods = targetClass.getDeclaredMethods();
+ for (int i = 0; i < methods.length; i++) {
+ if (mf != null && !mf.matches(methods[i])) {
+ continue;
+ }
+ try {
+ mc.doWith(methods[i]);
+ }
+ catch (IllegalAccessException ex) {
+ throw new IllegalStateException(
+ "Shouldn't be illegal to access method '" + methods[i].getName() + "': " + ex);
+ }
+ }
+ targetClass = targetClass.getSuperclass();
+ }
+ while (targetClass != null);
+ }
+
+ /**
+ * Get all declared methods on the leaf class and all superclasses.
+ * Leaf class methods are included first.
+ */
+ public static Method[] getAllDeclaredMethods(Class leafClass) throws IllegalArgumentException {
+ final List list = new ArrayList(32);
+ doWithMethods(leafClass, new MethodCallback() {
+ public void doWith(Method method) {
+ list.add(method);
+ }
+ });
+ return (Method[]) list.toArray(new Method[list.size()]);
+ }
+
+
+ /**
+ * Invoke the given callback on all fields in the target class,
+ * going up the class hierarchy to get all declared fields.
+ * @param targetClass the target class to analyze
+ * @param fc the callback to invoke for each field
+ */
+ public static void doWithFields(Class targetClass, FieldCallback fc) throws IllegalArgumentException {
+ doWithFields(targetClass, fc, null);
+ }
+
+ /**
+ * Invoke the given callback on all fields in the target class,
+ * going up the class hierarchy to get all declared fields.
+ * @param targetClass the target class to analyze
+ * @param fc the callback to invoke for each field
+ * @param ff the filter that determines the fields to apply the callback to
+ */
+ public static void doWithFields(Class targetClass, FieldCallback fc, FieldFilter ff)
+ throws IllegalArgumentException {
+
+ // Keep backing up the inheritance hierarchy.
+ do {
+ // Copy each field declared on this class unless it's static or file.
+ Field[] fields = targetClass.getDeclaredFields();
+ for (int i = 0; i < fields.length; i++) {
+ // Skip static and final fields.
+ if (ff != null && !ff.matches(fields[i])) {
+ continue;
+ }
+ try {
+ fc.doWith(fields[i]);
+ }
+ catch (IllegalAccessException ex) {
+ throw new IllegalStateException(
+ "Shouldn't be illegal to access field '" + fields[i].getName() + "': " + ex);
+ }
+ }
+ targetClass = targetClass.getSuperclass();
+ }
+ while (targetClass != null && targetClass != Object.class);
+ }
+
+ /**
+ * Given the source object and the destination, which must be the same class
+ * or a subclass, copy all fields, including inherited fields. Designed to
+ * work on objects with public no-arg constructors.
+ * @throws IllegalArgumentException if the arguments are incompatible
+ */
+ public static void shallowCopyFieldState(final Object src, final Object dest) throws IllegalArgumentException {
+ if (src == null) {
+ throw new IllegalArgumentException("Source for field copy cannot be null");
+ }
+ if (dest == null) {
+ throw new IllegalArgumentException("Destination for field copy cannot be null");
+ }
+ if (!src.getClass().isAssignableFrom(dest.getClass())) {
+ throw new IllegalArgumentException("Destination class [" + dest.getClass().getName() +
+ "] must be same or subclass as source class [" + src.getClass().getName() + "]");
+ }
+ doWithFields(src.getClass(), new FieldCallback() {
+ public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
+ makeAccessible(field);
+ Object srcValue = field.get(src);
+ field.set(dest, srcValue);
+ }
+ }, COPYABLE_FIELDS);
+ }
+
+
+ /**
+ * Action to take on each method.
+ */
+ public static interface MethodCallback {
+
+ /**
+ * Perform an operation using the given method.
+ * @param method the method to operate on
+ */
+ void doWith(Method method) throws IllegalArgumentException, IllegalAccessException;
+ }
+
+
+ /**
+ * Callback optionally used to method fields to be operated on by a method callback.
+ */
+ public static interface MethodFilter {
+
+ /**
+ * Determine whether the given method matches.
+ * @param method the method to check
+ */
+ boolean matches(Method method);
+ }
+
+
+ /**
+ * Callback interface invoked on each field in the hierarchy.
+ */
+ public static interface FieldCallback {
+
+ /**
+ * Perform an operation using the given field.
+ * @param field the field to operate on
+ */
+ void doWith(Field field) throws IllegalArgumentException, IllegalAccessException;
+ }
+
+
+ /**
+ * Callback optionally used to filter fields to be operated on by a field callback.
+ */
+ public static interface FieldFilter {
+
+ /**
+ * Determine whether the given field matches.
+ * @param field the field to check
+ */
+ boolean matches(Field field);
+ }
+
+
+ /**
+ * Pre-built FieldFilter that matches all non-static, non-final fields.
+ */
+ public static FieldFilter COPYABLE_FIELDS = new FieldFilter() {
+ public boolean matches(Field field) {
+ return !(Modifier.isStatic(field.getModifiers()) ||
+ Modifier.isFinal(field.getModifiers()));
+ }
+ };
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/ResourceUtils.java b/org.springframework.core/src/main/java/org/springframework/util/ResourceUtils.java
new file mode 100644
index 00000000000..bf52694bb7c
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/ResourceUtils.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+/**
+ * Utility methods for resolving resource locations to files in the
+ * file system. Mainly for internal use within the framework.
+ *
+ *
Consider using Spring's Resource abstraction in the core package
+ * for handling all kinds of file resources in a uniform manner.
+ * {@link org.springframework.core.io.ResourceLoader}'s getResource
+ * method can resolve any location to a {@link org.springframework.core.io.Resource}
+ * object, which in turn allows to obtain a java.io.File in the
+ * file system through its getFile() method.
+ *
+ *
The main reason for these utility methods for resource location handling
+ * is to support {@link Log4jConfigurer}, which must be able to resolve
+ * resource locations before the logging system has been initialized.
+ * Spring' Resource abstraction in the core package, on the other hand,
+ * already expects the logging system to be available.
+ *
+ * @author Juergen Hoeller
+ * @since 1.1.5
+ * @see org.springframework.core.io.Resource
+ * @see org.springframework.core.io.ClassPathResource
+ * @see org.springframework.core.io.FileSystemResource
+ * @see org.springframework.core.io.UrlResource
+ * @see org.springframework.core.io.ResourceLoader
+ */
+public abstract class ResourceUtils {
+
+ /** Pseudo URL prefix for loading from the class path: "classpath:" */
+ public static final String CLASSPATH_URL_PREFIX = "classpath:";
+
+ /** URL prefix for loading from the file system: "file:" */
+ public static final String FILE_URL_PREFIX = "file:";
+
+ /** URL protocol for a file in the file system: "file" */
+ public static final String URL_PROTOCOL_FILE = "file";
+
+ /** URL protocol for an entry from a jar file: "jar" */
+ public static final String URL_PROTOCOL_JAR = "jar";
+
+ /** URL protocol for an entry from a zip file: "zip" */
+ public static final String URL_PROTOCOL_ZIP = "zip";
+
+ /** URL protocol for an entry from a JBoss jar file: "vfszip" */
+ public static final String URL_PROTOCOL_VFSZIP = "vfszip";
+
+ /** URL protocol for an entry from a WebSphere jar file: "wsjar" */
+ public static final String URL_PROTOCOL_WSJAR = "wsjar";
+
+ /** URL protocol for an entry from an OC4J jar file: "code-source" */
+ public static final String URL_PROTOCOL_CODE_SOURCE = "code-source";
+
+ /** Separator between JAR URL and file path within the JAR */
+ public static final String JAR_URL_SEPARATOR = "!/";
+
+
+ /**
+ * Return whether the given resource location is a URL:
+ * either a special "classpath" pseudo URL or a standard URL.
+ * @param resourceLocation the location String to check
+ * @return whether the location qualifies as a URL
+ * @see #CLASSPATH_URL_PREFIX
+ * @see java.net.URL
+ */
+ public static boolean isUrl(String resourceLocation) {
+ if (resourceLocation == null) {
+ return false;
+ }
+ if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) {
+ return true;
+ }
+ try {
+ new URL(resourceLocation);
+ return true;
+ }
+ catch (MalformedURLException ex) {
+ return false;
+ }
+ }
+
+ /**
+ * Resolve the given resource location to a java.net.URL.
+ *
Does not check whether the URL actually exists; simply returns
+ * the URL that the given location would correspond to.
+ * @param resourceLocation the resource location to resolve: either a
+ * "classpath:" pseudo URL, a "file:" URL, or a plain file path
+ * @return a corresponding URL object
+ * @throws FileNotFoundException if the resource cannot be resolved to a URL
+ */
+ public static URL getURL(String resourceLocation) throws FileNotFoundException {
+ Assert.notNull(resourceLocation, "Resource location must not be null");
+ if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) {
+ String path = resourceLocation.substring(CLASSPATH_URL_PREFIX.length());
+ URL url = ClassUtils.getDefaultClassLoader().getResource(path);
+ if (url == null) {
+ String description = "class path resource [" + path + "]";
+ throw new FileNotFoundException(
+ description + " cannot be resolved to URL because it does not exist");
+ }
+ return url;
+ }
+ try {
+ // try URL
+ return new URL(resourceLocation);
+ }
+ catch (MalformedURLException ex) {
+ // no URL -> treat as file path
+ try {
+ return new File(resourceLocation).toURI().toURL();
+ }
+ catch (MalformedURLException ex2) {
+ throw new FileNotFoundException("Resource location [" + resourceLocation +
+ "] is neither a URL not a well-formed file path");
+ }
+ }
+ }
+
+ /**
+ * Resolve the given resource location to a java.io.File,
+ * i.e. to a file in the file system.
+ *
Does not check whether the fil actually exists; simply returns
+ * the File that the given location would correspond to.
+ * @param resourceLocation the resource location to resolve: either a
+ * "classpath:" pseudo URL, a "file:" URL, or a plain file path
+ * @return a corresponding File object
+ * @throws FileNotFoundException if the resource cannot be resolved to
+ * a file in the file system
+ */
+ public static File getFile(String resourceLocation) throws FileNotFoundException {
+ Assert.notNull(resourceLocation, "Resource location must not be null");
+ if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) {
+ String path = resourceLocation.substring(CLASSPATH_URL_PREFIX.length());
+ String description = "class path resource [" + path + "]";
+ URL url = ClassUtils.getDefaultClassLoader().getResource(path);
+ if (url == null) {
+ throw new FileNotFoundException(
+ description + " cannot be resolved to absolute file path " +
+ "because it does not reside in the file system");
+ }
+ return getFile(url, description);
+ }
+ try {
+ // try URL
+ return getFile(new URL(resourceLocation));
+ }
+ catch (MalformedURLException ex) {
+ // no URL -> treat as file path
+ return new File(resourceLocation);
+ }
+ }
+
+ /**
+ * Resolve the given resource URL to a java.io.File,
+ * i.e. to a file in the file system.
+ * @param resourceUrl the resource URL to resolve
+ * @return a corresponding File object
+ * @throws FileNotFoundException if the URL cannot be resolved to
+ * a file in the file system
+ */
+ public static File getFile(URL resourceUrl) throws FileNotFoundException {
+ return getFile(resourceUrl, "URL");
+ }
+
+ /**
+ * Resolve the given resource URL to a java.io.File,
+ * i.e. to a file in the file system.
+ * @param resourceUrl the resource URL to resolve
+ * @param description a description of the original resource that
+ * the URL was created for (for example, a class path location)
+ * @return a corresponding File object
+ * @throws FileNotFoundException if the URL cannot be resolved to
+ * a file in the file system
+ */
+ public static File getFile(URL resourceUrl, String description) throws FileNotFoundException {
+ Assert.notNull(resourceUrl, "Resource URL must not be null");
+ if (!URL_PROTOCOL_FILE.equals(resourceUrl.getProtocol())) {
+ throw new FileNotFoundException(
+ description + " cannot be resolved to absolute file path " +
+ "because it does not reside in the file system: " + resourceUrl);
+ }
+ try {
+ return new File(toURI(resourceUrl).getSchemeSpecificPart());
+ }
+ catch (URISyntaxException ex) {
+ // Fallback for URLs that are not valid URIs (should hardly ever happen).
+ return new File(resourceUrl.getFile());
+ }
+ }
+
+ /**
+ * Resolve the given resource URI to a java.io.File,
+ * i.e. to a file in the file system.
+ * @param resourceUri the resource URI to resolve
+ * @return a corresponding File object
+ * @throws FileNotFoundException if the URL cannot be resolved to
+ * a file in the file system
+ */
+ public static File getFile(URI resourceUri) throws FileNotFoundException {
+ return getFile(resourceUri, "URI");
+ }
+
+ /**
+ * Resolve the given resource URI to a java.io.File,
+ * i.e. to a file in the file system.
+ * @param resourceUri the resource URI to resolve
+ * @param description a description of the original resource that
+ * the URI was created for (for example, a class path location)
+ * @return a corresponding File object
+ * @throws FileNotFoundException if the URL cannot be resolved to
+ * a file in the file system
+ */
+ public static File getFile(URI resourceUri, String description) throws FileNotFoundException {
+ Assert.notNull(resourceUri, "Resource URI must not be null");
+ if (!URL_PROTOCOL_FILE.equals(resourceUri.getScheme())) {
+ throw new FileNotFoundException(
+ description + " cannot be resolved to absolute file path " +
+ "because it does not reside in the file system: " + resourceUri);
+ }
+ return new File(resourceUri.getSchemeSpecificPart());
+ }
+
+ /**
+ * Determine whether the given URL points to a resource in a jar file,
+ * that is, has protocol "jar", "zip", "wsjar" or "code-source".
+ *
"zip" and "wsjar" are used by BEA WebLogic Server and IBM WebSphere, respectively,
+ * but can be treated like jar files. The same applies to "code-source" URLs on Oracle
+ * OC4J, provided that the path contains a jar separator.
+ * @param url the URL to check
+ * @return whether the URL has been identified as a JAR URL
+ */
+ public static boolean isJarURL(URL url) {
+ String protocol = url.getProtocol();
+ return (URL_PROTOCOL_JAR.equals(protocol) ||
+ URL_PROTOCOL_ZIP.equals(protocol) ||
+ URL_PROTOCOL_VFSZIP.equals(protocol) ||
+ URL_PROTOCOL_WSJAR.equals(protocol) ||
+ (URL_PROTOCOL_CODE_SOURCE.equals(protocol) && url.getPath().indexOf(JAR_URL_SEPARATOR) != -1));
+ }
+
+ /**
+ * Extract the URL for the actual jar file from the given URL
+ * (which may point to a resource in a jar file or to a jar file itself).
+ * @param jarUrl the original URL
+ * @return the URL for the actual jar file
+ * @throws MalformedURLException if no valid jar file URL could be extracted
+ */
+ public static URL extractJarFileURL(URL jarUrl) throws MalformedURLException {
+ String urlFile = jarUrl.getFile();
+ int separatorIndex = urlFile.indexOf(JAR_URL_SEPARATOR);
+ if (separatorIndex != -1) {
+ String jarFile = urlFile.substring(0, separatorIndex);
+ try {
+ return new URL(jarFile);
+ }
+ catch (MalformedURLException ex) {
+ // Probably no protocol in original jar URL, like "jar:C:/mypath/myjar.jar".
+ // This usually indicates that the jar file resides in the file system.
+ if (!jarFile.startsWith("/")) {
+ jarFile = "/" + jarFile;
+ }
+ return new URL(FILE_URL_PREFIX + jarFile);
+ }
+ }
+ else {
+ return jarUrl;
+ }
+ }
+
+ /**
+ * Create a URI instance for the given URL,
+ * replacing spaces with "%20" quotes first.
+ *
Furthermore, this method works on JDK 1.4 as well,
+ * in contrast to the URL.toURI() method.
+ * @param url the URL to convert into a URI instance
+ * @return the URI instance
+ * @throws URISyntaxException if the URL wasn't a valid URI
+ * @see java.net.URL#toURI()
+ */
+ public static URI toURI(URL url) throws URISyntaxException {
+ return toURI(url.toString());
+ }
+
+ /**
+ * Create a URI instance for the given location String,
+ * replacing spaces with "%20" quotes first.
+ * @param location the location String to convert into a URI instance
+ * @return the URI instance
+ * @throws URISyntaxException if the location wasn't a valid URI
+ */
+ public static URI toURI(String location) throws URISyntaxException {
+ return new URI(StringUtils.replace(location, " ", "%20"));
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/ResponseTimeMonitor.java b/org.springframework.core/src/main/java/org/springframework/util/ResponseTimeMonitor.java
new file mode 100644
index 00000000000..d1b3f659d24
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/ResponseTimeMonitor.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util;
+
+/**
+ * Interface implemented by objects that can provide performance information
+ * as well as a record of the number of times they are accessed.
+ *
+ *
Implementing objects must ensure that implementing this interface
+ * does not compromise thread safety. However, it may be acceptable
+ * for slight innaccuracies in reported statistics to result from the
+ * avoidance of synchronization: performance may be well be more important
+ * than exact reporting, so long as the errors are not likely to be misleading.
+ *
+ * @author Rod Johnson
+ * @since November 21, 2000
+ * @deprecated as of Spring 2.5, to be removed in Spring 3.0
+ */
+public interface ResponseTimeMonitor {
+
+ /**
+ * Return the number of accesses to this resource.
+ */
+ int getAccessCount();
+
+ /**
+ * Return the average response time in milliseconds.
+ */
+ int getAverageResponseTimeMillis();
+
+ /**
+ * Return the best (quickest) response time in milliseconds.
+ */
+ int getBestResponseTimeMillis();
+
+ /**
+ * Return the worst (slowest) response time in milliseconds.
+ */
+ int getWorstResponseTimeMillis();
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/ResponseTimeMonitorImpl.java b/org.springframework.core/src/main/java/org/springframework/util/ResponseTimeMonitorImpl.java
new file mode 100644
index 00000000000..7f083deeadc
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/ResponseTimeMonitorImpl.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.util.Date;
+
+/**
+ * Default implementation of {@link ResponseTimeMonitor}.
+ *
+ * @author Rod Johnson
+ * @since November 21, 2000
+ * @deprecated as of Spring 2.5, to be removed in Spring 3.0
+ */
+public class ResponseTimeMonitorImpl implements ResponseTimeMonitor {
+
+ /** The system time at which this object was initialized */
+ private final long initedMillis = System.currentTimeMillis();
+
+ /** The number of operations recorded by this object */
+ private volatile int accessCount;
+
+ /** The sum of the response times for all operations */
+ private volatile int totalResponseTimeMillis = 0;
+
+ /** The best response time this object has recorded */
+ private volatile int bestResponseTimeMillis = Integer.MAX_VALUE;
+
+ /** The worst response time this object has recorded */
+ private volatile int worstResponseTimeMillis = Integer.MIN_VALUE;
+
+
+ /**
+ * Return the date when this object was loaded.
+ */
+ public Date getLoadDate() {
+ return new Date(this.initedMillis);
+ }
+
+ /**
+ * Return the number of hits this object has handled.
+ */
+ public int getAccessCount() {
+ return this.accessCount;
+ }
+
+ /**
+ * Return the number of milliseconds since this object was loaded.
+ */
+ public long getUptimeMillis() {
+ return System.currentTimeMillis() - this.initedMillis;
+ }
+
+ /**
+ * Return the average response time achieved by this object.
+ */
+ public int getAverageResponseTimeMillis() {
+ int count = getAccessCount();
+ // avoid division by 0
+ return (count != 0 ? this.totalResponseTimeMillis / count : 0);
+ }
+
+ /**
+ * Return the best (lowest) response time achieved by this object.
+ */
+ public int getBestResponseTimeMillis() {
+ return this.bestResponseTimeMillis;
+ }
+
+ /**
+ * Return the worst (slowest) response time achieved by this object.
+ */
+ public int getWorstResponseTimeMillis() {
+ return this.worstResponseTimeMillis;
+ }
+
+
+ /**
+ * Utility method to record this response time, updating
+ * the best and worst response times if necessary.
+ * @param responseTimeMillis the response time of this request
+ */
+ public synchronized void recordResponseTime(long responseTimeMillis) {
+ ++this.accessCount;
+ int iResponseTime = (int) responseTimeMillis;
+ this.totalResponseTimeMillis += iResponseTime;
+ if (iResponseTime < this.bestResponseTimeMillis) {
+ this.bestResponseTimeMillis = iResponseTime;
+ }
+ if (iResponseTime > this.worstResponseTimeMillis) {
+ this.worstResponseTimeMillis = iResponseTime;
+ }
+ }
+
+ /**
+ * Return a human-readable string showing the performance
+ * data recorded by this object.
+ */
+ public synchronized String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("hits=[").append(getAccessCount()).append("]; ");
+ sb.append("average=[").append(getAverageResponseTimeMillis()).append("ms]; ");
+ sb.append("best=[").append(getBestResponseTimeMillis()).append("ms]; ");
+ sb.append("worst=[").append(getWorstResponseTimeMillis()).append("ms]");
+ return sb.toString();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/StopWatch.java b/org.springframework.core/src/main/java/org/springframework/util/StopWatch.java
new file mode 100644
index 00000000000..7fc0c9ab74c
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/StopWatch.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.text.NumberFormat;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Simple stop watch, allowing for timing of a number of tasks,
+ * exposing total running time and running time for each named task.
+ *
+ *
Conceals use of System.currentTimeMillis(), improving the
+ * readability of application code and reducing the likelihood of calculation errors.
+ *
+ *
Note that this object is not designed to be thread-safe and does not
+ * use synchronization.
+ *
+ *
This class is normally used to verify performance during proof-of-concepts
+ * and in development, rather than as part of production applications.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @since May 2, 2001
+ */
+public class StopWatch {
+
+ /**
+ * Identifier of this stop watch.
+ * Handy when we have output from multiple stop watches
+ * and need to distinguish between them in log or console output.
+ */
+ private final String id;
+
+ private boolean keepTaskList = true;
+
+ /** List of TaskInfo objects */
+ private final List taskList = new LinkedList();
+
+ /** Start time of the current task */
+ private long startTimeMillis;
+
+ /** Is the stop watch currently running? */
+ private boolean running;
+
+ /** Name of the current task */
+ private String currentTaskName;
+
+ private TaskInfo lastTaskInfo;
+
+ private int taskCount;
+
+ /** Total running time */
+ private long totalTimeMillis;
+
+
+ /**
+ * Construct a new stop watch. Does not start any task.
+ */
+ public StopWatch() {
+ this.id = "";
+ }
+
+ /**
+ * Construct a new stop watch with the given id.
+ * Does not start any task.
+ * @param id identifier for this stop watch.
+ * Handy when we have output from multiple stop watches
+ * and need to distinguish between them.
+ */
+ public StopWatch(String id) {
+ this.id = id;
+ }
+
+ /**
+ * Determine whether the TaskInfo array is built over time. Set this to
+ * "false" when using a StopWatch for millions of intervals, or the task
+ * info structure will consume excessive memory. Default is "true".
+ */
+ public void setKeepTaskList(boolean keepTaskList) {
+ this.keepTaskList = keepTaskList;
+ }
+
+
+ /**
+ * Start an unnamed task. The results are undefined if {@link #stop()}
+ * or timing methods are called without invoking this method.
+ * @see #stop()
+ */
+ public void start() throws IllegalStateException {
+ start("");
+ }
+
+ /**
+ * Start a named task. The results are undefined if {@link #stop()}
+ * or timing methods are called without invoking this method.
+ * @param taskName the name of the task to start
+ * @see #stop()
+ */
+ public void start(String taskName) throws IllegalStateException {
+ if (this.running) {
+ throw new IllegalStateException("Can't start StopWatch: it's already running");
+ }
+ this.startTimeMillis = System.currentTimeMillis();
+ this.running = true;
+ this.currentTaskName = taskName;
+ }
+
+ /**
+ * Stop the current task. The results are undefined if timing
+ * methods are called without invoking at least one pair
+ * {@link #start()} / {@link #stop()} methods.
+ * @see #start()
+ */
+ public void stop() throws IllegalStateException {
+ if (!this.running) {
+ throw new IllegalStateException("Can't stop StopWatch: it's not running");
+ }
+ long lastTime = System.currentTimeMillis() - this.startTimeMillis;
+ this.totalTimeMillis += lastTime;
+ this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
+ if (this.keepTaskList) {
+ this.taskList.add(lastTaskInfo);
+ }
+ ++this.taskCount;
+ this.running = false;
+ this.currentTaskName = null;
+ }
+
+ /**
+ * Return whether the stop watch is currently running.
+ */
+ public boolean isRunning() {
+ return this.running;
+ }
+
+
+ /**
+ * Return the time taken by the last task.
+ */
+ public long getLastTaskTimeMillis() throws IllegalStateException {
+ if (this.lastTaskInfo == null) {
+ throw new IllegalStateException("No tests run: can't get last interval");
+ }
+ return this.lastTaskInfo.getTimeMillis();
+ }
+
+ /**
+ * Return the total time in milliseconds for all tasks.
+ */
+ public long getTotalTimeMillis() {
+ return totalTimeMillis;
+ }
+
+ /**
+ * Return the total time in seconds for all tasks.
+ */
+ public double getTotalTimeSeconds() {
+ return totalTimeMillis / 1000.0;
+ }
+
+ /**
+ * Return the number of tasks timed.
+ */
+ public int getTaskCount() {
+ return taskCount;
+ }
+
+ /**
+ * Return an array of the data for tasks performed.
+ */
+ public TaskInfo[] getTaskInfo() {
+ if (!this.keepTaskList) {
+ throw new UnsupportedOperationException("Task info is not being kept!");
+ }
+ return (TaskInfo[]) this.taskList.toArray(new TaskInfo[this.taskList.size()]);
+ }
+
+
+ /**
+ * Return a short description of the total running time.
+ */
+ public String shortSummary() {
+ return "StopWatch '" + this.id + "': running time (millis) = " + getTotalTimeMillis();
+ }
+
+ /**
+ * Return a string with a table describing all tasks performed.
+ * For custom reporting, call getTaskInfo() and use the task info directly.
+ */
+ public String prettyPrint() {
+ StringBuffer sb = new StringBuffer(shortSummary());
+ sb.append('\n');
+ if (!this.keepTaskList) {
+ sb.append("No task info kept");
+ }
+ else {
+ TaskInfo[] tasks = getTaskInfo();
+ sb.append("-----------------------------------------\n");
+ sb.append("ms % Task name\n");
+ sb.append("-----------------------------------------\n");
+ NumberFormat nf = NumberFormat.getNumberInstance();
+ nf.setMinimumIntegerDigits(5);
+ nf.setGroupingUsed(false);
+ NumberFormat pf = NumberFormat.getPercentInstance();
+ pf.setMinimumIntegerDigits(3);
+ pf.setGroupingUsed(false);
+ for (int i = 0; i < tasks.length; i++) {
+ sb.append(nf.format(tasks[i].getTimeMillis()) + " ");
+ sb.append(pf.format(tasks[i].getTimeSeconds() / getTotalTimeSeconds()) + " ");
+ sb.append(tasks[i].getTaskName() + "\n");
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Return an informative string describing all tasks performed
+ * For custom reporting, call getTaskInfo() and use the task info directly.
+ */
+ public String toString() {
+ StringBuffer sb = new StringBuffer(shortSummary());
+ if (this.keepTaskList) {
+ TaskInfo[] tasks = getTaskInfo();
+ for (int i = 0; i < tasks.length; i++) {
+ sb.append("; [" + tasks[i].getTaskName() + "] took " + tasks[i].getTimeMillis());
+ long percent = Math.round((100.0 * tasks[i].getTimeSeconds()) / getTotalTimeSeconds());
+ sb.append(" = " + percent + "%");
+ }
+ }
+ else {
+ sb.append("; no task info kept");
+ }
+ return sb.toString();
+ }
+
+
+ /**
+ * Inner class to hold data about one task executed within the stop watch.
+ */
+ public static class TaskInfo {
+
+ private final String taskName;
+
+ private final long timeMillis;
+
+ private TaskInfo(String taskName, long timeMillis) {
+ this.taskName = taskName;
+ this.timeMillis = timeMillis;
+ }
+
+ /**
+ * Return the name of this task.
+ */
+ public String getTaskName() {
+ return taskName;
+ }
+
+ /**
+ * Return the time in milliseconds this task took.
+ */
+ public long getTimeMillis() {
+ return timeMillis;
+ }
+
+ /**
+ * Return the time in seconds this task took.
+ */
+ public double getTimeSeconds() {
+ return timeMillis / 1000.0;
+ }
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/StringUtils.java b/org.springframework.core/src/main/java/org/springframework/util/StringUtils.java
new file mode 100644
index 00000000000..832fba5a113
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/StringUtils.java
@@ -0,0 +1,1113 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+
+/**
+ * Miscellaneous {@link String} utility methods.
+ *
+ *
Mainly for internal use within the framework; consider
+ * Jakarta's Commons Lang
+ * for a more comprehensive suite of String utilities.
+ *
+ *
This class delivers some simple functionality that should really
+ * be provided by the core Java String and {@link StringBuffer}
+ * classes, such as the ability to {@link #replace} all occurrences of a given
+ * substring in a target string. It also provides easy-to-use methods to convert
+ * between delimited strings, such as CSV strings, and collections and arrays.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Keith Donald
+ * @author Rob Harrop
+ * @author Rick Evans
+ * @since 16 April 2001
+ * @see org.apache.commons.lang.StringUtils
+ */
+public abstract class StringUtils {
+
+ private static final String FOLDER_SEPARATOR = "/";
+
+ private static final String WINDOWS_FOLDER_SEPARATOR = "\\";
+
+ private static final String TOP_PATH = "..";
+
+ private static final String CURRENT_PATH = ".";
+
+ private static final char EXTENSION_SEPARATOR = '.';
+
+
+ //---------------------------------------------------------------------
+ // General convenience methods for working with Strings
+ //---------------------------------------------------------------------
+
+ /**
+ * Check that the given CharSequence is neither null nor of length 0.
+ * Note: Will return true for a CharSequence that purely consists of whitespace.
+ *
+ * StringUtils.hasLength(null) = false
+ * StringUtils.hasLength("") = false
+ * StringUtils.hasLength(" ") = true
+ * StringUtils.hasLength("Hello") = true
+ *
+ * @param str the CharSequence to check (may be null)
+ * @return true if the CharSequence is not null and has length
+ * @see #hasText(String)
+ */
+ public static boolean hasLength(CharSequence str) {
+ return (str != null && str.length() > 0);
+ }
+
+ /**
+ * Check that the given String is neither null nor of length 0.
+ * Note: Will return true for a String that purely consists of whitespace.
+ * @param str the String to check (may be null)
+ * @return true if the String is not null and has length
+ * @see #hasLength(CharSequence)
+ */
+ public static boolean hasLength(String str) {
+ return hasLength((CharSequence) str);
+ }
+
+ /**
+ * Check whether the given CharSequence has actual text.
+ * More specifically, returns true if the string not null,
+ * its length is greater than 0, and it contains at least one non-whitespace character.
+ *
+ * StringUtils.hasText(null) = false
+ * StringUtils.hasText("") = false
+ * StringUtils.hasText(" ") = false
+ * StringUtils.hasText("12345") = true
+ * StringUtils.hasText(" 12345 ") = true
+ *
+ * @param str the CharSequence to check (may be null)
+ * @return true if the CharSequence is not null,
+ * its length is greater than 0, and it does not contain whitespace only
+ * @see java.lang.Character#isWhitespace
+ */
+ public static boolean hasText(CharSequence str) {
+ if (!hasLength(str)) {
+ return false;
+ }
+ int strLen = str.length();
+ for (int i = 0; i < strLen; i++) {
+ if (!Character.isWhitespace(str.charAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the given String has actual text.
+ * More specifically, returns true if the string not null,
+ * its length is greater than 0, and it contains at least one non-whitespace character.
+ * @param str the String to check (may be null)
+ * @return true if the String is not null, its length is
+ * greater than 0, and it does not contain whitespace only
+ * @see #hasText(CharSequence)
+ */
+ public static boolean hasText(String str) {
+ return hasText((CharSequence) str);
+ }
+
+ /**
+ * Check whether the given CharSequence contains any whitespace characters.
+ * @param str the CharSequence to check (may be null)
+ * @return true if the CharSequence is not empty and
+ * contains at least 1 whitespace character
+ * @see java.lang.Character#isWhitespace
+ */
+ public static boolean containsWhitespace(CharSequence str) {
+ if (!hasLength(str)) {
+ return false;
+ }
+ int strLen = str.length();
+ for (int i = 0; i < strLen; i++) {
+ if (Character.isWhitespace(str.charAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the given String contains any whitespace characters.
+ * @param str the String to check (may be null)
+ * @return true if the String is not empty and
+ * contains at least 1 whitespace character
+ * @see #containsWhitespace(CharSequence)
+ */
+ public static boolean containsWhitespace(String str) {
+ return containsWhitespace((CharSequence) str);
+ }
+
+ /**
+ * Trim leading and trailing whitespace from the given String.
+ * @param str the String to check
+ * @return the trimmed String
+ * @see java.lang.Character#isWhitespace
+ */
+ public static String trimWhitespace(String str) {
+ if (!hasLength(str)) {
+ return str;
+ }
+ StringBuffer buf = new StringBuffer(str);
+ while (buf.length() > 0 && Character.isWhitespace(buf.charAt(0))) {
+ buf.deleteCharAt(0);
+ }
+ while (buf.length() > 0 && Character.isWhitespace(buf.charAt(buf.length() - 1))) {
+ buf.deleteCharAt(buf.length() - 1);
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Trim all whitespace from the given String:
+ * leading, trailing, and inbetween characters.
+ * @param str the String to check
+ * @return the trimmed String
+ * @see java.lang.Character#isWhitespace
+ */
+ public static String trimAllWhitespace(String str) {
+ if (!hasLength(str)) {
+ return str;
+ }
+ StringBuffer buf = new StringBuffer(str);
+ int index = 0;
+ while (buf.length() > index) {
+ if (Character.isWhitespace(buf.charAt(index))) {
+ buf.deleteCharAt(index);
+ }
+ else {
+ index++;
+ }
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Trim leading whitespace from the given String.
+ * @param str the String to check
+ * @return the trimmed String
+ * @see java.lang.Character#isWhitespace
+ */
+ public static String trimLeadingWhitespace(String str) {
+ if (!hasLength(str)) {
+ return str;
+ }
+ StringBuffer buf = new StringBuffer(str);
+ while (buf.length() > 0 && Character.isWhitespace(buf.charAt(0))) {
+ buf.deleteCharAt(0);
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Trim trailing whitespace from the given String.
+ * @param str the String to check
+ * @return the trimmed String
+ * @see java.lang.Character#isWhitespace
+ */
+ public static String trimTrailingWhitespace(String str) {
+ if (!hasLength(str)) {
+ return str;
+ }
+ StringBuffer buf = new StringBuffer(str);
+ while (buf.length() > 0 && Character.isWhitespace(buf.charAt(buf.length() - 1))) {
+ buf.deleteCharAt(buf.length() - 1);
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Trim all occurences of the supplied leading character from the given String.
+ * @param str the String to check
+ * @param leadingCharacter the leading character to be trimmed
+ * @return the trimmed String
+ */
+ public static String trimLeadingCharacter(String str, char leadingCharacter) {
+ if (!hasLength(str)) {
+ return str;
+ }
+ StringBuffer buf = new StringBuffer(str);
+ while (buf.length() > 0 && buf.charAt(0) == leadingCharacter) {
+ buf.deleteCharAt(0);
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Trim all occurences of the supplied trailing character from the given String.
+ * @param str the String to check
+ * @param trailingCharacter the trailing character to be trimmed
+ * @return the trimmed String
+ */
+ public static String trimTrailingCharacter(String str, char trailingCharacter) {
+ if (!hasLength(str)) {
+ return str;
+ }
+ StringBuffer buf = new StringBuffer(str);
+ while (buf.length() > 0 && buf.charAt(buf.length() - 1) == trailingCharacter) {
+ buf.deleteCharAt(buf.length() - 1);
+ }
+ return buf.toString();
+ }
+
+
+ /**
+ * Test if the given String starts with the specified prefix,
+ * ignoring upper/lower case.
+ * @param str the String to check
+ * @param prefix the prefix to look for
+ * @see java.lang.String#startsWith
+ */
+ public static boolean startsWithIgnoreCase(String str, String prefix) {
+ if (str == null || prefix == null) {
+ return false;
+ }
+ if (str.startsWith(prefix)) {
+ return true;
+ }
+ if (str.length() < prefix.length()) {
+ return false;
+ }
+ String lcStr = str.substring(0, prefix.length()).toLowerCase();
+ String lcPrefix = prefix.toLowerCase();
+ return lcStr.equals(lcPrefix);
+ }
+
+ /**
+ * Test if the given String ends with the specified suffix,
+ * ignoring upper/lower case.
+ * @param str the String to check
+ * @param suffix the suffix to look for
+ * @see java.lang.String#endsWith
+ */
+ public static boolean endsWithIgnoreCase(String str, String suffix) {
+ if (str == null || suffix == null) {
+ return false;
+ }
+ if (str.endsWith(suffix)) {
+ return true;
+ }
+ if (str.length() < suffix.length()) {
+ return false;
+ }
+
+ String lcStr = str.substring(str.length() - suffix.length()).toLowerCase();
+ String lcSuffix = suffix.toLowerCase();
+ return lcStr.equals(lcSuffix);
+ }
+
+ /**
+ * Test whether the given string matches the given substring
+ * at the given index.
+ * @param str the original string (or StringBuffer)
+ * @param index the index in the original string to start matching against
+ * @param substring the substring to match at the given index
+ */
+ public static boolean substringMatch(CharSequence str, int index, CharSequence substring) {
+ for (int j = 0; j < substring.length(); j++) {
+ int i = index + j;
+ if (i >= str.length() || str.charAt(i) != substring.charAt(j)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Count the occurrences of the substring in string s.
+ * @param str string to search in. Return 0 if this is null.
+ * @param sub string to search for. Return 0 if this is null.
+ */
+ public static int countOccurrencesOf(String str, String sub) {
+ if (str == null || sub == null || str.length() == 0 || sub.length() == 0) {
+ return 0;
+ }
+ int count = 0, pos = 0, idx = 0;
+ while ((idx = str.indexOf(sub, pos)) != -1) {
+ ++count;
+ pos = idx + sub.length();
+ }
+ return count;
+ }
+
+ /**
+ * Replace all occurences of a substring within a string with
+ * another string.
+ * @param inString String to examine
+ * @param oldPattern String to replace
+ * @param newPattern String to insert
+ * @return a String with the replacements
+ */
+ public static String replace(String inString, String oldPattern, String newPattern) {
+ if (!hasLength(inString) || !hasLength(oldPattern) || newPattern == null) {
+ return inString;
+ }
+ StringBuffer sbuf = new StringBuffer();
+ // output StringBuffer we'll build up
+ int pos = 0; // our position in the old string
+ int index = inString.indexOf(oldPattern);
+ // the index of an occurrence we've found, or -1
+ int patLen = oldPattern.length();
+ while (index >= 0) {
+ sbuf.append(inString.substring(pos, index));
+ sbuf.append(newPattern);
+ pos = index + patLen;
+ index = inString.indexOf(oldPattern, pos);
+ }
+ sbuf.append(inString.substring(pos));
+ // remember to append any characters to the right of a match
+ return sbuf.toString();
+ }
+
+ /**
+ * Delete all occurrences of the given substring.
+ * @param inString the original String
+ * @param pattern the pattern to delete all occurrences of
+ * @return the resulting String
+ */
+ public static String delete(String inString, String pattern) {
+ return replace(inString, pattern, "");
+ }
+
+ /**
+ * Delete any character in a given String.
+ * @param inString the original String
+ * @param charsToDelete a set of characters to delete.
+ * E.g. "az\n" will delete 'a's, 'z's and new lines.
+ * @return the resulting String
+ */
+ public static String deleteAny(String inString, String charsToDelete) {
+ if (!hasLength(inString) || !hasLength(charsToDelete)) {
+ return inString;
+ }
+ StringBuffer out = new StringBuffer();
+ for (int i = 0; i < inString.length(); i++) {
+ char c = inString.charAt(i);
+ if (charsToDelete.indexOf(c) == -1) {
+ out.append(c);
+ }
+ }
+ return out.toString();
+ }
+
+
+ //---------------------------------------------------------------------
+ // Convenience methods for working with formatted Strings
+ //---------------------------------------------------------------------
+
+ /**
+ * Quote the given String with single quotes.
+ * @param str the input String (e.g. "myString")
+ * @return the quoted String (e.g. "'myString'"),
+ * or null if the input was null
+ */
+ public static String quote(String str) {
+ return (str != null ? "'" + str + "'" : null);
+ }
+
+ /**
+ * Turn the given Object into a String with single quotes
+ * if it is a String; keeping the Object as-is else.
+ * @param obj the input Object (e.g. "myString")
+ * @return the quoted String (e.g. "'myString'"),
+ * or the input object as-is if not a String
+ */
+ public static Object quoteIfString(Object obj) {
+ return (obj instanceof String ? quote((String) obj) : obj);
+ }
+
+ /**
+ * Unqualify a string qualified by a '.' dot character. For example,
+ * "this.name.is.qualified", returns "qualified".
+ * @param qualifiedName the qualified name
+ */
+ public static String unqualify(String qualifiedName) {
+ return unqualify(qualifiedName, '.');
+ }
+
+ /**
+ * Unqualify a string qualified by a separator character. For example,
+ * "this:name:is:qualified" returns "qualified" if using a ':' separator.
+ * @param qualifiedName the qualified name
+ * @param separator the separator
+ */
+ public static String unqualify(String qualifiedName, char separator) {
+ return qualifiedName.substring(qualifiedName.lastIndexOf(separator) + 1);
+ }
+
+ /**
+ * Capitalize a String, changing the first letter to
+ * upper case as per {@link Character#toUpperCase(char)}.
+ * No other letters are changed.
+ * @param str the String to capitalize, may be null
+ * @return the capitalized String, null if null
+ */
+ public static String capitalize(String str) {
+ return changeFirstCharacterCase(str, true);
+ }
+
+ /**
+ * Uncapitalize a String, changing the first letter to
+ * lower case as per {@link Character#toLowerCase(char)}.
+ * No other letters are changed.
+ * @param str the String to uncapitalize, may be null
+ * @return the uncapitalized String, null if null
+ */
+ public static String uncapitalize(String str) {
+ return changeFirstCharacterCase(str, false);
+ }
+
+ private static String changeFirstCharacterCase(String str, boolean capitalize) {
+ if (str == null || str.length() == 0) {
+ return str;
+ }
+ StringBuffer buf = new StringBuffer(str.length());
+ if (capitalize) {
+ buf.append(Character.toUpperCase(str.charAt(0)));
+ }
+ else {
+ buf.append(Character.toLowerCase(str.charAt(0)));
+ }
+ buf.append(str.substring(1));
+ return buf.toString();
+ }
+
+ /**
+ * Extract the filename from the given path,
+ * e.g. "mypath/myfile.txt" -> "myfile.txt".
+ * @param path the file path (may be null)
+ * @return the extracted filename, or null if none
+ */
+ public static String getFilename(String path) {
+ if (path == null) {
+ return null;
+ }
+ int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR);
+ return (separatorIndex != -1 ? path.substring(separatorIndex + 1) : path);
+ }
+
+ /**
+ * Extract the filename extension from the given path,
+ * e.g. "mypath/myfile.txt" -> "txt".
+ * @param path the file path (may be null)
+ * @return the extracted filename extension, or null if none
+ */
+ public static String getFilenameExtension(String path) {
+ if (path == null) {
+ return null;
+ }
+ int sepIndex = path.lastIndexOf(EXTENSION_SEPARATOR);
+ return (sepIndex != -1 ? path.substring(sepIndex + 1) : null);
+ }
+
+ /**
+ * Strip the filename extension from the given path,
+ * e.g. "mypath/myfile.txt" -> "mypath/myfile".
+ * @param path the file path (may be null)
+ * @return the path with stripped filename extension,
+ * or null if none
+ */
+ public static String stripFilenameExtension(String path) {
+ if (path == null) {
+ return null;
+ }
+ int sepIndex = path.lastIndexOf(EXTENSION_SEPARATOR);
+ return (sepIndex != -1 ? path.substring(0, sepIndex) : path);
+ }
+
+ /**
+ * Apply the given relative path to the given path,
+ * assuming standard Java folder separation (i.e. "/" separators);
+ * @param path the path to start from (usually a full file path)
+ * @param relativePath the relative path to apply
+ * (relative to the full file path above)
+ * @return the full file path that results from applying the relative path
+ */
+ public static String applyRelativePath(String path, String relativePath) {
+ int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR);
+ if (separatorIndex != -1) {
+ String newPath = path.substring(0, separatorIndex);
+ if (!relativePath.startsWith(FOLDER_SEPARATOR)) {
+ newPath += FOLDER_SEPARATOR;
+ }
+ return newPath + relativePath;
+ }
+ else {
+ return relativePath;
+ }
+ }
+
+ /**
+ * Normalize the path by suppressing sequences like "path/.." and
+ * inner simple dots.
+ * The result is convenient for path comparison. For other uses,
+ * notice that Windows separators ("\") are replaced by simple slashes.
+ * @param path the original path
+ * @return the normalized path
+ */
+ public static String cleanPath(String path) {
+ if (path == null) {
+ return null;
+ }
+ String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR);
+
+ // Strip prefix from path to analyze, to not treat it as part of the
+ // first path element. This is necessary to correctly parse paths like
+ // "file:core/../core/io/Resource.class", where the ".." should just
+ // strip the first "core" directory while keeping the "file:" prefix.
+ int prefixIndex = pathToUse.indexOf(":");
+ String prefix = "";
+ if (prefixIndex != -1) {
+ prefix = pathToUse.substring(0, prefixIndex + 1);
+ pathToUse = pathToUse.substring(prefixIndex + 1);
+ }
+ if (pathToUse.startsWith(FOLDER_SEPARATOR)) {
+ prefix = prefix + FOLDER_SEPARATOR;
+ pathToUse = pathToUse.substring(1);
+ }
+
+ String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR);
+ List pathElements = new LinkedList();
+ int tops = 0;
+
+ for (int i = pathArray.length - 1; i >= 0; i--) {
+ String element = pathArray[i];
+ if (CURRENT_PATH.equals(element)) {
+ // Points to current directory - drop it.
+ }
+ else if (TOP_PATH.equals(element)) {
+ // Registering top path found.
+ tops++;
+ }
+ else {
+ if (tops > 0) {
+ // Merging path element with element corresponding to top path.
+ tops--;
+ }
+ else {
+ // Normal path element found.
+ pathElements.add(0, element);
+ }
+ }
+ }
+
+ // Remaining top paths need to be retained.
+ for (int i = 0; i < tops; i++) {
+ pathElements.add(0, TOP_PATH);
+ }
+
+ return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR);
+ }
+
+ /**
+ * Compare two paths after normalization of them.
+ * @param path1 first path for comparison
+ * @param path2 second path for comparison
+ * @return whether the two paths are equivalent after normalization
+ */
+ public static boolean pathEquals(String path1, String path2) {
+ return cleanPath(path1).equals(cleanPath(path2));
+ }
+
+ /**
+ * Parse the given localeString into a {@link Locale}.
+ *
This is the inverse operation of {@link Locale#toString Locale's toString}.
+ * @param localeString the locale string, following Locale's
+ * toString() format ("en", "en_UK", etc);
+ * also accepts spaces as separators, as an alternative to underscores
+ * @return a corresponding Locale instance
+ */
+ public static Locale parseLocaleString(String localeString) {
+ String[] parts = tokenizeToStringArray(localeString, "_ ", false, false);
+ String language = (parts.length > 0 ? parts[0] : "");
+ String country = (parts.length > 1 ? parts[1] : "");
+ String variant = "";
+ if (parts.length >= 2) {
+ // There is definitely a variant, and it is everything after the country
+ // code sans the separator between the country code and the variant.
+ int endIndexOfCountryCode = localeString.indexOf(country) + country.length();
+ // Strip off any leading '_' and whitespace, what's left is the variant.
+ variant = trimLeadingWhitespace(localeString.substring(endIndexOfCountryCode));
+ if (variant.startsWith("_")) {
+ variant = trimLeadingCharacter(variant, '_');
+ }
+ }
+ return (language.length() > 0 ? new Locale(language, country, variant) : null);
+ }
+
+ /**
+ * Determine the RFC 3066 compliant language tag,
+ * as used for the HTTP "Accept-Language" header.
+ * @param locale the Locale to transform to a language tag
+ * @return the RFC 3066 compliant language tag as String
+ */
+ public static String toLanguageTag(Locale locale) {
+ return locale.getLanguage() + (hasText(locale.getCountry()) ? "-" + locale.getCountry() : "");
+ }
+
+
+ //---------------------------------------------------------------------
+ // Convenience methods for working with String arrays
+ //---------------------------------------------------------------------
+
+ /**
+ * Append the given String to the given String array, returning a new array
+ * consisting of the input array contents plus the given String.
+ * @param array the array to append to (can be null)
+ * @param str the String to append
+ * @return the new array (never null)
+ */
+ public static String[] addStringToArray(String[] array, String str) {
+ if (ObjectUtils.isEmpty(array)) {
+ return new String[] {str};
+ }
+ String[] newArr = new String[array.length + 1];
+ System.arraycopy(array, 0, newArr, 0, array.length);
+ newArr[array.length] = str;
+ return newArr;
+ }
+
+ /**
+ * Concatenate the given String arrays into one,
+ * with overlapping array elements included twice.
+ *
The order of elements in the original arrays is preserved.
+ * @param array1 the first array (can be null)
+ * @param array2 the second array (can be null)
+ * @return the new array (null if both given arrays were null)
+ */
+ public static String[] concatenateStringArrays(String[] array1, String[] array2) {
+ if (ObjectUtils.isEmpty(array1)) {
+ return array2;
+ }
+ if (ObjectUtils.isEmpty(array2)) {
+ return array1;
+ }
+ String[] newArr = new String[array1.length + array2.length];
+ System.arraycopy(array1, 0, newArr, 0, array1.length);
+ System.arraycopy(array2, 0, newArr, array1.length, array2.length);
+ return newArr;
+ }
+
+ /**
+ * Merge the given String arrays into one, with overlapping
+ * array elements only included once.
+ *
The order of elements in the original arrays is preserved
+ * (with the exception of overlapping elements, which are only
+ * included on their first occurence).
+ * @param array1 the first array (can be null)
+ * @param array2 the second array (can be null)
+ * @return the new array (null if both given arrays were null)
+ */
+ public static String[] mergeStringArrays(String[] array1, String[] array2) {
+ if (ObjectUtils.isEmpty(array1)) {
+ return array2;
+ }
+ if (ObjectUtils.isEmpty(array2)) {
+ return array1;
+ }
+ List result = new ArrayList();
+ result.addAll(Arrays.asList(array1));
+ for (int i = 0; i < array2.length; i++) {
+ String str = array2[i];
+ if (!result.contains(str)) {
+ result.add(str);
+ }
+ }
+ return toStringArray(result);
+ }
+
+ /**
+ * Turn given source String array into sorted array.
+ * @param array the source array
+ * @return the sorted array (never null)
+ */
+ public static String[] sortStringArray(String[] array) {
+ if (ObjectUtils.isEmpty(array)) {
+ return new String[0];
+ }
+ Arrays.sort(array);
+ return array;
+ }
+
+ /**
+ * Copy the given Collection into a String array.
+ * The Collection must contain String elements only.
+ * @param collection the Collection to copy
+ * @return the String array (null if the passed-in
+ * Collection was null)
+ */
+ public static String[] toStringArray(Collection collection) {
+ if (collection == null) {
+ return null;
+ }
+ return (String[]) collection.toArray(new String[collection.size()]);
+ }
+
+ /**
+ * Copy the given Enumeration into a String array.
+ * The Enumeration must contain String elements only.
+ * @param enumeration the Enumeration to copy
+ * @return the String array (null if the passed-in
+ * Enumeration was null)
+ */
+ public static String[] toStringArray(Enumeration enumeration) {
+ if (enumeration == null) {
+ return null;
+ }
+ List list = Collections.list(enumeration);
+ return (String[]) list.toArray(new String[list.size()]);
+ }
+
+ /**
+ * Trim the elements of the given String array,
+ * calling String.trim() on each of them.
+ * @param array the original String array
+ * @return the resulting array (of the same size) with trimmed elements
+ */
+ public static String[] trimArrayElements(String[] array) {
+ if (ObjectUtils.isEmpty(array)) {
+ return new String[0];
+ }
+ String[] result = new String[array.length];
+ for (int i = 0; i < array.length; i++) {
+ String element = array[i];
+ result[i] = (element != null ? element.trim() : null);
+ }
+ return result;
+ }
+
+ /**
+ * Remove duplicate Strings from the given array.
+ * Also sorts the array, as it uses a TreeSet.
+ * @param array the String array
+ * @return an array without duplicates, in natural sort order
+ */
+ public static String[] removeDuplicateStrings(String[] array) {
+ if (ObjectUtils.isEmpty(array)) {
+ return array;
+ }
+ Set set = new TreeSet();
+ for (int i = 0; i < array.length; i++) {
+ set.add(array[i]);
+ }
+ return toStringArray(set);
+ }
+
+ /**
+ * Split a String at the first occurrence of the delimiter.
+ * Does not include the delimiter in the result.
+ * @param toSplit the string to split
+ * @param delimiter to split the string up with
+ * @return a two element array with index 0 being before the delimiter, and
+ * index 1 being after the delimiter (neither element includes the delimiter);
+ * or null if the delimiter wasn't found in the given input String
+ */
+ public static String[] split(String toSplit, String delimiter) {
+ if (!hasLength(toSplit) || !hasLength(delimiter)) {
+ return null;
+ }
+ int offset = toSplit.indexOf(delimiter);
+ if (offset < 0) {
+ return null;
+ }
+ String beforeDelimiter = toSplit.substring(0, offset);
+ String afterDelimiter = toSplit.substring(offset + delimiter.length());
+ return new String[] {beforeDelimiter, afterDelimiter};
+ }
+
+ /**
+ * Take an array Strings and split each element based on the given delimiter.
+ * A Properties instance is then generated, with the left of the
+ * delimiter providing the key, and the right of the delimiter providing the value.
+ *
Will trim both the key and value before adding them to the
+ * Properties instance.
+ * @param array the array to process
+ * @param delimiter to split each element using (typically the equals symbol)
+ * @return a Properties instance representing the array contents,
+ * or null if the array to process was null or empty
+ */
+ public static Properties splitArrayElementsIntoProperties(String[] array, String delimiter) {
+ return splitArrayElementsIntoProperties(array, delimiter, null);
+ }
+
+ /**
+ * Take an array Strings and split each element based on the given delimiter.
+ * A Properties instance is then generated, with the left of the
+ * delimiter providing the key, and the right of the delimiter providing the value.
+ *
Will trim both the key and value before adding them to the
+ * Properties instance.
+ * @param array the array to process
+ * @param delimiter to split each element using (typically the equals symbol)
+ * @param charsToDelete one or more characters to remove from each element
+ * prior to attempting the split operation (typically the quotation mark
+ * symbol), or null if no removal should occur
+ * @return a Properties instance representing the array contents,
+ * or null if the array to process was null or empty
+ */
+ public static Properties splitArrayElementsIntoProperties(
+ String[] array, String delimiter, String charsToDelete) {
+
+ if (ObjectUtils.isEmpty(array)) {
+ return null;
+ }
+ Properties result = new Properties();
+ for (int i = 0; i < array.length; i++) {
+ String element = array[i];
+ if (charsToDelete != null) {
+ element = deleteAny(array[i], charsToDelete);
+ }
+ String[] splittedElement = split(element, delimiter);
+ if (splittedElement == null) {
+ continue;
+ }
+ result.setProperty(splittedElement[0].trim(), splittedElement[1].trim());
+ }
+ return result;
+ }
+
+ /**
+ * Tokenize the given String into a String array via a StringTokenizer.
+ * Trims tokens and omits empty tokens.
+ *
The given delimiters string is supposed to consist of any number of
+ * delimiter characters. Each of those characters can be used to separate
+ * tokens. A delimiter is always a single character; for multi-character
+ * delimiters, consider using delimitedListToStringArray
+ * @param str the String to tokenize
+ * @param delimiters the delimiter characters, assembled as String
+ * (each of those characters is individually considered as delimiter).
+ * @return an array of the tokens
+ * @see java.util.StringTokenizer
+ * @see java.lang.String#trim()
+ * @see #delimitedListToStringArray
+ */
+ public static String[] tokenizeToStringArray(String str, String delimiters) {
+ return tokenizeToStringArray(str, delimiters, true, true);
+ }
+
+ /**
+ * Tokenize the given String into a String array via a StringTokenizer.
+ *
The given delimiters string is supposed to consist of any number of
+ * delimiter characters. Each of those characters can be used to separate
+ * tokens. A delimiter is always a single character; for multi-character
+ * delimiters, consider using delimitedListToStringArray
+ * @param str the String to tokenize
+ * @param delimiters the delimiter characters, assembled as String
+ * (each of those characters is individually considered as delimiter)
+ * @param trimTokens trim the tokens via String's trim
+ * @param ignoreEmptyTokens omit empty tokens from the result array
+ * (only applies to tokens that are empty after trimming; StringTokenizer
+ * will not consider subsequent delimiters as token in the first place).
+ * @return an array of the tokens (null if the input String
+ * was null)
+ * @see java.util.StringTokenizer
+ * @see java.lang.String#trim()
+ * @see #delimitedListToStringArray
+ */
+ public static String[] tokenizeToStringArray(
+ String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {
+
+ if (str == null) {
+ return null;
+ }
+ StringTokenizer st = new StringTokenizer(str, delimiters);
+ List tokens = new ArrayList();
+ while (st.hasMoreTokens()) {
+ String token = st.nextToken();
+ if (trimTokens) {
+ token = token.trim();
+ }
+ if (!ignoreEmptyTokens || token.length() > 0) {
+ tokens.add(token);
+ }
+ }
+ return toStringArray(tokens);
+ }
+
+ /**
+ * Take a String which is a delimited list and convert it to a String array.
+ *
A single delimiter can consists of more than one character: It will still
+ * be considered as single delimiter string, rather than as bunch of potential
+ * delimiter characters - in contrast to tokenizeToStringArray.
+ * @param str the input String
+ * @param delimiter the delimiter between elements (this is a single delimiter,
+ * rather than a bunch individual delimiter characters)
+ * @return an array of the tokens in the list
+ * @see #tokenizeToStringArray
+ */
+ public static String[] delimitedListToStringArray(String str, String delimiter) {
+ return delimitedListToStringArray(str, delimiter, null);
+ }
+
+ /**
+ * Take a String which is a delimited list and convert it to a String array.
+ *
A single delimiter can consists of more than one character: It will still
+ * be considered as single delimiter string, rather than as bunch of potential
+ * delimiter characters - in contrast to tokenizeToStringArray.
+ * @param str the input String
+ * @param delimiter the delimiter between elements (this is a single delimiter,
+ * rather than a bunch individual delimiter characters)
+ * @param charsToDelete a set of characters to delete. Useful for deleting unwanted
+ * line breaks: e.g. "\r\n\f" will delete all new lines and line feeds in a String.
+ * @return an array of the tokens in the list
+ * @see #tokenizeToStringArray
+ */
+ public static String[] delimitedListToStringArray(String str, String delimiter, String charsToDelete) {
+ if (str == null) {
+ return new String[0];
+ }
+ if (delimiter == null) {
+ return new String[] {str};
+ }
+ List result = new ArrayList();
+ if ("".equals(delimiter)) {
+ for (int i = 0; i < str.length(); i++) {
+ result.add(deleteAny(str.substring(i, i + 1), charsToDelete));
+ }
+ }
+ else {
+ int pos = 0;
+ int delPos = 0;
+ while ((delPos = str.indexOf(delimiter, pos)) != -1) {
+ result.add(deleteAny(str.substring(pos, delPos), charsToDelete));
+ pos = delPos + delimiter.length();
+ }
+ if (str.length() > 0 && pos <= str.length()) {
+ // Add rest of String, but not in case of empty input.
+ result.add(deleteAny(str.substring(pos), charsToDelete));
+ }
+ }
+ return toStringArray(result);
+ }
+
+ /**
+ * Convert a CSV list into an array of Strings.
+ * @param str the input String
+ * @return an array of Strings, or the empty array in case of empty input
+ */
+ public static String[] commaDelimitedListToStringArray(String str) {
+ return delimitedListToStringArray(str, ",");
+ }
+
+ /**
+ * Convenience method to convert a CSV string list to a set.
+ * Note that this will suppress duplicates.
+ * @param str the input String
+ * @return a Set of String entries in the list
+ */
+ public static Set commaDelimitedListToSet(String str) {
+ Set set = new TreeSet();
+ String[] tokens = commaDelimitedListToStringArray(str);
+ for (int i = 0; i < tokens.length; i++) {
+ set.add(tokens[i]);
+ }
+ return set;
+ }
+
+ /**
+ * Convenience method to return a Collection as a delimited (e.g. CSV)
+ * String. E.g. useful for toString() implementations.
+ * @param coll the Collection to display
+ * @param delim the delimiter to use (probably a ",")
+ * @param prefix the String to start each element with
+ * @param suffix the String to end each element with
+ * @return the delimited String
+ */
+ public static String collectionToDelimitedString(Collection coll, String delim, String prefix, String suffix) {
+ if (CollectionUtils.isEmpty(coll)) {
+ return "";
+ }
+ StringBuffer sb = new StringBuffer();
+ Iterator it = coll.iterator();
+ while (it.hasNext()) {
+ sb.append(prefix).append(it.next()).append(suffix);
+ if (it.hasNext()) {
+ sb.append(delim);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Convenience method to return a Collection as a delimited (e.g. CSV)
+ * String. E.g. useful for toString() implementations.
+ * @param coll the Collection to display
+ * @param delim the delimiter to use (probably a ",")
+ * @return the delimited String
+ */
+ public static String collectionToDelimitedString(Collection coll, String delim) {
+ return collectionToDelimitedString(coll, delim, "", "");
+ }
+
+ /**
+ * Convenience method to return a Collection as a CSV String.
+ * E.g. useful for toString() implementations.
+ * @param coll the Collection to display
+ * @return the delimited String
+ */
+ public static String collectionToCommaDelimitedString(Collection coll) {
+ return collectionToDelimitedString(coll, ",");
+ }
+
+ /**
+ * Convenience method to return a String array as a delimited (e.g. CSV)
+ * String. E.g. useful for toString() implementations.
+ * @param arr the array to display
+ * @param delim the delimiter to use (probably a ",")
+ * @return the delimited String
+ */
+ public static String arrayToDelimitedString(Object[] arr, String delim) {
+ if (ObjectUtils.isEmpty(arr)) {
+ return "";
+ }
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < arr.length; i++) {
+ if (i > 0) {
+ sb.append(delim);
+ }
+ sb.append(arr[i]);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Convenience method to return a String array as a CSV String.
+ * E.g. useful for toString() implementations.
+ * @param arr the array to display
+ * @return the delimited String
+ */
+ public static String arrayToCommaDelimitedString(Object[] arr) {
+ return arrayToDelimitedString(arr, ",");
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/StringValueResolver.java b/org.springframework.core/src/main/java/org/springframework/util/StringValueResolver.java
new file mode 100644
index 00000000000..acc77c1e645
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/StringValueResolver.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.util;
+
+/**
+ * Simple strategy interface for resolving a String value.
+ * Used by {@link org.springframework.beans.factory.config.ConfigurableBeanFactory}.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#resolveAliases
+ * @see org.springframework.beans.factory.config.BeanDefinitionVisitor#BeanDefinitionVisitor(StringValueResolver)
+ * @see org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
+ */
+public interface StringValueResolver {
+
+ /**
+ * Resolve the given String value, for example parsing placeholders.
+ * @param strVal the original String value
+ * @return the resolved String value
+ */
+ String resolveStringValue(String strVal);
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/SystemPropertyUtils.java b/org.springframework.core/src/main/java/org/springframework/util/SystemPropertyUtils.java
new file mode 100644
index 00000000000..ab61950e966
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/SystemPropertyUtils.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util;
+
+/**
+ * Helper class for resolving placeholders in texts. Usually applied to file paths.
+ *
+ *
A text may contain ${...} placeholders, to be resolved as
+ * system properties: e.g. ${user.dir}.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2.5
+ * @see #PLACEHOLDER_PREFIX
+ * @see #PLACEHOLDER_SUFFIX
+ * @see System#getProperty(String)
+ */
+public abstract class SystemPropertyUtils {
+
+ /** Prefix for system property placeholders: "${" */
+ public static final String PLACEHOLDER_PREFIX = "${";
+
+ /** Suffix for system property placeholders: "}" */
+ public static final String PLACEHOLDER_SUFFIX = "}";
+
+
+ /**
+ * Resolve ${...} placeholders in the given text,
+ * replacing them with corresponding system property values.
+ * @param text the String to resolve
+ * @return the resolved String
+ * @see #PLACEHOLDER_PREFIX
+ * @see #PLACEHOLDER_SUFFIX
+ */
+ public static String resolvePlaceholders(String text) {
+ StringBuffer buf = new StringBuffer(text);
+
+ int startIndex = buf.indexOf(PLACEHOLDER_PREFIX);
+ while (startIndex != -1) {
+ int endIndex = buf.indexOf(PLACEHOLDER_SUFFIX, startIndex + PLACEHOLDER_PREFIX.length());
+ if (endIndex != -1) {
+ String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
+ int nextIndex = endIndex + PLACEHOLDER_SUFFIX.length();
+ try {
+ String propVal = System.getProperty(placeholder);
+ if (propVal == null) {
+ // Fall back to searching the system environment.
+ propVal = System.getenv(placeholder);
+ }
+ if (propVal != null) {
+ buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), propVal);
+ nextIndex = startIndex + propVal.length();
+ }
+ else {
+ System.err.println("Could not resolve placeholder '" + placeholder + "' in [" + text +
+ "] as system property: neither system property nor environment variable found");
+ }
+ }
+ catch (Throwable ex) {
+ System.err.println("Could not resolve placeholder '" + placeholder + "' in [" + text +
+ "] as system property: " + ex);
+ }
+ startIndex = buf.indexOf(PLACEHOLDER_PREFIX, nextIndex);
+ }
+ else {
+ startIndex = -1;
+ }
+ }
+
+ return buf.toString();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/TypeUtils.java b/org.springframework.core/src/main/java/org/springframework/util/TypeUtils.java
new file mode 100644
index 00000000000..a4f0742e803
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/TypeUtils.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
+
+/**
+ * Utility to work with Java 5 generic type parameters.
+ * Mainly for internal use within the framework.
+ *
+ * @author Ramnivas Laddad
+ * @author Juergen Hoeller
+ * @since 2.0.7
+ */
+public abstract class TypeUtils {
+
+ /**
+ * Check if the right-hand side type may be assigned to the left-hand side
+ * type following the Java generics rules.
+ * @param lhsType the target type
+ * @param rhsType the value type that should be assigned to the target type
+ * @return true if rhs is assignable to lhs
+ */
+ public static boolean isAssignable(Type lhsType, Type rhsType) {
+ Assert.notNull(lhsType, "Left-hand side type must not be null");
+ Assert.notNull(rhsType, "Right-hand side type must not be null");
+ if (lhsType.equals(rhsType)) {
+ return true;
+ }
+ if (lhsType instanceof Class && rhsType instanceof Class) {
+ return ClassUtils.isAssignable((Class) lhsType, (Class) rhsType);
+ }
+ if (lhsType instanceof ParameterizedType && rhsType instanceof ParameterizedType) {
+ return isAssignable((ParameterizedType) lhsType, (ParameterizedType) rhsType);
+ }
+ if (lhsType instanceof WildcardType) {
+ return isAssignable((WildcardType) lhsType, rhsType);
+ }
+ return false;
+ }
+
+ private static boolean isAssignable(ParameterizedType lhsType, ParameterizedType rhsType) {
+ if (lhsType.equals(rhsType)) {
+ return true;
+ }
+ Type[] lhsTypeArguments = lhsType.getActualTypeArguments();
+ Type[] rhsTypeArguments = rhsType.getActualTypeArguments();
+ if (lhsTypeArguments.length != rhsTypeArguments.length) {
+ return false;
+ }
+ for (int size = lhsTypeArguments.length, i = 0; i < size; ++i) {
+ Type lhsArg = lhsTypeArguments[i];
+ Type rhsArg = rhsTypeArguments[i];
+ if (!lhsArg.equals(rhsArg) &&
+ !(lhsArg instanceof WildcardType && isAssignable((WildcardType) lhsArg, rhsArg))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean isAssignable(WildcardType lhsType, Type rhsType) {
+ Type[] upperBounds = lhsType.getUpperBounds();
+ Type[] lowerBounds = lhsType.getLowerBounds();
+ for (int size = upperBounds.length, i = 0; i < size; ++i) {
+ if (!isAssignable(upperBounds[i], rhsType)) {
+ return false;
+ }
+ }
+ for (int size = lowerBounds.length, i = 0; i < size; ++i) {
+ if (!isAssignable(rhsType, lowerBounds[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/WeakReferenceMonitor.java b/org.springframework.core/src/main/java/org/springframework/util/WeakReferenceMonitor.java
new file mode 100644
index 00000000000..57aeae99a0e
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/WeakReferenceMonitor.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.util;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Track references to arbitrary objects using proxy and weak references. To
+ * monitor a handle, one should call {@link #monitor(Object, ReleaseListener)},
+ * with the given handle object usually being a holder that uses the target
+ * object underneath, and the release listener performing cleanup of the
+ * target object once the handle is not strongly referenced anymore.
+ *
+ *
When a given handle becomes weakly reachable, the specified listener
+ * will be called by a background thread. This thread will only be started
+ * lazily and will be stopped once no handles are registered for monitoring
+ * anymore, to be restarted if further handles are added.
+ *
+ *
Thanks to Tomasz Wysocki for the suggestion and the original
+ * implementation of this class!
+ *
+ * @author Colin Sampaleanu
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see #monitor
+ */
+public class WeakReferenceMonitor {
+
+ private static final Log logger = LogFactory.getLog(WeakReferenceMonitor.class);
+
+ // Queue receiving reachability events
+ private static final ReferenceQueue handleQueue = new ReferenceQueue();
+
+ // All tracked entries (WeakReference => ReleaseListener)
+ private static final Map trackedEntries = Collections.synchronizedMap(new HashMap());
+
+ // Thread polling handleQueue, lazy initialized
+ private static Thread monitoringThread = null;
+
+
+ /**
+ * Start to monitor given handle object for becoming weakly reachable.
+ * When the handle isn't used anymore, the given listener will be called.
+ * @param handle the object that will be monitored
+ * @param listener the listener that will be called upon release of the handle
+ */
+ public static void monitor(Object handle, ReleaseListener listener) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Monitoring handle [" + handle + "] with release listener [" + listener + "]");
+ }
+
+ // Make weak reference to this handle, so we can say when
+ // handle is not used any more by polling on handleQueue.
+ WeakReference weakRef = new WeakReference(handle, handleQueue);
+
+ // Add monitored entry to internal map of all monitored entries.
+ addEntry(weakRef, listener);
+ }
+
+ /**
+ * Add entry to internal map of tracked entries.
+ * Internal monitoring thread is started if not already running.
+ * @param ref reference to tracked handle
+ * @param entry the associated entry
+ */
+ private static void addEntry(Reference ref, ReleaseListener entry) {
+ // Add entry, the key is given reference.
+ trackedEntries.put(ref, entry);
+
+ // Start monitoring thread lazily.
+ synchronized (WeakReferenceMonitor.class) {
+ if (!isMonitoringThreadRunning()) {
+ monitoringThread = new Thread(new MonitoringProcess(), WeakReferenceMonitor.class.getName());
+ monitoringThread.setDaemon(true);
+ monitoringThread.start();
+ }
+ }
+ }
+
+ /**
+ * Remove entry from internal map of tracked entries.
+ * @param reference the reference that should be removed
+ * @return entry object associated with given reference
+ */
+ private static ReleaseListener removeEntry(Reference reference) {
+ return (ReleaseListener) trackedEntries.remove(reference);
+ }
+
+ /**
+ * Check if monitoring thread is currently running.
+ */
+ private static boolean isMonitoringThreadRunning() {
+ synchronized (WeakReferenceMonitor.class) {
+ return (monitoringThread != null);
+ }
+ }
+
+
+ /**
+ * Thread implementation that performs the actual monitoring.
+ */
+ private static class MonitoringProcess implements Runnable {
+
+ public void run() {
+ logger.debug("Starting reference monitor thread");
+ try {
+ // Check if there are any tracked entries left.
+ while (!trackedEntries.isEmpty()) {
+ try {
+ Reference reference = handleQueue.remove();
+ // Stop tracking this reference.
+ ReleaseListener entry = removeEntry(reference);
+ if (entry != null) {
+ // Invoke listener callback.
+ entry.released();
+ }
+ }
+ catch (InterruptedException ex) {
+ logger.debug("Reference monitor thread interrupted", ex);
+ break;
+ }
+ }
+ }
+ finally {
+ logger.debug("Stopping reference monitor thread");
+ synchronized (WeakReferenceMonitor.class) {
+ monitoringThread = null;
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Listener that is notified when the handle is being released.
+ * To be implemented by users of this reference monitor.
+ */
+ public static interface ReleaseListener {
+
+ /**
+ * This callback method is invoked once the associated handle has been released,
+ * i.e. once there are no monitored strong references to the handle anymore.
+ */
+ void released();
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/comparator/BooleanComparator.java b/org.springframework.core/src/main/java/org/springframework/util/comparator/BooleanComparator.java
new file mode 100644
index 00000000000..4095b2f3c8c
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/comparator/BooleanComparator.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * 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.springframework.util.comparator;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+/**
+ * A Comparator for Boolean objects that can sort either true or false first.
+ *
+ * @author Keith Donald
+ * @since 1.2.2
+ */
+public final class BooleanComparator implements Comparator, Serializable {
+
+ /**
+ * A shared default instance of this comparator, treating true lower
+ * than false.
+ */
+ public static final BooleanComparator TRUE_LOW = new BooleanComparator(true);
+
+ /**
+ * A shared default instance of this comparator, treating true higher
+ * than false.
+ */
+ public static final BooleanComparator TRUE_HIGH = new BooleanComparator(false);
+
+
+ private final boolean trueLow;
+
+
+ /**
+ * Create a BooleanComparator that sorts boolean values based on
+ * the provided flag.
+ *
Alternatively, you can use the default shared instances:
+ * BooleanComparator.TRUE_LOW and
+ * BooleanComparator.TRUE_HIGH.
+ * @param trueLow whether to treat true as lower or higher than false
+ * @see #TRUE_LOW
+ * @see #TRUE_HIGH
+ */
+ public BooleanComparator(boolean trueLow) {
+ this.trueLow = trueLow;
+ }
+
+
+ public int compare(Object o1, Object o2) {
+ boolean v1 = ((Boolean) o1).booleanValue();
+ boolean v2 = ((Boolean) o2).booleanValue();
+ return (v1 ^ v2) ? ((v1 ^ this.trueLow) ? 1 : -1) : 0;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof BooleanComparator)) {
+ return false;
+ }
+ return (this.trueLow == ((BooleanComparator) obj).trueLow);
+ }
+
+ public int hashCode() {
+ return (this.trueLow ? -1 : 1) * getClass().hashCode();
+ }
+
+ public String toString() {
+ return "BooleanComparator: " + (this.trueLow ? "true low" : "true high");
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/comparator/ComparableComparator.java b/org.springframework.core/src/main/java/org/springframework/util/comparator/ComparableComparator.java
new file mode 100644
index 00000000000..221139f02f0
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/comparator/ComparableComparator.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * 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.springframework.util.comparator;
+
+import java.util.Comparator;
+
+import org.springframework.util.Assert;
+
+/**
+ * Comparator that adapts Comparables to the Comparator interface.
+ * Mainly for internal use in other Comparators, when supposed
+ * to work on Comparables.
+ *
+ * @author Keith Donald
+ * @since 1.2.2
+ * @see Comparable
+ */
+public class ComparableComparator implements Comparator {
+
+ public int compare(Object o1, Object o2) {
+ Assert.isTrue(o1 instanceof Comparable, "The first object provided is not Comparable");
+ Assert.isTrue(o2 instanceof Comparable, "The second object provided is not Comparable");
+ return ((Comparable) o1).compareTo(o2);
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/comparator/CompoundComparator.java b/org.springframework.core/src/main/java/org/springframework/util/comparator/CompoundComparator.java
new file mode 100644
index 00000000000..3de42f56ab0
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/comparator/CompoundComparator.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * 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.springframework.util.comparator;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import org.springframework.util.Assert;
+
+/**
+ * A comparator that chains a sequence of one or more more Comparators.
+ *
+ *
A compound comparator calls each Comparator in sequence until a single
+ * Comparator returns a non-zero result, or the comparators are exhausted and
+ * zero is returned.
+ *
+ *
This facilitates in-memory sorting similar to multi-column sorting in SQL.
+ * The order of any single Comparator in the list can also be reversed.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ */
+public class CompoundComparator implements Comparator, Serializable {
+
+ private final List comparators;
+
+
+ /**
+ * Construct a CompoundComparator with initially no Comparators. Clients
+ * must add at least one Comparator before calling the compare method or an
+ * IllegalStateException is thrown.
+ */
+ public CompoundComparator() {
+ this.comparators = new ArrayList();
+ }
+
+ /**
+ * Construct a CompoundComparator from the Comparators in the provided array.
+ *
All Comparators will default to ascending sort order,
+ * unless they are InvertibleComparators.
+ * @param comparators the comparators to build into a compound comparator
+ * @see InvertibleComparator
+ */
+ public CompoundComparator(Comparator[] comparators) {
+ this.comparators = new ArrayList(comparators.length);
+ for (int i = 0; i < comparators.length; i++) {
+ addComparator(comparators[i]);
+ }
+ }
+
+
+ /**
+ * Add a Comparator to the end of the chain.
+ *
The Comparator will default to ascending sort order,
+ * unless it is a InvertibleComparator.
+ * @param comparator the Comparator to add to the end of the chain
+ * @see InvertibleComparator
+ */
+ public void addComparator(Comparator comparator) {
+ if (comparator instanceof InvertibleComparator) {
+ this.comparators.add(comparator);
+ }
+ else {
+ this.comparators.add(new InvertibleComparator(comparator));
+ }
+ }
+
+ /**
+ * Add a Comparator to the end of the chain using the provided sort order.
+ * @param comparator the Comparator to add to the end of the chain
+ * @param ascending the sort order: ascending (true) or descending (false)
+ */
+ public void addComparator(Comparator comparator, boolean ascending) {
+ this.comparators.add(new InvertibleComparator(comparator, ascending));
+ }
+
+ /**
+ * Replace the Comparator at the given index.
+ *
The Comparator will default to ascending sort order,
+ * unless it is a InvertibleComparator.
+ * @param index the index of the Comparator to replace
+ * @param comparator the Comparator to place at the given index
+ * @see InvertibleComparator
+ */
+ public void setComparator(int index, Comparator comparator) {
+ if (comparator instanceof InvertibleComparator) {
+ this.comparators.set(index, comparator);
+ }
+ else {
+ InvertibleComparator invComp = new InvertibleComparator(comparator);
+ this.comparators.set(index, invComp);
+ }
+ }
+
+ /**
+ * Replace the Comparator at the given index using the given sort order.
+ * @param index the index of the Comparator to replace
+ * @param comparator the Comparator to place at the given index
+ * @param ascending the sort order: ascending (true) or descending (false)
+ */
+ public void setComparator(int index, Comparator comparator, boolean ascending) {
+ InvertibleComparator invComp = new InvertibleComparator(comparator, ascending);
+ this.comparators.set(index, invComp);
+ }
+
+ /**
+ * Invert the sort order of each sort definition contained by this compound
+ * comparator.
+ */
+ public void invertOrder() {
+ Iterator it = this.comparators.iterator();
+ while (it.hasNext()) {
+ ((InvertibleComparator) it.next()).invertOrder();
+ }
+ }
+
+ /**
+ * Invert the sort order of the sort definition at the specified index.
+ * @param index the index of the comparator to invert
+ */
+ public void invertOrder(int index) {
+ getInvertibleComparator(index).invertOrder();
+ }
+
+ /**
+ * Change the sort order at the given index to ascending.
+ * @param index the index of the comparator to change
+ */
+ public void setAscendingOrder(int index) {
+ getInvertibleComparator(index).setAscending(true);
+ }
+
+ /**
+ * Change the sort order at the given index to descending sort.
+ * @param index the index of the comparator to change
+ */
+ public void setDescendingOrder(int index) {
+ getInvertibleComparator(index).setAscending(false);
+ }
+
+ /**
+ * Return the InvertibleComparator for the given index, if any.
+ */
+ private InvertibleComparator getInvertibleComparator(int index) {
+ return (InvertibleComparator) this.comparators.get(index);
+ }
+
+ /**
+ * Returns the number of aggregated comparators.
+ */
+ public int getComparatorCount() {
+ return comparators.size();
+ }
+
+
+ public int compare(Object o1, Object o2) {
+ Assert.state(this.comparators.size() > 0,
+ "No sort definitions have been added to this CompoundComparator to compare");
+ for (Iterator it = this.comparators.iterator(); it.hasNext();) {
+ InvertibleComparator def = (InvertibleComparator) it.next();
+ int result = def.compare(o1, o2);
+ if (result != 0) {
+ return result;
+ }
+ }
+ return 0;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof CompoundComparator)) {
+ return false;
+ }
+ CompoundComparator other = (CompoundComparator) obj;
+ return this.comparators.equals(other.comparators);
+ }
+
+ public int hashCode() {
+ return this.comparators.hashCode();
+ }
+
+ public String toString() {
+ return "CompoundComparator: " + this.comparators;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/comparator/InvertibleComparator.java b/org.springframework.core/src/main/java/org/springframework/util/comparator/InvertibleComparator.java
new file mode 100644
index 00000000000..22f73f771b3
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/comparator/InvertibleComparator.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * 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.springframework.util.comparator;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+/**
+ * A decorator for a comparator, with an "ascending" flag denoting
+ * whether comparison results should be treated in forward (standard
+ * ascending) order or flipped for reverse (descending) order.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ */
+public class InvertibleComparator implements Comparator, Serializable {
+
+ private final Comparator comparator;
+
+ private boolean ascending = true;
+
+
+ /**
+ * Create an InvertibleComparator that sorts ascending by default.
+ * For the actual comparison, the specified Comparator will be used.
+ * @param comparator the comparator to decorate
+ */
+ public InvertibleComparator(Comparator comparator) {
+ this.comparator = comparator;
+ }
+
+ /**
+ * Create an InvertibleComparator that sorts based on the provided order.
+ * For the actual comparison, the specified Comparator will be used.
+ * @param comparator the comparator to decorate
+ * @param ascending the sort order: ascending (true) or descending (false)
+ */
+ public InvertibleComparator(Comparator comparator, boolean ascending) {
+ this.comparator = comparator;
+ setAscending(ascending);
+ }
+
+
+ /**
+ * Specify the sort order: ascending (true) or descending (false).
+ */
+ public void setAscending(boolean ascending) {
+ this.ascending = ascending;
+ }
+
+ /**
+ * Return the sort order: ascending (true) or descending (false).
+ */
+ public boolean isAscending() {
+ return ascending;
+ }
+
+ /**
+ * Invert the sort order: ascending -> descending or
+ * descending -> ascending.
+ */
+ public void invertOrder() {
+ this.ascending = !this.ascending;
+ }
+
+
+ public int compare(Object o1, Object o2) {
+ int result = this.comparator.compare(o1, o2);
+ if (result != 0) {
+ // Invert the order if it is a reverse sort.
+ if (!this.ascending) {
+ if (Integer.MIN_VALUE == result) {
+ result = Integer.MAX_VALUE;
+ }
+ else {
+ result *= -1;
+ }
+ }
+ return result;
+ }
+ return 0;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof InvertibleComparator)) {
+ return false;
+ }
+ InvertibleComparator other = (InvertibleComparator) obj;
+ return (this.comparator.equals(other.comparator) && this.ascending == other.ascending);
+ }
+
+ public int hashCode() {
+ return this.comparator.hashCode();
+ }
+
+ public String toString() {
+ return "InvertibleComparator: [" + this.comparator + "]; ascending=" + this.ascending;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java b/org.springframework.core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java
new file mode 100644
index 00000000000..edec70ff4a9
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * 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.springframework.util.comparator;
+
+import java.util.Comparator;
+
+import org.springframework.util.Assert;
+
+/**
+ * A Comparator that will safely compare nulls to be lower or higher than
+ * other objects. Can decorate a given Comparator or work on Comparables.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ * @see Comparable
+ */
+public class NullSafeComparator implements Comparator {
+
+ /**
+ * A shared default instance of this comparator, treating nulls lower
+ * than non-null objects.
+ */
+ public static final NullSafeComparator NULLS_LOW = new NullSafeComparator(true);
+
+ /**
+ * A shared default instance of this comparator, treating nulls higher
+ * than non-null objects.
+ */
+ public static final NullSafeComparator NULLS_HIGH = new NullSafeComparator(false);
+
+
+ private final Comparator nonNullComparator;
+
+ private final boolean nullsLow;
+
+
+ /**
+ * Create a NullSafeComparator that sorts null based on
+ * the provided flag, working on Comparables.
+ *
When comparing two non-null objects, their Comparable implementation
+ * will be used: this means that non-null elements (that this Comparator
+ * will be applied to) need to implement Comparable.
+ *
As a convenience, you can use the default shared instances:
+ * NullSafeComparator.NULLS_LOW and
+ * NullSafeComparator.NULLS_HIGH.
+ * @param nullsLow whether to treat nulls lower or higher than non-null objects
+ * @see java.lang.Comparable
+ * @see #NULLS_LOW
+ * @see #NULLS_HIGH
+ */
+ private NullSafeComparator(boolean nullsLow) {
+ this(new ComparableComparator(), nullsLow);
+ }
+
+ /**
+ * Create a NullSafeComparator that sorts null based on the
+ * provided flag, decorating the given Comparator.
+ *
When comparing two non-null objects, the specified Comparator will be used.
+ * The given underlying Comparator must be able to handle the elements that this
+ * Comparator will be applied to.
+ * @param comparator the comparator to use when comparing two non-null objects
+ * @param nullsLow whether to treat nulls lower or higher than non-null objects
+ */
+ public NullSafeComparator(Comparator comparator, boolean nullsLow) {
+ Assert.notNull(comparator, "The non-null comparator is required");
+ this.nonNullComparator = comparator;
+ this.nullsLow = nullsLow;
+ }
+
+
+ public int compare(Object o1, Object o2) {
+ if (o1 == o2) {
+ return 0;
+ }
+ if (o1 == null) {
+ return (this.nullsLow ? -1 : 1);
+ }
+ if (o2 == null) {
+ return (this.nullsLow ? 1 : -1);
+ }
+ return this.nonNullComparator.compare(o1, o2);
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof NullSafeComparator)) {
+ return false;
+ }
+ NullSafeComparator other = (NullSafeComparator) obj;
+ return (this.nonNullComparator.equals(other.nonNullComparator) && this.nullsLow == other.nullsLow);
+ }
+
+ public int hashCode() {
+ return (this.nullsLow ? -1 : 1) * this.nonNullComparator.hashCode();
+ }
+
+ public String toString() {
+ return "NullSafeComparator: non-null comparator [" + this.nonNullComparator + "]; " +
+ (this.nullsLow ? "nulls low" : "nulls high");
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/comparator/package.html b/org.springframework.core/src/main/java/org/springframework/util/comparator/package.html
new file mode 100644
index 00000000000..9427b96d8e8
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/comparator/package.html
@@ -0,0 +1,8 @@
+
+
+
+Useful generic java.util.Comparator implementations,
+such as an invertible comparator and a compound comparator.
+
+
+
diff --git a/org.springframework.core/src/main/java/org/springframework/util/package.html b/org.springframework.core/src/main/java/org/springframework/util/package.html
new file mode 100644
index 00000000000..2e3ec55c936
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/package.html
@@ -0,0 +1,8 @@
+
+
+
+Miscellaneous utility classes, such as String manipulation utilities,
+a Log4J configurer, and a state holder for paged lists of objects.
+
+
+
diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/DomUtils.java b/org.springframework.core/src/main/java/org/springframework/util/xml/DomUtils.java
new file mode 100644
index 00000000000..9e62e252717
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/xml/DomUtils.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util.xml;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.w3c.dom.CharacterData;
+import org.w3c.dom.Comment;
+import org.w3c.dom.Element;
+import org.w3c.dom.EntityReference;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import org.springframework.util.Assert;
+
+/**
+ * Convenience methods for working with the DOM API,
+ * in particular for working with DOM Nodes and DOM Elements.
+ *
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @author Costin Leau
+ * @since 1.2
+ * @see org.w3c.dom.Node
+ * @see org.w3c.dom.Element
+ */
+public abstract class DomUtils {
+
+ /**
+ * Retrieve all child elements of the given DOM element that match any of
+ * the given element names. Only look at the direct child level of the
+ * given element; do not go into further depth (in contrast to the
+ * DOM API's getElementsByTagName method).
+ * @param ele the DOM element to analyze
+ * @param childEleNames the child element names to look for
+ * @return a List of child org.w3c.dom.Element instances
+ * @see org.w3c.dom.Element
+ * @see org.w3c.dom.Element#getElementsByTagName
+ */
+ public static List getChildElementsByTagName(Element ele, String[] childEleNames) {
+ Assert.notNull(ele, "Element must not be null");
+ Assert.notNull(childEleNames, "Element names collection must not be null");
+ List childEleNameList = Arrays.asList(childEleNames);
+ NodeList nl = ele.getChildNodes();
+ List childEles = new ArrayList();
+ for (int i = 0; i < nl.getLength(); i++) {
+ Node node = nl.item(i);
+ if (node instanceof Element && nodeNameMatch(node, childEleNameList)) {
+ childEles.add(node);
+ }
+ }
+ return childEles;
+ }
+
+ /**
+ * Retrieve all child elements of the given DOM element that match
+ * the given element name. Only look at the direct child level of the
+ * given element; do not go into further depth (in contrast to the
+ * DOM API's getElementsByTagName method).
+ * @param ele the DOM element to analyze
+ * @param childEleName the child element name to look for
+ * @return a List of child org.w3c.dom.Element instances
+ * @see org.w3c.dom.Element
+ * @see org.w3c.dom.Element#getElementsByTagName
+ */
+ public static List getChildElementsByTagName(Element ele, String childEleName) {
+ return getChildElementsByTagName(ele, new String[] {childEleName});
+ }
+
+ /**
+ * Utility method that returns the first child element
+ * identified by its name.
+ * @param ele the DOM element to analyze
+ * @param childEleName the child element name to look for
+ * @return the org.w3c.dom.Element instance,
+ * or null if none found
+ */
+ public static Element getChildElementByTagName(Element ele, String childEleName) {
+ Assert.notNull(ele, "Element must not be null");
+ Assert.notNull(childEleName, "Element name must not be null");
+ NodeList nl = ele.getChildNodes();
+ for (int i = 0; i < nl.getLength(); i++) {
+ Node node = nl.item(i);
+ if (node instanceof Element && nodeNameMatch(node, childEleName)) {
+ return (Element) node;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Utility method that returns the first child element value
+ * identified by its name.
+ * @param ele the DOM element to analyze
+ * @param childEleName the child element name to look for
+ * @return the extracted text value,
+ * or null if no child element found
+ */
+ public static String getChildElementValueByTagName(Element ele, String childEleName) {
+ Element child = getChildElementByTagName(ele, childEleName);
+ return (child != null ? getTextValue(child) : null);
+ }
+
+ /**
+ * Extract the text value from the given DOM element, ignoring XML comments.
+ * Appends all CharacterData nodes and EntityReference nodes
+ * into a single String value, excluding Comment nodes.
+ * @see CharacterData
+ * @see EntityReference
+ * @see Comment
+ */
+ public static String getTextValue(Element valueEle) {
+ Assert.notNull(valueEle, "Element must not be null");
+ StringBuffer value = new StringBuffer();
+ NodeList nl = valueEle.getChildNodes();
+ for (int i = 0; i < nl.getLength(); i++) {
+ Node item = nl.item(i);
+ if ((item instanceof CharacterData && !(item instanceof Comment)) || item instanceof EntityReference) {
+ value.append(item.getNodeValue());
+ }
+ }
+ return value.toString();
+ }
+
+ /**
+ * Namespace-aware equals comparison. Returns true if either
+ * {@link Node#getLocalName} or {@link Node#getNodeName} equals desiredName,
+ * otherwise returns false.
+ */
+ public static boolean nodeNameEquals(Node node, String desiredName) {
+ Assert.notNull(node, "Node must not be null");
+ Assert.notNull(desiredName, "Desired name must not be null");
+ return nodeNameMatch(node, desiredName);
+ }
+
+ /**
+ * Matches the given node's name and local name against the given desired name.
+ */
+ private static boolean nodeNameMatch(Node node, String desiredName) {
+ return (desiredName.equals(node.getNodeName()) || desiredName.equals(node.getLocalName()));
+ }
+
+ /**
+ * Matches the given node's name and local name against the given desired names.
+ */
+ private static boolean nodeNameMatch(Node node, Collection desiredNames) {
+ return (desiredNames.contains(node.getNodeName()) || desiredNames.contains(node.getLocalName()));
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/SimpleSaxErrorHandler.java b/org.springframework.core/src/main/java/org/springframework/util/xml/SimpleSaxErrorHandler.java
new file mode 100644
index 00000000000..5867384c68c
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/xml/SimpleSaxErrorHandler.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * 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.springframework.util.xml;
+
+import org.apache.commons.logging.Log;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+/**
+ * Simple org.xml.sax.ErrorHandler implementation:
+ * logs warnings using the given Commons Logging logger instance,
+ * and rethrows errors to discontinue the XML transformation.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ */
+public class SimpleSaxErrorHandler implements ErrorHandler {
+
+ private final Log logger;
+
+
+ /**
+ * Create a new SimpleSaxErrorHandler for the given
+ * Commons Logging logger instance.
+ */
+ public SimpleSaxErrorHandler(Log logger) {
+ this.logger = logger;
+ }
+
+
+ public void warning(SAXParseException ex) throws SAXException {
+ logger.warn("Ignored XML validation warning", ex);
+ }
+
+ public void error(SAXParseException ex) throws SAXException {
+ throw ex;
+ }
+
+ public void fatalError(SAXParseException ex) throws SAXException {
+ throw ex;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/SimpleTransformErrorListener.java b/org.springframework.core/src/main/java/org/springframework/util/xml/SimpleTransformErrorListener.java
new file mode 100644
index 00000000000..e4fed7285ef
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/xml/SimpleTransformErrorListener.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util.xml;
+
+import javax.xml.transform.ErrorListener;
+import javax.xml.transform.TransformerException;
+
+import org.apache.commons.logging.Log;
+
+/**
+ * Simple javax.xml.transform.ErrorListener implementation:
+ * logs warnings using the given Commons Logging logger instance,
+ * and rethrows errors to discontinue the XML transformation.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ */
+public class SimpleTransformErrorListener implements ErrorListener {
+
+ private final Log logger;
+
+
+ /**
+ * Create a new SimpleTransformErrorListener for the given
+ * Commons Logging logger instance.
+ */
+ public SimpleTransformErrorListener(Log logger) {
+ this.logger = logger;
+ }
+
+
+ public void warning(TransformerException ex) throws TransformerException {
+ logger.warn("XSLT transformation warning", ex);
+ }
+
+ public void error(TransformerException ex) throws TransformerException {
+ logger.error("XSLT transformation error", ex);
+ }
+
+ public void fatalError(TransformerException ex) throws TransformerException {
+ throw ex;
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/TransformerUtils.java b/org.springframework.core/src/main/java/org/springframework/util/xml/TransformerUtils.java
new file mode 100644
index 00000000000..4b1f4c0033a
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/xml/TransformerUtils.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2008 the original author or authors.
+ *
+ * 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.springframework.util.xml;
+
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+
+import org.springframework.util.Assert;
+
+/**
+ * Contains common behavior relating to {@link javax.xml.transform.Transformer Transformers}.
+ *
+ * @author Rick Evans
+ * @author Juergen Hoeller
+ * @since 2.5.5
+ */
+public abstract class TransformerUtils {
+
+ /**
+ * The indent amount of characters if
+ * {@link #enableIndenting(javax.xml.transform.Transformer) indenting is enabled}.
+ *
Defaults to "2".
+ */
+ public static final int DEFAULT_INDENT_AMOUNT = 2;
+
+
+ /**
+ * Enable indenting for the supplied {@link javax.xml.transform.Transformer}.
+ *
If the underlying XSLT engine is Xalan, then the special output
+ * key indent-amount will be also be set to a value
+ * of {@link #DEFAULT_INDENT_AMOUNT} characters.
+ * @param transformer the target transformer
+ * @see javax.xml.transform.Transformer#setOutputProperty(String, String)
+ * @see javax.xml.transform.OutputKeys#INDENT
+ */
+ public static void enableIndenting(Transformer transformer) {
+ enableIndenting(transformer, DEFAULT_INDENT_AMOUNT);
+ }
+
+ /**
+ * Enable indenting for the supplied {@link javax.xml.transform.Transformer}.
+ *
If the underlying XSLT engine is Xalan, then the special output
+ * key indent-amount will be also be set to a value
+ * of {@link #DEFAULT_INDENT_AMOUNT} characters.
+ * @param transformer the target transformer
+ * @param indentAmount the size of the indent (2 characters, 3 characters, etc.)
+ * @see javax.xml.transform.Transformer#setOutputProperty(String, String)
+ * @see javax.xml.transform.OutputKeys#INDENT
+ */
+ public static void enableIndenting(Transformer transformer, int indentAmount) {
+ Assert.notNull(transformer, "Transformer must not be null");
+ Assert.isTrue(indentAmount > -1, "The indent amount cannot be less than zero : got " + indentAmount);
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ try {
+ // Xalan-specific, but this is the most common XSLT engine in any case
+ transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", String.valueOf(indentAmount));
+ }
+ catch (IllegalArgumentException ignored) {
+ }
+ }
+
+ /**
+ * Disable indenting for the supplied {@link javax.xml.transform.Transformer}.
+ * @param transformer the target transformer
+ * @see javax.xml.transform.OutputKeys#INDENT
+ */
+ public static void disableIndenting(Transformer transformer) {
+ Assert.notNull(transformer, "Transformer must not be null");
+ transformer.setOutputProperty(OutputKeys.INDENT, "no");
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java b/org.springframework.core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java
new file mode 100644
index 00000000000..e3077e5941d
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * 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.springframework.util.xml;
+
+import java.io.BufferedReader;
+import java.io.CharConversionException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * Detects whether an XML stream is using DTD- or XSD-based validation.
+ *
+ * @author Rob Harrop
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class XmlValidationModeDetector {
+
+ /**
+ * Indicates that the validation should be disabled.
+ */
+ public static final int VALIDATION_NONE = 0;
+
+ /**
+ * Indicates that the validation mode should be auto-guessed, since we cannot find
+ * a clear indication (probably choked on some special characters, or the like).
+ */
+ public static final int VALIDATION_AUTO = 1;
+
+ /**
+ * Indicates that DTD validation should be used (we found a "DOCTYPE" declaration).
+ */
+ public static final int VALIDATION_DTD = 2;
+
+ /**
+ * Indicates that XSD validation should be used (found no "DOCTYPE" declaration).
+ */
+ public static final int VALIDATION_XSD = 3;
+
+
+ /**
+ * The token in a XML document that declares the DTD to use for validation
+ * and thus that DTD validation is being used.
+ */
+ private static final String DOCTYPE = "DOCTYPE";
+
+ /**
+ * The token that indicates the start of an XML comment.
+ */
+ private static final String START_COMMENT = "";
+
+
+ /**
+ * Indicates whether or not the current parse position is inside an XML comment.
+ */
+ private boolean inComment;
+
+
+ /**
+ * Detect the validation mode for the XML document in the supplied {@link InputStream}.
+ * Note that the supplied {@link InputStream} is closed by this method before returning.
+ * @param inputStream the InputStream to parse
+ * @throws IOException in case of I/O failure
+ * @see #VALIDATION_DTD
+ * @see #VALIDATION_XSD
+ */
+ public int detectValidationMode(InputStream inputStream) throws IOException {
+ // Peek into the file to look for DOCTYPE.
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+ try {
+ boolean isDtdValidated = false;
+ String content;
+ while ((content = reader.readLine()) != null) {
+ content = consumeCommentTokens(content);
+ if (this.inComment || !StringUtils.hasText(content)) {
+ continue;
+ }
+ if (hasDoctype(content)) {
+ isDtdValidated = true;
+ break;
+ }
+ if (hasOpeningTag(content)) {
+ // End of meaningful data...
+ break;
+ }
+ }
+ return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
+ }
+ catch (CharConversionException ex) {
+ // Choked on some character encoding...
+ // Leave the decision up to the caller.
+ return VALIDATION_AUTO;
+ }
+ finally {
+ reader.close();
+ }
+ }
+
+
+ /**
+ * Does the content contain the the DTD DOCTYPE declaration?
+ */
+ private boolean hasDoctype(String content) {
+ return (content.indexOf(DOCTYPE) > -1);
+ }
+
+ /**
+ * Does the supplied content contain an XML opening tag. If the parse state is currently
+ * in an XML comment then this method always returns false. It is expected that all comment
+ * tokens will have consumed for the supplied content before passing the remainder to this method.
+ */
+ private boolean hasOpeningTag(String content) {
+ if (this.inComment) {
+ return false;
+ }
+ int openTagIndex = content.indexOf('<');
+ return (openTagIndex > -1 && content.length() > openTagIndex && Character.isLetter(content.charAt(openTagIndex + 1)));
+ }
+
+ /**
+ * Consumes all the leading comment data in the given String and returns the remaining content, which
+ * may be empty since the supplied content might be all comment data. For our purposes it is only important
+ * to strip leading comment content on a line since the first piece of non comment content will be either
+ * the DOCTYPE declaration or the root element of the document.
+ */
+ private String consumeCommentTokens(String line) {
+ if (line.indexOf(START_COMMENT) == -1 && line.indexOf(END_COMMENT) == -1) {
+ return line;
+ }
+ while ((line = consume(line)) != null) {
+ if (!this.inComment && !line.trim().startsWith(START_COMMENT)) {
+ return line;
+ }
+ }
+ return line;
+ }
+
+ /**
+ * Consume the next comment token, update the "inComment" flag
+ * and return the remaining content.
+ */
+ private String consume(String line) {
+ int index = (this.inComment ? endComment(line) : startComment(line));
+ return (index == -1 ? null : line.substring(index));
+ }
+
+ /**
+ * Try to consume the {@link #START_COMMENT} token.
+ * @see #commentToken(String, String, boolean)
+ */
+ private int startComment(String line) {
+ return commentToken(line, START_COMMENT, true);
+ }
+
+ private int endComment(String line) {
+ return commentToken(line, END_COMMENT, false);
+ }
+
+ /**
+ * Try to consume the supplied token against the supplied content and update the
+ * in comment parse state to the supplied value. Returns the index into the content
+ * which is after the token or -1 if the token is not found.
+ */
+ private int commentToken(String line, String token, boolean inCommentIfPresent) {
+ int index = line.indexOf(token);
+ if (index > - 1) {
+ this.inComment = inCommentIfPresent;
+ }
+ return (index == -1 ? index : index + token.length());
+ }
+
+}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/xml/package.html b/org.springframework.core/src/main/java/org/springframework/util/xml/package.html
new file mode 100644
index 00000000000..2efc64f84a8
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/xml/package.html
@@ -0,0 +1,8 @@
+
+
+
+Miscellaneous utility classes for XML parsing and transformation,
+such as error handlers that log warnings via Commons Logging.
+
+
+
diff --git a/org.springframework.core/template.mf b/org.springframework.core/template.mf
index 2d41627335d..f2119275e63 100644
--- a/org.springframework.core/template.mf
+++ b/org.springframework.core/template.mf
@@ -3,4 +3,13 @@ Bundle-Name: Spring Core
Bundle-Vendor: SpringSource
Bundle-ManifestVersion: 2
Import-Template:
- org.apache.commons.logging;version="[1.1.1, 2.0.0)",
+ edu.emory.mathcs.backport.*;version="[3.0.0, 4.0.0)";resolution:=optional,
+ org.apache.commons.attributes.*;version="[2.2.0, 3.0.0)";resolution:=optional,
+ org.apache.commons.collections.*;version="[3.2.0, 4.0.0)";resolution:=optional,
+ org.apache.commons.logging.*;version="[1.1.1, 2.0.0)",
+ org.objectweb.asm.*;version="[2.2.3, 3.0.0)";resolution:=optional,
+ org.apache.log4j.*;version="[1.2.15, 2.0.0)";resolution:=optional,
+Unversioned-Imports:
+ javax.xml.transform.*,
+ org.w3c.dom.*,
+ org.xml.sax.*
\ No newline at end of file