Moved Spring Web Flow to a top level project

This commit is contained in:
Ben Hale
2006-12-21 16:02:27 +00:00
commit e7ecbbc7e5
1083 changed files with 80775 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
/*
* 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.binding.collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import org.springframework.util.Assert;
/**
* Iterator that combines multiple other iterators. This is a simple implementation
* that just maintains a list of iterators which are invoked in sequence untill
* all iterators are exhausted.
*
* @author Erwin Vervaet
*/
public class CompositeIterator implements Iterator {
private List iterators = new LinkedList();
private boolean inUse = false;
/**
* Create a new composite iterator. Add iterators using the {@link #add(Iterator)} method.
*/
public CompositeIterator() {
}
/**
* Add given iterator to this composite.
*/
public void add(Iterator iterator) {
Assert.state(!inUse, "You can no longer add iterator to a composite iterator that's already in use");
if (iterators.contains(iterator)) {
throw new IllegalArgumentException("You cannot add the same iterator twice");
}
iterators.add(iterator);
}
public boolean hasNext() {
inUse = true;
for (Iterator it = iterators.iterator(); it.hasNext(); ) {
if (((Iterator)it.next()).hasNext()) {
return true;
}
}
return false;
}
public Object next() {
inUse = true;
for (Iterator it = iterators.iterator(); it.hasNext(); ) {
Iterator iterator = (Iterator)it.next();
if (iterator.hasNext()) {
return iterator.next();
}
}
throw new NoSuchElementException("Exhaused all iterators");
}
public void remove() {
throw new UnsupportedOperationException("Remove is not supported");
}
}

View File

@@ -0,0 +1,463 @@
/*
* 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.binding.collection;
import java.util.Collection;
import java.util.Map;
import org.springframework.util.Assert;
/**
* A simple, generic decorator for getting attributes out of a map. May be
* instantiated directly or used as a base class as a convenience.
*
* @author Keith Donald
*/
public class MapAccessor implements MapAdaptable {
/**
* The target map.
*/
private Map map;
/**
* Creates a new attribute map accessor.
* @param map the map
*/
public MapAccessor(Map map) {
Assert.notNull(map, "The map to decorate is required");
this.map = map;
}
// implementing MapAdaptable
public Map asMap() {
return map;
}
/**
* Returns a value in the map, returning the defaultValue if no value was
* found.
* @param key the key
* @param defaultValue the default
* @return the attribute value
*/
public Object get(Object key, Object defaultValue) {
if (!map.containsKey(key)) {
return defaultValue;
}
return map.get(key);
}
/**
* Returns a value in the map, asserting it is of the required type if
* present and returning <code>null</code> if not found.
* @param key the key
* @param requiredType the required type
* @return the value
* @throws IllegalArgumentException if the key is present but the value is
* not of the required type
*/
public Object get(Object key, Class requiredType) throws IllegalArgumentException {
return get(key, requiredType, null);
}
/**
* Returns a value in the map of the specified type, returning the
* defaultValue if no value is found.
* @param key the key
* @param requiredType the required type
* @param defaultValue the default
* @return the attribute value
* @throws IllegalArgumentException if the key is present but the value is
* not of the required type
*/
public Object get(Object key, Class requiredType, Object defaultValue) {
if (!map.containsKey(key)) {
return defaultValue;
}
return assertKeyValueOfType(key, requiredType);
}
/**
* Returns a value in the map, throwing an exception if the attribute is not
* present and of the correct type.
* @param key the key
* @return the value
*/
public Object getRequired(Object key) throws IllegalArgumentException {
assertContainsKey(key);
return map.get(key);
}
/**
* Returns an value in the map, asserting it is present and of the required
* type.
* @param key the key
* @param requiredType the required type
* @return the value
*/
public Object getRequired(Object key, Class requiredType) throws IllegalArgumentException {
assertContainsKey(key);
return assertKeyValueOfType(key, requiredType);
}
/**
* Returns a string value in the map, returning <code>null</code> if no
* value was found.
* @param key the key
* @return the string value
* @throws IllegalArgumentException if the key is present but the value is
* not a string
*/
public String getString(Object key) throws IllegalArgumentException {
return getString(key, null);
}
/**
* Returns a string value in the map, returning the defaultValue if no value
* was found.
* @param key the key
* @param defaultValue the default
* @return the string value
* @throws IllegalArgumentException if the key is present but the value is
* not a string
*/
public String getString(Object key, String defaultValue) throws IllegalArgumentException {
if (!map.containsKey(key)) {
return defaultValue;
}
return (String)assertKeyValueOfType(key, String.class);
}
/**
* Returns a string value in the map, throwing an exception if the attribute
* is not present and of the correct type.
* @param key the key
* @return the string value
* @throws IllegalArgumentException if the key is not present or present but
* the value is not a string
*/
public String getRequiredString(Object key) throws IllegalArgumentException {
assertContainsKey(key);
return (String)assertKeyValueOfType(key, String.class);
}
/**
* Returns a collection value in the map, returning <code>null</code> if
* no value was found.
* @param key the key
* @return the collection value
* @throws IllegalArgumentException if the key is present but the value is
* not a collection
*/
public Collection getCollection(Object key) throws IllegalArgumentException {
if (!map.containsKey(key)) {
return null;
}
return (Collection)assertKeyValueOfType(key, Collection.class);
}
/**
* Returns a collection value in the map, asserting it is of the required
* type if present and returning <code>null</code> if not found.
* @param key the key
* @return the collection value
* @throws IllegalArgumentException if the key is present but the value is
* not a collection
*/
public Collection getCollection(Object key, Class requiredType) throws IllegalArgumentException {
if (!map.containsKey(key)) {
return null;
}
assertAssignableTo(Collection.class, requiredType);
return (Collection)assertKeyValueOfType(key, requiredType);
}
/**
* Returns a collection value in the map, throwing an exception if not
* found.
* @param key the key
* @return the collection value
* @throws IllegalArgumentException if the key is not present or present but
* the value is not a collection
*/
public Collection getRequiredCollection(Object key) throws IllegalArgumentException {
assertContainsKey(key);
return (Collection)assertKeyValueOfType(key, Collection.class);
}
/**
* Returns a collection value in the map, asserting it is of the required
* type if present and throwing an exception if not found.
* @param key the key
* @return the collection value
* @throws IllegalArgumentException if the key is not present or present but
* the value is not a collection of the required type
*/
public Collection getRequiredCollection(Object key, Class requiredType) throws IllegalArgumentException {
assertContainsKey(key);
assertAssignableTo(Collection.class, requiredType);
return (Collection)assertKeyValueOfType(key, requiredType);
}
/**
* Returns a array value in the map, asserting it is of the required type if
* present and returning <code>null</code> if not found.
* @param key the key
* @return the array value
* @throws IllegalArgumentException if the key is present but the value is
* not an array of the required type
*/
public Object[] getArray(Object key, Class requiredType) throws IllegalArgumentException {
assertAssignableTo(Object[].class, requiredType);
if (!map.containsKey(key)) {
return null;
}
return (Object[])assertKeyValueOfType(key, requiredType);
}
/**
* Returns an array value in the map, asserting it is of the required type
* if present and throwing an exception if not found.
* @param key the key
* @return the array value
* @throws IllegalArgumentException if the key is not present or present but
* the value is not a array of the required type
*/
public Object[] getRequiredArray(Object key, Class requiredType) throws IllegalArgumentException {
assertContainsKey(key);
assertAssignableTo(Object[].class, requiredType);
return (Object[])assertKeyValueOfType(key, requiredType);
}
/**
* Returns a number value in the map that is of the specified type,
* returning <code>null</code> if no value was found.
* @param key the key
* @param requiredType the required number type
* @return the numbervalue
* @throws IllegalArgumentException if the key is present but the value is
* not a number of the required type
*/
public Number getNumber(Object key, Class requiredType) throws IllegalArgumentException {
return getNumber(key, requiredType, null);
}
/**
* Returns a number attribute value in the map of the specified type,
* returning the defaultValue if no value was found.
* @param key the attribute name
* @return the number value
* @param defaultValue the default
* @throws IllegalArgumentException if the key is present but the value is
* not a number of the required type
*/
public Number getNumber(Object key, Class requiredType, Number defaultValue) throws IllegalArgumentException {
if (!map.containsKey(key)) {
return defaultValue;
}
assertAssignableTo(Number.class, requiredType);
return (Number)assertKeyValueOfType(key, requiredType);
}
/**
* Returns a number value in the map, throwing an exception if the attribute
* is not present and of the correct type.
* @param key the key
* @return the number value
* @throws IllegalArgumentException if the key is not present or present but
* the value is not a number of the required type
*/
public Number getRequiredNumber(Object key, Class requiredType) throws IllegalArgumentException {
assertContainsKey(key);
return (Number)assertKeyValueOfType(key, requiredType);
}
/**
* Returns an integer value in the map, returning <code>null</code> if no
* value was found.
* @param key the key
* @return the integer value
* @throws IllegalArgumentException if the key is present but the value is
* not an integer
*/
public Integer getInteger(Object key) throws IllegalArgumentException {
return getInteger(key, null);
}
/**
* Returns an integer value in the map, returning the defaultValue if no
* value was found.
* @param key the key
* @param defaultValue the default
* @return the integer value
* @throws IllegalArgumentException if the key is present but the value is
* not an integer
*/
public Integer getInteger(Object key, Integer defaultValue) throws IllegalArgumentException {
return (Integer)getNumber(key, Integer.class, defaultValue);
}
/**
* Returns an integer value in the map, throwing an exception if the value
* is not present and of the correct type.
* @param key the attribute name
* @return the integer attribute value
* @throws IllegalArgumentException if the key is not present or present but
* the value is not an integer
*/
public Integer getRequiredInteger(Object key) throws IllegalArgumentException {
return (Integer)getRequiredNumber(key, Integer.class);
}
/**
* Returns a long value in the map, returning <code>null</code> if no
* value was found.
* @param key the key
* @return the long value
* @throws IllegalArgumentException if the key is present but not a long
*/
public Long getLong(Object key) throws IllegalArgumentException {
return getLong(key, null);
}
/**
* Returns a long value in the map, returning the defaultValue if no value
* was found.
* @param key the key
* @param defaultValue the default
* @return the long attribute value
* @throws IllegalArgumentException if the key is present but the value is
* not a long
*/
public Long getLong(Object key, Long defaultValue) throws IllegalArgumentException {
return (Long)getNumber(key, Long.class, defaultValue);
}
/**
* Returns a long value in the map, throwing an exception if the value is
* not present and of the correct type.
* @param key the key
* @return the long attribute value
* @throws IllegalArgumentException if the key is not present or present but
* the value is not a long
*/
public Long getRequiredLong(Object key) throws IllegalArgumentException {
return (Long)getRequiredNumber(key, Long.class);
}
/**
* Returns a boolean value in the map, returning <code>null</code> if no
* value was found.
* @param key the key
* @return the boolean value
* @throws IllegalArgumentException if the key is present but the value is
* not a boolean
*/
public Boolean getBoolean(Object key) throws IllegalArgumentException {
return getBoolean(key, null);
}
/**
* Returns a boolean value in the map, returning the defaultValue if no
* value was found.
* @param key the key
* @param defaultValue the default
* @return the boolean value
* @throws IllegalArgumentException if the key is present but the value is
* not a boolean
*/
public Boolean getBoolean(Object key, Boolean defaultValue) throws IllegalArgumentException {
if (!map.containsKey(key)) {
return defaultValue;
}
return (Boolean)assertKeyValueOfType(key, Boolean.class);
}
/**
* Returns a boolean value in the map, throwing an exception if the value is
* not present and of the correct type.
* @param key the attribute
* @return the boolean value
* @throws IllegalArgumentException if the key is not present or present but
* the value is not a boolean
*/
public Boolean getRequiredBoolean(Object key) throws IllegalArgumentException {
assertContainsKey(key);
return (Boolean)assertKeyValueOfType(key, Boolean.class);
}
/**
* Asserts that the attribute is present in the attribute map.
* @param key the key
* @throws IllegalArgumentException if the key is not present
*/
public void assertContainsKey(Object key) throws IllegalArgumentException {
if (!map.containsKey(key)) {
throw new IllegalArgumentException("Required attribute '" + key
+ "' is not present in map; attributes present are [" + asMap() + "]");
}
}
/**
* Indicates if the attribute is present in the attribute map and of the
* required type.
* @param key the attribute name
* @return true if present and of the required type, false if not present.
*/
public boolean containsKey(Object key, Class requiredType) throws IllegalArgumentException {
if (map.containsKey(key)) {
assertKeyValueOfType(key, requiredType);
return true;
}
else {
return false;
}
}
/**
* Assert that value of the mak key is of the required type.
* @param key the attribute name
* @param requiredType the required attribute value type
* @return the attribute value
*/
public Object assertKeyValueOfType(Object key, Class requiredType) {
return assertKeyValueInstanceOf(key, map.get(key), requiredType);
}
/**
* Assert that the key value is an instance of the required type.
* @param key the key
* @param value the value
* @param requiredType the required type
* @return the value
*/
public Object assertKeyValueInstanceOf(Object key, Object value, Class requiredType) {
Assert.notNull(requiredType, "The required type to assert is required");
if (!requiredType.isInstance(value)) {
throw new IllegalArgumentException("Map key '" + key + "' has value [" + value
+ "] that is not of expected type [" + requiredType + "], instead it is of type ["
+ (value != null ? value.getClass().getName() : "null") + "]");
}
return value;
}
private void assertAssignableTo(Class clazz, Class requiredType) {
Assert.isTrue(clazz.isAssignableFrom(requiredType), "The provided required type must be assignable to ["
+ clazz + "]");
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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.binding.collection;
import java.util.Map;
/**
* An object whose contents are capable of being exposed as an unmodifiable map.
*
* @author Keith Donald
*/
public interface MapAdaptable {
/**
* Returns this object's contents as a {@link Map}. The returned map may or
* may not be modifiable depending on this implementation.
* <p>
* Warning: this operation may be called frequently; if so care should be
* taken so that the map contents (if calculated) be cached as appropriate.
* @return the object's contents as a map
*/
public Map asMap();
}

View File

@@ -0,0 +1,47 @@
/*
* 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.binding.collection;
import java.util.Map;
/**
* A simple subinterface of {@link Map} that exposes a mutex that
* application code can synchronize on.
* <p>
* Expected to be implemented by Maps that are backed by shared objects that
* require synchronization between multiple threads. An example would be the
* HTTP session map.
*
* @author Keith Donald
*/
public interface SharedMap extends Map {
/**
* Returns the shared mutex that may be synchronized on using a
* synchronized block. The returned mutex is guaranteed to be non-null.
*
* Example usage:
*
* <pre>
* synchronized (sharedMap.getMutex()) {
* // do synchronized work
* }
* </pre>
*
* @return the mutex
*/
public Object getMutex();
}

View File

@@ -0,0 +1,106 @@
/*
* 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.binding.collection;
import java.io.Serializable;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import org.springframework.core.style.ToStringCreator;
/**
* A map decorator that implements <code>SharedMap</code>. By default, simply
* returns the map itself as the mutex. Subclasses may override to return a
* different mutex object.
*
* @author Keith Donald
*/
public class SharedMapDecorator implements SharedMap, Serializable {
/**
* The wrapped, target map.
*/
private Map map;
/**
* Creates a new shared map decorator.
* @param map the map that is shared by multiple threads, to be synced
*/
public SharedMapDecorator(Map map) {
this.map = map;
}
// implementing Map
public void clear() {
map.clear();
}
public boolean containsKey(Object key) {
return map.containsKey(key);
}
public boolean containsValue(Object value) {
return map.containsValue(value);
}
public Set entrySet() {
return map.entrySet();
}
public Object get(Object key) {
return map.get(key);
}
public boolean isEmpty() {
return map.isEmpty();
}
public Set keySet() {
return map.keySet();
}
public Object put(Object key, Object value) {
return map.put(key, value);
}
public void putAll(Map map) {
this.map.putAll(map);
}
public Object remove(Object key) {
return map.remove(key);
}
public int size() {
return map.size();
}
public Collection values() {
return map.values();
}
// implementing SharedMap
public Object getMutex() {
return map;
}
public String toString() {
return new ToStringCreator(this).append("map", map).append("mutex", getMutex()).toString();
}
}

View File

@@ -0,0 +1,285 @@
/*
* 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.binding.collection;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* Base class for map adapters whose keys are String values. Concrete
* classes need only implement the abstract hook methods defined by this class.
*
* @author Keith Donald
*/
public abstract class StringKeyedMapAdapter implements Map {
private Set keySet;
private Collection values;
private Set entrySet;
// implementing Map
public void clear() {
for (Iterator it = getAttributeNames(); it.hasNext();) {
removeAttribute((String)it.next());
}
}
public boolean containsKey(Object key) {
return getAttribute(key.toString()) != null;
}
public boolean containsValue(Object value) {
if (value == null) {
return false;
}
for (Iterator it = getAttributeNames(); it.hasNext();) {
Object aValue = getAttribute((String)it.next());
if (value.equals(aValue)) {
return true;
}
}
return false;
}
public Set entrySet() {
return (entrySet != null) ? entrySet : (entrySet = new EntrySet());
}
public Object get(Object key) {
return getAttribute(key.toString());
}
public boolean isEmpty() {
return !getAttributeNames().hasNext();
}
public Set keySet() {
return (keySet != null) ? keySet : (keySet = new KeySet());
}
public Object put(Object key, Object value) {
String stringKey = String.valueOf(key);
Object previousValue = getAttribute(stringKey);
setAttribute(stringKey, value);
return previousValue;
}
public void putAll(Map map) {
for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
Entry entry = (Entry)it.next();
setAttribute(entry.getKey().toString(), entry.getValue());
}
}
public Object remove(Object key) {
String stringKey = key.toString();
Object retval = getAttribute(stringKey);
removeAttribute(stringKey);
return retval;
}
public int size() {
int size = 0;
for (Iterator it = getAttributeNames(); it.hasNext();) {
size++;
it.next();
}
return size;
}
public Collection values() {
return (values != null) ? values : (values = new Values());
}
// hook methods
/**
* Hook method that needs to be implemented by concrete subclasses.
* Gets a value associated with a key.
* @param key the key to lookup
* @return the associated value, or null if none
*/
protected abstract Object getAttribute(String key);
/**
* Hook method that needs to be implemented by concrete subclasses.
* Puts a key-value pair in the map, overwriting any possible earlier
* value associated with the same key.
* @param key the key to associate the value with
* @param value the value to associate with the key
*/
protected abstract void setAttribute(String key, Object value);
/**
* Hook method that needs to be implemented by concrete subclasses.
* Removes a key and its associated value from the map.
* @param key the key to remove
*/
protected abstract void removeAttribute(String key);
/**
* Hook method that needs to be implemented by concrete subclasses.
* Returns an enumeration listing all keys known to the map.
* @return the key enumeration
*/
protected abstract Iterator getAttributeNames();
// internal helper classes
private abstract class AbstractSet extends java.util.AbstractSet {
public boolean isEmpty() {
return StringKeyedMapAdapter.this.isEmpty();
}
public int size() {
return StringKeyedMapAdapter.this.size();
}
public void clear() {
StringKeyedMapAdapter.this.clear();
}
}
private class KeySet extends AbstractSet {
public Iterator iterator() {
return new KeyIterator();
}
public boolean contains(Object o) {
return StringKeyedMapAdapter.this.containsKey(o);
}
public boolean remove(Object o) {
return StringKeyedMapAdapter.this.remove(o) != null;
}
}
private class KeyIterator implements Iterator {
protected final Iterator it = getAttributeNames();
protected Object currentKey;
public void remove() {
if (currentKey == null) {
throw new NoSuchElementException("You must call next() at least once");
}
StringKeyedMapAdapter.this.remove(currentKey);
}
public boolean hasNext() {
return it.hasNext();
}
public Object next() {
return currentKey = it.next();
}
}
private class Values extends AbstractSet {
public Iterator iterator() {
return new ValuesIterator();
}
public boolean contains(Object o) {
return StringKeyedMapAdapter.this.containsValue(o);
}
public boolean remove(Object o) {
if (o == null) {
return false;
}
for (Iterator it = iterator(); it.hasNext();) {
if (o.equals(it.next())) {
it.remove();
return true;
}
}
return false;
}
}
private class ValuesIterator extends KeyIterator {
public Object next() {
super.next();
return StringKeyedMapAdapter.this.get(currentKey);
}
}
private class EntrySet extends AbstractSet {
public Iterator iterator() {
return new EntryIterator();
}
public boolean contains(Object o) {
if (!(o instanceof Entry)) {
return false;
}
Entry entry = (Entry)o;
Object key = entry.getKey();
Object value = entry.getValue();
if (key == null || value == null) {
return false;
}
return value.equals(StringKeyedMapAdapter.this.get(key));
}
public boolean remove(Object o) {
if (!(o instanceof Entry)) {
return false;
}
Entry entry = (Entry)o;
Object key = entry.getKey();
Object value = entry.getValue();
if (key == null || value == null || !value.equals(StringKeyedMapAdapter.this.get(key))) {
return false;
}
return StringKeyedMapAdapter.this.remove(((Entry)o).getKey()) != null;
}
}
private class EntryIterator extends KeyIterator {
public Object next() {
super.next();
return new EntrySetEntry(currentKey);
}
}
private class EntrySetEntry implements Entry {
private final Object currentKey;
public EntrySetEntry(Object currentKey) {
this.currentKey = currentKey;
}
public Object getKey() {
return currentKey;
}
public Object getValue() {
return StringKeyedMapAdapter.this.get(currentKey);
}
public Object setValue(Object value) {
return StringKeyedMapAdapter.this.put(currentKey, value);
}
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Collection related classes usable by other packages and systems.
</p>
</body>
</html>

View File

@@ -0,0 +1,33 @@
/*
* 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.binding.convert;
/**
* A context object with two main responsibities:
* <ol>
* <li>Exposing information to a converter to influence
* a type conversion attempt.
* <li>Providing operations for recording progress or
* errors during the type conversion process.
* </ol>
* Empty for now; subclasses may define their own custom context behavior
* accessible by a converter with a downcast.
*
* @author Keith Donald
*/
public interface ConversionContext {
}

View File

@@ -0,0 +1,125 @@
/*
* 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.binding.convert;
import org.springframework.core.NestedRuntimeException;
/**
* Base class for exceptions thrown by the type conversion system.
*
* @author Keith Donald
*/
public class ConversionException extends NestedRuntimeException {
/**
* The source type we tried to convert from
*/
private Class sourceClass;
/**
* The value we tried to convert.
*/
private Object value;
/**
* The target type we tried to convert to.
*/
private Class targetClass;
/**
* Creates a new conversion exception.
* @param value the value we tried to convert
* @param targetClass the target type
*/
public ConversionException(Object value, Class targetClass) {
super("Unable to convert value '" + value + "' of type '" + (value != null ? value.getClass().getName() : null)
+ "' to class '" + targetClass.getName() + "'");
this.value = value;
this.targetClass = targetClass;
}
/**
* Creates a new conversion exception.
* @param value the value we tried to convert
* @param targetClass the target type
* @param cause underlying cause of this exception
*/
public ConversionException(Object value, Class targetClass, Throwable cause) {
super("Unable to convert value '" + value + "' of type '" + (value != null ? value.getClass().getName() : null)
+ "' to class '" + targetClass.getName() + "'", cause);
this.value = value;
this.targetClass = targetClass;
}
/**
* Creates a new conversion exception.
* @param value the value we tried to convert
* @param targetClass the target type
* @param message a descriptive message
* @param cause underlying cause of this exception
*/
public ConversionException(Object value, Class targetClass, String message, Throwable cause) {
super(message, cause);
this.value = value;
this.targetClass = targetClass;
}
/**
* Creates a new conversion exception.
* @param sourceClass the source type
* @param targetClass the target type
* @param message a descriptive message
*/
public ConversionException(Class sourceClass, Class targetClass, String message) {
super(message);
this.sourceClass = sourceClass;
this.value = null; // not available
this.targetClass = targetClass;
}
/**
* Creates a new conversion exception.
* @param sourceClass the source type
* @param message a descriptive message
*/
public ConversionException(Class sourceClass, String message) {
super(message);
this.sourceClass = sourceClass;
this.value = null; // not available
this.targetClass = null; // not available
}
/**
* Returns the source type.
*/
public Class getSourceClass() {
return sourceClass;
}
/**
* Returns the value we tried to convert.
*/
public Object getValue() {
return value;
}
/**
* Returns the target type.
*/
public Class getTargetClass() {
return targetClass;
}
}

View File

@@ -0,0 +1,125 @@
/*
* 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.binding.convert;
import java.io.Serializable;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
/**
* A command object that is parameterized with the information necessary to
* perform a conversion of a source input to a target output.
* <p>
* Specifically, encapsulates knowledge about how to convert source objects to a
* specific target type using a specific converter.
*
* @author Keith Donald
*/
public class ConversionExecutor implements Serializable {
/**
* The source value type this executor will attempt to convert from.
*/
private final Class sourceClass;
/**
* The target value type this executor will attempt to convert to.
*/
private final Class targetClass;
/**
* The converter that will perform the conversion.
*/
private final Converter converter;
/**
* Creates a conversion executor.
* @param sourceClass the source type that the converter will convert from
* @param targetClass the target type that the converter will convert to
* @param converter the converter that will perform the conversion
*/
public ConversionExecutor(Class sourceClass, Class targetClass, Converter converter) {
Assert.notNull(sourceClass, "The source class is required");
Assert.notNull(targetClass, "The target class is required");
Assert.notNull(converter, "The converter is required");
this.sourceClass = sourceClass;
this.targetClass = targetClass;
this.converter = converter;
}
/**
* Returns the source class of conversions performed by this executor.
* @return the source class
*/
public Class getSourceClass() {
return sourceClass;
}
/**
* Returns the target class of conversions performed by this executor.
* @return the target class
*/
public Class getTargetClass() {
return targetClass;
}
/**
* Returns the converter that will perform the conversion.
* @return the converter
*/
public Converter getConverter() {
return converter;
}
/**
* Execute the conversion for the provided source object.
* @param source the source object to convert
*/
public Object execute(Object source) throws ConversionException {
return execute(source, null);
}
/**
* Execute the conversion for the provided source object.
* @param source the source object to convert
* @param context the conversion context, useful for influencing the
* behavior of the converter
*/
public Object execute(Object source, ConversionContext context) throws ConversionException {
if (source != null) {
Assert.isInstanceOf(sourceClass, source, "Not of source type: ");
}
return converter.convert(source, targetClass, context);
}
public boolean equals(Object o) {
if (!(o instanceof ConversionExecutor)) {
return false;
}
ConversionExecutor other = (ConversionExecutor)o;
return sourceClass.equals(other.sourceClass) && targetClass.equals(other.targetClass);
}
public int hashCode() {
return sourceClass.hashCode() + targetClass.hashCode();
}
public String toString() {
return new ToStringCreator(this).append("sourceClass", sourceClass).append("targetClass", targetClass)
.toString();
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.binding.convert;
/**
* A service interface for retrieving type conversion executors. The returned
* command objects are thread-safe and may be safely cached for use by client
* code.
*
* @author Keith Donald
*/
public interface ConversionService {
/**
* Return a conversion executor command object capable of converting source
* objects of the specified <code>sourceClass</code> to instances of the
* <code>targetClass</code>.
* <p>
* The returned ConversionExecutor is thread-safe and may safely be cached
* for use in client code.
* @param sourceClass the source class to convert from
* @param targetClass the target class to convert to
* @return the executor that can execute instance conversion, never null
* @throws ConversionException an exception occured retrieving a converter
* for the source-to-target pair
*/
public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass)
throws ConversionException;
/**
* Return a conversion executor command object capable of converting source
* objects of the specified <code>sourceClass</code> to target objects of
* the type associated with the specified alias.
* @param sourceClass the sourceClass
* @param targetAlias the target alias
* @return the conversion executor, or null if the alias cannot be found
* @throws ConversionException an exception occured retrieving a converter
* for the source-to-target pair
*/
public ConversionExecutor getConversionExecutorByTargetAlias(Class sourceClass, String targetAlias)
throws ConversionException;
/**
* Return all conversion executors capable of converting source objects of
* the the specified <code>sourceClass</code>.
* @param sourceClass the source class to convert from
* @return the matching conversion executors
* @throws ConversionException an exception occured retrieving the converters
*/
public ConversionExecutor[] getConversionExecutorsForSource(Class sourceClass)
throws ConversionException;
/**
* Return the class with the specified alias.
* @param alias the class alias
* @return the class, or null if not aliased
* @throws ConversionException when an error occurs looking up the class by alias
*/
public Class getClassByAlias(String alias) throws ConversionException;
}

View File

@@ -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.binding.convert;
/**
* A type converter converts objects from one type to another. They may support
* conversion of multiple source types to multiple target types.
* <p>
* Implementations of this interface are thread-safe.
*
* @author Keith Donald
*/
public interface Converter {
/**
* The source classes this converter can convert from.
* @return the supported source classes
*/
public Class[] getSourceClasses();
/**
* The target classes this converter can convert to.
* @return the supported target classes
*/
public Class[] getTargetClasses();
/**
* Convert the provided source object argument to an instance of the
* specified target class.
* @param source the source object to convert, its class must be one of the
* supported <code>sourceClasses</code>
* @param targetClass the target class to convert the source to, must be one
* of the supported <code>targetClasses</code>
* @param context an optional conversion context that may be used to
* influence the conversion process
* @return the converted object, an instance of the target type
* @throws ConversionException an exception occured during the conversion
*/
public Object convert(Object source, Class targetClass, ConversionContext context) throws ConversionException;
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Core services for converting objects from one type to another.
</p>
</body>
</html>

View File

@@ -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.binding.convert.support;
import org.springframework.binding.convert.ConversionContext;
import org.springframework.binding.convert.ConversionException;
import org.springframework.binding.convert.Converter;
/**
* Base class for converters provided as a convenience to implementors.
*
* @author Keith Donald
*/
public abstract class AbstractConverter implements Converter {
/**
* Convenience convert method that converts the provided source to the first
* target object supported by this converter. Useful when a converter only
* supports conversion to a single target.
* @param source the source to convert
* @return the converted object
* @throws ConversionException an exception occured converting the source
* value
*/
public Object convert(Object source) throws ConversionException {
return convert(source, getTargetClasses()[0], null);
}
/**
* Convenience convert method that converts the provided source to the
* target class specified with an empty conversion context.
* @param source the source to convert
* @param targetClass the target class to convert the source to, must be one
* of the supported <code>targetClasses</code>
* @return the converted object
* @throws ConversionException an exception occured converting the source
* value
*/
public Object convert(Object source, Class targetClass) throws ConversionException {
return convert(source, targetClass, null);
}
/**
* Convenience convert method that converts the provided source to the first
* target object supported by this converter. Useful when a converter only
* supports conversion to a single target.
* @param source the source to convert
* @param context the conversion context, useful for influencing the
* behavior of the converter
* @return the converted object
* @throws ConversionException an exception occured converting the source
* value
*/
public Object convert(Object source, ConversionContext context) throws ConversionException {
return convert(source, getTargetClasses()[0], context);
}
public Object convert(Object source, Class targetClass, ConversionContext context) throws ConversionException {
try {
return doConvert(source, targetClass, context);
}
catch (ConversionException e) {
throw e;
}
catch (Throwable e) {
// wrap in a ConversionException
if (targetClass == null) {
targetClass = getTargetClasses()[0];
}
throw new ConversionException(source, targetClass, e);
}
}
/**
* Template method subclasses should override to actually perform the type
* conversion.
* @param source the source to convert from
* @param targetClass the target type to convert to
* @param context an optional conversion context that may be used to
* influence the conversion process, could be null
* @return the converted source value
* @throws Exception an exception occured, will be wrapped in a conversion
* exception if necessary
*/
protected abstract Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception;
}

View File

@@ -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.binding.convert.support;
import org.springframework.binding.format.FormatterFactory;
/**
* A converter that delegates to a formatter to perform the conversion.
* Formatters are typically not thread safe, so we use a FormatterFactory that
* is expected to provide us with thread-safe instances as necessary.
*
* @author Keith Donald
*/
public abstract class AbstractFormattingConverter extends AbstractConverter {
/**
* The formatter factory.
*/
private FormatterFactory formatterFactory;
/**
* Creates a new converter that delegates to a formatter.
* @param formatterFactory the factory to use
*/
protected AbstractFormattingConverter(FormatterFactory formatterFactory) {
setFormatterFactory(formatterFactory);
}
protected FormatterFactory getFormatterFactory() {
return formatterFactory;
}
public void setFormatterFactory(FormatterFactory formatterSource) {
this.formatterFactory = formatterSource;
}
}

View File

@@ -0,0 +1,118 @@
/*
* 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.binding.convert.support;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.springframework.binding.convert.ConversionException;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.binding.convert.ConversionService;
import org.springframework.util.Assert;
/**
* A conversion service that delegates to an ordered chain of other conversion
* services. The first correct reply received from a conversion service in
* the chain is returned to the caller.
*
* @author Erwin Vervaet
*/
public class CompositeConversionService implements ConversionService {
private ConversionService[] chain;
/**
* Create a new composite conversion service.
* @param conversionServices the conversion services in the chain
*/
public CompositeConversionService(ConversionService[] conversionServices) {
Assert.notNull(conversionServices, "The conversion services chain is required");
this.chain = conversionServices;
}
/**
* Returns the conversion services in the chain managed by this
* composite conversion service.
*/
public ConversionService[] getConversionServices() {
return chain;
}
public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass)
throws ConversionException {
for (int i = 0; i < chain.length; i++) {
try {
return chain[i].getConversionExecutor(sourceClass, targetClass);
}
catch (ConversionException e) {
// ignore and try the next conversion service in the chain
}
}
throw new ConversionException(sourceClass, targetClass,
"No converter registered to convert from sourceClass '" + sourceClass +
"' to target class '" + targetClass + "'");
}
public ConversionExecutor getConversionExecutorByTargetAlias(Class sourceClass, String targetAlias)
throws ConversionException {
boolean exceptionThrown = false;
for (int i = 0; i < chain.length; i++) {
try {
ConversionExecutor res = chain[i].getConversionExecutorByTargetAlias(sourceClass, targetAlias);
if (res != null) {
return res;
}
}
catch (ConversionException e) {
exceptionThrown = true;
}
}
if (exceptionThrown) {
throw new ConversionException(sourceClass,
"No converter registered to convert from sourceClass '" + sourceClass +
"' to aliased target type '" + targetAlias + "'");
}
else {
// alias was not recognized by any conversion service in the chain
return null;
}
}
public ConversionExecutor[] getConversionExecutorsForSource(Class sourceClass)
throws ConversionException {
Set executors = new HashSet();
for (int i = 0; i < chain.length; i++) {
executors.addAll(Arrays.asList(chain[i].getConversionExecutorsForSource(sourceClass)));
}
return (ConversionExecutor[])executors.toArray(new ConversionExecutor[executors.size()]);
}
public Class getClassByAlias(String alias) throws ConversionException {
for (int i = 0; i < chain.length; i++) {
try {
Class res = chain[i].getClassByAlias(alias);
if (res != null) {
return res;
}
}
catch (ConversionException e) {
// ignore and try the next conversion service in the chain
}
}
return null;
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.binding.convert.support;
import org.springframework.binding.convert.ConversionService;
/**
* Marker interface that denotes an object has a dependency on a conversion
* service that is expected to be fulfilled.
*
* @author Keith Donald
*/
public interface ConversionServiceAware {
/**
* Set the conversion service this object should be made aware of (as it
* presumably depends on it).
*
* @param conversionService the conversion service
*/
public void setConversionService(ConversionService conversionService);
}

View File

@@ -0,0 +1,104 @@
/*
* 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.binding.convert.support;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.expression.Expression;
/**
* Base class for converters that use other converters to convert things, thus
* they are conversion-service aware.
*
* @author Keith Donald
*/
public abstract class ConversionServiceAwareConverter extends AbstractConverter implements ConversionServiceAware {
/**
* The conversion service this converter is aware of.
*/
private ConversionService conversionService;
/**
* Default constructor, expectes to conversion service to be injected
* using {@link #setConversionService(ConversionService)}.
*/
protected ConversionServiceAwareConverter() {
}
/**
* Create a converter using given conversion service.
*/
protected ConversionServiceAwareConverter(ConversionService conversionService) {
setConversionService(conversionService);
}
/**
* Returns the conversion service used.
*/
public ConversionService getConversionService() {
if (conversionService == null) {
throw new IllegalStateException("Conversion service not yet set: set it first before calling this method");
}
return conversionService;
}
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
/**
* Returns a conversion executor capable of converting string objects to the
* specified target class.
* @param targetClass the target class
* @return the conversion executor, never null
*/
protected ConversionExecutor fromStringTo(Class targetClass) {
return getConversionService().getConversionExecutor(String.class, targetClass);
}
/**
* Returns a conversion executor capable of converting string objects to the
* target class aliased by the provided alias.
* @param targetAlias the target class alias, e.g "long" or "float"
* @return the conversion executor, or <code>null</code> if no suitable
* converter exists for alias
*/
protected ConversionExecutor fromStringToAliased(String targetAlias) {
return getConversionService().getConversionExecutorByTargetAlias(String.class, targetAlias);
}
/**
* Returns a conversion executor capable of converting objects from one
* class to another.
* @param sourceClass the source class to convert from
* @param targetClass the target class to convert to
* @return the conversion executor, never null
*/
protected ConversionExecutor converterFor(Class sourceClass, Class targetClass) {
return getConversionService().getConversionExecutor(sourceClass, targetClass);
}
/**
* Helper that parsers the given expression string into an expression, using
* the installed String-&gt;Expression converter.
* @param expressionString the expression string to parse
* @return the parsed, evaluatable expression
*/
protected Expression parseExpression(String expressionString) {
return (Expression)fromStringTo(Expression.class).execute(expressionString);
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.binding.convert.support;
import java.beans.PropertyEditorSupport;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.util.Assert;
/**
* Adapts a Converter to the PropertyEditor interface.
* <p>
* Note: with a converter, only forward conversion from-string-to-value is
* supported. Value-to-string conversion is not supported. If you need this
* capability, use a Formatter with a FormatterPropertyEditor adapter.
*
* @see org.springframework.binding.format.Formatter
* @see org.springframework.binding.format.support.FormatterPropertyEditor
*
* @author Keith Donald
*/
public class ConverterPropertyEditorAdapter extends PropertyEditorSupport {
private ConversionExecutor conversionExecutor;
/**
* Adapt given conversion executor to the PropertyEditor contract.
*/
public ConverterPropertyEditorAdapter(ConversionExecutor conversionExecutor) {
Assert.notNull(conversionExecutor, "A conversion executor is required");
Assert.isTrue(conversionExecutor.getSourceClass().equals(String.class),
"A string conversion executor is required");
this.conversionExecutor = conversionExecutor;
}
/**
* Returns the type strings will be converted to.
*/
public Class getTargetClass() {
return conversionExecutor.getTargetClass();
}
public void setAsText(String text) throws IllegalArgumentException {
setValue(conversionExecutor.execute(text));
}
public String getAsText() {
throw new UnsupportedOperationException();
}
}

View File

@@ -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.binding.convert.support;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.binding.convert.ConversionService;
import org.springframework.util.Assert;
/**
* Registers all 'from string' converters known to a conversion service with
* a Spring bean factory.
* <p>
* Acts as bean factory post processor, registering property editor adapters for
* each supported conversion with a <code>java.lang.String sourceClass</code>.
* This makes for very convenient use with the Spring container.
*
* @author Keith Donald
*/
public class CustomConverterConfigurer implements BeanFactoryPostProcessor, InitializingBean {
private ConversionService conversionService;
/**
* Create a new configurer.
* @param conversionService the conversion service to take converters from
*/
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(conversionService, "The conversion service is required");
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
ConversionExecutor[] executors = conversionService.getConversionExecutorsForSource(String.class);
for (int i = 0; i < executors.length; i++) {
ConverterPropertyEditorAdapter editor = new ConverterPropertyEditorAdapter(executors[i]);
beanFactory.registerCustomEditor(editor.getTargetClass(), editor);
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.binding.convert.support;
import java.math.BigDecimal;
import java.math.BigInteger;
import org.springframework.binding.format.support.SimpleFormatterFactory;
import org.springframework.core.enums.LabeledEnum;
/**
* Default, local implementation of a conversion service. Will automatically
* register <i>from string</i> converters for a number of standard Java
* types like Class, Number, Boolean and so on.
*
* @author Keith Donald
*/
public class DefaultConversionService extends GenericConversionService {
/**
* Creates a new default conversion service, installing the default
* converters.
*/
public DefaultConversionService() {
addDefaultConverters();
}
/**
* Add all default converters to the conversion service.
*/
protected void addDefaultConverters() {
addConverter(new TextToClass());
addConverter(new TextToNumber(new SimpleFormatterFactory()));
addConverter(new TextToBoolean());
addConverter(new TextToLabeledEnum());
addDefaultAlias(String.class);
addDefaultAlias(Short.class);
addDefaultAlias(Integer.class);
addAlias("int", Integer.class);
addDefaultAlias(Byte.class);
addDefaultAlias(Long.class);
addDefaultAlias(Float.class);
addDefaultAlias(Double.class);
addDefaultAlias(BigInteger.class);
addDefaultAlias(BigDecimal.class);
addDefaultAlias(Boolean.class);
addDefaultAlias(Class.class);
addDefaultAlias(LabeledEnum.class);
}
}

View File

@@ -0,0 +1,300 @@
/*
* 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.binding.convert.support;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import org.springframework.binding.convert.ConversionException;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.convert.Converter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Base implementation of a conversion service. Initially empty, e.g. no converters
* are registered by default.
*
* @author Keith Donald
*/
public class GenericConversionService implements ConversionService {
/**
* An indexed map of converters. Each entry key is a source class that can
* be converted from, and each entry value is a map of target classes that
* can be convertered to, ultimately mapping to a specific converter that
* can perform the source->target conversion.
*/
private Map sourceClassConverters = new HashMap();
/**
* A map of string aliases to convertible classes. Allows lookup of
* converters by alias.
*/
private Map aliasMap = new HashMap();
/**
* An optional parent conversion service.
*/
private ConversionService parent;
/**
* Returns the parent of this conversion service. Could be null.
*/
public ConversionService getParent() {
return parent;
}
/**
* Set the parent of this conversion service. This is optional.
*/
public void setParent(ConversionService parent) {
this.parent = parent;
}
/**
* Add given converter to this conversion service. If the converter is
* {@link ConversionServiceAware}, it will get the conversion service
* injected.
*/
public void addConverter(Converter converter) {
Class[] sourceClasses = converter.getSourceClasses();
Class[] targetClasses = converter.getTargetClasses();
for (int i = 0; i < sourceClasses.length; i++) {
Class sourceClass = sourceClasses[i];
Map sourceMap = (Map)sourceClassConverters.get(sourceClass);
if (sourceMap == null) {
sourceMap = new HashMap();
sourceClassConverters.put(sourceClass, sourceMap);
}
for (int j = 0; j < targetClasses.length; j++) {
Class targetClass = targetClasses[j];
sourceMap.put(targetClass, converter);
}
}
if (converter instanceof ConversionServiceAware) {
((ConversionServiceAware)converter).setConversionService(this);
}
}
/**
* Add all given converters. If the converters are
* {@link ConversionServiceAware}, they will get the conversion service
* injected.
*/
public void addConverters(Converter[] converters) {
for (int i = 0; i < converters.length; i++) {
addConverter(converters[i]);
}
}
/**
* Add given converter with an alias to the conversion service. If the
* converter is {@link ConversionServiceAware}, it will get the conversion
* service injected.
*/
public void addConverter(Converter converter, String alias) {
aliasMap.put(alias, converter);
addConverter(converter);
}
/**
* Add an alias for given target type.
*/
public void addAlias(String alias, Class targetType) {
aliasMap.put(alias, targetType);
}
/**
* Generate a conventions based alias for given target type. For instance,
* "java.lang.Boolean" will get the "boolean" alias.
*/
public void addDefaultAlias(Class targetType) {
addAlias(StringUtils.uncapitalize(ClassUtils.getShortName(targetType)), targetType);
}
public ConversionExecutor getConversionExecutor(Class sourceClass, Class targetClass) throws ConversionException {
Assert.notNull(sourceClass, "The source class to convert from is required");
Assert.notNull(targetClass, "The target class to convert to is required");
if (this.sourceClassConverters == null || this.sourceClassConverters.isEmpty()) {
throw new IllegalStateException("No converters have been added to this service's registry");
}
if (sourceClass.equals(targetClass)) {
return new ConversionExecutor(sourceClass, targetClass, new NoOpConverter(sourceClass, targetClass));
}
Map sourceTargetConverters = findConvertersForSource(sourceClass);
Converter converter = findTargetConverter(sourceTargetConverters, targetClass);
if (converter != null) {
// we found a converter
return new ConversionExecutor(sourceClass, targetClass, converter);
}
else {
if (parent != null) {
// try the parent
return parent.getConversionExecutor(sourceClass, targetClass);
}
else {
throw new ConversionException(sourceClass, targetClass,
"No converter registered to convert from sourceClass '" + sourceClass +
"' to target class '" + targetClass + "'");
}
}
}
public ConversionExecutor getConversionExecutorByTargetAlias(Class sourceClass, String alias)
throws IllegalArgumentException {
Assert.notNull(sourceClass, "The source class to convert from is required");
Assert.hasText(alias, "The target alias is required and must either be a type alias (e.g 'boolean') "
+ "or a generic converter alias (e.g. 'bean') ");
Object targetType = aliasMap.get(alias);
if (targetType == null) {
if (parent != null) {
// try the parent
return parent.getConversionExecutorByTargetAlias(sourceClass, alias);
}
else {
// not aliased
return null;
}
}
else if (targetType instanceof Class) {
return getConversionExecutor(sourceClass, (Class)targetType);
}
else {
Assert.isInstanceOf(Converter.class, targetType, "Not a converter: ");
Converter conv = (Converter)targetType;
return new ConversionExecutor(sourceClass, Object.class, conv);
}
}
public ConversionExecutor[] getConversionExecutorsForSource(Class sourceClass) {
Assert.notNull(sourceClass, "The source class to convert from is required");
Map sourceTargetConverters = findConvertersForSource(sourceClass);
if (sourceTargetConverters.isEmpty()) {
if (parent != null) {
// use the parent
return parent.getConversionExecutorsForSource(sourceClass);
}
else {
// no converters for source class
return new ConversionExecutor[0];
}
}
else {
Set executors = new HashSet();
if (parent != null) {
executors.addAll(Arrays.asList(parent.getConversionExecutorsForSource(sourceClass)));
}
Iterator it = sourceTargetConverters.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
executors.add(new ConversionExecutor(sourceClass, (Class)entry.getKey(), (Converter)entry.getValue()));
}
return (ConversionExecutor[])executors.toArray(new ConversionExecutor[executors.size()]);
}
}
public Class getClassByAlias(String alias) {
Assert.hasText(alias, "The alias is required and must be a type alias (e.g 'boolean')");
Object clazz = aliasMap.get(alias);
if (clazz != null) {
Assert.isInstanceOf(Class.class, clazz, "Not a Class alias '" + alias + "': ");
return (Class)clazz;
}
else {
if (parent != null) {
// try parent service
return parent.getClassByAlias(alias);
}
else {
// alias does not index a class, return null
return null;
}
}
}
// internal helpers
private Map findConvertersForSource(Class sourceClass) {
LinkedList classQueue = new LinkedList();
classQueue.addFirst(sourceClass);
while (!classQueue.isEmpty()) {
sourceClass = (Class)classQueue.removeLast();
Map sourceTargetConverters = (Map)sourceClassConverters.get(sourceClass);
if (sourceTargetConverters != null && !sourceTargetConverters.isEmpty()) {
return sourceTargetConverters;
}
if (!sourceClass.isInterface() && (sourceClass.getSuperclass() != null)) {
classQueue.addFirst(sourceClass.getSuperclass());
}
// queue up source class's implemented interfaces.
Class[] interfaces = sourceClass.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
classQueue.addFirst(interfaces[i]);
}
}
return Collections.EMPTY_MAP;
}
private Converter findTargetConverter(Map sourceTargetConverters, Class targetClass) {
LinkedList classQueue = new LinkedList();
classQueue.addFirst(targetClass);
while (!classQueue.isEmpty()) {
targetClass = (Class)classQueue.removeLast();
Converter converter = (Converter)sourceTargetConverters.get(targetClass);
if (converter != null) {
return converter;
}
if (!targetClass.isInterface() && (targetClass.getSuperclass() != null)) {
classQueue.addFirst(targetClass.getSuperclass());
}
// queue up target class's implemented interfaces.
Class[] interfaces = targetClass.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
classQueue.addFirst(interfaces[i]);
}
}
return null;
}
// subclassing support
/**
* Returns an indexed map of converters. Each entry key is a source class that
* can be converted from, and each entry value is a map of target classes that
* can be convertered to, ultimately mapping to a specific converter that can
* perform the source->target conversion.
*/
protected Map getSourceClassConverters() {
return sourceClassConverters;
}
/**
* Returns a map of known aliases. Each entry key is a String alias and the
* associated value is either a target class or a converter.
*/
protected Map getAliasMap() {
return aliasMap;
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.binding.convert.support;
import org.springframework.binding.convert.ConversionContext;
/**
* Package private converter that is a "no op".
*
* @author Keith Donald
*/
class NoOpConverter extends AbstractConverter {
private Class sourceClass;
private Class targetClass;
/**
* Create a "no op" converter from given source to given target class.
*/
public NoOpConverter(Class sourceClass, Class targetClass) {
this.sourceClass = sourceClass;
this.targetClass = targetClass;
}
protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception {
return source;
}
public Class[] getSourceClasses() {
return new Class[] { sourceClass };
}
public Class[] getTargetClasses() {
return new Class[] { targetClass };
}
}

View File

@@ -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.binding.convert.support;
import org.springframework.binding.convert.ConversionContext;
import org.springframework.util.StringUtils;
/**
* Converts a textual representation of a boolean object to a <code>Boolean</code>
* instance.
*
* @author Keith Donald
*/
public class TextToBoolean extends AbstractConverter {
private static final String VALUE_TRUE = "true";
private static final String VALUE_FALSE = "false";
private static final String VALUE_ON = "on";
private static final String VALUE_OFF = "off";
private static final String VALUE_YES = "yes";
private static final String VALUE_NO = "no";
private static final String VALUE_1 = "1";
private static final String VALUE_0 = "0";
private String trueString;
private String falseString;
/**
* Default constructor. No special true or false strings are considered.
*/
public TextToBoolean() {
this(null, null);
}
/**
* Create a text to boolean converter. Take given <i>special</i> string representations
* of true and false into account.
* @param trueString special true string to consider
* @param falseString special false string to consider
*/
public TextToBoolean(String trueString, String falseString) {
this.trueString = trueString;
this.falseString = falseString;
}
public Class[] getSourceClasses() {
return new Class[] { String.class };
}
public Class[] getTargetClasses() {
return new Class[] { Boolean.class };
}
protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception {
String text = (String)source;
if (!StringUtils.hasText(text)) {
return null;
}
else if (this.trueString != null && text.equalsIgnoreCase(this.trueString)) {
return Boolean.TRUE;
}
else if (this.falseString != null && text.equalsIgnoreCase(this.falseString)) {
return Boolean.FALSE;
}
else if (this.trueString == null
&& (text.equalsIgnoreCase(VALUE_TRUE) || text.equalsIgnoreCase(VALUE_ON)
|| text.equalsIgnoreCase(VALUE_YES) || text.equals(VALUE_1))) {
return Boolean.TRUE;
}
else if (this.falseString == null
&& (text.equalsIgnoreCase(VALUE_FALSE) || text.equalsIgnoreCase(VALUE_OFF)
|| text.equalsIgnoreCase(VALUE_NO) || text.equals(VALUE_0))) {
return Boolean.FALSE;
}
else {
throw new IllegalArgumentException("Invalid boolean value [" + text + "]");
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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.binding.convert.support;
import org.springframework.binding.convert.ConversionContext;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Converts a textual representation of a class object to a <code>Class</code>
* instance.
*
* @author Keith Donald
*/
public class TextToClass extends ConversionServiceAwareConverter {
private static final String ALIAS_PREFIX = "type:";
private static final String CLASS_PREFIX = "class:";
public Class[] getSourceClasses() {
return new Class[] { String.class };
}
public Class[] getTargetClasses() {
return new Class[] { Class.class };
}
protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception {
String text = (String)source;
if (StringUtils.hasText(text)) {
String classNameOrAlias = text.trim();
if (classNameOrAlias.startsWith(CLASS_PREFIX)) {
return ClassUtils.forName(text.substring(CLASS_PREFIX.length()));
}
else if (classNameOrAlias.startsWith(ALIAS_PREFIX)) {
String alias = text.substring(ALIAS_PREFIX.length());
Class clazz = getConversionService().getClassByAlias(alias);
Assert.notNull(clazz, "No class found associated with type alias '" + alias + "'");
return clazz;
}
else {
// try first an aliased based lookup
if (getConversionService() != null) {
Class aliasedClass = getConversionService().getClassByAlias(text);
if (aliasedClass != null) {
return aliasedClass;
}
}
// treat as a class name
return ClassUtils.forName(classNameOrAlias);
}
}
else {
return null;
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.binding.convert.support;
import org.springframework.binding.convert.ConversionContext;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.binding.expression.SettableExpression;
import org.springframework.binding.expression.support.StaticExpression;
import org.springframework.util.Assert;
/**
* Converter that converts a String into an Expression object.
*
* @see org.springframework.binding.expression.Expression
* @see org.springframework.binding.expression.SettableExpression
*
* @author Erwin Vervaet
*/
public class TextToExpression extends AbstractConverter {
/**
* The expression string parser.
*/
private ExpressionParser expressionParser;
/**
* Creates a new string-to-expression converter.
* @param expressionParser the expression string parser
*/
public TextToExpression(ExpressionParser expressionParser) {
Assert.notNull(expressionParser, "The expression parser is required");
this.expressionParser = expressionParser;
}
/**
* Returns the expression parser used by this converter.
*/
public ExpressionParser getExpressionParser() {
return expressionParser;
}
public Class[] getSourceClasses() {
return new Class[] { String.class };
}
public Class[] getTargetClasses() {
return new Class[] { Expression.class, SettableExpression.class };
}
protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception {
String expressionString = (String)source;
if (getExpressionParser().isDelimitedExpression(expressionString)) {
return getExpressionParser().parseExpression((String)source);
}
else {
return new StaticExpression(expressionString);
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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.binding.convert.support;
import org.springframework.binding.convert.ConversionContext;
import org.springframework.binding.format.support.LabeledEnumFormatter;
import org.springframework.core.enums.LabeledEnum;
/**
* Converter that converts textual representations of enum
* instances to a specific instance of <code>LabeledEnum</code>.
*
* @author Keith Donald
*/
public class TextToLabeledEnum extends AbstractConverter {
private LabeledEnumFormatter labeledEnumFormatter = new LabeledEnumFormatter();
public Class[] getSourceClasses() {
return new Class[] { String.class };
}
public Class[] getTargetClasses() {
return new Class[] { LabeledEnum.class };
}
protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception {
return labeledEnumFormatter.parseValue((String)source, targetClass);
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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.binding.convert.support;
import java.math.BigDecimal;
import java.math.BigInteger;
import org.springframework.binding.convert.ConversionContext;
import org.springframework.binding.format.FormatterFactory;
import org.springframework.binding.format.support.SimpleFormatterFactory;
/**
* Converts textual representations of numbers to a <code>Number</code>
* specialization. Delegates to a synchronized formatter to parse text strings.
*
* @author Keith Donald
*/
public class TextToNumber extends AbstractFormattingConverter {
/**
* Default constructor that uses a {@link SimpleFormatterFactory}.
*/
public TextToNumber() {
super(new SimpleFormatterFactory());
}
/**
* Create a string to number converter using given formatter factory.
* @param formatterFactory the factory to use
*/
public TextToNumber(FormatterFactory formatterFactory) {
super(formatterFactory);
}
public Class[] getSourceClasses() {
return new Class[] { String.class };
}
public Class[] getTargetClasses() {
return new Class[] { Integer.class, Short.class, Byte.class, Long.class, Float.class, Double.class,
BigInteger.class, BigDecimal.class };
}
protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception {
return getFormatterFactory().getNumberFormatter(targetClass).parseValue((String)source, targetClass);
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Supporting type converter implementations that are generically applicable and frequently used.
</p>
</body>
</html>

View File

@@ -0,0 +1,85 @@
/*
* 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.binding.expression;
import java.io.Serializable;
import org.springframework.core.style.ToStringCreator;
/**
* A simple holder for information about an evaluation attempt.
*
* @author Keith Donald
*/
public class EvaluationAttempt implements Serializable {
/**
* The expression that attempted to evaluate.
*/
private Expression expression;
/**
* The target object being evaluated.
*/
private Object target;
/**
* The evaluation context.
*/
private EvaluationContext context;
/**
* Create an evaluation attempt.
* @param expression the expression that failed to evaluate
* @param target the target of the expression
* @param context the context attributes that might have affected evaluation behavior
*/
public EvaluationAttempt(Expression expression, Object target, EvaluationContext context) {
this.expression = expression;
this.target = target;
this.context = context;
}
/**
* Returns the expression that attempted to evaluate.
*/
public Expression getExpression() {
return expression;
}
/**
* Returns the target object upon which evaluation was attempted.
*/
public Object getTarget() {
return target;
}
/**
* Returns context attributes that may have influenced the evaluation process.
*/
public EvaluationContext getContext() {
return context;
}
public String toString() {
return createToString(new ToStringCreator(this)).toString();
}
protected ToStringCreator createToString(ToStringCreator creator) {
return creator.append("expression", expression).append("target", target).append("context",
context);
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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.binding.expression;
import java.util.Map;
/**
* A context object with two main responsibities:
* <ol>
* <li>Exposing information to an expression to influence
* an evaluation attempt.
* <li>Providing operations for recording progress or
* errors during the expression evaluation process.
* </ol>
*
* @author Keith Donald
*/
public interface EvaluationContext {
/**
* Returns a map of attributes that can be used to influence expression evaluation.
* @return the evaluation attributes
*/
public Map getAttributes();
}

View File

@@ -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.binding.expression;
import org.springframework.core.NestedRuntimeException;
/**
* Indicates an expression evaluation failed.
*
* @author Keith Donald
*/
public class EvaluationException extends NestedRuntimeException {
/**
* The evaluation attempt that failed.
*/
private EvaluationAttempt evaluationAttempt;
/**
* Creates a new evaluation exception.
* @param evaluationAttempt the evaluation attempt that failed
* @param cause the underlying cause of this exception
*/
public EvaluationException(EvaluationAttempt evaluationAttempt, Throwable cause) {
super("Expression " + evaluationAttempt
+ " failed - make sure the expression is evaluatable on the target object", cause);
this.evaluationAttempt = evaluationAttempt;
}
/**
* Returns the evaluation attempt that failed.
*/
public EvaluationAttempt getEvaluationAttempt() {
return evaluationAttempt;
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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.binding.expression;
/**
* Evaluates a single parsed expression on the provided input object in the
* specified context. This provides a common abstraction for expression
* evaluation independent of any language like OGNL or Spring's BeanWrapper.
*
* @author Keith Donald
*/
public interface Expression {
/**
* Evaluate the expression encapsulated by this evaluator against the
* provided target object and return the result of the evaluation.
* @param target the target of the expression
* @param context the expression evaluation context
* @return the evaluation result
* @throws EvaluationException an exception occured during evaluation
*/
public Object evaluate(Object target, EvaluationContext context) throws EvaluationException;
}

View File

@@ -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.binding.expression;
/**
* Parses expression strings, returing a configured evaluator instance capable
* of performing parsed expression evaluation in a thread safe way.
*
* @author Keith Donald
*/
public interface ExpressionParser {
/**
* Is this expression string delimited in a manner that indicates it is a
* parseable expression? For example "${expression}".
* @param expressionString the proposed expression string
* @return true if yes, false if not
*/
public boolean isDelimitedExpression(String expressionString);
/**
* Parse the provided expression string, returning an evaluator capable of
* evaluating it against input.
* @param expressionString the parseable expression string
* @return the evaluator for the parsed expression
* @throws ParserException an exception occured during parsing
*/
public Expression parseExpression(String expressionString) throws ParserException;
/**
* Parse the provided settable expression string, returning an evaluator
* capable of evaluating its value as well as setting its value.
* @param expressionString the parseable expression string
* @return the evaluator for the parsed expression
* @throws ParserException an exception occured during parsing
* @throws UnsupportedOperationException this parser does not support
* settable expressions
*/
public SettableExpression parseSettableExpression(String expressionString) throws ParserException,
UnsupportedOperationException;
}

View File

@@ -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.binding.expression;
import org.springframework.core.NestedRuntimeException;
/**
* Base class for exceptions thrown during expression parsing.
*
* @author Keith Donald
*/
public class ParserException extends NestedRuntimeException {
/**
* The expression string that could not be parsed.
*/
private String expressionString;
/**
* Creates a new expression parsing exception.
* @param expressionString the expression string that could not be parsed
* @param cause the underlying cause of this exception
*/
public ParserException(String expressionString, Throwable cause) {
this(expressionString, "Unable to parse expression string '" + expressionString + "'", cause);
}
/**
* Creates a new expression parsing exception.
* @param expressionString the expression string that could not be parsed
* @param message a descriptive message
* @param cause the underlying cause of this exception
*/
public ParserException(String expressionString, String message, Throwable cause) {
super(message, cause);
this.expressionString = expressionString;
}
/**
* Returns the expression string that could not be parsed.
*/
public Object getExpressionString() {
return expressionString;
}
}

View File

@@ -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.binding.expression;
import org.springframework.core.style.ToStringCreator;
/**
* Records an attempt to set an expression value.
*
* @author Keith Donald
*/
public class SetValueAttempt extends EvaluationAttempt {
/**
* The new value.
*/
private Object value;
/**
* Creates a new set attempt.
* @param expression the settable expression
* @param target the target of the expression
* @param value the value that was attempted to be set
* @param context context attributes that may have influenced the evaluation and set process
*/
public SetValueAttempt(SettableExpression expression, Object target, Object value, EvaluationContext context) {
super(expression, target, context);
this.value = value;
}
/**
* Returns the value that was attempted to be set.
*/
public Object getValue() {
return value;
}
protected ToStringCreator createToString(ToStringCreator creator) {
return super.createToString(creator).append("value", value);
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.binding.expression;
/**
* An evaluator that is capable of setting a value on a target object at the
* path defined by this expression.
*
* @author Keith Donald
*/
public interface SettableExpression extends Expression {
/**
* Evaluate this expression against the target object to set its value to
* the value provided.
* @param target the target object
* @param value the new value to be set
* @param context the evaluation context
* @throws EvaluationException an exception occured during evaluation
*/
public void evaluateToSet(Object target, Object value, EvaluationContext context) throws EvaluationException;
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Core expression language abstraction for parsing and evaluating expressions.
</p>
</body>
</html>

View File

@@ -0,0 +1,185 @@
/*
* 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.binding.expression.support;
import java.util.LinkedList;
import java.util.List;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.binding.expression.ParserException;
import org.springframework.binding.expression.SettableExpression;
import org.springframework.util.StringUtils;
/**
* Abstract base class for expression parsers.
*
* @author Keith Donald
*/
public abstract class AbstractExpressionParser implements ExpressionParser {
/**
* The expression prefix.
*/
private static final String DEFAULT_EXPRESSION_PREFIX = "${";
/**
* The expression suffix.
*/
private static final String DEFAULT_EXPRESSION_SUFFIX = "}";
/**
* The marked expression delimter prefix.
*/
private String expressionPrefix = DEFAULT_EXPRESSION_PREFIX;
/**
* The marked expression delimiter suffix.
*/
private String expressionSuffix = DEFAULT_EXPRESSION_SUFFIX;
/**
* Returns the configured expression delimiter prefix. Defaults to "${".
*/
public String getExpressionPrefix() {
return expressionPrefix;
}
/**
* Sets the expression delimiter prefix.
*/
public void setExpressionPrefix(String expressionPrefix) {
this.expressionPrefix = expressionPrefix;
}
/**
* Returns the expression delimiter suffix. Defaults to "}".
*/
public String getExpressionSuffix() {
return expressionSuffix;
}
/**
* Sets the expression delimiter suffix.
*/
public void setExpressionSuffix(String expressionSuffix) {
this.expressionSuffix = expressionSuffix;
}
/**
* Check whether or not given criteria are expressed as an expression.
*/
public boolean isDelimitedExpression(String expressionString) {
int prefixIndex = expressionString.indexOf(getExpressionPrefix());
if (prefixIndex == -1) {
return false;
}
int suffixIndex = expressionString.indexOf(getExpressionSuffix(), prefixIndex);
if (suffixIndex == -1) {
return false;
}
else {
if (suffixIndex == prefixIndex + getExpressionPrefix().length()) {
return false;
}
else {
return true;
}
}
}
public final Expression parseExpression(String expressionString) throws ParserException {
Expression[] expressions = parseExpressions(expressionString);
if (expressions.length == 1) {
return expressions[0];
}
else {
return new CompositeStringExpression(expressions);
}
}
public abstract SettableExpression parseSettableExpression(String expressionString) throws ParserException,
UnsupportedOperationException;
/**
* Helper that parses given expression string using the configured parser.
* The expression string can contain any number of expressions all contained
* in "${...}" markers. For instance: "foo${expr0}bar${expr1}". The static
* pieces of text will also be returned as Expressions that just return that
* static piece of text. As a result, evaluating all returned expressions
* and concating the results produces the complete evaluated string.
* @param expressionString the expression string
* @return the parsed expressions
* @throws ParserException when the expressions cannot be parsed
*/
private Expression[] parseExpressions(String expressionString) throws ParserException {
List expressions = new LinkedList();
if (StringUtils.hasText(expressionString)) {
int startIdx = 0;
while (startIdx < expressionString.length()) {
int prefixIndex = expressionString.indexOf(getExpressionPrefix(), startIdx);
if (prefixIndex >= startIdx) {
// an expression was found
if (prefixIndex > startIdx) {
expressions.add(new StaticExpression(expressionString.substring(startIdx, prefixIndex)));
startIdx = prefixIndex;
}
int suffixIndex = expressionString.indexOf(getExpressionSuffix(), prefixIndex);
if (suffixIndex == -1) {
throw new ParserException(expressionString, "No ending suffix '" + getExpressionSuffix()
+ "' for expression starting at character " + prefixIndex + ": "
+ expressionString.substring(prefixIndex), null);
}
else if (suffixIndex == prefixIndex + getExpressionPrefix().length()) {
throw new ParserException(expressionString, "No expression defined within delimiter '"
+ getExpressionPrefix() + getExpressionSuffix() + "' at character " + prefixIndex,
null);
}
else {
String expr = expressionString.substring(prefixIndex + getExpressionPrefix().length(),
suffixIndex);
expressions.add(doParseExpression(expr));
startIdx = suffixIndex + 1;
}
}
else {
if (startIdx == 0) {
// treat entire string as one expression
expressions.add(doParseExpression(expressionString));
}
else {
// no more ${expressions} found in string
expressions.add(new StaticExpression(expressionString.substring(startIdx)));
}
startIdx = expressionString.length();
}
}
}
else {
expressions.add(new StaticExpression(expressionString));
}
return (Expression[]) expressions.toArray(new Expression[expressions.size()]);
}
/**
* Template method for parsing a filtered expression string. Subclasses should
* override.
* @param expressionString the expression string
* @return the parsed expression
*/
protected abstract Expression doParseExpression(String expressionString);
}

View File

@@ -0,0 +1,77 @@
/*
* 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.binding.expression.support;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.BeansException;
import org.springframework.binding.expression.EvaluationAttempt;
import org.springframework.binding.expression.EvaluationContext;
import org.springframework.binding.expression.EvaluationException;
import org.springframework.binding.expression.SetValueAttempt;
import org.springframework.binding.expression.SettableExpression;
import org.springframework.util.Assert;
/**
* An expression evaluator that uses the Spring bean wrapper.
*
* @author Keith Donald
*/
class BeanWrapperExpression implements SettableExpression {
/**
* The expression.
*/
private String expression;
public BeanWrapperExpression(String expression) {
this.expression = expression;
}
public int hashCode() {
return expression.hashCode();
}
public boolean equals(Object o) {
if (!(o instanceof BeanWrapperExpression)) {
return false;
}
BeanWrapperExpression other = (BeanWrapperExpression)o;
return expression.equals(other.expression);
}
public Object evaluate(Object target, EvaluationContext context) throws EvaluationException {
try {
return new BeanWrapperImpl(target).getPropertyValue(expression);
}
catch (BeansException e) {
throw new EvaluationException(new EvaluationAttempt(this, target, context), e);
}
}
public void evaluateToSet(Object target, Object value, EvaluationContext context) throws EvaluationException {
try {
Assert.notNull(target, "The target object to evaluate is required");
new BeanWrapperImpl(target).setPropertyValue(expression, value);
}
catch (BeansException e) {
throw new EvaluationException(new SetValueAttempt(this, target, value, context), e);
}
}
public String toString() {
return expression;
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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.binding.expression.support;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ParserException;
import org.springframework.binding.expression.SettableExpression;
/**
* An expression parser that parses bean wrapper expressions.
*
* @author Keith
*/
public class BeanWrapperExpressionParser extends AbstractExpressionParser {
protected Expression doParseExpression(String expressionString) throws ParserException {
return parseSettableExpression(expressionString);
}
public SettableExpression parseSettableExpression(String expressionString) throws ParserException {
return new BeanWrapperExpression(expressionString);
}
}

View File

@@ -0,0 +1,68 @@
/*
* 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.binding.expression.support;
import java.util.Collection;
import org.springframework.binding.expression.EvaluationContext;
import org.springframework.binding.expression.EvaluationException;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.SetValueAttempt;
import org.springframework.binding.expression.SettableExpression;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
/**
* A settable expression that adds non-null values to a collection.
*
* @author Keith Donald
*/
public class CollectionAddingExpression implements SettableExpression {
/**
* The expression that resolves a mutable collection reference.
*/
private Expression collectionExpression;
/**
* Creates a collection adding property expression.
* @param collectionExpression the collection expression
*/
public CollectionAddingExpression(Expression collectionExpression) {
this.collectionExpression = collectionExpression;
}
public Object evaluate(Object target, EvaluationContext context) throws EvaluationException {
return collectionExpression.evaluate(target, context);
}
public void evaluateToSet(Object target, Object value, EvaluationContext context) throws EvaluationException {
Object result = evaluate(target, context);
if (result == null) {
throw new EvaluationException(new SetValueAttempt(this, target, value, null),
new IllegalArgumentException("The collection expression evaluated to a [null] reference"));
}
Assert.isInstanceOf(Collection.class, result, "Not a collection: ");
if (value != null) {
// add the value to the collection
((Collection)result).add(value);
}
}
public String toString() {
return new ToStringCreator(this).append("collectionExpression", collectionExpression).toString();
}
}

View File

@@ -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.binding.expression.support;
import org.springframework.binding.expression.EvaluationContext;
import org.springframework.binding.expression.EvaluationException;
import org.springframework.binding.expression.Expression;
import org.springframework.core.style.ToStringCreator;
/**
* Evaluates an array of expressions to build a concatenated string.
*
* @author Keith Donald
*/
public class CompositeStringExpression implements Expression {
/**
* The expression array.
*/
private Expression[] expressions;
/**
* Creates a new composite string expression.
* @param expressions the ordered set of expressions that when evaluated
* will have their results stringed together to build the composite string
*/
public CompositeStringExpression(Expression[] expressions) {
this.expressions = expressions;
}
public Object evaluate(Object target, EvaluationContext evaluationContext) throws EvaluationException {
StringBuffer buffer = new StringBuffer(128);
for (int i = 0; i < expressions.length; i++) {
buffer.append(expressions[i].evaluate(target, evaluationContext));
}
return buffer.toString();
}
public String toString() {
return new ToStringCreator(this).append("expressions", expressions).toString();
}
}

View File

@@ -0,0 +1,95 @@
/*
* 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.binding.expression.support;
import java.util.Collections;
import java.util.Map;
import ognl.Ognl;
import ognl.OgnlException;
import org.springframework.binding.expression.EvaluationAttempt;
import org.springframework.binding.expression.EvaluationContext;
import org.springframework.binding.expression.EvaluationException;
import org.springframework.binding.expression.SetValueAttempt;
import org.springframework.binding.expression.SettableExpression;
import org.springframework.util.Assert;
/**
* Evaluates a parsed Ognl expression.
* <p>
* IMPLEMENTATION NOTE: Ognl 2.6.7 expression objects do not respect equality
* properly, so the equality operations defined within this class do not
* function properly.
*
* @author Keith Donald
*/
class OgnlExpression implements SettableExpression {
/**
* The expression.
*/
private Object expression;
/**
* Creates a new OGNL expression.
* @param expression the parsed expression
*/
public OgnlExpression(Object expression) {
this.expression = expression;
}
public int hashCode() {
return expression.hashCode();
}
public boolean equals(Object o) {
if (!(o instanceof OgnlExpression)) {
return false;
}
// as late as Ognl 2.6.7, their expression objects don't implement
// equals
// so this always returns false
OgnlExpression other = (OgnlExpression) o;
return expression.equals(other.expression);
}
public Object evaluate(Object target, EvaluationContext context) throws EvaluationException {
Assert.notNull(target, "The target object to evaluate is required");
Map contextAttributes = (context != null ? context.getAttributes() : Collections.EMPTY_MAP);
try {
return Ognl.getValue(expression, contextAttributes, target);
}
catch (OgnlException e) {
throw new EvaluationException(new EvaluationAttempt(this, target, context), e);
}
}
public void evaluateToSet(Object target, Object value, EvaluationContext context) {
Assert.notNull(target, "The target object to evaluate is required");
Map contextAttributes = (context != null ? context.getAttributes() : Collections.EMPTY_MAP);
try {
Ognl.setValue(expression, contextAttributes, target, value);
}
catch (OgnlException e) {
throw new EvaluationException(new SetValueAttempt(this, target, value, context), e);
}
}
public String toString() {
return expression.toString();
}
}

View File

@@ -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.binding.expression.support;
import ognl.Ognl;
import ognl.OgnlException;
import ognl.OgnlRuntime;
import ognl.PropertyAccessor;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ParserException;
import org.springframework.binding.expression.SettableExpression;
/**
* An expression parser that parses Ognl expressions.
*
* @author Keith Donald
*/
public class OgnlExpressionParser extends AbstractExpressionParser {
protected Expression doParseExpression(String expressionString) throws ParserException {
return parseSettableExpression(expressionString);
}
public SettableExpression parseSettableExpression(String expressionString) throws ParserException {
try {
return new OgnlExpression(Ognl.parseExpression(expressionString));
}
catch (OgnlException e) {
throw new ParserException(expressionString, e);
}
}
/**
* Add a property access strategy for the given class.
* @param clazz the class that contains properties needing access
* @param propertyAccessor the property access strategy
*/
public void addPropertyAccessor(Class clazz, PropertyAccessor propertyAccessor) {
OgnlRuntime.setPropertyAccessor(clazz, propertyAccessor);
}
}

View File

@@ -0,0 +1,68 @@
/*
* 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.binding.expression.support;
import org.springframework.binding.expression.EvaluationContext;
import org.springframework.binding.expression.EvaluationException;
import org.springframework.binding.expression.Expression;
import org.springframework.util.ObjectUtils;
/**
* A simple expression evaluator that just returns a fixed result on each
* evaluation.
*
* @author Keith Donald
*/
public class StaticExpression implements Expression {
/**
* The value expression.
*/
private Object value;
/**
* Create a static evaluator for the given value.
* @param value the value
*/
public StaticExpression(Object value) {
this.value = value;
}
public int hashCode() {
if (value == null) {
return 0;
}
else {
return value.hashCode();
}
}
public boolean equals(Object o) {
if (!(o instanceof StaticExpression)) {
return false;
}
StaticExpression other = (StaticExpression)o;
return ObjectUtils.nullSafeEquals(value, other.value);
}
public Object evaluate(Object target, EvaluationContext context) throws EvaluationException {
return value;
}
public String toString() {
return String.valueOf(value);
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Expression abstraction implementations, integrated with BeanWrapper and OGNL.
</p>
</body>
</html>

View File

@@ -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.binding.format;
/**
* A lightweight interface for formatting a value and parsing a value from its
* formatted form.
*
* @author Keith Donald
*/
public interface Formatter {
/**
* Format the value.
* @param value the value to format
* @return the formatted string, fit for display in a UI
* @throws IllegalArgumentException the value could not be formatted
*/
public String formatValue(Object value) throws IllegalArgumentException;
/**
* Parse the formatted string representation of a value, restoring the
* value.
* @param formattedString the formatted string representation
* @param targetClass the target class to convert the formatted value to
* @return the parsed value
* @throws InvalidFormatException the string was in an invalid form
*/
public Object parseValue(String formattedString, Class targetClass) throws InvalidFormatException;
}

View File

@@ -0,0 +1,99 @@
/*
* 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.binding.format;
import java.text.Format;
/**
* Source for shared and commonly used <code>Formatters</code>.
* <p>
* Formatters are typically not thread safe as <code>Format</code> objects
* aren't thread safe: so implementations of this service should take care to
* synchronize them as neccessary.
*
* @see Format
*
* @author Keith Donald
*/
public interface FormatterFactory {
/**
* Returns a date formatter for the encoded date format.
* @param encodedFormat the format
* @return the formatter
*/
public Formatter getDateFormatter(String encodedFormat);
/**
* Returns the default date format for the current locale.
* @return the date formatter
*/
public Formatter getDateFormatter();
/**
* Returns the date format with the specified style for the current locale.
* @param style the style
* @return the formatter
*/
public Formatter getDateFormatter(Style style);
/**
* Returns the default date/time format for the current locale.
* @return the date/time formatter
*/
public Formatter getDateTimeFormatter();
/**
* Returns the date format with the specified styles for the current locale.
* @param dateStyle the date style
* @param timeStyle the time style
* @return the formatter
*/
public Formatter getDateTimeFormatter(Style dateStyle, Style timeStyle);
/**
* Returns the default time format for the current locale.
* @return the time formatter
*/
public Formatter getTimeFormatter();
/**
* Returns the time format with the specified style for the current locale.
* @param style the style
* @return the formatter
*/
public Formatter getTimeFormatter(Style style);
/**
* Returns a number formatter for the specified class.
* @param numberClass the number class
* @return the number formatter
*/
public Formatter getNumberFormatter(Class numberClass);
/**
* Returns a percent number formatter.
* @return the percent formatter
*/
public Formatter getPercentFormatter();
/**
* Returns a currency number formatter.
* @return the currency formatter
*/
public Formatter getCurrencyFormatter();
}

View File

@@ -0,0 +1,78 @@
/*
* 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.binding.format;
import org.springframework.core.NestedRuntimeException;
/**
* Thrown when a formatted value is of the wrong form.
*
* @author Keith Donald
*/
public class InvalidFormatException extends NestedRuntimeException {
private String invalidValue;
private String expectedFormat;
/**
* Create a new invalid format exception
* @param invalidValue the invalid value
* @param expectedFormat the expected format
*/
public InvalidFormatException(String invalidValue, String expectedFormat) {
this(invalidValue, expectedFormat, (Throwable)null);
}
/**
* Create a new invalid format exception
* @param invalidValue the invalid value
* @param expectedFormat the expected format
* @param cause the underlying cause of this exception
*/
public InvalidFormatException(String invalidValue, String expectedFormat, Throwable cause) {
super("Invalid format for value " + invalidValue + "; the expected format was '" + expectedFormat + "'", cause);
this.invalidValue = invalidValue;
this.expectedFormat = expectedFormat;
}
/**
* Create a new invalid format exception
* @param invalidValue the invalid value
* @param expectedFormat the expected format
* @param message a descriptive message
* @param cause the underlying cause of this exception
*/
public InvalidFormatException(String invalidValue, String expectedFormat, String message, Throwable cause) {
super(message, cause);
this.invalidValue = invalidValue;
this.expectedFormat = expectedFormat;
}
/**
* Returns the invalid value.
*/
public String getInvalidValue() {
return invalidValue;
}
/**
* Returns the expected format.
*/
public String getExpectedFormat() {
return expectedFormat;
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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.binding.format;
import org.springframework.core.enums.StaticLabeledEnum;
/**
* Format styles.
* @author Keith Donald
*/
public class Style extends StaticLabeledEnum {
public static final Style FULL = new Style(0, "Full");
public static final Style LONG = new Style(1, "Long");
public static final Style MEDIUM = new Style(2, "Medium");
public static final Style SHORT = new Style(3, "Short");
/**
* Private constructor since this is a type-safe enum.
*/
private Style(int code, String label) {
super(code, label);
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Core services for formatting objects in string form.
</p>
</body>
</html>

View File

@@ -0,0 +1,134 @@
/*
* 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.binding.format.support;
import java.text.ParseException;
import org.springframework.binding.format.Formatter;
import org.springframework.binding.format.InvalidFormatException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Abstract base class for all formatters.
*
* @author Keith Donald
*/
public abstract class AbstractFormatter implements Formatter {
/**
* Does this formatter allow empty values?
*/
private boolean allowEmpty = true;
/**
* Constructs a formatter.
*/
protected AbstractFormatter() {
}
/**
* Constructs a formatter.
* @param allowEmpty allow formatting of empty (null or blank) values?
*/
protected AbstractFormatter(boolean allowEmpty) {
this.allowEmpty = allowEmpty;
}
/**
* Allow formatting of empty (null or blank) values?
*/
public boolean isAllowEmpty() {
return allowEmpty;
}
public final String formatValue(Object value) {
if (allowEmpty && isEmpty(value)) {
return getEmptyFormattedValue();
}
Assert.isTrue(!isEmpty(value), "Object to format cannot be empty");
return doFormatValue(value);
}
/**
* Template method subclasses should override to encapsulate formatting
* logic.
* @param value the value to format
* @return the formatted string representation
*/
protected abstract String doFormatValue(Object value);
/**
* Returns the formatted form of an empty value. Default implementation
* just returns the empty string.
*/
protected String getEmptyFormattedValue() {
return "";
}
public final Object parseValue(String formattedString, Class targetClass) throws InvalidFormatException {
try {
if (allowEmpty && isEmpty(formattedString)) {
return getEmptyValue();
}
return doParseValue(formattedString, targetClass);
}
catch (ParseException ex) {
throw new InvalidFormatException(formattedString, getExpectedFormat(targetClass), ex);
}
}
/**
* Template method subclasses should override to encapsulate parsing logic.
* @param formattedString the formatted string to parse
* @return the parsed value
* @throws InvalidFormatException an exception occured parsing
* @throws ParseException when parse exceptions occur
*/
protected abstract Object doParseValue(String formattedString, Class targetClass) throws InvalidFormatException,
ParseException;
/**
* Returns the empty value (resulting from parsing an empty input string).
* This default implementation just returns null.
*/
protected Object getEmptyValue() {
return null;
}
/**
* Returns the expected string format for the given target class.
* The default implementation just returns null.
*/
protected String getExpectedFormat(Class targetClass) {
return null;
}
/**
* Is given object <i>empty</i> (null or empty string)?
*/
protected boolean isEmpty(Object o) {
if (o == null) {
return true;
}
else if (o instanceof String) {
return !StringUtils.hasText((String)o);
}
else {
return false;
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.binding.format.support;
import java.util.Locale;
import org.springframework.binding.format.Formatter;
import org.springframework.binding.format.FormatterFactory;
import org.springframework.binding.format.Style;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.SimpleLocaleContext;
/**
* Base class for formatter factories.
*
* @author Keith Donald
*/
public abstract class AbstractFormatterFactory implements FormatterFactory {
private LocaleContext localeContext = new SimpleLocaleContext(Locale.getDefault());
private Style defaultDateStyle = Style.MEDIUM;
private Style defaultTimeStyle = Style.MEDIUM;
/**
* Set's the locale context used. Defaults to a SimpleLocaleContext holding
* the system default locale.
*/
public void setLocaleContext(LocaleContext localeContext) {
this.localeContext = localeContext;
}
/**
* Returns the locale in use.
*/
protected Locale getLocale() {
return localeContext.getLocale();
}
/**
* Returns the default date style. Defaults to {@link Style#MEDIUM}.
*/
protected Style getDefaultDateStyle() {
return defaultDateStyle;
}
/**
* Set the default date style.
*/
public void setDefaultDateStyle(Style defaultDateStyle) {
this.defaultDateStyle = defaultDateStyle;
}
/**
* Returns the default time style. Defaults to {@link Style#MEDIUM}.
*/
public Style getDefaultTimeStyle() {
return defaultTimeStyle;
}
/**
* Set the default time style.
*/
public void setDefaultTimeStyle(Style defaultTimeStyle) {
this.defaultTimeStyle = defaultTimeStyle;
}
public Formatter getDateFormatter() {
return getDateFormatter(getDefaultDateStyle());
}
public Formatter getDateTimeFormatter() {
return getDateTimeFormatter(getDefaultDateStyle(), getDefaultTimeStyle());
}
public Formatter getTimeFormatter() {
return getTimeFormatter(getDefaultTimeStyle());
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.binding.format.support;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import org.springframework.binding.format.InvalidFormatException;
/**
* Formatter that formats date objects.
*
* @author Keith Donald
*/
public class DateFormatter extends AbstractFormatter {
private DateFormat dateFormat;
/**
* Constructs a date formatter that will delegate to the specified date
* format.
* @param dateFormat the date format to use
*/
public DateFormatter(DateFormat dateFormat) {
this.dateFormat = dateFormat;
}
/**
* Constructs a date formatter that will delegate to the specified date
* format.
* @param dateFormat the date format to use
* @param allowEmpty should this formatter allow empty input arguments?
*/
public DateFormatter(DateFormat dateFormat, boolean allowEmpty) {
super(allowEmpty);
this.dateFormat = dateFormat;
}
// convert from date to string
protected String doFormatValue(Object date) {
return dateFormat.format((Date)date);
}
// convert back from string to date
protected Object doParseValue(String formattedString, Class targetClass) throws ParseException {
return dateFormat.parse(formattedString);
}
/**
* Convenience method to parse a date.
*/
public Date parseDate(String formattedString) throws InvalidFormatException {
return (Date)parseValue(formattedString, Date.class);
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.binding.format.support;
import java.beans.PropertyEditorSupport;
import org.springframework.binding.format.Formatter;
/**
* Adapts a formatter to the property editor contract.
*
* @author Keith Donald
*/
public class FormatterPropertyEditor extends PropertyEditorSupport {
/**
* The formatter
*/
private Formatter formatter;
/**
* The target value class (may be null).
*/
private Class targetClass;
/**
* Creates a formatter property editor.
* @param formatter the formatter to adapt
*/
public FormatterPropertyEditor(Formatter formatter) {
this.formatter = formatter;
}
/**
* Creates a formatter property editor.
* @param formatter the formatter to adapt
* @param targetClass the target class for "setAsText" conversions
*/
public FormatterPropertyEditor(Formatter formatter, Class targetClass) {
this.formatter = formatter;
this.targetClass = targetClass;
}
public String getAsText() {
return formatter.formatValue(getValue());
}
public void setAsText(String text) throws IllegalArgumentException {
setValue(formatter.parseValue(text, targetClass));
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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.binding.format.support;
import org.springframework.binding.format.InvalidFormatException;
import org.springframework.core.enums.LabeledEnum;
import org.springframework.core.enums.LabeledEnumResolver;
import org.springframework.core.enums.StaticLabeledEnumResolver;
import org.springframework.util.Assert;
/**
* Converts from string to a <cod>LabeledEnum</code> instance and back.
*
* @author Keith Donald
*/
public class LabeledEnumFormatter extends AbstractFormatter {
private LabeledEnumResolver labeledEnumResolver = StaticLabeledEnumResolver.instance();
/**
* Default constructor.
*/
public LabeledEnumFormatter() {
}
/**
* Create a new LabeledEnum formatter.
* @param allowEmpty should this formatter allow empty input arguments?
*/
public LabeledEnumFormatter(boolean allowEmpty) {
super(allowEmpty);
}
/**
* Set the LabeledEnumResolver used. Defaults to {@link StaticLabeledEnumResolver}.
*/
public void setLabeledEnumResolver(LabeledEnumResolver labeledEnumResolver) {
Assert.notNull(labeledEnumResolver, "The labeled enum resolver is required");
this.labeledEnumResolver = labeledEnumResolver;
}
protected String doFormatValue(Object value) {
LabeledEnum labeledEnum = (LabeledEnum)value;
return labeledEnum.getLabel();
}
protected Object doParseValue(String formattedString, Class targetClass) throws IllegalArgumentException {
LabeledEnum labeledEnum = labeledEnumResolver.getLabeledEnumByLabel(targetClass, formattedString);
if (!isAllowEmpty()) {
Assert.notNull(labeledEnum, "The label '" + formattedString
+ "' did not map to a valid enum instance for type " + targetClass);
Assert.isInstanceOf(targetClass, labeledEnum);
}
return labeledEnum;
}
/**
* Convenience method to parse a LabeledEnum.
*/
public LabeledEnum parseLabeledEnum(String formattedString, Class enumClass) throws InvalidFormatException {
return (LabeledEnum)parseValue(formattedString, enumClass);
}
}

View File

@@ -0,0 +1,109 @@
/*
* 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.binding.format.support;
import java.math.BigInteger;
import java.text.NumberFormat;
import org.springframework.binding.format.InvalidFormatException;
import org.springframework.util.NumberUtils;
/**
* Converts from various
* <code>Number<code> specializations to <code>String</code> and back.
*
* @author Keith Donald
*/
public class NumberFormatter extends AbstractFormatter {
private NumberFormat numberFormat;
/**
* Default constructor.
*/
public NumberFormatter() {
}
/**
* Create a new number formatter.
* @param numberFormat the number format to use
*/
public NumberFormatter(NumberFormat numberFormat) {
this.numberFormat = numberFormat;
}
/**
* Create a new number formatter.
* @param numberFormat the number format to use
* @param allowEmpty should this formatter allow empty input arguments?
*/
public NumberFormatter(NumberFormat numberFormat, boolean allowEmpty) {
super(allowEmpty);
this.numberFormat = numberFormat;
}
protected String doFormatValue(Object number) {
if (this.numberFormat != null) {
// use NumberFormat for rendering value
return this.numberFormat.format(number);
}
else {
// use toString method for rendering value
return number.toString();
}
}
protected Object doParseValue(String text, Class targetClass) throws IllegalArgumentException {
// use given NumberFormat for parsing text
if (this.numberFormat != null) {
return NumberUtils.parseNumber(text, targetClass, this.numberFormat);
}
// use default valueOf methods for parsing text
else {
return NumberUtils.parseNumber(text, targetClass);
}
}
// convenience methods
public Short parseShort(String formattedString) throws InvalidFormatException {
return (Short)parseValue(formattedString, Short.class);
}
public Integer parseInteger(String formattedString) throws InvalidFormatException {
return (Integer)parseValue(formattedString, Integer.class);
}
public Long parseLong(String formattedString) throws InvalidFormatException {
return (Long)parseValue(formattedString, Long.class);
}
public Double parseDouble(String formattedString) throws InvalidFormatException {
return (Double)parseValue(formattedString, Double.class);
}
public Float parseFloat(String formattedString) throws InvalidFormatException {
return (Float)parseValue(formattedString, Float.class);
}
public BigInteger parseBigInteger(String formattedString) throws InvalidFormatException {
return (BigInteger)parseValue(formattedString, BigInteger.class);
}
public Byte parseByte(String formattedString) throws InvalidFormatException {
return (Byte)parseValue(formattedString, Byte.class);
}
}

View File

@@ -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.binding.format.support;
import java.beans.PropertyEditor;
import org.springframework.util.Assert;
/**
* Adapts a property editor to the formatter interface.
*
* @author Keith Donald
*/
public class PropertyEditorFormatter extends AbstractFormatter {
private PropertyEditor propertyEditor;
/**
* Wrap given property editor in a formatter.
*/
public PropertyEditorFormatter(PropertyEditor propertyEditor) {
Assert.notNull(propertyEditor, "Property editor is required");
this.propertyEditor = propertyEditor;
}
/**
* Returns the wrapped property editor.
*/
public PropertyEditor getPropertyEditor() {
return propertyEditor;
}
protected String doFormatValue(Object value) {
propertyEditor.setValue(value);
return propertyEditor.getAsText();
}
protected Object doParseValue(String formattedValue, Class targetClass) {
propertyEditor.setAsText(formattedValue);
return propertyEditor.getValue();
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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.binding.format.support;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import org.springframework.binding.format.Formatter;
import org.springframework.binding.format.Style;
/**
* Simple FormatterFactory implementation.
*
* @author Keith Donald
*/
public class SimpleFormatterFactory extends AbstractFormatterFactory {
public SimpleFormatterFactory() {
}
public Formatter getDateFormatter(Style style) {
return new DateFormatter(SimpleDateFormat.getDateInstance(style.shortValue(), getLocale()));
}
public Formatter getDateTimeFormatter(Style dateStyle, Style timeStyle) {
return new DateFormatter(SimpleDateFormat.getDateTimeInstance(dateStyle.shortValue(), timeStyle.shortValue(),
getLocale()));
}
public Formatter getTimeFormatter(Style style) {
return new DateFormatter(SimpleDateFormat.getTimeInstance(style.shortValue(), getLocale()));
}
public Formatter getNumberFormatter(Class numberClass) {
return new NumberFormatter(NumberFormat.getNumberInstance(getLocale()));
}
public Formatter getCurrencyFormatter() {
return new NumberFormatter(NumberFormat.getCurrencyInstance(getLocale()));
}
public Formatter getDateFormatter(String encodedFormat) {
return new DateFormatter(new SimpleDateFormat(encodedFormat));
}
public Formatter getPercentFormatter() {
return new NumberFormatter(NumberFormat.getPercentInstance(getLocale()));
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Supporting formatter implementations that are generically applicable and frequently used.
</p>
</body>
</html>

View File

@@ -0,0 +1,36 @@
/*
* 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.binding.mapping;
/**
* A lightweight service interface for mapping between two attribute sources.
* <p>
* Implementations of this interface are expected to encapsulate the mapping
* configuration information as well as the logic to act on it to perform
* mapping between a given source and target attribute source.
*
* @author Keith Donald
*/
public interface AttributeMapper {
/**
* Map data from a source object to a target object.
* @param source the source
* @param target the target
* @param context the mapping context
*/
public void map(Object source, Object target, MappingContext context);
}

View File

@@ -0,0 +1,82 @@
/*
* 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.binding.mapping;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.springframework.core.style.ToStringCreator;
/**
* Generic attributes mapper implementation that allows mappings to be
* configured programatically.
*
* @author Erwin Vervaet
* @author Keith Donald
* @author Colin Sampaleanu
*/
public class DefaultAttributeMapper implements AttributeMapper {
/**
* The ordered list of mappings to apply.
*/
private List mappings = new LinkedList();
/**
* Add a mapping to this mapper.
* @param mapping the mapping to add (as an AttributeMapper)
* @return this, to support convenient call chaining
*/
public DefaultAttributeMapper addMapping(AttributeMapper mapping) {
mappings.add(mapping);
return this;
}
/**
* Add a set of mappings.
* @param mappings the mappings
*/
public void addMappings(AttributeMapper[] mappings) {
if (mappings == null) {
return;
}
this.mappings.addAll(Arrays.asList(mappings));
}
/**
* Returns this mapper's list of mappings.
* @return the list of mappings
*/
public AttributeMapper[] getMappings() {
return (AttributeMapper[])mappings.toArray(new AttributeMapper[mappings.size()]);
}
public void map(Object source, Object target, MappingContext context) {
if (mappings != null) {
Iterator it = mappings.iterator();
while (it.hasNext()) {
AttributeMapper mapping = (AttributeMapper)it.next();
mapping.map(source, target, context);
}
}
}
public String toString() {
return new ToStringCreator(this).append("mappings", mappings).toString();
}
}

View File

@@ -0,0 +1,136 @@
/*
* 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.binding.mapping;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.SettableExpression;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
/**
* A single mapping definition, encapulating the information neccessary to map
* the result of evaluating an expression on a source object to a property on a
* target object, optionally applying a type conversion during the mapping
* process.
*
* @author Keith Donald
*/
public class Mapping implements AttributeMapper {
private static final Log logger = LogFactory.getLog(Mapping.class);
/**
* The source expression to evaluate against a source object to map from.
*/
private final Expression sourceExpression;
/**
* The target expression to set on a target object to map to.
*/
private final SettableExpression targetExpression;
/**
* A type converter to apply during the mapping process.
*/
private final ConversionExecutor typeConverter;
/**
* Whether or not this is a required mapping; if true, the source expression
* must return a non-null value.
*/
private boolean required;
/**
* Creates a new mapping.
* @param sourceExpression the source expression
* @param targetExpression the target expression
* @param typeConverter a type converter
*/
public Mapping(Expression sourceExpression, SettableExpression targetExpression,
ConversionExecutor typeConverter) {
this(sourceExpression, targetExpression, typeConverter, false);
}
/**
* Creates a new mapping.
* @param sourceExpression the source expression
* @param targetExpression the target expression
* @param typeConverter a type converter
* @param required whether or not this mapping is required
*/
protected Mapping(Expression sourceExpression, SettableExpression targetExpression,
ConversionExecutor typeConverter, boolean required) {
Assert.notNull(sourceExpression, "The source expression is required");
Assert.notNull(targetExpression, "The target expression is required");
this.sourceExpression = sourceExpression;
this.targetExpression = targetExpression;
this.typeConverter = typeConverter;
this.required = required;
}
/**
* Map the <code>sourceAttribute</code> in to the
* <code>targetAttribute</code> target map, performing type conversion if
* necessary.
* @param source The source data structure
* @param target The target data structure
*/
public void map(Object source, Object target, MappingContext context) {
// get source value
Object sourceValue = sourceExpression.evaluate(source, null);
if (sourceValue == null) {
if (required) {
throw new RequiredMappingException("This mapping is required; evaluation of expression '"
+ sourceExpression + "' against source of type [" + source.getClass()
+ "] must return a non-null value");
}
else {
// source expression returned no value, simply abort mapping
return;
}
}
Object targetValue = sourceValue;
if (typeConverter != null) {
targetValue = typeConverter.execute(sourceValue);
}
// set target value
if (logger.isDebugEnabled()) {
logger.debug("Mapping '" + sourceExpression + "' value [" + sourceValue + "] to target property '"
+ targetExpression + "'; setting property value to [" + targetValue + "]");
}
targetExpression.evaluateToSet(target, targetValue, null);
}
public boolean equals(Object o) {
if (!(o instanceof Mapping)) {
return false;
}
Mapping other = (Mapping)o;
return sourceExpression.equals(other.sourceExpression)
&& targetExpression.equals(other.targetExpression);
}
public int hashCode() {
return sourceExpression.hashCode() + targetExpression.hashCode();
}
public String toString() {
return new ToStringCreator(this).append(sourceExpression + " -> " + targetExpression).toString();
}
}

View File

@@ -0,0 +1,187 @@
/*
* 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.binding.mapping;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.convert.support.DefaultConversionService;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.binding.expression.SettableExpression;
import org.springframework.util.Assert;
/**
* A stateful builder that builds {@link Mapping} objects. Designed for
* convenience to build mappings in a clear, readable manner.
* <p>
* Example usage:
*
* <pre>
* MappingBuilder mapping = new MappingBuilder();
* Mapping result = mapping.source(&quot;foo&quot;).target(&quot;bar&quot;).from(String.class).to(Long.class).value();
* </pre>
*
* Calling the {@link #value()} result method clears out this builder's state so
* it can be reused to build another mapping.
*
* @author Keith Donald
*/
public class MappingBuilder {
/**
* The expression string parser.
*/
private ExpressionParser expressionParser;
/**
* The conversion service for applying type conversions.
*/
private ConversionService conversionService = new DefaultConversionService();
/**
* The source mapping expression.
*/
private Expression sourceExpression;
/**
* The target mapping settable expression.
*/
private SettableExpression targetExpression;
/**
* The type of the object returned by evaluating the source expression.
*/
private Class sourceType;
/**
* The type of the property settable by the target expression.
*/
private Class targetType;
/**
* Whether or not the built mapping is a required mapping.
*/
private boolean required;
/**
* Creates a mapping builder that uses the expression parser to parse
* attribute mapping expressions.
* @param expressionParser the expression parser
*/
public MappingBuilder(ExpressionParser expressionParser) {
Assert.notNull(expressionParser, "The expression parser is required");
this.expressionParser = expressionParser;
}
/**
* Sets the conversion service that will convert the object returned by
* evaluating the source expression to the {@link #to(Class)} type if
* necessary.
* @param conversionService the conversion service
*/
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
/**
* Sets the source expression of the mapping built by this builder.
* @param expressionString the expression string
* @return this, to support call-chaining
*/
public MappingBuilder source(String expressionString) {
sourceExpression = expressionParser.parseExpression(expressionString);
return this;
}
/**
* Sets the target property expression of the mapping built by this builder.
* @param expressionString the expression string
* @return this, to support call-chaining
*/
public MappingBuilder target(String expressionString) {
targetExpression = (SettableExpression)expressionParser.parseExpression(expressionString);
return this;
}
/**
* Sets the expected type of the object returned by evaluating the source
* expression. Used in conjunction with {@link #to(Class)} to perform a type
* conversion during the mapping process.
* @param sourceType the source type
* @return this, to support call-chaining
*/
public MappingBuilder from(Class sourceType) {
this.sourceType = sourceType;
return this;
}
/**
* Sets the target type of the property writeable by the target expression.
* @param targetType the target type
* @return this, to support call-chaining
*/
public MappingBuilder to(Class targetType) {
this.targetType = targetType;
return this;
}
/**
* Marks the mapping to be built a "required" mapping.
* @return this, to support call-chaining
*/
public MappingBuilder required() {
this.required = true;
return this;
}
/**
* The logical GoF builder getResult method, returning a fully constructed
* Mapping from the configured pieces. Once called, the state of this
* builder is nulled out to support building a new mapping object again.
* @return the mapping result
*/
public Mapping value() {
Assert.notNull(sourceExpression, "The source expression must be set at a minimum");
if (targetExpression == null) {
targetExpression = (SettableExpression)sourceExpression;
}
ConversionExecutor typeConverter = null;
if (sourceType != null) {
Assert.notNull(targetType, "The target type is required when the source type is specified");
typeConverter = conversionService.getConversionExecutor(sourceType, targetType);
}
Mapping result;
if (required) {
result = new RequiredMapping(sourceExpression, targetExpression, typeConverter);
}
else {
result = new Mapping(sourceExpression, targetExpression, typeConverter);
}
reset();
return result;
}
/**
* Reset this mapping builder.
*/
public void reset() {
sourceExpression = null;
targetExpression = null;
sourceType = null;
targetType = null;
required = false;
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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.binding.mapping;
/**
* A context object with two main responsibities:
* <ol>
* <li>Exposing information to a mapper to influence
* a mapping attempt.
* <li>Providing operations for recording progress or
* errors during the mapping process.
* </ol>
* Empty for now; subclasses may define their own custom context behavior
* accessible by a mapper with a downcast.
*
* @author Keith Donald
*/
public interface MappingContext {
}

View File

@@ -0,0 +1,39 @@
/*
* 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.binding.mapping;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.expression.SettableExpression;
/**
* A mapping that is required.
*
* @author Keith Donald
*/
public class RequiredMapping extends Mapping {
/**
* Creates a required mapping.
* @param sourceExpression the source mapping expression
* @param targetPropertyExpression the target property expression
* @param typeConverter a type converter
*/
public RequiredMapping(Expression sourceExpression, SettableExpression targetPropertyExpression,
ConversionExecutor typeConverter) {
super(sourceExpression, targetPropertyExpression, typeConverter, true);
}
}

View File

@@ -0,0 +1,32 @@
/*
* 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.binding.mapping;
/**
* Thrown when a required mapping could not be performed.
*
* @author Keith Donald
*/
public class RequiredMappingException extends IllegalStateException {
/**
* Create a new required mapping exception.
* @param message a descriptive message
*/
public RequiredMappingException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Support for mapping attribute values between data structures.
</p>
</body>
</html>

View File

@@ -0,0 +1,50 @@
/*
* 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.binding.method;
import org.springframework.core.NestedRuntimeException;
/**
* Thrown when a method key could not be resolved to an invokable java Method on
* a Class.
*
* @author Keith Donald
*/
public class InvalidMethodKeyException extends NestedRuntimeException {
/**
* The method key that could not be resolved.
*/
private MethodKey methodKey;
/**
* Creates an exception signaling an invalid method signature.
* @param methodKey the class method key
* @param cause the cause
*/
public InvalidMethodKeyException(MethodKey methodKey, Exception cause) {
super("Could not resolve method with key " + methodKey, cause);
this.methodKey = methodKey;
}
/**
* Returns the invalid method key.
* @return the method key.
*/
public MethodKey getMethodKey() {
return methodKey;
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.binding.method;
import java.lang.reflect.InvocationTargetException;
import org.springframework.core.NestedRuntimeException;
import org.springframework.core.style.StylerUtils;
/**
* Base class for exceptions that report a method invocation failure.
*
* @author Keith Donald
*/
public class MethodInvocationException extends NestedRuntimeException {
/**
* The method signature.
*/
private MethodSignature methodSignature;
/**
* The method invocation argument values.
*/
private Object[] arguments;
/**
* Signals that the method with the specified signature could not be invoked
* with the provided arguments.
* @param methodSignature the method signature
* @param arguments the arguments
* @param cause the root cause
*/
public MethodInvocationException(MethodSignature methodSignature, Object[] arguments, Exception cause) {
super("Unable to invoke method " + methodSignature + " with arguments " + StylerUtils.style(arguments), cause);
}
/**
* Returns the invoked method's signature.
*/
public MethodSignature getMethodSignature() {
return methodSignature;
}
/**
* Returns the method invocation arguments.
*/
public Object[] getArguments() {
return arguments;
}
/**
* Returns the target root cause exception of the method invocation failure.
* @return the target throwable
*/
public Throwable getTargetException() {
if (getCause() instanceof InvocationTargetException) {
return ((InvocationTargetException)getCause()).getTargetException();
}
else {
return getCause();
}
}
}

View File

@@ -0,0 +1,120 @@
/*
* 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.binding.method;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.convert.support.DefaultConversionService;
import org.springframework.core.style.StylerUtils;
import org.springframework.util.CachingMapDecorator;
/**
* A helper for invoking typed methods on abritrary objects, with support for
* argument value type conversion from values retrieved from a argument
* attribute source.
*
* @author Keith Donald
*/
public class MethodInvoker {
private static final Log logger = LogFactory.getLog(MethodInvoker.class);
/**
* Conversion service for converting arguments to the neccessary type if
* required.
*/
private ConversionService conversionService = new DefaultConversionService();
/**
* A cache of invoked bean methods, keyed weakly.
*/
private CachingMapDecorator methodCache = new CachingMapDecorator(true) {
public Object create(Object key) {
return ((MethodKey) key).getMethod();
}
};
/**
* Sets the conversion service to convert argument values as needed.
*/
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
/**
* Invoke the method on the bean provided. Argument values are pulled from
* the provided argument source.
* @param signature the definition of the method to invoke, including the
* method name and the method argument types
* @param bean the bean to invoke
* @param argumentSource the source for method arguments
* @return the invoked method's return value
* @throws MethodInvocationException the method could not be invoked
*/
public Object invoke(MethodSignature signature, Object bean, Object argumentSource)
throws MethodInvocationException {
Parameters parameters = signature.getParameters();
Object[] arguments = new Object[parameters.size()];
for (int i = 0; i < parameters.size(); i++) {
Parameter parameter = parameters.getParameter(i);
Object argument = parameter.evaluateArgument(argumentSource, null);
arguments[i] = applyTypeConversion(argument, parameter.getType());
}
Class[] parameterTypes = parameters.getTypesArray();
for (int i = 0; i < parameterTypes.length; i++) {
if (parameterTypes[i] == null) {
Object argument = arguments[i];
if (argument != null) {
parameterTypes[i] = argument.getClass();
}
}
}
MethodKey key = new MethodKey(bean.getClass(), signature.getMethodName(), parameterTypes);
try {
Method method = (Method) methodCache.get(key);
if (logger.isDebugEnabled()) {
logger.debug("Invoking method with signature [" + key + "] with arguments "
+ StylerUtils.style(arguments) + " on bean [" + bean + "]");
}
Object returnValue = method.invoke(bean, arguments);
if (logger.isDebugEnabled()) {
logger.debug("Invoked method with signature [" + key + "] returned value [" + returnValue + "]");
}
return returnValue;
}
catch (Exception e) {
throw new MethodInvocationException(signature, arguments, e);
}
}
/**
* Apply type conversion on the event parameter if neccessary
*
* @param parameterValue the raw argument value
* @param targetType the target type for the matching method argument
* @return the converted method argument
*/
protected Object applyTypeConversion(Object parameterValue, Class targetType) {
if (parameterValue == null || targetType == null) {
return parameterValue;
}
return conversionService.getConversionExecutor(parameterValue.getClass(), targetType).execute(parameterValue);
}
}

View File

@@ -0,0 +1,251 @@
/*
* 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.binding.method;
import java.io.Serializable;
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.ObjectUtils;
/**
* A helper for resolving and caching a Java method by reflection.
*
* @author Keith Donald
*/
public class MethodKey implements Serializable {
/**
* The class the method is a member of.
*/
private Class declaredType;
/**
* The method name.
*/
private String methodName;
/**
* The method's actual parameter types.
*/
private Class[] parameterTypes;
/**
* A cached handle to the resolved method (may be null).
*/
private transient Method method;
/**
* Create a new method key.
* @param declaredType the class the method is a member of
* @param methodName the method name
* @param parameterTypes the method's parameter types, or <code>null</code>
* if the method has no parameters
*/
public MethodKey(Class declaredType, String methodName, Class[] parameterTypes) {
Assert.notNull(declaredType, "The method's declared type is required");
Assert.notNull(methodName, "The method name is required");
this.declaredType = declaredType;
this.methodName = methodName;
this.parameterTypes = parameterTypes;
}
/**
* Return the class the method is a member of.
*/
public Class getDeclaredType() {
return declaredType;
}
/**
* Returns the method name.
*/
public String getMethodName() {
return methodName;
}
/**
* Returns the method parameter types.
*/
public Class[] getParameterTypes() {
return parameterTypes;
}
/**
* Returns the keyed method, resolving it if necessary via reflection.
*/
public Method getMethod() throws InvalidMethodKeyException {
if (method == null) {
method = resolveMethod();
}
return method;
}
// internal helpers
/**
* Resolve the keyed method.
*/
protected Method resolveMethod() throws InvalidMethodKeyException {
try {
return declaredType.getMethod(methodName, getParameterTypes());
}
catch (NoSuchMethodException e) {
Method method = findMethodConsiderAssignableParameterTypes();
if (method != null) {
return method;
}
else {
throw new InvalidMethodKeyException(this, e);
}
}
}
/**
* Find the keyed method using 'relaxed' typing.
*/
protected Method findMethodConsiderAssignableParameterTypes() {
Method[] candidateMethods = getDeclaredType().getMethods();
for (int i = 0; i < candidateMethods.length; i++) {
if (candidateMethods[i].getName().equals(methodName)) {
// Check if the method has the correct number of parameters.
Class[] candidateParameterTypes = candidateMethods[i].getParameterTypes();
if (candidateParameterTypes.length == getParameterTypes().length) {
int numberOfCorrectArguments = 0;
for (int j = 0; j < candidateParameterTypes.length; j++) {
// Check if the candidate type is assignable to the sig
// parameter type.
Class candidateType = candidateParameterTypes[j];
Class parameterType = parameterTypes[j];
if (parameterType != null) {
if (isAssignable(candidateType, parameterType)) {
numberOfCorrectArguments++;
}
}
else {
// just match on a null param type (effectively
// 'any')
numberOfCorrectArguments++;
}
}
if (numberOfCorrectArguments == parameterTypes.length) {
return candidateMethods[i];
}
}
}
}
return null;
}
public boolean equals(Object obj) {
if (!(obj instanceof MethodKey)) {
return false;
}
MethodKey other = (MethodKey) obj;
return declaredType.equals(other.declaredType) && methodName.equals(other.methodName)
&& parameterTypesEqual(other.parameterTypes);
}
private boolean parameterTypesEqual(Class[] other) {
if (parameterTypes == other) {
return true;
}
if (parameterTypes.length != other.length) {
return false;
}
for (int i = 0; i < this.parameterTypes.length; i++) {
if (!ObjectUtils.nullSafeEquals(parameterTypes[i], other[i])) {
return false;
}
}
return true;
}
public int hashCode() {
return declaredType.hashCode() + methodName.hashCode() + parameterTypesHash();
}
private int parameterTypesHash() {
if (parameterTypes == null) {
return 0;
}
int hash = 0;
for (int i = 0; i < parameterTypes.length; i++) {
Class parameterType = parameterTypes[i];
if (parameterType != null) {
hash += parameterTypes[i].hashCode();
}
}
return hash;
}
// internal helpers
/**
* Determine if the given target type is assignable from the given value
* type, assuming setting by reflection. Considers primitive wrapper classes
* as assignable to the corresponding primitive types. <p> NOTE: Pulled from
* ClassUtils in Spring 2.0 for 1.2.8 compatability. Should be collapsed
* when 1.2.9 is released.
* @param targetType the target type
* @param valueType the value type that should be assigned to the target
* type
* @return if the target type is assignable from the value type
*/
private static boolean isAssignable(Class targetType, Class valueType) {
return (targetType.isAssignableFrom(valueType) || targetType.equals(primitiveWrapperTypeMap.get(valueType)));
}
/**
* 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);
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);
}
public String toString() {
return methodName + "(" + parameterTypesString() + ")";
}
/**
* Convenience method that returns the parameter types describing the
* signature of the method as a string.
*/
private String parameterTypesString() {
StringBuffer parameterTypesString = new StringBuffer();
for (int i = 0; i < parameterTypes.length; i++) {
parameterTypesString.append(ClassUtils.getShortName(parameterTypes[i]));
if (i < parameterTypes.length - 1) {
parameterTypesString.append(',');
}
}
return parameterTypesString.toString();
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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.binding.method;
import java.io.Serializable;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
/**
* A specification for a method consisting of the methodName and an optional set
* of named arguments. This class provides the ability to resolve a method with
* parameters and evaluate its argument values as part of a
* {@link MethodInvoker method invoker attempt}.
*
* @author Keith Donald
*/
public class MethodSignature implements Serializable {
/**
* The name of the method, e.g "execute".
*/
private String methodName;
/**
* The parameter types of the method, e.g "int param1".
*/
private Parameters parameters;
/**
* Creates a method signature with no parameters.
* @param methodName the name of the method
*/
public MethodSignature(String methodName) {
this(methodName, Parameters.NONE);
}
/**
* Creates a method signature with a single parameter.
* @param methodName the name of the method
* @param parameter the method parameter
*/
public MethodSignature(String methodName, Parameter parameter) {
this(methodName, new Parameters(parameter));
}
/**
* Creates a method signature with a list of parameters.
* @param methodName the name of the method
* @param parameters the method parameters
*/
public MethodSignature(String methodName, Parameters parameters) {
Assert.notNull(methodName, "The method name is required");
Assert.notNull(parameters, "The parameters are required");
this.methodName = methodName;
this.parameters = parameters;
}
/**
* Returns the method name.
*/
public String getMethodName() {
return methodName;
}
/**
* Returns the method parameters.
*/
public Parameters getParameters() {
return parameters;
}
public boolean equals(Object obj) {
if (!(obj instanceof MethodSignature)) {
return false;
}
MethodSignature other = (MethodSignature) obj;
return methodName.equals(methodName) && parameters.equals(other.parameters);
}
public int hashCode() {
return methodName.hashCode() + parameters.hashCode();
}
public String toString() {
return new ToStringCreator(this).append("methodName", methodName).append("parameters", parameters).toString();
}
}

View File

@@ -0,0 +1,97 @@
/*
* 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.binding.method;
import java.io.Serializable;
import org.springframework.binding.expression.EvaluationContext;
import org.springframework.binding.expression.Expression;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* A named method parameter. Each parameter has an identifying name and is of a
* specified type (class).
*
* @author Keith Donald
*/
public class Parameter implements Serializable {
/**
* The class of the parameter, e.g "springbank.AccountNumber".
*/
private Class type;
/**
* The name of the parameter as an evaluatable expression, e.g
* "accountNumber".
*/
private Expression name;
/**
* Create a new named parameter definition. Named parameters are capable of resolving
* parameter values (arguments) from argument sources.
* @param type the type the parameter type, may be null
* @param name the name the method argument expression (required)
*/
public Parameter(Class type, Expression name) {
Assert.notNull(name, "The parameter name expression is required");
this.type = type;
this.name = name;
}
/**
* Returns the parameter type.
*/
public Class getType() {
return type;
}
/**
* Returns the method name.
*/
public Expression getName() {
return name;
}
/**
* Evaluate this method parameter against the provided argument source,
* returning a single method argument value.
* @param argumentSource the meyhod argument source
* @param context the evaluation context
* @return the method argument value
*/
public Object evaluateArgument(Object argumentSource, EvaluationContext context) {
return name.evaluate(argumentSource, context);
}
public boolean equals(Object obj) {
if (!(obj instanceof Parameter)) {
return false;
}
Parameter other = (Parameter) obj;
return ObjectUtils.nullSafeEquals(type, other.type) && name.equals(other.name);
}
public int hashCode() {
return (type != null ? type.hashCode() : 0) + name.hashCode();
}
public String toString() {
return new ToStringCreator(this).append("type", type).append("name", name).toString();
}
}

View File

@@ -0,0 +1,146 @@
/*
* 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.binding.method;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
/**
* An ordered list of method parameters.
*
* @author Keith
*/
public class Parameters implements Serializable {
/**
* Canonical instance for an empty parameters list.
*/
public static final Parameters NONE = new Parameters(0);
/**
* The list.
*/
private List parameters;
/**
* Create a parameter list of the default size (3 elements).
*/
public Parameters() {
this(3);
}
/**
* Create a parameter list with the specified size.
* @param size the size
*/
public Parameters(int size) {
this.parameters = new ArrayList(size);
}
/**
* Create a parameter list with one parameter.
* @param parameter the single parameter
*/
public Parameters(Parameter parameter) {
this.parameters = new ArrayList(1);
add(parameter);
}
/**
* Create a parameter list from the parameter array.
* @param parameters the parameters
*/
public Parameters(Parameter[] parameters) {
this.parameters = new ArrayList(parameters.length);
addAll(parameters);
}
/**
* Add a new parameter to this list.
* @param parameter the parameter
*/
public boolean add(Parameter parameter) {
return this.parameters.add(parameter);
}
/**
* Add new parameters to this list.
* @param parameters the parameters
*/
public boolean addAll(Parameter[] parameters) {
return this.parameters.addAll(Arrays.asList(parameters));
}
/**
* Return a parameter iterator.
* @return the iterator
*/
public Iterator iterator() {
return parameters.iterator();
}
/**
* Get an array containing each parameter type.
* @return the types
*/
public Class[] getTypesArray() {
int i = 0;
Class[] types = new Class[parameters.size()];
for (Iterator it = parameters.iterator(); it.hasNext();) {
Parameter param = (Parameter)it.next();
types[i] = param.getType();
i++;
}
return types;
}
/**
* Returns the number of parameters in this list.
* @return the size
*/
public int size() {
return parameters.size();
}
/**
* Return the parameter at the provided index.
* @param index the parameter index
* @return the parameter at that index
* @throws IndexOutOfBoundsException if the provided index is out of bounds
*/
public Parameter getParameter(int index) throws IndexOutOfBoundsException {
return (Parameter)parameters.get(index);
}
public boolean equals(Object obj) {
if (!(obj instanceof Parameters)) {
return false;
}
Parameters other = (Parameters)obj;
return parameters.equals(other.parameters);
}
public int hashCode() {
return parameters.hashCode();
}
public String toString() {
return parameters.toString();
}
}

View File

@@ -0,0 +1,99 @@
/*
* 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.binding.method;
import org.springframework.binding.convert.ConversionContext;
import org.springframework.binding.convert.ConversionException;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.convert.support.ConversionServiceAwareConverter;
import org.springframework.util.StringUtils;
/**
* Converter that takes an encoded string representation and produces a
* corresponding <code>MethodSignature</code> object.
* <p>
* This converter supports the following encoded forms:
* <ul>
* <li> "methodName" - the name of the method to invoke, where the method is
* expected to have no arguments. </li>
* <li> "methodName(param1Type param1Name, paramNType paramNName)" - the name of
* the method to invoke, where the method is expected to have parameters
* delimited by a comma. In this example, the method has two parameters. The
* type is either the fully-qualified class of the argument OR a known type
* alias. The name is the logical name of the argument, which is used during
* data binding to retrieve the argument value. </li>
* </ul>
*
* @see MethodSignature
*
* @author Keith Donald
*/
public class TextToMethodSignature extends ConversionServiceAwareConverter {
/**
* Create a new converter that converts strings to MethodSignature objects.
*/
public TextToMethodSignature() {
}
/**
* Create a new converter that converts strings to MethodSignature objects.
* @param conversionService the conversion service to use
*/
public TextToMethodSignature(ConversionService conversionService) {
super(conversionService);
}
public Class[] getSourceClasses() {
return new Class[] { String.class };
}
public Class[] getTargetClasses() {
return new Class[] { MethodSignature.class };
}
protected Object doConvert(Object source, Class targetClass, ConversionContext context) throws Exception {
String encodedMethodKey = (String)source;
encodedMethodKey = encodedMethodKey.trim();
int openParan = encodedMethodKey.indexOf('(');
if (openParan == -1) {
return new MethodSignature(encodedMethodKey);
}
else {
String methodName = encodedMethodKey.substring(0, openParan);
int closeParan = encodedMethodKey.lastIndexOf(')');
if (closeParan == -1) {
throw new ConversionException(encodedMethodKey, MethodSignature.class,
"Syntax error: No close parenthesis specified for method parameter list", null);
}
String delimParamList = encodedMethodKey.substring(openParan + 1, closeParan);
String[] paramArray = StringUtils.commaDelimitedListToStringArray(delimParamList);
Parameters params = new Parameters(paramArray.length);
for (int i = 0; i < paramArray.length; i++) {
String param = paramArray[i].trim();
String[] typeAndName = StringUtils.split(param, " ");
if (typeAndName != null && typeAndName.length == 2) {
Class type = (Class)converterFor(String.class, Class.class).execute(typeAndName[0]);
params.add(new Parameter(type, parseExpression(typeAndName[1].trim())));
}
else {
params.add(new Parameter(null, parseExpression(param)));
}
}
return new MethodSignature(methodName, params);
}
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Method binding support for invoking abritrary methods on target beans.
</p>
</body>
</html>