提交 913bc03a 编写于 作者: K Keith Donald

ui.format system refining from RC1 feedback: Support for one format annotation...

ui.format system refining from RC1 feedback: Support for one format annotation applying to multiple field types, Printer/Parser building blocks for more flexibility, full Joda time formatting support, FormattingService as a service entry-point for clients to use
上级 471fbf7e
......@@ -17,6 +17,7 @@ package org.springframework.mapping.support;
import java.beans.PropertyDescriptor;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.BeanUtils;
......@@ -31,7 +32,13 @@ final class BeanMappableType implements MappableType<Object> {
return true;
}
public Set<String> getFields(Object object) {
public EvaluationContext getEvaluationContext(Object object, ConversionService conversionService) {
StandardEvaluationContext context = new StandardEvaluationContext(object);
context.setTypeConverter(new StandardTypeConverter(conversionService));
return context;
}
public Set<String> getFieldNames(Object object) {
Set<String> fields = new LinkedHashSet<String>();
PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(object.getClass());
for (PropertyDescriptor descriptor : descriptors) {
......@@ -44,10 +51,9 @@ final class BeanMappableType implements MappableType<Object> {
return fields;
}
public EvaluationContext getEvaluationContext(Object object, ConversionService conversionService) {
StandardEvaluationContext context = new StandardEvaluationContext(object);
context.setTypeConverter(new StandardTypeConverter(conversionService));
return context;
public Map<String, Object> getNestedFields(String fieldName, Object object) {
return null;
}
}
\ No newline at end of file
/*
* Copyright 2002-2009 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.mapping.support;
import org.springframework.core.style.StylerUtils;
import org.springframework.expression.Expression;
import org.springframework.mapping.Mapper;
/**
* A mapping between several source fields and one or more target fields.
* @author Keith Donald
*/
final class FlexibleFieldMapping implements SpelMapping {
private String[] fields;
@SuppressWarnings("unchecked")
private final Mapper flexibleFieldMapper;
private Expression condition;
public FlexibleFieldMapping(String[] fields, Mapper<?, ?> flexibleFieldMapper, Expression condition) {
this.fields = fields;
this.flexibleFieldMapper = flexibleFieldMapper;
this.condition = condition;
}
public String[] getSourceFields() {
return fields;
}
public boolean mapsField(String field) {
for (String f : this.fields) {
if (f.equals(field)) {
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
public void map(SpelMappingContext context) {
if (!context.conditionHolds(this.condition)) {
return;
}
try {
this.flexibleFieldMapper.map(context.getSource(), context.getTarget());
} catch (Exception e) {
context.addMappingFailure(e);
}
}
public int hashCode() {
return this.flexibleFieldMapper.hashCode();
}
public boolean equals(Object o) {
if (!(o instanceof FlexibleFieldMapping)) {
return false;
}
FlexibleFieldMapping m = (FlexibleFieldMapping) o;
return this.flexibleFieldMapper.equals(m.flexibleFieldMapper);
}
public String toString() {
return "[FlexibleFieldMapping<" + StylerUtils.style(this.fields) + " -> " + this.flexibleFieldMapper + ">]";
}
}
\ No newline at end of file
......@@ -15,6 +15,7 @@
*/
package org.springframework.mapping.support;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
......@@ -31,16 +32,6 @@ final class MapMappableType implements MappableType<Map<? extends Object, ? exte
return object instanceof Map<?, ?>;
}
public Set<String> getFields(Map<? extends Object, ? extends Object> object) {
LinkedHashSet<String> fields = new LinkedHashSet<String>(object.size(), 1);
for (Object key : object.keySet()) {
if (key != null && key instanceof String) {
fields.add((String) key);
}
}
return fields;
}
public EvaluationContext getEvaluationContext(Map<? extends Object, ? extends Object> object,
ConversionService conversionService) {
StandardEvaluationContext context = new StandardEvaluationContext(object);
......@@ -49,4 +40,28 @@ final class MapMappableType implements MappableType<Map<? extends Object, ? exte
return context;
}
public Set<String> getFieldNames(Map<? extends Object, ? extends Object> object) {
Set<String> fieldNames = new LinkedHashSet<String>(object.size(), 1);
for (Object key : object.keySet()) {
if (key != null && key instanceof String) {
fieldNames.add((String) key);
}
}
return fieldNames;
}
public Map<String, Object> getNestedFields(String fieldName, Map<? extends Object, ? extends Object> object) {
Map<String, Object> fields = new LinkedHashMap<String, Object>(object.size(), 1);
for (Map.Entry<? extends Object, ? extends Object> entry : object.entrySet()) {
Object key = entry.getKey();
if (key != null && key instanceof String) {
String name = (String) key;
if (name.startsWith(fieldName + ".")) {
fields.put(name.substring(fieldName.length() + 1), entry.getValue());
}
}
}
return fields;
}
}
\ No newline at end of file
......@@ -15,6 +15,7 @@
*/
package org.springframework.mapping.support;
import java.util.Map;
import java.util.Set;
import org.springframework.core.convert.ConversionService;
......@@ -28,9 +29,10 @@ import org.springframework.expression.EvaluationContext;
interface MappableType<T> {
/**
* The fields of the object that are eligible for mapping, including any nested fields.
* Is this object to map an instanceof this mappable type?
* @param object the object to test
*/
Set<String> getFields(T object);
boolean isInstance(Object object);
/**
* A evaluation context for accessing the object.
......@@ -38,9 +40,13 @@ interface MappableType<T> {
EvaluationContext getEvaluationContext(T object, ConversionService conversionService);
/**
* Is this object to map an instanceof this mappable type?
* @param object the object to test
* The names of the fields on the object that are eligible for mapping, including any nested fields.
*/
boolean isInstance(Object object);
Set<String> getFieldNames(T object);
/**
* A map of the nested fields on the object that are nested relative to <code>fieldName</code>.
*/
Map<String, Object> getNestedFields(String fieldName, T object);
}
\ No newline at end of file
......@@ -15,6 +15,9 @@
*/
package org.springframework.mapping.support;
import java.util.Map;
import org.joda.time.DateTime;
import org.springframework.core.convert.converter.Converter;
import org.springframework.mapping.Mapper;
......@@ -107,6 +110,17 @@ public interface MapperBuilder<S, T> {
*/
MapperBuilder<S, T> addMapping(String[] fields, Mapper<S, T> mapper);
/**
* Register a mapping that delegates to an assembler to convert multiple source field values to a single target field value.
* The source field names mapped begin with <code>field.</code>.
* For example, adding an assembler mapping for a field named <code>dateTime</code> would pass all nested <code>dateTime.*</code> fields to the assembler.
* Such fields might be <code>year</code>, <code>month</code>, <code>day</code>, etc.
* @param fields the name of the source field whose value will be assembled from multiple nested fields that begin with the same name
* @param converter the assembler that will perform value assembly from the field values
* @return this, for configuring additional field mapping options fluently
*/
MapperBuilder<S, T> addAssemblerMapping(String field, Converter<? extends Map<?, ?>, ?> converter);
/**
* Register a conditional mapping between a source field and a target field.
* The source and target field names will be the same value.
......@@ -170,7 +184,7 @@ public interface MapperBuilder<S, T> {
* @param condition the boolean expression that determines if this mapping executes
* @return this, for configuring additional field mapping options fluently
*/
MapperBuilder<S, T> addMapping(String[] fields, Mapper<S, T> mapper, String condition);
MapperBuilder<S, T> addConditionalMapping(String[] fields, Mapper<S, T> mapper, String condition);
/**
* Register a Mapper that will be used to map between nested source and target fields of a specific sourceType/targetType pair.
......
......@@ -61,11 +61,7 @@ final class MappingConverter implements GenericConverter {
}
private boolean isCopyByReference(TypeDescriptor sourceType, TypeDescriptor targetType) {
if (BeanUtils.isSimpleValueType(targetType.getType())) {
return true;
} else {
return false;
}
return true;
}
private Object createTargetAndMap(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
......
......@@ -15,9 +15,11 @@
*/
package org.springframework.mapping.support;
import java.util.Map;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.style.StylerUtils;
import org.springframework.expression.Expression;
import org.springframework.mapping.Mapper;
/**
* A mapping between several source fields and a target field.
......@@ -25,46 +27,51 @@ import org.springframework.mapping.Mapper;
*/
final class MultiFieldToFieldMapping implements SpelMapping {
private String[] fields;
private final String sourceField;
private final Expression targetField;
@SuppressWarnings("unchecked")
private final Mapper multiFieldMapper;
private final Converter targetFieldValueAssembler;
private Expression condition;
public MultiFieldToFieldMapping(String[] fields, Mapper<?, ?> multiFieldMapper, Expression condition) {
this.fields = fields;
this.multiFieldMapper = multiFieldMapper;
private final Expression condition;
public MultiFieldToFieldMapping(String sourceField, Expression targetField, Converter<? extends Map<?,?>, ?> assembler,
Expression condition) {
this.sourceField = sourceField;
this.targetField = targetField;
this.targetFieldValueAssembler = assembler;
this.condition = condition;
}
public String[] getSourceFields() {
return fields;
public String getSourceField() {
return this.sourceField + ".*";
}
public String getTargetField() {
return this.targetField.getExpressionString();
}
public boolean mapsField(String field) {
for (String f : this.fields) {
if (f.equals(field)) {
return true;
}
}
return false;
return field.startsWith(this.sourceField + ".");
}
@SuppressWarnings("unchecked")
public void map(SpelMappingContext context) {
if (!context.conditionHolds(this.condition)) {
return;
}
}
try {
this.multiFieldMapper.map(context.getSource(), context.getTarget());
Map<String, Object> nestedFields = context.getSourceNestedFields(this.sourceField);
Object value = this.targetFieldValueAssembler.convert(nestedFields);
context.setTargetFieldValue(this.targetField, value);
} catch (Exception e) {
context.addMappingFailure(e);
}
}
public int hashCode() {
return this.multiFieldMapper.hashCode();
return getSourceField().hashCode() + getTargetField().hashCode();
}
public boolean equals(Object o) {
......@@ -72,11 +79,11 @@ final class MultiFieldToFieldMapping implements SpelMapping {
return false;
}
MultiFieldToFieldMapping m = (MultiFieldToFieldMapping) o;
return this.multiFieldMapper.equals(m.multiFieldMapper);
return getSourceField().equals(m.getSourceField()) && getTargetField().equals(m.getTargetField());
}
public String toString() {
return "[MultiFieldToFieldMapping<" + StylerUtils.style(this.fields) + " -> " + this.multiFieldMapper + ">]";
return "[MultiFieldToFieldMapping<" + StylerUtils.style(getSourceField()) + " -> " + getTargetField() + ">]";
}
}
\ No newline at end of file
......@@ -17,6 +17,7 @@ package org.springframework.mapping.support;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
......@@ -24,7 +25,6 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.ParseException;
......@@ -74,22 +74,25 @@ final class SpelMapper implements Mapper<Object, Object> {
public void setExcludedFields(String[] fields) {
// TODO
}
public void addMapping(String sourceFieldExpression, String targetFieldExpression, Converter<?, ?> converter,
public void addFieldToFieldMapping(String sourceField, String targetField, Converter<?, ?> converter,
String condition) {
Expression sourceField = parseSourceField(sourceFieldExpression);
Expression targetField = parseTargetField(targetFieldExpression);
FieldToFieldMapping mapping = new FieldToFieldMapping(sourceField, targetField, converter,
parseCondition(condition));
this.mappings.add(mapping);
this.mappings.add(new FieldToFieldMapping(parseSourceField(sourceField), parseTargetField(targetField),
converter, parseCondition(condition)));
}
public void addMapping(String field, Mapper<?, ?> mapper, String condition) {
public void addFieldToMultiFieldMapping(String field, Mapper<?, ?> mapper, String condition) {
this.mappings.add(new FieldToMultiFieldMapping(parseSourceField(field), mapper, parseCondition(condition)));
}
public void addMapping(String[] fields, Mapper<?, ?> mapper, String condition) {
this.mappings.add(new MultiFieldToFieldMapping(fields, mapper, parseCondition(condition)));
public void addMultiFieldToFieldMapping(String[] fields, Mapper<?, ?> mapper, String condition) {
this.mappings.add(new FlexibleFieldMapping(fields, mapper, parseCondition(condition)));
}
public void addMultiFieldToFieldMapping(String sourceField, String targetField,
Converter<? extends Map<?, ?>, ?> assembler, String condition) {
this.mappings.add(new MultiFieldToFieldMapping(sourceField, parseTargetField(targetField), assembler,
parseCondition(condition)));
}
public void addNestedMapper(Mapper<?, ?> nestedMapper, MappingTargetFactory targetFactory) {
......@@ -112,16 +115,17 @@ final class SpelMapper implements Mapper<Object, Object> {
Assert.notNull(target, "The target to map to cannot be null");
try {
MappingContextHolder.push(source);
EvaluationContext sourceContext = getEvaluationContext(source);
EvaluationContext targetContext = getEvaluationContext(target);
SpelMappingContext context = new SpelMappingContext(sourceContext, targetContext);
MappableType<?> sourceType = this.mappableTypeFactory.getMappableType(source);
MappableType<?> targetType = this.mappableTypeFactory.getMappableType(target);
SpelMappingContext context = new SpelMappingContext(source, sourceType, target, targetType,
this.conversionService);
for (SpelMapping mapping : this.mappings) {
if (logger.isDebugEnabled()) {
logger.debug(MappingContextHolder.getLevel() + mapping);
}
mapping.map(context);
}
Set<FieldToFieldMapping> autoMappings = getAutoMappings(sourceContext, targetContext);
Set<FieldToFieldMapping> autoMappings = getAutoMappings(context);
for (SpelMapping mapping : autoMappings) {
if (logger.isDebugEnabled()) {
logger.debug(MappingContextHolder.getLevel() + mapping + " (auto)");
......@@ -171,33 +175,29 @@ final class SpelMapper implements Mapper<Object, Object> {
return GenericTypeResolver.resolveTypeArguments(mapper.getClass(), Mapper.class);
}
private EvaluationContext getEvaluationContext(Object object) {
return mappableTypeFactory.getMappableType(object).getEvaluationContext(object, this.conversionService);
}
private Set<FieldToFieldMapping> getAutoMappings(EvaluationContext sourceContext, EvaluationContext targetContext) {
private Set<FieldToFieldMapping> getAutoMappings(SpelMappingContext context) {
if (this.autoMappingEnabled) {
Set<FieldToFieldMapping> autoMappings = new LinkedHashSet<FieldToFieldMapping>();
Set<String> sourceFields = getMappableFields(sourceContext.getRootObject().getValue());
Set<String> sourceFields = context.getSourceFieldNames();
for (String field : sourceFields) {
if (!explicitlyMapped(field)) {
Expression sourceExpression;
Expression targetExpression;
Expression sourceField;
try {
sourceExpression = sourceExpressionParser.parseExpression(field);
sourceField = sourceExpressionParser.parseExpression(field);
} catch (ParseException e) {
throw new IllegalArgumentException("The mapping source '" + field
throw new IllegalArgumentException("The source field '" + field
+ "' is not a parseable value expression", e);
}
Expression targetField;
try {
targetExpression = targetExpressionParser.parseExpression(field);
targetField = targetExpressionParser.parseExpression(field);
} catch (ParseException e) {
throw new IllegalArgumentException("The mapping target '" + field
throw new IllegalArgumentException("The target field '" + field
+ "' is not a parseable value expression", e);
}
try {
if (targetExpression.isWritable(targetContext)) {
autoMappings.add(new FieldToFieldMapping(sourceExpression, targetExpression, null, null));
if (context.isTargetFieldWriteable(targetField)) {
autoMappings.add(new FieldToFieldMapping(sourceField, targetField, null, null));
}
} catch (EvaluationException e) {
......@@ -209,11 +209,7 @@ final class SpelMapper implements Mapper<Object, Object> {
return Collections.emptySet();
}
}
private Set<String> getMappableFields(Object object) {
return mappableTypeFactory.getMappableType(object).getFields(object);
}
private boolean explicitlyMapped(String field) {
for (SpelMapping mapping : this.mappings) {
if (mapping.mapsField(field)) {
......
......@@ -15,6 +15,8 @@
*/
package org.springframework.mapping.support;
import java.util.Map;
import org.springframework.core.convert.converter.Converter;
import org.springframework.mapping.Mapper;
......@@ -40,63 +42,68 @@ final class SpelMapperBuilder<S, T> implements MapperBuilder<S, T> {
}
public MapperBuilder<S, T> addMapping(String field) {
this.mapper.addMapping(field, field, null, null);
this.mapper.addFieldToFieldMapping(field, field, null, null);
return this;
}
public MapperBuilder<S, T> addMapping(String field, Converter<?, ?> converter) {
this.mapper.addMapping(field, field, converter, null);
this.mapper.addFieldToFieldMapping(field, field, converter, null);
return this;
}
public MapperBuilder<S, T> addMapping(String field, Mapper<?, T> mapper) {
this.mapper.addMapping(field, mapper, null);
this.mapper.addFieldToMultiFieldMapping(field, mapper, null);
return this;
}
public MapperBuilder<S, T> addMapping(String sourceField, String targetField) {
this.mapper.addMapping(sourceField, targetField, null, null);
this.mapper.addFieldToFieldMapping(sourceField, targetField, null, null);
return this;
}
public MapperBuilder<S, T> addMapping(String sourceField, String targetField, Converter<?, ?> converter) {
this.mapper.addMapping(sourceField, targetField, converter, null);
this.mapper.addFieldToFieldMapping(sourceField, targetField, converter, null);
return this;
}
public MapperBuilder<S, T> addMapping(String[] fields, Mapper<S, T> mapper) {
this.mapper.addMapping(fields, mapper, null);
this.mapper.addMultiFieldToFieldMapping(fields, mapper, null);
return this;
}
public MapperBuilder<S, T> addAssemblerMapping(String field, Converter<? extends Map<?, ?>, ?> assembler) {
this.mapper.addMultiFieldToFieldMapping(field, field, assembler, null);
return this;
}
public MapperBuilder<S, T> addConditionalMapping(String field, String condition) {
this.mapper.addMapping(field, field, null, condition);
this.mapper.addFieldToFieldMapping(field, field, null, condition);
return this;
}
public MapperBuilder<S, T> addConditionalMapping(String field, Converter<?, ?> converter, String condition) {
this.mapper.addMapping(field, field, converter, condition);
this.mapper.addFieldToFieldMapping(field, field, converter, condition);
return this;
}
public MapperBuilder<S, T> addConditionalMapping(String field, Mapper<?, T> mapper, String condition) {
this.mapper.addMapping(field, mapper, condition);
this.mapper.addFieldToMultiFieldMapping(field, mapper, condition);
return this;
}
public MapperBuilder<S, T> addConditionalMapping(String sourceField, String targetField, String condition) {
this.mapper.addMapping(sourceField, targetField, null, condition);
this.mapper.addFieldToFieldMapping(sourceField, targetField, null, condition);
return this;
}
public MapperBuilder<S, T> addConditionalMapping(String sourceField, String targetField, Converter<?, ?> converter,
String condition) {
this.mapper.addMapping(sourceField, targetField, converter, condition);
this.mapper.addFieldToFieldMapping(sourceField, targetField, converter, condition);
return this;
}
public MapperBuilder<S, T> addMapping(String[] fields, Mapper<S, T> mapper, String condition) {
this.mapper.addMapping(fields, mapper, condition);
public MapperBuilder<S, T> addConditionalMapping(String[] fields, Mapper<S, T> mapper, String condition) {
this.mapper.addMultiFieldToFieldMapping(fields, mapper, condition);
return this;
}
......
......@@ -17,7 +17,10 @@ package org.springframework.mapping.support;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.core.convert.ConversionService;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.mapping.MappingException;
......@@ -25,21 +28,25 @@ import org.springframework.mapping.MappingFailure;
final class SpelMappingContext {
private final MappableType sourceType;
private final EvaluationContext sourceEvaluationContext;
private final EvaluationContext targetEvaluationContext;
private final List<MappingFailure> failures = new LinkedList<MappingFailure>();
public SpelMappingContext(EvaluationContext sourceEvaluationContext, EvaluationContext targetEvaluationContext) {
this.sourceEvaluationContext = sourceEvaluationContext;
this.targetEvaluationContext = targetEvaluationContext;
public SpelMappingContext(Object source, MappableType sourceType, Object target, MappableType targetType,
ConversionService conversionService) {
this.sourceType = sourceType;
this.sourceEvaluationContext = sourceType.getEvaluationContext(source, conversionService);
this.targetEvaluationContext = targetType.getEvaluationContext(target, conversionService);
}
public Object getSource() {
return this.sourceEvaluationContext.getRootObject().getValue();
}
public Object getTarget() {
return this.targetEvaluationContext.getRootObject().getValue();
}
......@@ -55,6 +62,14 @@ final class SpelMappingContext {
return sourceField.getValue(this.sourceEvaluationContext);
}
public Set<String> getSourceFieldNames() {
return this.sourceType.getFieldNames(getSource());
}
public Map<String, Object> getSourceNestedFields(String sourceFieldName) {
return this.sourceType.getNestedFields(sourceFieldName, getSource());
}
public void setTargetFieldValue(Expression targetField, Object value) {
targetField.setValue(this.targetEvaluationContext, value);
}
......@@ -69,4 +84,8 @@ final class SpelMappingContext {
}
}
public boolean isTargetFieldWriteable(Expression targetField) {
return targetField.isWritable(this.targetEvaluationContext);
}
}
......@@ -13,31 +13,42 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.format;
import java.lang.annotation.Annotation;
import java.util.Set;
/**
* A factory that creates {@link Formatter formatters} to format property values on properties
* annotated with a particular format {@link Annotation}.
* A factory that creates formatters to format values of fields annotated with a particular format {@link Annotation}.
*
* <p>For example, a <code>CurrencyAnnotationFormatterFactory</code> might create a <code>Formatter</code>
* that formats a <code>BigDecimal</code> value set on a property annotated with <code>@CurrencyFormat</code>.
* <p>For example, a <code>DateTimeFormatAnnotationFormatterFactory</code> might create a formatter
* that formats a <code>Date</code> objects set on properties annotated with <code>@DateFormat</code>.
*
* @author Keith Donald
* @since 3.0
* @param <A> the type of Annotation this factory uses to create Formatter instances
* @param <T> the type of object that the factory's Formatters are dealing with
* @param <A> the type of Annotation that should trigger property formatting
*/
public interface AnnotationFormatterFactory<A extends Annotation, T> {
public interface AnnotationFormatterFactory<A extends Annotation> {
/**
* The types of fields that may be annotated with the &lt;A&gt; annotation.
*/
Set<Class<?>> getFieldTypes();
/**
* Get the Formatter that will format the value of the property annotated with the provided annotation.
* The annotation instance can contain properties that may be used to configure the Formatter that is returned.
* Get the Printer to print the value of a property of <code>fieldType</code> annotated with <code>annotation</code>.
* @param annotation the annotation instance
* @return the Formatter to use to format values of properties annotated with the annotation.
* @param fieldType the type of property being annotated
* @return the printer
*/
Formatter<T> getFormatter(A annotation);
Printer<?> getPrinter(A annotation, Class<?> fieldType);
/**
* Get the Parser to parse the printed value of a property of <code>fieldType</code> annotated with <code>annotation</code>.
* @param annotation the annotation instance
* @param fieldType the type of field being annotated
* @return the parser
*/
Parser<?> getParser(A annotation, Class<?> fieldType);
}
......@@ -16,35 +16,14 @@
package org.springframework.ui.format;
import java.text.ParseException;
import java.util.Locale;
/**
* Formats objects of type T for display.
* Formats objects of type T.
* A Formatter is both a Printer <i>and</i> a Parser for an object type.
*
* @author Keith Donald
* @since 3.0
* @param <T> the type of object this formatter can format
* @param <T> the type of object this Formatter formats
*/
public interface Formatter<T> {
/**
* Format the object of type T for display.
* @param object the object to format
* @param locale the user's locale
* @return the formatted display string
*/
String format(T object, Locale locale);
/**
* Parse an object from its formatted representation.
* @param formatted a formatted representation
* @param locale the user's locale
* @return the parsed object
* @throws ParseException when a parse exception occurs
* @throws RuntimeException when thrown by coercion methods that are
*
*/
T parse(String formatted, Locale locale) throws ParseException;
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
......@@ -13,15 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.format;
import java.lang.annotation.Annotation;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConverterRegistry;
/**
* A shared registry of Formatters.
* A registry of field formatting logic.
*
* @author Keith Donald
* @since 3.0
......@@ -29,46 +26,43 @@ import org.springframework.core.convert.TypeDescriptor;
public interface FormatterRegistry {
/**
* Adds a Formatter to this registry indexed by type.
* <p>Use this add method when type differs from &lt;T&gt;.
* Calling <code>getFormatter(type)</code> returns a decorator that wraps
* the <code>targetFormatter</code> instance.
* <p>On format, the decorator first coerces the instance of type to &lt;T&gt;,
* then delegates to <code>targetFormatter</code> to format the value.
* <p>On parse, the decorator first delegates to the formatter to parse a &lt;T&gt;,
* then coerces the parsed value to type.
* @param type the object type
* @param targetFormatter the target formatter
*/
void addFormatterByType(Class<?> type, Formatter<?> targetFormatter);
/**
* Adds a Formatter to this registry indexed by &lt;T&gt;.
* <p>Calling <code>getFormatter(&lt;T&gt;.class)</code> returns <code>formatter</code>.
* @param formatter the formatter
* Adds a Formatter to format fields of a specific type.
* The Formatter will delegate to the specified <code>printer</code> for printing and <code>parser</code> for parsing.
* <p>
* On print, if the Printer's type T is declared and <code>fieldType</code> is not assignable to T,
* a coersion to T will be attempted before delegating to <code>printer</code> to print a field value.
* On parse, if the object returned by the Parser is not assignable to the runtime field type,
* a coersion to the field type will be attempted before returning the parsed field value.
* @param fieldType the field type to format
* @param formatter the formatter to add
*/
void addFormatterByType(Formatter<?> formatter);
void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);
/**
* Adds a Formatter to this registry indexed by the given annotation type.
* <p>Calling <code>getFormatter(...)</code> on a field or accessor method
* with the given annotation returns <code>formatter</code>.
* @param formatter the formatter
* Adds a Formatter to format fields of a specific type.
* <p>
* On print, if the Formatter's type T is declared and <code>fieldType</code> is not assignable to T,
* a coersion to T will be attempted before delegating to <code>formatter</code> to print a field value.
* On parse, if the parsed object returned by <code>formatter</code> is not assignable to the runtime field type,
* a coersion to the field type will be attempted before returning the parsed field value.
* @param fieldType the field type to format
* @param formatter the formatter to add
*/
void addFormatterByAnnotation(Class<? extends Annotation> annotationType, Formatter<?> formatter);
void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);
/**
* Adds a AnnotationFormatterFactory that returns the Formatter for properties annotated with a specific annotation.
* <p>Calling <code>getFormatter(...)</code> on a field or accessor method
* with the given annotation returns <code>formatter</code>.
* @param factory the annotation formatter factory
* Adds a Formatter to format fields annotated with a specific format annotation.
* @param annotationFormatterFactory the annotation formatter factory to add
*/
void addFormatterByAnnotation(AnnotationFormatterFactory<?, ?> factory);
void addFormatterForFieldAnnotation(AnnotationFormatterFactory<?> annotationFormatterFactory);
/**
* Get the Formatter for the type descriptor.
* @return the Formatter, or <code>null</code> if no suitable one is registered
* Returns the registry of Converters that coerse field values to types required by Formatters.
* Allows clients to register their own custom converters.
* For example, a date/time formatting configuration might expect a java.util.Date field value to be converted to a Long for formatting.
* Registering a simpler DateToLongConverter allievates the need to register multiple formatters for closely related types.
* @return the converter registry, allowing new Converters to be registered
*/
Formatter<Object> getFormatter(TypeDescriptor type);
ConverterRegistry getConverterRegistry();
}
/*
* Copyright 2002-2009 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.ui.format;
import java.text.ParseException;
import java.util.Locale;
import org.springframework.core.convert.TypeDescriptor;
/**
* A service interface for formatting localized field values.
* This is the entry point into the <code>ui.format</code> system.
*
* @author Keith Donald
* @since 3.0
*/
public interface FormattingService {
/**
* Print the field value for display in the locale.
* @param fieldValue the field value
* @param fieldType the field type
* @param locale the user's locale
* @return the printed string
*/
String print(Object fieldValue, TypeDescriptor fieldType, Locale locale);
/**
* Parse the the value submitted by the user.
* @param submittedValue the submitted field value
* @param fieldType the field type
* @param locale the user's locale
* @return the parsed field value
*/
Object parse(String submittedValue, TypeDescriptor fieldType, Locale locale) throws ParseException;
}
......@@ -13,23 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.convert.support;
import java.util.Calendar;
import java.util.Date;
package org.springframework.ui.format;
import org.springframework.core.convert.converter.Converter;
import java.text.ParseException;
import java.util.Locale;
/**
* Converts from a java.util.Date to a java.util.Calendar.
* Parses objects of type T from their printed representations.
*
* @author Keith Donald
* @since 3.0
* @param <T> the type of object this Parser parses
*/
final class DateToCalendarConverter implements Converter<Date, Calendar> {
public interface Parser<T> {
public Calendar convert(Date source) {
Calendar cal = Calendar.getInstance();
cal.setTime(source);
return cal;
}
/**
* Parse an object from its printed representation.
* @param printed a printed representation
* @param locale the current user locale
* @return the parsed object
* @throws ParseException when a parse exception occurs in a java.text parsing library
* @throws RuntimeException when a parse exception occurs
*/
T parse(String printed, Locale locale) throws ParseException;
}
......@@ -13,21 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.convert.support;
import java.util.Calendar;
import java.util.Date;
package org.springframework.ui.format;
import org.springframework.core.convert.converter.Converter;
import java.util.Locale;
/**
* Converts from a java.util.Calendar to a java.util.Date.
* Prints objects of type T for display.
*
* @author Keith Donald
* @since 3.0
* @param <T> the type of object this Printer prints
*/
final class CalendarToDateConverter implements Converter<Calendar, Date> {
public Date convert(Calendar source) {
return source.getTime();
}
public interface Printer<T> {
/**
* Print the object of type T for display.
* @param object the object to print
* @param locale the current user locale
* @return the printed string
*/
String print(T object, Locale locale);
}
......@@ -97,17 +97,11 @@ public class DateFormatter implements Formatter<Date> {
}
public String format(Date date, Locale locale) {
if (date == null) {
return "";
}
public String print(Date date, Locale locale) {
return getDateFormat(locale).format(date);
}
public Date parse(String formatted, Locale locale) throws ParseException {
if (formatted.length() == 0) {
return null;
}
return getDateFormat(locale).parse(formatted);
}
......
/*
* Copyright 2002-2009 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.ui.format.jodatime;
import java.lang.annotation.Annotation;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.joda.time.DateTime;
import org.joda.time.ReadableInstant;
import org.joda.time.ReadablePartial;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.ui.format.AnnotationFormatterFactory;
import org.springframework.ui.format.Parser;
import org.springframework.ui.format.Printer;
/**
* Base class for annotation-based Joda DateTime formatters.
* Can format any Joda {@link ReadableInstant} or {@link ReadablePartial} property, as well as standard {@link Date}, {@link Calendar}, and Long properties.
* @author Keith Donald
* @param <A> the format annotation parameter type to be declared by subclasses
*/
abstract class AbstractDateTimeAnnotationFormatterFactory<A extends Annotation> implements AnnotationFormatterFactory<A> {
private final Set<Class<?>> propertyTypes;
public AbstractDateTimeAnnotationFormatterFactory() {
this.propertyTypes = Collections.unmodifiableSet(createPropertyTypes());
}
public Set<Class<?>> getFieldTypes() {
return propertyTypes;
}
public Printer<?> getPrinter(A annotation, Class<?> propertyType) {
DateTimeFormatter formatter = configureDateTimeFormatterFrom(annotation);
if (ReadableInstant.class.isAssignableFrom(propertyType)) {
return new ReadableInstantPrinter(formatter);
} else if (ReadablePartial.class.equals(propertyType)) {
return new ReadablePartialPrinter(formatter);
} else if (Calendar.class.isAssignableFrom(propertyType)) {
// assumes Calendar->ReadableInstant converter is registered
return new ReadableInstantPrinter(formatter);
} else {
// assumes Date->Long converter is registered
return new MillisecondInstantPrinter(formatter);
}
}
public Parser<DateTime> getParser(A annotation, Class<?> propertyType) {
return new DateTimeParser(configureDateTimeFormatterFrom(annotation));
}
/**
* Hook method subclasses should override to configure the Joda {@link DateTimeFormatter} from the <code>annotation</code> values.
* @param annotation the annotation containing formatter configuration rules
* @return the configured date time formatter
*/
protected abstract DateTimeFormatter configureDateTimeFormatterFrom(A annotation);
// internal helpers
private Set<Class<?>> createPropertyTypes() {
Set<Class<?>> propertyTypes = new HashSet<Class<?>>();
propertyTypes.add(ReadableInstant.class);
propertyTypes.add(ReadablePartial.class);
propertyTypes.add(Date.class);
propertyTypes.add(Calendar.class);
propertyTypes.add(Long.class);
return propertyTypes;
}
}
......@@ -15,43 +15,66 @@
*/
package org.springframework.ui.format.jodatime;
import java.text.ParseException;
import java.util.Locale;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.springframework.ui.format.Formatter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* {@link Formatter} implementation to handle JodaTime {@link DateTime} instances.
* @author Oliver Gierke
* Indicates a property should be formatted as a date time.
* @author Keith Donald
*/
public class DateTimeFormatter implements Formatter<DateTime> {
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DateTimeFormat {
private org.joda.time.format.DateTimeFormatter formatter;
/**
* Creates a new {@link DateTimeFormatter} for the given JodaTime formatting pattern.
* @param pattern
* @see DateTimeFormat#forPattern(String)
* The style to use for formatting the date portion of the property.
* Defaults to {@link FormatStyle#NONE}.
*/
public DateTimeFormatter(String pattern) {
this.formatter = DateTimeFormat.forPattern(pattern);
}
FormatStyle dateStyle() default FormatStyle.NONE;
/**
* Creates a new {@link DateTimeFormatter} for the given JodaTime formatter.
* @param formatter the Date Formatter instance
* The style to use for formatting the time portion of the property.
* Defaults to {@link FormatStyle#NONE}.
*/
public DateTimeFormatter(org.joda.time.format.DateTimeFormatter formatter) {
this.formatter = formatter;
}
public String format(DateTime object, Locale locale) {
return null == object ? "" : object.toString(formatter);
}
FormatStyle timeStyle() default FormatStyle.NONE;
public DateTime parse(String formatted, Locale locale) throws ParseException {
return formatter.withLocale(locale).parseDateTime(formatted);
/**
* A pattern String that defines a custom format for the property.
* Use this method or the <code>*Style</code> methods, not both.
*/
String pattern() default "";
/**
* Supported DateTimeFormat styles.
*/
public enum FormatStyle {
SHORT {
public String toString() {
return "S";
}
},
MEDIUM {
public String toString() {
return "M";
}
},
LONG {
public String toString() {
return "L";
}
},
FULL {
public String toString() {
return "F";
}
},
NONE {
public String toString() {
return "-";
}
}
}
}
/*
* Copyright 2002-2009 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.ui.format.jodatime;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.ui.format.jodatime.DateTimeFormat.FormatStyle;
/**
* Formats properties annotated with the {@link DateTimeFormat} annotation.
*
* @author Keith Donald
* @since 3.0
* @see DateTimeFormat
*/
public final class DateTimeFormatAnnotationFormatterFactory extends AbstractDateTimeAnnotationFormatterFactory<DateTimeFormat> {
protected DateTimeFormatter configureDateTimeFormatterFrom(DateTimeFormat annotation) {
String pattern = annotation.pattern();
if (!pattern.isEmpty()) {
return forPattern(pattern);
} else {
FormatStyle dateStyle = annotation.dateStyle();
FormatStyle timeStyle = annotation.timeStyle();
return forDateTimeStyle(dateStyle, timeStyle);
}
}
private DateTimeFormatter forDateTimeStyle(FormatStyle dateStyle, FormatStyle timeStyle) {
return org.joda.time.format.DateTimeFormat.forStyle(dateStyle.toString() + timeStyle.toString());
}
private DateTimeFormatter forPattern(String pattern) {
return org.joda.time.format.DateTimeFormat.forPattern(pattern);
}
}
\ No newline at end of file
......@@ -13,35 +13,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.format.jodatime;
import static org.junit.Assert.assertEquals;
import java.text.ParseException;
import java.util.Locale;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.junit.Test;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.ui.format.Parser;
/**
* Parses Joda Time {@link DateTime} instances using a {@link DateTimeFormatter}.
* @author Keith Donald
*/
public class DateTimeFormatterTests {
public final class DateTimeParser implements Parser<DateTime> {
private DateTimeFormatter formatter = new DateTimeFormatter("yyyy-MM-dd");
@Test
public void formatValue() {
DateTime dateTime = DateTimeFormat.forPattern("yyyy-MM-dd").parseDateTime("2009-06-01");
assertEquals("2009-06-01", formatter.format(dateTime, Locale.US));
}
@Test
public void parseValue() throws ParseException {
DateTime dateTime = DateTimeFormat.forPattern("yyyy-MM-dd").parseDateTime("2009-06-01");
assertEquals(dateTime, formatter.parse("2009-06-01", Locale.US));
private final DateTimeFormatter formatter;
/**
* Creates a new DateTimeParser.
* @param formatter the Joda DateTimeFormatter instance
*/
public DateTimeParser(DateTimeFormatter formatter) {
this.formatter = formatter;
}
public DateTime parse(String printed, Locale locale) throws ParseException {
return JodaTimeContextHolder.getFormatter(this.formatter, locale).parseDateTime(printed);
}
}
......@@ -13,29 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ui.format.jodatime;
package org.springframework.ui.format;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* A type that can be formatted as a String for display in a user interface.
*
* Indicates a property should be formatted as a ISO date time.
* @author Keith Donald
* @since 3.0
*/
@Target({ElementType.TYPE})
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Formatted {
public @interface ISODateTimeFormat {
/**
* The Formatter that handles the formatting for the annotated element.
*/
Class<? extends Formatter> value();
FormatStyle value();
public enum FormatStyle {
DATE, TIME, DATE_TIME
}
}
/*
* Copyright 2002-2009 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.ui.format.jodatime;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.ui.format.jodatime.ISODateTimeFormat.FormatStyle;
/**
* Formats properties annotated with the {@link ISODateTimeFormat} annotation.
*
* @author Keith Donald
* @since 3.0
* @see ISODateTimeFormat
*/
public final class ISODateTimeFormatAnnotationFormatterFactory extends AbstractDateTimeAnnotationFormatterFactory<ISODateTimeFormat> {
protected DateTimeFormatter configureDateTimeFormatterFrom(ISODateTimeFormat annotation) {
FormatStyle style = annotation.value();
if (style == FormatStyle.DATE) {
return org.joda.time.format.ISODateTimeFormat.date();
} else if (style == FormatStyle.TIME) {
return org.joda.time.format.ISODateTimeFormat.time();
} else {
return org.joda.time.format.ISODateTimeFormat.dateTime();
}
}
}
\ No newline at end of file
/*
* Copyright 2002-2009 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.ui.format.jodatime;
import org.joda.time.Chronology;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormatter;
/**
* A context that holds user-specific Joda Time settings such as the user's Chronology (calendar system) and time zone.
* A <code>null</code> property value indicate the user has not specified a setting.
* @author Keith Donald
* @see JodaTimeContextHolder
*/
public class JodaTimeContext {
private Chronology chronology;
private DateTimeZone timeZone;
/**
* The user's chronology (calendar system).
* Null if not specified.
*/
public Chronology getChronology() {
return chronology;
}
/**
* Sets the user's chronology.
*/
public void setChronology(Chronology chronology) {
this.chronology = chronology;
}
/**
* The user's timezone.
* Null if not specified.
*/
public DateTimeZone getTimeZone() {
return timeZone;
}
/**
* Sets the user's timezone.
*/
public void setTimeZone(DateTimeZone timeZone) {
this.timeZone = timeZone;
}
/**
* Gets the Formatter with the this context's settings applied to the base <code>formatter</code>.
* @param formatter the base formatter that establishes default formatting rules, generally context independent
* @return the context DateTimeFormatter
*/
public DateTimeFormatter getFormatter(DateTimeFormatter formatter) {
if (this.chronology != null) {
formatter = formatter.withChronology(this.chronology);
}
if (this.timeZone != null) {
formatter = formatter.withZone(this.timeZone);
}
return formatter;
}
}
/*
* Copyright 2002-2009 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.ui.format.jodatime;
import java.util.Locale;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.core.NamedInheritableThreadLocal;
/**
* A holder for a thread-local user {@link JodaTimeContext}.
* @author Keith Donald
*/
public final class JodaTimeContextHolder {
private static final ThreadLocal<JodaTimeContext> jodaTimeContextHolder = new NamedInheritableThreadLocal<JodaTimeContext>(
"Joda Time Context");
/**
* Return the JodaTimeContext associated with the current thread, if any.
* @return the current JodaTimeContext, or <code>null</code> if none
*/
public static JodaTimeContext getJodaTimeContext() {
return jodaTimeContextHolder.get();
}
/**
* Associate the given JodaTimeContext with the current thread.
* @param context the current JodaTimeContext, or <code>null</code> to clear
* the thread-bound context
*/
public static void setLocaleContext(JodaTimeContext context) {
jodaTimeContextHolder.set(context);
}
/**
* Gets the Formatter with the user-specific settings applied to thefrom the base <code>formatter</code>.
* @param formatter the base formatter that establishes default formatting rules, generally user independent
* @param locale the current user locale (may be null if not known)
* @return the user's DateTimeFormatter
*/
public static DateTimeFormatter getFormatter(DateTimeFormatter formatter, Locale locale) {
if (locale != null) {
formatter = formatter.withLocale(locale);
}
JodaTimeContext context = getJodaTimeContext();
return context != null ? context.getFormatter(formatter) : formatter;
}
}
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.convert.support;
package org.springframework.ui.format.jodatime;
import java.util.Calendar;
import java.util.Date;
......@@ -23,106 +23,89 @@ import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.LocalTime;
import org.joda.time.ReadableInstant;
import org.springframework.core.convert.converter.Converter;
import org.springframework.util.ClassUtils;
import org.springframework.core.convert.converter.ConverterRegistry;
class JodaTimeConverters {
public static void addConverters(GenericConversionService registry) {
if (!isJodaTimePresent()) {
return;
}
registry.addConverter(DateTime.class, LocalDate.class, new DateTimeToLocalDateConverter());
registry.addConverter(LocalDate.class, DateTime.class, new LocalDateToDateTimeConverter());
registry.addConverter(DateTime.class, LocalTime.class, new DateTimeToLocalTimeConverter());
registry.addConverter(LocalTime.class, DateTime.class, new LocalTimeToDateTimeConverter());
registry.addConverter(DateTime.class, LocalDateTime.class, new DateTimeToLocalDateTimeConverter());
registry.addConverter(LocalDateTime.class, DateTime.class, new LocalDateTimeToDateTimeConverter());
registry.addConverter(DateTime.class, DateMidnight.class, new DateTimeToDateMidnightConverter());
registry.addConverter(DateMidnight.class, Date.class, new DateMidnightToDateTimeConverter());
/**
* Installs lower-level type converters required to integrate Joda Time support into Spring's formatting and property binding systems.
* @author Keith Donald
*/
final class JodaTimeConverters {
registry.addConverter(DateTime.class, Date.class, new DateTimeToDateConverter());
registry.addConverter(Date.class, DateTime.class, new DateToDateTimeConverter());
private JodaTimeConverters() {
registry.addConverter(DateTime.class, Calendar.class, new DateTimeToCalendarConverter());
registry.addConverter(Calendar.class, DateTime.class, new CalendarToDateTimeConverter());
}
private static boolean isJodaTimePresent() {
return ClassUtils.isPresent("org.joda.time.DateTime", JodaTimeConverters.class.getClassLoader());
/**
* Installs the converters into the converter registry.
* @param registry the converter registry
*/
public static void registerConverters(ConverterRegistry registry) {
registry.addConverter(new DateTimeToLocalDateConverter());
registry.addConverter(new DateTimeToLocalTimeConverter());
registry.addConverter(new DateTimeToLocalDateTimeConverter());
registry.addConverter(new DateTimeToDateMidnightConverter());
registry.addConverter(new DateTimeToDateConverter());
registry.addConverter(new DateTimeToCalendarConverter());
registry.addConverter(new DateToLongConverter());
registry.addConverter(new CalendarToDateTimeConverter());
}
// internal helpers
// used when binding a parsed DateTime to a LocalDate property
private static class DateTimeToLocalDateConverter implements Converter<DateTime, LocalDate> {
public LocalDate convert(DateTime source) {
return source.toLocalDate();
}
}
private static class LocalDateToDateTimeConverter implements Converter<LocalDate, DateTime> {
public DateTime convert(LocalDate source) {
return source.toDateTimeAtStartOfDay();
}
}
// used when binding a parsed DateTime to a LocalTime property
private static class DateTimeToLocalTimeConverter implements Converter<DateTime, LocalTime> {
public LocalTime convert(DateTime source) {
return source.toLocalTime();
}
}
private static class LocalTimeToDateTimeConverter implements Converter<LocalTime, DateTime> {
public DateTime convert(LocalTime source) {
return source.toDateTimeToday();
}
}
// used when binding a parsed DateTime to a LocalDateTime property
private static class DateTimeToLocalDateTimeConverter implements Converter<DateTime, LocalDateTime> {
public LocalDateTime convert(DateTime source) {
return source.toLocalDateTime();
}
}
private static class LocalDateTimeToDateTimeConverter implements Converter<LocalDateTime, DateTime> {
public DateTime convert(LocalDateTime source) {
return source.toDateTime();
}
}
// used when binding a parsed DateTime to a DateMidnight property
private static class DateTimeToDateMidnightConverter implements Converter<DateTime, DateMidnight> {
public DateMidnight convert(DateTime source) {
return source.toDateMidnight();
}
}
private static class DateMidnightToDateTimeConverter implements Converter<DateMidnight, DateTime> {
public DateTime convert(DateMidnight source) {
return source.toDateTime();
}
}
// used when binding a parsed DateTime to a java.util.Date property
private static class DateTimeToDateConverter implements Converter<DateTime, Date> {
public Date convert(DateTime source) {
return source.toDate();
}
}
private static class DateToDateTimeConverter implements Converter<Date, DateTime> {
public DateTime convert(Date source) {
return new DateTime(source);
}
}
// used when binding a parsed DateTime to a java.util.Calendar property
private static class DateTimeToCalendarConverter implements Converter<DateTime, Calendar> {
public Calendar convert(DateTime source) {
return source.toGregorianCalendar();
}
}
private static class CalendarToDateTimeConverter implements Converter<Calendar, DateTime> {
public DateTime convert(Calendar source) {
// used when formatting a java.util.Date property with a MillisecondInstantPrinter
private static class DateToLongConverter implements Converter<Date, Long> {
public Long convert(Date source) {
return source.getTime();
}
}
// used when formatting a java.util.Calendar property with a ReadableInstantPrinter
private static class CalendarToDateTimeConverter implements Converter<Calendar, ReadableInstant> {
public ReadableInstant convert(Calendar source) {
return new DateTime(source);
}
}
......
/*
* Copyright 2002-2009 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.ui.format.jodatime;
import java.util.Calendar;
import java.util.Date;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.LocalTime;
import org.joda.time.ReadableInstant;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.springframework.ui.format.FormatterRegistry;
import org.springframework.ui.format.Parser;
import org.springframework.ui.format.Printer;
/**
* Configures Joda Time's Formatting system for use with Spring.
* @author Keith Donald
*/
public class JodaTimeFormattingConfigurer {
private FormatterRegistry formatterRegistry;
private String dateStyle;
private String timeStyle;
private String dateTimeStyle;
private boolean useISOFormat;
/**
* Creates a new JodaTimeFormattingConfigurer that installs into the provided FormatterRegistry.
* Call {@link #registerJodaTimeFormatting()} to install.
* @param formatterRegistry the registry to register Joda Time formatters with
*/
public JodaTimeFormattingConfigurer(FormatterRegistry formatterRegistry) {
this.formatterRegistry = formatterRegistry;
}
/**
* Set the default format style of Joda {@link LocalDate} objects.
* Default is {@link DateTimeFormat#shortDate()}.
* @param dateStyle the date format style
*/
public void setDateStyle(String dateStyle) {
this.dateStyle = dateStyle;
}
/**
* Set the default format style of Joda {@link LocalTime} objects.
* Default is {@link DateTimeFormat#shortTime()}.
* @param timeStyle the time format style
*/
public void setTimeStyle(String timeStyle) {
this.timeStyle = timeStyle;
}
/**
* Set the default format style of Joda {@link LocalDateTime} and {@link DateTime} objects, as well as JDK {@link Date} and {@link Calendar} objects.
* Default is {@link DateTimeFormat#shortDateTime()}.
* @param dateTimeStyle the date time format style
*/
public void setDateTimeStyle(String dateTimeStyle) {
this.dateTimeStyle = dateTimeStyle;
}
/**
* Set whether standard ISO formatting should be applied to all Date/Time types.
* Default is false (no).
* If set to true, the dateStyle, timeStyle, and dateTimeStyle properties are ignored.
* @param useISOFormat true to enable ISO formatting
*/
public void setUseISOFormat(boolean useISOFormat) {
this.useISOFormat = useISOFormat;
}
/**
* Install Joda Time formatters given the current configuration of this {@link JodaTimeFormattingConfigurer}.
*/
public void registerJodaTimeFormatting() {
JodaTimeConverters.registerConverters(this.formatterRegistry.getConverterRegistry());
DateTimeFormatter jodaDateFormatter = getJodaDateFormatter();
this.formatterRegistry.addFormatterForFieldType(LocalDate.class, new ReadablePartialPrinter(jodaDateFormatter), new DateTimeParser(jodaDateFormatter));
DateTimeFormatter jodaTimeFormatter = getJodaTimeFormatter();
this.formatterRegistry.addFormatterForFieldType(LocalTime.class, new ReadablePartialPrinter(jodaTimeFormatter), new DateTimeParser(jodaTimeFormatter));
DateTimeFormatter jodaDateTimeFormatter = getJodaDateTimeFormatter();
Parser<DateTime> dateTimeParser = new DateTimeParser(jodaDateTimeFormatter);
this.formatterRegistry.addFormatterForFieldType(LocalDateTime.class, new ReadablePartialPrinter(jodaDateTimeFormatter), dateTimeParser);
Printer<ReadableInstant> readableInstantPrinter = new ReadableInstantPrinter(jodaDateTimeFormatter);
this.formatterRegistry.addFormatterForFieldType(ReadableInstant.class, readableInstantPrinter, dateTimeParser);
this.formatterRegistry.addFormatterForFieldType(Calendar.class, readableInstantPrinter, dateTimeParser);
this.formatterRegistry.addFormatterForFieldType(Calendar.class, new MillisecondInstantPrinter(jodaDateTimeFormatter), dateTimeParser);
this.formatterRegistry.addFormatterForFieldAnnotation(new DateTimeFormatAnnotationFormatterFactory());
}
// internal helpers
private DateTimeFormatter getJodaDateFormatter() {
if (this.useISOFormat) {
return ISODateTimeFormat.date();
}
if (this.dateStyle != null) {
return DateTimeFormat.forStyle(this.dateStyle + "-");
} else {
return DateTimeFormat.shortDate();
}
}
private DateTimeFormatter getJodaTimeFormatter() {
if (this.useISOFormat) {
return ISODateTimeFormat.time();
}
if (this.timeStyle != null) {
return DateTimeFormat.forStyle("-" + this.timeStyle);
} else {
return DateTimeFormat.shortTime();
}
}
private DateTimeFormatter getJodaDateTimeFormatter() {
if (this.useISOFormat) {
return ISODateTimeFormat.dateTime();
}
if (this.dateTimeStyle != null) {
return DateTimeFormat.forStyle(this.dateTimeStyle);
} else {
return DateTimeFormat.shortDateTime();
}
}
}
/*
* Copyright 2002-2009 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.ui.format.jodatime;
import java.util.Locale;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.ui.format.Printer;
/**
* Prints Long instances using a {@link DateTimeFormatter}.
* @author Keith Donald
*/
public final class MillisecondInstantPrinter implements Printer<Long> {
private final DateTimeFormatter formatter;
/**
* Creates a new ReadableInstantPrinter.
* @param formatter the Joda DateTimeFormatter instance
*/
public MillisecondInstantPrinter(DateTimeFormatter formatter) {
this.formatter = formatter;
}
public String print(Long instant, Locale locale) {
return JodaTimeContextHolder.getFormatter(this.formatter, locale).print(instant);
}
}
/*
* Copyright 2002-2009 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.ui.format.jodatime;
import java.util.Locale;
import org.joda.time.ReadableInstant;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.ui.format.Printer;
/**
* Prints Joda Time {@link ReadableInstant} instances using a {@link DateTimeFormatter}.
* @author Keith Donald
*/
public final class ReadableInstantPrinter implements Printer<ReadableInstant> {
private final DateTimeFormatter formatter;
/**
* Creates a new ReadableInstantPrinter.
* @param formatter the Joda DateTimeFormatter instance
*/
public ReadableInstantPrinter(DateTimeFormatter formatter) {
this.formatter = formatter;
}
public String print(ReadableInstant instant, Locale locale) {
return JodaTimeContextHolder.getFormatter(this.formatter, locale).print(instant);
}
}
/*
* Copyright 2002-2009 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.ui.format.jodatime;
import java.util.Locale;
import org.joda.time.ReadablePartial;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.ui.format.Printer;
/**
* Prints Joda Time {@link ReadablePartial} instances using a {@link DateTimeFormatter}.
* @author Keith Donald
*/
public final class ReadablePartialPrinter implements Printer<ReadablePartial> {
private final DateTimeFormatter formatter;
/**
* Creates a new ReadableInstantPrinter.
* @param formatter the Joda DateTimeFormatter instance
*/
public ReadablePartialPrinter(DateTimeFormatter formatter) {
this.formatter = formatter;
}
public String print(ReadablePartial partial, Locale locale) {
return JodaTimeContextHolder.getFormatter(this.formatter, locale).print(partial);
}
}
/**
* Integration with the Joda Time for formatting Joda types as well as standard JDK Date types.
*/
package org.springframework.ui.format.jodatime;
......@@ -46,18 +46,11 @@ public abstract class AbstractNumberFormatter implements Formatter<Number> {
}
public String format(Number integer, Locale locale) {
if (integer == null) {
return "";
}
NumberFormat format = getNumberFormat(locale);
return format.format(integer);
public String print(Number integer, Locale locale) {
return getNumberFormat(locale).format(integer);
}
public Number parse(String formatted, Locale locale) throws ParseException {
if (formatted.length() == 0) {
return null;
}
NumberFormat format = getNumberFormat(locale);
ParsePosition position = new ParsePosition(0);
Number number = format.parse(formatted, position);
......
/**
* A SPI for defining Formatters to format field model values for display in a UI.
* An API for defining Formatters to format field model values for display in a UI.
*/
package org.springframework.ui.format;
......@@ -19,79 +19,40 @@ package org.springframework.ui.format.support;
import java.text.ParseException;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.convert.support.GenericConverter;
import org.springframework.ui.format.Formatter;
import org.springframework.ui.format.FormatterRegistry;
import org.springframework.util.Assert;
import org.springframework.ui.format.FormattingService;
/**
* Adapter that exposes a {@link ConversionService} reference for a given
* {@link org.springframework.ui.format.FormatterRegistry}, retrieving the current
* Locale from {@link org.springframework.context.i18n.LocaleContextHolder}.
* Adapter that exposes a {@link ConversionService} reference for a given {@link FormattingService},
* retrieving the current Locale from {@link LocaleContextHolder}.
*
* @author Juergen Hoeller
* @since 3.0
*/
public class FormattingConversionServiceAdapter extends GenericConversionService {
private final FormatterRegistry formatterRegistry;
/**
* Create a new FormattingConversionServiceAdapter for the given FormatterRegistry.
* @param formatterRegistry the FormatterRegistry to wrap
*/
public FormattingConversionServiceAdapter(FormatterRegistry formatterRegistry) {
Assert.notNull(formatterRegistry, "FormatterRegistry must not be null");
this.formatterRegistry = formatterRegistry;
if (formatterRegistry instanceof GenericFormatterRegistry) {
setParent(((GenericFormatterRegistry) formatterRegistry).getConversionService());
}
else {
setParent(new DefaultConversionService());
}
}
@Override
protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
if (String.class.equals(sourceType.getType())) {
Formatter formatter = this.formatterRegistry.getFormatter(targetType);
if (formatter != null) {
return new FormattingConverter(formatter);
}
}
return super.getConverter(sourceType, targetType);
}
/**
* Adapter that exposes the Converter interface on top of a given Formatter.
*/
private static class FormattingConverter implements GenericConverter {
private final Formatter formatter;
public FormattingConverter(Formatter formatter) {
this.formatter = formatter;
}
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
}
try {
return this.formatter.parse((String) source, LocaleContextHolder.getLocale());
private final FormattingService formattingService;
public FormattingConversionServiceAdapter(FormattingService formattingService) {
this.formattingService = formattingService;
addGenericConverter(String.class, Object.class, new GenericConverter() {
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
try {
return FormattingConversionServiceAdapter.this.formattingService.parse((String) source, targetType, LocaleContextHolder.getLocale());
} catch (ParseException e) {
throw new ConversionFailedException(sourceType, targetType, source, e);
}
}
catch (ParseException ex) {
throw new IllegalArgumentException("Could not convert formatted value '" + source + "'", ex);
});
addGenericConverter(Object.class, String.class, new GenericConverter() {
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return FormattingConversionServiceAdapter.this.formattingService.print(source, targetType, LocaleContextHolder.getLocale());
}
}
});
}
}
......@@ -20,7 +20,8 @@ import java.beans.PropertyEditorSupport;
import java.text.ParseException;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.ui.format.Formatter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.ui.format.FormattingService;
import org.springframework.util.Assert;
/**
......@@ -33,23 +34,26 @@ import org.springframework.util.Assert;
*/
public class FormattingPropertyEditorAdapter extends PropertyEditorSupport {
private final Formatter<Object> formatter;
private final FormattingService formattingService;
private final TypeDescriptor fieldType;
/**
* Create a new FormattingPropertyEditorAdapter for the given Formatter.
* @param formatter the Formatter to wrap
*/
public FormattingPropertyEditorAdapter(Formatter<Object> formatter) {
Assert.notNull(formatter, "Formatter must not be null");
this.formatter = formatter;
public FormattingPropertyEditorAdapter(FormattingService formattingService, Class<?> fieldType) {
Assert.notNull(formattingService, "FormattingService must not be null");
Assert.notNull(formattingService, "FieldType must not be null");
this.formattingService = formattingService;
this.fieldType = TypeDescriptor.valueOf(fieldType);
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
try {
setValue(this.formatter.parse(text, LocaleContextHolder.getLocale()));
setValue(this.formattingService.parse(text, this.fieldType, LocaleContextHolder.getLocale()));
}
catch (ParseException ex) {
throw new IllegalArgumentException("Failed to parse formatted value", ex);
......@@ -58,7 +62,7 @@ public class FormattingPropertyEditorAdapter extends PropertyEditorSupport {
@Override
public String getAsText() {
return this.formatter.format(getValue(), LocaleContextHolder.getLocale());
return this.formattingService.print(getValue(), this.fieldType, LocaleContextHolder.getLocale());
}
}
/*
* Copyright 2002-2009 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.ui.format.support;
import java.lang.annotation.Annotation;
import java.text.ParseException;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.ui.format.AnnotationFormatterFactory;
import org.springframework.ui.format.Formatted;
import org.springframework.ui.format.Formatter;
import org.springframework.ui.format.FormatterRegistry;
import org.springframework.util.Assert;
/**
* A generic implementation of {@link org.springframework.ui.format.FormatterRegistry}
* suitable for use in most environments.
*
* @author Keith Donald
* @author Juergen Hoeller
* @since 3.0
* @see #setFormatters(Set)
* @see #setFormatterMap(Map)
* @see #setAnnotationFormatterMap(Map)
* @see #setAnnotationFormatterFactories(Set)
* @see #setConversionService(ConversionService)
* @see #addFormatterByType(Formatter)
* @see #addFormatterByType(Class, Formatter)
* @see #addFormatterByAnnotation(Class, Formatter)
* @see #addFormatterByAnnotation(AnnotationFormatterFactory)
*/
public class GenericFormatterRegistry implements FormatterRegistry, ApplicationContextAware, Cloneable {
private final Map<Class<?>, FormatterHolder> typeFormatters = new ConcurrentHashMap<Class<?>, FormatterHolder>();
private final Map<Class<?>, AnnotationFormatterFactoryHolder> annotationFormatters = new ConcurrentHashMap<Class<?>, AnnotationFormatterFactoryHolder>();
private ConversionService conversionService;
private ApplicationContext applicationContext;
private boolean shared = true;
/**
* Registers the formatters in the set provided.
* JavaBean-friendly alternative to calling {@link #addFormatterByType(Formatter)}.
* @see #add(Formatter)
*/
public void setFormatters(Set<Formatter<?>> formatters) {
for (Formatter<?> formatter : formatters) {
addFormatterByType(formatter);
}
}
/**
* Registers the formatters in the map provided by type.
* JavaBean-friendly alternative to calling {@link #addFormatterByType(Class, Formatter)}.
* @see #add(Class, Formatter)
*/
public void setFormatterMap(Map<Class<?>, Formatter<?>> formatters) {
for (Map.Entry<Class<?>, Formatter<?>> entry : formatters.entrySet()) {
addFormatterByType(entry.getKey(), entry.getValue());
}
}
/**
* Registers the formatters in the map provided by annotation type.
* JavaBean-friendly alternative to calling {@link #addFormatterByAnnotation(Class, Formatter)}.
* @see #add(Class, Formatter)
*/
public void setAnnotationFormatterMap(Map<Class<? extends Annotation>, Formatter<?>> formatters) {
for (Map.Entry<Class<? extends Annotation>, Formatter<?>> entry : formatters.entrySet()) {
addFormatterByAnnotation(entry.getKey(), entry.getValue());
}
}
/**
* Registers the annotation formatter factories in the set provided.
* JavaBean-friendly alternative to calling {@link #addFormatterByAnnotation(AnnotationFormatterFactory)}.
* @see #add(AnnotationFormatterFactory)
*/
public void setAnnotationFormatterFactories(Set<AnnotationFormatterFactory<?, ?>> factories) {
for (AnnotationFormatterFactory<?, ?> factory : factories) {
addFormatterByAnnotation(factory);
}
}
/**
* Specify the type conversion service that will be used to coerce objects to the
* types required for formatting. Defaults to a {@link DefaultConversionService}.
* @see #addFormatterByType(Class, Formatter)
*/
public void setConversionService(ConversionService conversionService) {
Assert.notNull(conversionService, "ConversionService must not be null");
this.conversionService = conversionService;
}
/**
* Return the type conversion service which this FormatterRegistry delegates to.
*/
public ConversionService getConversionService() {
return this.conversionService;
}
/**
* Take the context's default ConversionService if none specified locally.
*/
public void setApplicationContext(ApplicationContext context) {
if (this.conversionService == null
&& context.containsBean(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME)) {
this.conversionService = context.getBean(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME,
ConversionService.class);
}
this.applicationContext = context;
}
// cloning support
/**
* Specify whether this FormatterRegistry is shared, in which case newly
* registered Formatters will be visible to other callers as well.
* <p>A new GenericFormatterRegistry is considered as shared by default,
* whereas a cloned GenericFormatterRegistry will be non-shared by default.
* @see #clone()
*/
public void setShared(boolean shared) {
this.shared = shared;
}
/**
* Return whether this FormatterRegistry is shared, in which case newly
* registered Formatters will be visible to other callers as well.
*/
public boolean isShared() {
return this.shared;
}
/**
* Create an independent clone of this FormatterRegistry.
* @see #setShared
*/
@Override
public GenericFormatterRegistry clone() {
GenericFormatterRegistry clone = new GenericFormatterRegistry();
clone.typeFormatters.putAll(this.typeFormatters);
clone.annotationFormatters.putAll(this.annotationFormatters);
clone.conversionService = this.conversionService;
clone.applicationContext = applicationContext;
clone.shared = false;
return clone;
}
// implementing FormatterRegistry
public void addFormatterByType(Formatter<?> formatter) {
Class<?> formatterObjectType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class);
if (formatterObjectType == null) {
throw new IllegalArgumentException("Unable to register Formatter " + formatter
+ "; cannot determine parameterized object type <T>");
}
this.typeFormatters.put(formatterObjectType, new FormatterHolder(formatterObjectType, formatter));
}
public void addFormatterByType(Class<?> type, Formatter<?> formatter) {
Class<?> formatterObjectType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class);
if (formatterObjectType != null && !type.isAssignableFrom(formatterObjectType)) {
if (this.conversionService == null) {
throw new IllegalStateException("Unable to index Formatter " + formatter + " under type ["
+ type.getName() + "]; not able to convert from a [" + formatterObjectType.getName()
+ "] parsed by the Formatter to [" + type.getName()
+ "] because this.conversionService is null");
}
if (!this.conversionService.canConvert(formatterObjectType, type)) {
throw new IllegalArgumentException("Unable to index Formatter " + formatter + " under type ["
+ type.getName() + "]; not able to convert from a [" + formatterObjectType.getName()
+ "] parsed by the Formatter to [" + type.getName() + "]");
}
if (!this.conversionService.canConvert(type, formatterObjectType)) {
throw new IllegalArgumentException("Unable to index Formatter " + formatter + " under type ["
+ type.getName() + "]; not able to convert to [" + formatterObjectType.getName()
+ "] to format a [" + type.getName() + "]");
}
}
this.typeFormatters.put(type, new FormatterHolder(formatterObjectType, formatter));
}
public void addFormatterByAnnotation(Class<? extends Annotation> annotationType, Formatter<?> formatter) {
Class<?> formatterObjectType = GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class);
SimpleAnnotationFormatterFactory factory = new SimpleAnnotationFormatterFactory(formatter);
this.annotationFormatters.put(annotationType,
new AnnotationFormatterFactoryHolder(formatterObjectType, factory));
}
public void addFormatterByAnnotation(AnnotationFormatterFactory<?, ?> factory) {
Class<?>[] typeArgs = GenericTypeResolver.resolveTypeArguments(factory.getClass(),
AnnotationFormatterFactory.class);
if (typeArgs == null || typeArgs.length != 2) {
throw new IllegalArgumentException(
"Unable to extract parameterized type arguments from AnnotationFormatterFactory ["
+ factory.getClass().getName()
+ "]; does the factory parameterize the <A> and <T> generic types?");
}
this.annotationFormatters.put(typeArgs[0], new AnnotationFormatterFactoryHolder(typeArgs[1], factory));
}
public Formatter<Object> getFormatter(TypeDescriptor type) {
Assert.notNull(type, "TypeDescriptor is required");
FormatterHolder holder = findFormatterHolderForAnnotatedProperty(type.getAnnotations());
Class<?> objectType = type.getObjectType();
if (holder == null) {
holder = findFormatterHolderForType(objectType);
}
if (holder == null) {
holder = getDefaultFormatterHolder(objectType);
}
if (holder == null) {
return null;
}
Class<?> formatterObjectType = holder.getFormatterObjectType();
if (formatterObjectType != null && !objectType.isAssignableFrom(formatterObjectType)) {
if (this.conversionService != null) {
return new ConvertingFormatter(type, holder);
} else {
return null;
}
} else {
return holder.getFormatter();
}
}
// internal helpers
private FormatterHolder findFormatterHolderForAnnotatedProperty(Annotation[] annotations) {
for (Annotation annotation : annotations) {
FormatterHolder holder = findFormatterHolderForAnnotation(annotation);
if (holder != null) {
return holder;
}
}
return null;
}
private FormatterHolder findFormatterHolderForAnnotation(Annotation annotation) {
Class<? extends Annotation> annotationType = annotation.annotationType();
AnnotationFormatterFactoryHolder factory = this.annotationFormatters.get(annotationType);
if (factory != null) {
return factory.getFormatterHolder(annotation);
} else {
Formatted formatted = annotationType.getAnnotation(Formatted.class);
if (formatted != null) {
// property annotation has @Formatted meta-annotation
Formatter<?> formatter = createFormatter(formatted.value());
addFormatterByAnnotation(annotationType, formatter);
return findFormatterHolderForAnnotation(annotation);
} else {
return null;
}
}
}
private FormatterHolder findFormatterHolderForType(Class<?> type) {
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(type);
while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast();
FormatterHolder holder = this.typeFormatters.get(currentClass);
if (holder != null) {
return holder;
}
if (currentClass.getSuperclass() != null) {
classQueue.addFirst(currentClass.getSuperclass());
}
Class<?>[] interfaces = currentClass.getInterfaces();
for (Class<?> ifc : interfaces) {
classQueue.addFirst(ifc);
}
}
return null;
}
private FormatterHolder getDefaultFormatterHolder(Class<?> type) {
Formatted formatted = AnnotationUtils.findAnnotation(type, Formatted.class);
if (formatted != null) {
Formatter<?> formatter = createFormatter(formatted.value());
addFormatterByType(type, formatter);
return findFormatterHolderForType(type);
} else {
return null;
}
}
private Formatter<?> createFormatter(Class<? extends Formatter> formatterClass) {
return (this.applicationContext != null ? this.applicationContext.getAutowireCapableBeanFactory().createBean(
formatterClass) : BeanUtils.instantiate(formatterClass));
}
private abstract static class AbstractFormatterHolder {
private Class<?> formatterObjectType;
public AbstractFormatterHolder(Class<?> formatterObjectType) {
this.formatterObjectType = formatterObjectType;
}
public Class<?> getFormatterObjectType() {
return formatterObjectType;
}
}
private static class FormatterHolder extends AbstractFormatterHolder {
private Formatter formatter;
public FormatterHolder(Class<?> formatterObjectType, Formatter<?> formatter) {
super(formatterObjectType);
this.formatter = formatter;
}
public Formatter getFormatter() {
return this.formatter;
}
}
private static class AnnotationFormatterFactoryHolder extends AbstractFormatterHolder {
private AnnotationFormatterFactory factory;
public AnnotationFormatterFactoryHolder(Class<?> formatterObjectType, AnnotationFormatterFactory<?, ?> factory) {
super(formatterObjectType);
this.factory = factory;
}
public FormatterHolder getFormatterHolder(Annotation annotation) {
return new FormatterHolder(getFormatterObjectType(), this.factory.getFormatter(annotation));
}
}
private static class SimpleAnnotationFormatterFactory implements AnnotationFormatterFactory {
private final Formatter instance;
public SimpleAnnotationFormatterFactory(Formatter<?> instance) {
this.instance = instance;
}
public Formatter getFormatter(Annotation annotation) {
return this.instance;
}
}
private class ConvertingFormatter implements Formatter {
private final TypeDescriptor type;
private final FormatterHolder formatterHolder;
public ConvertingFormatter(TypeDescriptor type, FormatterHolder formatterHolder) {
this.type = type;
this.formatterHolder = formatterHolder;
}
public String format(Object object, Locale locale) {
object = GenericFormatterRegistry.this.conversionService.convert(object, this.formatterHolder
.getFormatterObjectType());
return this.formatterHolder.getFormatter().format(object, locale);
}
public Object parse(String formatted, Locale locale) throws ParseException {
Object parsed = this.formatterHolder.getFormatter().parse(formatted, locale);
parsed = GenericFormatterRegistry.this.conversionService.convert(parsed, TypeDescriptor
.valueOf(this.formatterHolder.getFormatterObjectType()), this.type);
return parsed;
}
}
}
/*
* Copyright 2002-2009 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.ui.format.support;
import java.lang.annotation.Annotation;
import java.text.ParseException;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.ui.format.AnnotationFormatterFactory;
import org.springframework.ui.format.Formatter;
import org.springframework.ui.format.FormatterRegistry;
import org.springframework.ui.format.FormattingService;
import org.springframework.ui.format.Parser;
import org.springframework.ui.format.Printer;
import org.springframework.util.Assert;
/**
* A generic implementation of {@link FormattingService} suitable for use in most environments.
* Is a {@link FormatterRegistry} to allow for registration of field formatting logic.
*
* @author Keith Donald
* @author Juergen Hoeller
* @since 3.0
*/
public class GenericFormattingService implements FormattingService, FormatterRegistry {
private final Map<Class<?>, GenericFormatter> typeFormatters = new ConcurrentHashMap<Class<?>, GenericFormatter>();
private final Map<Class<? extends Annotation>, GenericAnnotationFormatterFactory> annotationFormatters = new ConcurrentHashMap<Class<? extends Annotation>, GenericAnnotationFormatterFactory>();
private GenericConversionService conversionService = new GenericConversionService();
/**
* Configure a parent of the type conversion service that will be used to coerce objects to types required for formatting.
*/
public void setParentConversionService(ConversionService parentConversionService) {
this.conversionService.setParent(parentConversionService);
}
// implementing FormattingService
public String print(Object fieldValue, TypeDescriptor fieldType, Locale locale) {
return getFormatter(fieldType).print(fieldValue, fieldType, locale);
}
public Object parse(String submittedValue, TypeDescriptor fieldType, Locale locale) throws ParseException {
return getFormatter(fieldType).parse(submittedValue, fieldType, locale);
}
// implementing FormatterRegistry
public void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser) {
Class<?> printerObjectType = resolvePrinterObjectType(printer);
Class<?> parserObjectType = resolveParserObjectType(parser);
this.typeFormatters.put(fieldType, new GenericFormatter(printerObjectType, printer, parserObjectType, parser));
}
public void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter) {
Class<?> formatterObjectType = resolveFormatterObjectType(formatter);
this.typeFormatters.put(fieldType, new GenericFormatter(formatterObjectType, formatter, formatterObjectType, formatter));
}
public void addFormatterForFieldAnnotation(AnnotationFormatterFactory<?> annotationFormatterFactory) {
Class<? extends Annotation> annotationType = resolveAnnotationType(annotationFormatterFactory);
if (annotationType == null) {
throw new IllegalArgumentException(
"Unable to extract parameterized Annotation type argument from AnnotationFormatterFactory ["
+ annotationFormatterFactory.getClass().getName()
+ "]; does the factory parameterize the <A extends Annotation> generic type?");
}
this.annotationFormatters.put(annotationType, new GenericAnnotationFormatterFactory(annotationFormatterFactory));
}
public ConverterRegistry getConverterRegistry() {
return this.conversionService;
}
// internal helpers
private Class<?> resolveParserObjectType(Parser<?> parser) {
return GenericTypeResolver.resolveTypeArgument(parser.getClass(), Parser.class);
}
private Class<?> resolvePrinterObjectType(Printer<?> printer) {
return GenericTypeResolver.resolveTypeArgument(printer.getClass(), Printer.class);
}
private Class<?> resolveFormatterObjectType(Formatter<?> formatter) {
return GenericTypeResolver.resolveTypeArgument(formatter.getClass(), Formatter.class);
}
@SuppressWarnings("unchecked")
private Class<? extends Annotation> resolveAnnotationType(AnnotationFormatterFactory<?> annotationFormatterFactory) {
return (Class<? extends Annotation>) GenericTypeResolver.resolveTypeArgument(annotationFormatterFactory.getClass(), AnnotationFormatterFactory.class);
}
private GenericFormatter getFormatter(TypeDescriptor fieldType) {
Assert.notNull(fieldType, "Field TypeDescriptor is required");
GenericFormatter formatter = findFormatterForAnnotatedField(fieldType);
Class<?> fieldObjectType = fieldType.getObjectType();
if (formatter == null) {
formatter = findFormatterForFieldType(fieldObjectType);
}
return formatter;
}
private GenericFormatter findFormatterForAnnotatedField(TypeDescriptor fieldType) {
for (Annotation annotation : fieldType.getAnnotations()) {
GenericFormatter formatter = findFormatterForAnnotation(annotation, fieldType.getObjectType());
if (formatter != null) {
return formatter;
}
}
return null;
}
private GenericFormatter findFormatterForAnnotation(Annotation annotation, Class<?> fieldType) {
Class<? extends Annotation> annotationType = annotation.annotationType();
GenericAnnotationFormatterFactory factory = this.annotationFormatters.get(annotationType);
if (factory != null) {
return factory.getFormatter(annotation, fieldType);
} else {
return null;
}
}
private GenericFormatter findFormatterForFieldType(Class<?> fieldType) {
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(fieldType);
while (!classQueue.isEmpty()) {
Class<?> currentClass = classQueue.removeLast();
GenericFormatter formatter = this.typeFormatters.get(currentClass);
if (formatter != null) {
return formatter;
}
if (currentClass.getSuperclass() != null) {
classQueue.addFirst(currentClass.getSuperclass());
}
Class<?>[] interfaces = currentClass.getInterfaces();
for (Class<?> ifc : interfaces) {
classQueue.addFirst(ifc);
}
}
return null;
}
@SuppressWarnings("unchecked")
private class GenericFormatter {
private TypeDescriptor printerObjectType;
private Printer printer;
private Parser parser;
public GenericFormatter(Class<?> printerObjectType, Printer<?> printer, Class<?> parserObjectType, Parser<?> parser) {
this.printerObjectType = TypeDescriptor.valueOf(printerObjectType);
this.printer = printer;
this.parser = parser;
}
public String print(Object fieldValue, TypeDescriptor fieldType, Locale locale) {
if (!fieldType.isAssignableTo(this.printerObjectType)) {
fieldValue = GenericFormattingService.this.conversionService.convert(fieldValue, fieldType, this.printerObjectType);
}
return fieldType != null ? this.printer.print(fieldValue, locale) : "";
}
public Object parse(String submittedValue, TypeDescriptor fieldType, Locale locale) throws ParseException {
if (submittedValue.isEmpty()) {
return null;
}
Object parsedValue = this.parser.parse(submittedValue, locale);
TypeDescriptor parsedObjectType = TypeDescriptor.valueOf(parsedValue.getClass());
if (!parsedObjectType.isAssignableTo(fieldType)) {
parsedValue = GenericFormattingService.this.conversionService.convert(parsedValue, parsedObjectType, fieldType);
}
return parsedValue;
}
}
@SuppressWarnings("unchecked")
private class GenericAnnotationFormatterFactory {
private AnnotationFormatterFactory annotationFormatterFactory;
public GenericAnnotationFormatterFactory(AnnotationFormatterFactory<?> annotationFormatterFactory) {
this.annotationFormatterFactory = annotationFormatterFactory;
}
public GenericFormatter getFormatter(Annotation annotation, Class<?> fieldType) {
Printer<?> printer = this.annotationFormatterFactory.getPrinter(annotation, fieldType);
Parser<?> parser = this.annotationFormatterFactory.getParser(annotation, fieldType);
return new GenericFormatter(getPrinterObjectType(printer, fieldType), printer, getParserObjectType(parser, fieldType), parser);
}
// internal helpers
private Class<?> getPrinterObjectType(Printer<?> printer, Class<?> fieldType) {
// TODO cache
return resolvePrinterObjectType(printer);
}
private Class<?> getParserObjectType(Parser<?> parser, Class<?> fieldType) {
// TODO cache
return resolveParserObjectType(parser);
}
}
}
/*
* Copyright 2002-2009 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.ui.format.support;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Locale;
import org.springframework.ui.format.Formatter;
import org.springframework.util.ReflectionUtils;
/**
* A generic Formatter that reflectively invokes methods on a class to carry out format and parse operations.
* The format method should be a public member method that returns a String and either accepts no arguments or a single Locale argument.
* The parse method should be a declared public static method that returns the formattedObjectType and either accepts a single String argument or String and Locale arguments.
* Throws an {@link IllegalArgumentException} if either the format method or parse method could not be resolved.
* Useful for invoking existing Formatter logic on a formattable type without needing a dedicated Formatter class.
*
* @author Keith Donald
*/
public class MethodInvokingFormatter implements Formatter {
private Class<?> formattedObjectType;
private Method formatMethod;
private boolean formatMethodLocaleArgumentPresent;
private Method parseMethod;
private boolean parseMethodLocaleArgumentPresent;
/**
* Creates a new reflective method invoking formatter.
* @param formattedObjectType the object type that contains format and parse methods
* @param formatMethodName the format method name e.g. "toString"
* @param parseMethodName the parse method name e.g. "valueOf"
*/
public MethodInvokingFormatter(Class<?> formattedObjectType, String formatMethodName, String parseMethodName) {
this.formattedObjectType = formattedObjectType;
resolveFormatMethod(formatMethodName);
resolveParseMethod(parseMethodName);
}
public String format(Object object, Locale locale) {
if (this.formatMethodLocaleArgumentPresent) {
return (String) ReflectionUtils.invokeMethod(this.formatMethod, object, locale);
} else {
return (String) ReflectionUtils.invokeMethod(this.formatMethod, object);
}
}
public Object parse(String formatted, Locale locale) {
if (this.parseMethodLocaleArgumentPresent) {
return ReflectionUtils.invokeMethod(this.parseMethod, null, formatted, locale);
} else {
return ReflectionUtils.invokeMethod(this.parseMethod, null, formatted);
}
}
private void resolveFormatMethod(String methodName) {
Method[] methods = this.formattedObjectType.getMethods();
for (Method method : methods) {
if (method.getName().equals(methodName) && method.getReturnType().equals(String.class)) {
if (method.getParameterTypes().length == 0) {
this.formatMethod = method;
} else if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(Locale.class)) {
this.formatMethod = method;
this.formatMethodLocaleArgumentPresent = true;
}
}
}
if (this.formatMethod == null) {
throw new IllegalArgumentException("Unable to resolve format method '" + methodName + "' on class ["
+ this.formattedObjectType.getName()
+ "] method should have signature [public String <methodName>()] "
+ "or [public String <methodName>(Locale)]");
}
}
private void resolveParseMethod(String methodName) {
Method[] methods = this.formattedObjectType.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(methodName) && method.getReturnType().equals(this.formattedObjectType)
&& Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers())) {
if (method.getParameterTypes().length == 1 && method.getParameterTypes()[0].equals(String.class)) {
this.parseMethod = method;
} else if (method.getParameterTypes().length == 2 && method.getParameterTypes()[0].equals(String.class)
&& method.getParameterTypes()[1].equals(Locale.class)) {
this.parseMethod = method;
this.parseMethodLocaleArgumentPresent = true;
}
}
}
if (this.parseMethod == null) {
throw new IllegalArgumentException("Unable to resolve parse method '" + methodName + "' on class ["
+ this.formattedObjectType.getName()
+ "]; method should have signature [public static T <methodName>(String)] "
+ "or [public static T <methodName>(String, Locale)]");
}
}
}
......@@ -24,8 +24,7 @@ import org.springframework.beans.PropertyAccessorUtils;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.ui.format.Formatter;
import org.springframework.ui.format.FormatterRegistry;
import org.springframework.ui.format.FormattingService;
import org.springframework.ui.format.support.FormattingConversionServiceAdapter;
import org.springframework.ui.format.support.FormattingPropertyEditorAdapter;
import org.springframework.util.Assert;
......@@ -44,7 +43,7 @@ import org.springframework.util.Assert;
*/
public abstract class AbstractPropertyBindingResult extends AbstractBindingResult {
private FormatterRegistry formatterRegistry;
private FormattingService formattingService;
/**
......@@ -57,10 +56,10 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul
}
public void initFormatterLookup(FormatterRegistry formatterRegistry) {
Assert.notNull(formatterRegistry, "FormatterRegistry must not be null");
this.formatterRegistry = formatterRegistry;
getPropertyAccessor().setConversionService(new FormattingConversionServiceAdapter(formatterRegistry));
public void initFormatting(FormattingService formattingService) {
Assert.notNull(formattingService, "FormattingService must not be null");
this.formattingService = formattingService;
getPropertyAccessor().setConversionService(new FormattingConversionServiceAdapter(formattingService));
}
/**
......@@ -117,14 +116,13 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul
return textValue;
}
}
// Try custom formatter...
TypeDescriptor td = getPropertyAccessor().getPropertyTypeDescriptor(fixedField);
Formatter<Object> formatter = (this.formatterRegistry != null ? this.formatterRegistry.getFormatter(td) : null);
if (formatter != null) {
return formatter.format(value, LocaleContextHolder.getLocale());
if (this.formattingService != null) {
// Try custom formatter...
TypeDescriptor td = getPropertyAccessor().getPropertyTypeDescriptor(fixedField);
return this.formattingService.print(value, td, LocaleContextHolder.getLocale());
} else {
return value;
}
// Nothing found: return value as-is.
return value;
}
/**
......@@ -147,16 +145,15 @@ public abstract class AbstractPropertyBindingResult extends AbstractBindingResul
*/
@Override
public PropertyEditor findEditor(String field, Class valueType) {
if (valueType == null) {
valueType = getFieldType(field);
}
PropertyEditor editor = super.findEditor(field, valueType);
if (editor == null) {
TypeDescriptor td = (field != null ?
getPropertyAccessor().getPropertyTypeDescriptor(fixedField(field)) :
TypeDescriptor.valueOf(valueType));
Formatter<Object> formatter =
(this.formatterRegistry != null ? this.formatterRegistry.getFormatter(td) : null);
if (formatter != null) {
editor = new FormattingPropertyEditorAdapter(formatter);
}
editor = new FormattingPropertyEditorAdapter(this.formattingService, valueType);
}
return editor;
}
......
......@@ -22,7 +22,6 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.ConfigurablePropertyAccessor;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyAccessException;
......@@ -35,10 +34,8 @@ import org.springframework.beans.SimpleTypeConverter;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.TypeMismatchException;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.ui.format.FormatterRegistry;
import org.springframework.ui.format.FormattingService;
import org.springframework.ui.format.support.FormattingConversionServiceAdapter;
import org.springframework.ui.format.support.GenericFormatterRegistry;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PatternMatchUtils;
......@@ -138,7 +135,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
private Validator validator;
private FormatterRegistry formatterRegistry;
private FormattingService formattingService;
/**
......@@ -186,8 +183,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
Assert.isNull(this.bindingResult,
"DataBinder is already initialized - call initBeanPropertyAccess before any other configuration methods");
this.bindingResult = new BeanPropertyBindingResult(getTarget(), getObjectName());
if (this.formatterRegistry != null) {
this.bindingResult.initFormatterLookup(this.formatterRegistry);
if (this.formattingService != null) {
this.bindingResult.initFormatting(this.formattingService);
}
}
......@@ -200,8 +197,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
Assert.isNull(this.bindingResult,
"DataBinder is already initialized - call initDirectFieldAccess before any other configuration methods");
this.bindingResult = new DirectFieldBindingResult(getTarget(), getObjectName());
if (this.formatterRegistry != null) {
this.bindingResult.initFormatterLookup(this.formatterRegistry);
if (this.formattingService != null) {
this.bindingResult.initFormatting(this.formattingService);
}
}
......@@ -229,8 +226,8 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
protected SimpleTypeConverter getSimpleTypeConverter() {
if (this.typeConverter == null) {
this.typeConverter = new SimpleTypeConverter();
if (this.formatterRegistry != null) {
this.typeConverter.setConversionService(new FormattingConversionServiceAdapter(this.formatterRegistry));
if (this.formattingService != null) {
this.typeConverter.setConversionService(new FormattingConversionServiceAdapter(this.formattingService));
}
}
return this.typeConverter;
......@@ -464,33 +461,12 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter {
}
/**
* Set the FormatterRegistry to use for obtaining Formatters in preference
* to JavaBeans PropertyEditors.
*/
public void setFormatterRegistry(FormatterRegistry formatterRegistry) {
this.formatterRegistry = formatterRegistry;
}
/**
* Return the FormatterRegistry to use for obtaining Formatters in preference
* to JavaBeans PropertyEditors.
* @return the FormatterRegistry (never <code>null</code>), which may also be
* used to register further Formatters for this DataBinder
* Set the FormattingService to use for field value formatting in preference to JavaBeans PropertyEditors.
*/
public FormatterRegistry getFormatterRegistry() {
if (this.formatterRegistry == null) {
GenericFormatterRegistry registry = new GenericFormatterRegistry();
registry.setConversionService(new DefaultConversionService());
this.formatterRegistry = registry;
}
else if (this.formatterRegistry instanceof GenericFormatterRegistry &&
((GenericFormatterRegistry) this.formatterRegistry).isShared()) {
this.formatterRegistry = ((GenericFormatterRegistry) this.formatterRegistry).clone();
}
return this.formatterRegistry;
public void setFormattingService(FormattingService formattingService) {
this.formattingService = formattingService;
}
//---------------------------------------------------------------------
// Implementation of PropertyEditorRegistry/TypeConverter interface
//---------------------------------------------------------------------
......
......@@ -11,6 +11,7 @@ import java.util.List;
import java.util.Map;
import org.joda.time.DateTime;
import org.joda.time.MutableDateTime;
import org.joda.time.format.ISODateTimeFormat;
import org.junit.Test;
import org.springframework.core.convert.converter.Converter;
......@@ -317,6 +318,35 @@ public class MappingTests {
.getActivationDateTime());
}
@Test
public void testMultiFieldToFieldMappingWithAssembler() {
Mapper<Map, Account> mapper = MapperFactory.mapperBuilder(Map.class, Account.class)
.setAutoMappingEnabled(false)
// field to multiple fields
.addAssemblerMapping("activationDateTime", new Converter<Map<String, String>, DateTime>() {
public DateTime convert(Map<String, String> source) {
MutableDateTime dateTime = new MutableDateTime();
dateTime.setYear(Integer.parseInt(source.get("year")));
dateTime.setMonthOfYear(Integer.parseInt(source.get("month")));
dateTime.setDayOfMonth(Integer.parseInt(source.get("day")));
dateTime.setHourOfDay(Integer.parseInt(source.get("hour")));
dateTime.setMinuteOfHour(Integer.parseInt(source.get("minute")));
dateTime.setSecondOfMinute(0);
dateTime.setMillisOfSecond(0);
return dateTime.toDateTime();
}
}).getMapper();
Map<String, Object> source = new HashMap<String, Object>();
source.put("activationDateTime.year", "2009");
source.put("activationDateTime.month", "10");
source.put("activationDateTime.day", "12");
source.put("activationDateTime.hour", "12");
source.put("activationDateTime.minute", "0");
Account account = mapper.map(source, new Account());
assertEquals(ISODateTimeFormat.dateTime().parseDateTime("2009-10-12T12:00:00.000-04:00"), account
.getActivationDateTime());
}
@Test
public void conditionalMapping() {
Map<String, String> domestic = new HashMap<String, String>();
......@@ -328,17 +358,16 @@ public class MappingTests {
domestic.put("cityCode", "whatever");
Mapper<Map, PhoneNumber> mapper = MapperFactory.mapperBuilder(Map.class, PhoneNumber.class)
.addConditionalMapping("countryCode", "international == 'true'")
.addConditionalMapping("cityCode", "international == 'true'")
.getMapper();
.addConditionalMapping("countryCode", "international == 'true'").addConditionalMapping("cityCode",
"international == 'true'").getMapper();
PhoneNumber number = mapper.map(domestic, new PhoneNumber());
assertEquals("205", number.getAreaCode());
assertEquals("339", number.getPrefix());
assertEquals("1234", number.getLine());
assertNull(number.getCountryCode());
assertNull(number.getCityCode());
Map<String, String> international = new HashMap<String, String>();
international.put("international", "true");
international.put("areaCode", "205");
......@@ -346,7 +375,7 @@ public class MappingTests {
international.put("line", "1234");
international.put("countryCode", "1");
international.put("cityCode", "2");
PhoneNumber number2 = mapper.map(international, new PhoneNumber());
assertEquals("205", number2.getAreaCode());
......@@ -479,7 +508,7 @@ public class MappingTests {
MapperFactory.defaultMapper().map(source, target);
assertEquals(1, target.getNumber());
assertTrue(item != target.getLineItem());
assertTrue(item == target.getLineItem());
assertEquals(new BigDecimal("30.00"), target.getLineItem().getAmount());
assertEquals(source, target.getLineItem().getOrder());
}
......
......@@ -37,7 +37,7 @@ public class DateFormatterTests {
cal.set(Calendar.YEAR, 2009);
cal.set(Calendar.MONTH, Calendar.JUNE);
cal.set(Calendar.DAY_OF_MONTH, 1);
assertEquals("2009-06-01", formatter.format(cal.getTime(), Locale.US));
assertEquals("2009-06-01", formatter.print(cal.getTime(), Locale.US));
}
@Test
......
......@@ -16,12 +16,13 @@
package org.springframework.ui.format.number;
import static org.junit.Assert.assertEquals;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.util.Locale;
import static org.junit.Assert.*;
import org.junit.Test;
/**
......@@ -33,7 +34,7 @@ public class CurrencyFormatterTests {
@Test
public void formatValue() {
assertEquals("$23.00", formatter.format(new BigDecimal("23"), Locale.US));
assertEquals("$23.00", formatter.print(new BigDecimal("23"), Locale.US));
}
@Test
......@@ -41,11 +42,6 @@ public class CurrencyFormatterTests {
assertEquals(new BigDecimal("23.56"), formatter.parse("$23.56", Locale.US));
}
@Test
public void parseEmptyValue() throws ParseException {
assertEquals(null, formatter.parse("", Locale.US));
}
@Test(expected = ParseException.class)
public void parseBogusValue() throws ParseException {
formatter.parse("bogus", Locale.US);
......
......@@ -34,7 +34,7 @@ public class DecimalFormatterTests {
@Test
public void formatValue() {
assertEquals("23.56", formatter.format(new BigDecimal("23.56"), Locale.US));
assertEquals("23.56", formatter.print(new BigDecimal("23.56"), Locale.US));
}
@Test
......@@ -42,11 +42,6 @@ public class DecimalFormatterTests {
assertEquals(new BigDecimal("23.56"), formatter.parse("23.56", Locale.US));
}
@Test
public void parseEmptyValue() throws ParseException {
assertEquals(null, formatter.parse("", Locale.US));
}
@Test(expected = ParseException.class)
public void parseBogusValue() throws ParseException {
formatter.parse("bogus", Locale.US);
......
......@@ -33,7 +33,7 @@ public class IntegerFormatterTests {
@Test
public void formatValue() {
assertEquals("23", formatter.format(23L, Locale.US));
assertEquals("23", formatter.print(23L, Locale.US));
}
@Test
......@@ -41,11 +41,6 @@ public class IntegerFormatterTests {
assertEquals((Long) 2356L, formatter.parse("2356", Locale.US));
}
@Test
public void parseEmptyValue() throws ParseException {
assertEquals(null, formatter.parse("", Locale.US));
}
@Test(expected = ParseException.class)
public void parseBogusValue() throws ParseException {
formatter.parse("bogus", Locale.US);
......
......@@ -34,7 +34,7 @@ public class PercentFormatterTests {
@Test
public void formatValue() {
assertEquals("23%", formatter.format(new BigDecimal(".23"), Locale.US));
assertEquals("23%", formatter.print(new BigDecimal(".23"), Locale.US));
}
@Test
......@@ -43,11 +43,6 @@ public class PercentFormatterTests {
Locale.US));
}
@Test
public void parseEmptyValue() throws ParseException {
assertEquals(null, formatter.parse("", Locale.US));
}
@Test(expected = ParseException.class)
public void parseBogusValue() throws ParseException {
formatter.parse("bogus", Locale.US);
......
/*
* Copyright 2002-2009 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.ui.format.support;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.ParseException;
import java.util.Locale;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.style.ToStringCreator;
import org.springframework.ui.format.AnnotationFormatterFactory;
import org.springframework.ui.format.Formatted;
import org.springframework.ui.format.Formatter;
import org.springframework.ui.format.number.CurrencyFormatter;
import org.springframework.ui.format.number.IntegerFormatter;
import org.springframework.ui.format.support.GenericFormatterRegistry;
/**
* @author Keith Donald
* @author Juergen Hoeller
*/
public class GenericFormatterRegistryTests {
private GenericFormatterRegistry registry;
@Before
public void setUp() {
registry = new GenericFormatterRegistry();
registry.setConversionService(new DefaultConversionService());
}
@Test
public void testAdd() throws ParseException {
registry.addFormatterByType(new IntegerFormatter());
Formatter formatter = registry.getFormatter(TypeDescriptor.valueOf(Integer.class));
String formatted = formatter.format(new Integer(3), Locale.US);
assertEquals("3", formatted);
Integer i = (Integer) formatter.parse("3", Locale.US);
assertEquals(new Integer(3), i);
}
@Test
public void testAddLookupByPrimitive() throws ParseException {
registry.addFormatterByType(new IntegerFormatter());
Formatter formatter = registry.getFormatter(TypeDescriptor.valueOf(int.class));
String formatted = formatter.format(3, Locale.US);
assertEquals("3", formatted);
int integer = (Integer) formatter.parse("3", Locale.US);
assertEquals(3, integer);
}
@Test
public void testAddByObjectType() {
registry.addFormatterByType(BigInteger.class, new IntegerFormatter());
Formatter formatter = registry.getFormatter(TypeDescriptor.valueOf(BigInteger.class));
String formatted = formatter.format(new BigInteger("3"), Locale.US);
assertEquals("3", formatted);
}
@Test
public void testAddByAnnotation() throws Exception {
registry.addFormatterByAnnotation(Currency.class, new CurrencyFormatter());
Formatter formatter = registry.getFormatter(new TypeDescriptor(getClass().getField("currencyField")));
String formatted = formatter.format(new BigDecimal("5.00"), Locale.US);
assertEquals("$5.00", formatted);
}
@Test
public void testAddAnnotationFormatterFactory() throws Exception {
registry.addFormatterByAnnotation(new CurrencyAnnotationFormatterFactory());
Formatter formatter = registry.getFormatter(new TypeDescriptor(getClass().getField("currencyField")));
String formatted = formatter.format(new BigDecimal("5.00"), Locale.US);
assertEquals("$5.00", formatted);
}
@Test
public void testGetDefaultFormatterFromMetaAnnotation() throws Exception {
Formatter formatter = registry.getFormatter(new TypeDescriptor(getClass().getField("smartCurrencyField")));
String formatted = formatter.format(new BigDecimal("5.00"), Locale.US);
assertEquals("$5.00", formatted);
}
@Test
public void testGetDefaultFormatterForType() {
Formatter formatter = registry.getFormatter(TypeDescriptor.valueOf(Address.class));
Address address = new Address();
address.street = "12345 Bel Aire Estates";
address.city = "Palm Bay";
address.state = "FL";
address.zip = "12345";
String formatted = formatter.format(address, Locale.US);
assertEquals("12345 Bel Aire Estates:Palm Bay:FL:12345", formatted);
}
@Test
public void testGetDefaultFormatterNull() throws ParseException {
assertNull(registry.getFormatter(TypeDescriptor.valueOf(Integer.class)));
}
@Test(expected = IllegalArgumentException.class)
public void testGetFormatterCannotConvert() {
registry.addFormatterByType(Integer.class, new AddressFormatter());
}
@Currency
public BigDecimal currencyField;
@SmartCurrency
public BigDecimal smartCurrencyField;
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Currency {
}
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Formatted(CurrencyFormatter.class)
public @interface SmartCurrency {
}
public static class CurrencyAnnotationFormatterFactory implements AnnotationFormatterFactory<Currency, Number> {
private final CurrencyFormatter currencyFormatter = new CurrencyFormatter();
public Formatter<Number> getFormatter(Currency annotation) {
return this.currencyFormatter;
}
}
@Formatted(AddressFormatter.class)
public static class Address {
private String street;
private String city;
private String state;
private String zip;
private String country;
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getZip() {
return zip;
}
public void setZip(String zip) {
this.zip = zip;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String toString() {
return new ToStringCreator(this).append("street", street).append("city", city).append("state", state)
.append("zip", zip).toString();
}
}
public static class AddressFormatter implements Formatter<Address> {
public String format(Address address, Locale locale) {
return address.getStreet() + ":" + address.getCity() + ":" + address.getState() + ":" + address.getZip();
}
public Address parse(String formatted, Locale locale) throws ParseException {
Address address = new Address();
String[] fields = formatted.split(":");
address.setStreet(fields[0]);
address.setCity(fields[1]);
address.setState(fields[2]);
address.setZip(fields[3]);
return address;
}
}
}
/*
* Copyright 2002-2009 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.ui.format.support;
import static org.junit.Assert.assertEquals;
import java.text.ParseException;
import java.util.Date;
import java.util.Locale;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.ui.format.jodatime.DateTimeFormatAnnotationFormatterFactory;
import org.springframework.ui.format.jodatime.DateTimeParser;
import org.springframework.ui.format.jodatime.ReadablePartialPrinter;
import org.springframework.ui.format.jodatime.DateTimeFormat.FormatStyle;
import org.springframework.ui.format.number.IntegerFormatter;
/**
* @author Keith Donald
* @author Juergen Hoeller
*/
public class GenericFormattingServiceTests {
private GenericFormattingService formattingService;
@Before
public void setUp() {
formattingService = new GenericFormattingService();
formattingService.setParentConversionService(new DefaultConversionService());
}
@Test
public void testFormatFieldForTypeWithFormatter() throws ParseException {
formattingService.addFormatterForFieldType(Number.class, new IntegerFormatter());
String formatted = formattingService.print(new Integer(3), TypeDescriptor.valueOf(Integer.class), Locale.US);
assertEquals("3", formatted);
Integer i = (Integer) formattingService.parse("3", TypeDescriptor.valueOf(Integer.class), Locale.US);
assertEquals(new Integer(3), i);
}
@Test
public void testFormatFieldForTypeWithPrinterParserWithCoersion() throws ParseException {
formattingService.getConverterRegistry().addConverter(new Converter<DateTime, LocalDate>() {
public LocalDate convert(DateTime source) {
return source.toLocalDate();
}
});
formattingService.addFormatterForFieldType(LocalDate.class, new ReadablePartialPrinter(DateTimeFormat
.shortDate()), new DateTimeParser(DateTimeFormat.shortDate()));
String formatted = formattingService.print(new LocalDate(2009, 10, 31), TypeDescriptor.valueOf(LocalDate.class), Locale.US);
assertEquals("10/31/09", formatted);
LocalDate date = (LocalDate) formattingService.parse("10/31/09", TypeDescriptor.valueOf(LocalDate.class), Locale.US);
assertEquals(new LocalDate(2009, 10, 31), date);
}
@Test
public void testFormatFieldForAnnotation() throws Exception {
formattingService.getConverterRegistry().addConverter(new Converter<Date, Long>() {
public Long convert(Date source) {
return source.getTime();
}
});
formattingService.getConverterRegistry().addConverter(new Converter<DateTime, Date>() {
public Date convert(DateTime source) {
return source.toDate();
}
});
formattingService.addFormatterForFieldAnnotation(new DateTimeFormatAnnotationFormatterFactory());
String formatted = formattingService.print(new LocalDate(2009, 10, 31).toDateTimeAtCurrentTime().toDate(), new TypeDescriptor(Model.class.getField("date")), Locale.US);
assertEquals("10/31/09", formatted);
LocalDate date = new LocalDate(formattingService.parse("10/31/09", new TypeDescriptor(Model.class.getField("date")), Locale.US));
assertEquals(new LocalDate(2009, 10, 31), date);
}
private static class Model {
@SuppressWarnings("unused")
@org.springframework.ui.format.jodatime.DateTimeFormat(dateStyle=FormatStyle.SHORT)
public Date date;
}
}
package org.springframework.ui.format.support;
import static org.junit.Assert.assertEquals;
import java.util.Locale;
import org.junit.Test;
import org.springframework.ui.format.support.MethodInvokingFormatter;
public class MethodInvokingFormatterTests {
private MethodInvokingFormatter formatter = new MethodInvokingFormatter(AccountNumber.class, "getFormatted",
"valueOf");
private MethodInvokingFormatter formatter2 = new MethodInvokingFormatter(I8nAccountNumber.class, "getFormatted",
"valueOf");
@Test
public void testFormat() {
assertEquals("123456789", formatter.format(new AccountNumber(123456789L), null));
}
@Test
public void testParse() {
assertEquals(new Long(123456789), ((AccountNumber) formatter.parse("123456789", null)).number);
}
@Test
public void testFormatI18n() {
assertEquals("123456789", formatter2.format(new I8nAccountNumber(123456789L), Locale.GERMAN));
}
@Test
public void testParseI18n() {
assertEquals(new Long(123456789), ((I8nAccountNumber) formatter2.parse("123456789", Locale.GERMAN)).number);
}
public static class AccountNumber {
private Long number;
public AccountNumber(Long number) {
this.number = number;
}
public String getFormatted() {
return number.toString();
}
public static AccountNumber valueOf(String str) {
return new AccountNumber(Long.valueOf(str));
}
}
public static class I8nAccountNumber {
private Long number;
public I8nAccountNumber(Long number) {
this.number = number;
}
public String getFormatted(Locale locale) {
assertEquals(Locale.GERMAN, locale);
return number.toString();
}
public static I8nAccountNumber valueOf(String str, Locale locale) {
assertEquals(Locale.GERMAN, locale);
return new I8nAccountNumber(Long.valueOf(str));
}
}
}
......@@ -16,21 +16,20 @@
package org.springframework.validation;
import java.beans.PropertyEditorSupport;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorSupport;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Retention;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.math.BigDecimal;
import junit.framework.TestCase;
......@@ -45,12 +44,12 @@ import org.springframework.beans.SerializablePerson;
import org.springframework.beans.TestBean;
import org.springframework.beans.propertyeditors.CustomCollectionEditor;
import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.ui.format.number.DecimalFormatter;
import org.springframework.ui.format.Formatted;
import org.springframework.ui.format.support.GenericFormatterRegistry;
import org.springframework.ui.format.support.GenericFormattingService;
import org.springframework.util.StringUtils;
/**
......@@ -304,7 +303,10 @@ public class DataBinderTests extends TestCase {
public void testBindingWithFormatter() {
TestBean tb = new TestBean();
DataBinder binder = new DataBinder(tb);
binder.getFormatterRegistry().addFormatterByType(Float.class, new DecimalFormatter());
GenericFormattingService formattingService = new GenericFormattingService();
formattingService.setParentConversionService(new DefaultConversionService());
formattingService.addFormatterForFieldType(Float.class, new DecimalFormatter());
binder.setFormattingService(formattingService);
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.addPropertyValue("myFloat", "1,2");
......@@ -329,46 +331,6 @@ public class DataBinderTests extends TestCase {
}
}
public void testBindingWithDefaultFormatterFromField() {
doTestBindingWithDefaultFormatter(new FormattedFieldTestBean());
}
public void testBindingWithDefaultFormatterFromGetter() {
doTestBindingWithDefaultFormatter(new FormattedGetterTestBean());
}
public void testBindingWithDefaultFormatterFromSetter() {
doTestBindingWithDefaultFormatter(new FormattedSetterTestBean());
}
private void doTestBindingWithDefaultFormatter(Object tb) {
DataBinder binder = new DataBinder(tb);
// force formatter registry to be created
binder.getFormatterRegistry();
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.addPropertyValue("number", "1,2");
LocaleContextHolder.setLocale(Locale.GERMAN);
try {
binder.bind(pvs);
assertEquals(new Float("1.2"), binder.getBindingResult().getRawFieldValue("number"));
assertEquals("1,2", binder.getBindingResult().getFieldValue("number"));
PropertyEditor editor = binder.getBindingResult().findEditor("number", Float.class);
assertNotNull(editor);
editor.setValue(new Float("1.4"));
assertEquals("1,4", editor.getAsText());
editor = binder.getBindingResult().findEditor("number", null);
assertNotNull(editor);
editor.setAsText("1,6");
assertEquals(new Float("1.6"), editor.getValue());
}
finally {
LocaleContextHolder.resetLocaleContext();
}
}
public void testBindingWithAllowedFields() throws Exception {
TestBean rod = new TestBean();
DataBinder binder = new DataBinder(rod);
......@@ -1423,56 +1385,4 @@ public class DataBinderTests extends TestCase {
}
}
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Formatted(DecimalFormatter.class)
public @interface Decimal {
}
private static class FormattedFieldTestBean {
@Decimal
private Float number;
public Float getNumber() {
return number;
}
public void setNumber(Float number) {
this.number = number;
}
}
private static class FormattedGetterTestBean {
private Float number;
@Decimal
public Float getNumber() {
return number;
}
public void setNumber(Float number) {
this.number = number;
}
}
private static class FormattedSetterTestBean {
private Float number;
public Float getNumber() {
return number;
}
@Decimal
public void setNumber(Float number) {
this.number = number;
}
}
}
......@@ -16,8 +16,6 @@
package org.springframework.core.convert.support;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
/**
......@@ -38,9 +36,6 @@ public class DefaultConversionService extends GenericConversionService {
addConverter(String.class, Character.class, new StringToCharacterConverter());
addConverter(String.class, Locale.class, new StringToLocaleConverter());
addConverter(Number.class, Character.class, new NumberToCharacterConverter());
addConverter(Date.class, Calendar.class, new DateToCalendarConverter());
addConverter(Calendar.class, Date.class, new CalendarToDateConverter());
JodaTimeConverters.addConverters(this);
addConverter(Object.class, String.class, new ObjectToStringConverter());
addConverterFactory(String.class, Number.class, new StringToNumberConverterFactory());
addConverterFactory(String.class, Enum.class, new StringToEnumConverterFactory());
......
......@@ -42,8 +42,6 @@ import org.springframework.util.ClassUtils;
* @author Keith Donald
* @author Juergen Hoeller
* @since 3.0
* @see #addConverter(Converter)
* @see #addConverterFactory(ConverterFactory)
*/
public class GenericConversionService implements ConversionService, ConverterRegistry {
......@@ -53,7 +51,7 @@ public class GenericConversionService implements ConversionService, ConverterReg
}
};
private final Map<Class, Map<Class, GenericConverter>> sourceTypeConverters = new HashMap<Class, Map<Class, GenericConverter>>(36);
private final Map<Class<?>, Map<Class<?>, GenericConverter>> sourceTypeConverters = new HashMap<Class<?>, Map<Class<?>, GenericConverter>>(36);
private ConversionService parent;
......@@ -90,8 +88,8 @@ public class GenericConversionService implements ConversionService, ConverterReg
* JavaBean-friendly alternative to calling {@link #addConverter(Converter)}.
* @see #addConverter(Converter)
*/
public void setConverters(Set<Converter> converters) {
for (Converter converter : converters) {
public void setConverters(Set<Converter<?, ?>> converters) {
for (Converter<?, ?> converter : converters) {
addConverter(converter);
}
}
......@@ -101,8 +99,8 @@ public class GenericConversionService implements ConversionService, ConverterReg
* JavaBean-friendly alternative to calling {@link #addConverterFactory(ConverterFactory)}.
* @see #addConverterFactory(ConverterFactory)
*/
public void setConverterFactories(Set<ConverterFactory> converters) {
for (ConverterFactory converterFactory : converters) {
public void setConverterFactories(Set<ConverterFactory<?, ?>> converters) {
for (ConverterFactory<?, ?> converterFactory : converters) {
addConverterFactory(converterFactory);
}
}
......@@ -124,24 +122,24 @@ public class GenericConversionService implements ConversionService, ConverterReg
// implementing ConverterRegistry
public void addConverter(Converter<?, ?> converter) {
Class[] typeInfo = getRequiredTypeInfo(converter, Converter.class);
Class<?>[] typeInfo = getRequiredTypeInfo(converter, Converter.class);
if (typeInfo == null) {
throw new IllegalArgumentException(
"Unable to the determine sourceType <S> and targetType <T> your Converter<S, T> converts between; declare these types or implement ConverterInfo");
}
Class sourceType = typeInfo[0];
Class targetType = typeInfo[1];
Class<?> sourceType = typeInfo[0];
Class<?> targetType = typeInfo[1];
addConverter(sourceType, targetType, converter);
}
public void addConverterFactory(ConverterFactory<?, ?> converterFactory) {
Class[] typeInfo = getRequiredTypeInfo(converterFactory, ConverterFactory.class);
Class<?>[] typeInfo = getRequiredTypeInfo(converterFactory, ConverterFactory.class);
if (typeInfo == null) {
throw new IllegalArgumentException(
"Unable to the determine sourceType <S> and targetRangeType R your ConverterFactory<S, R> converts between; declare these types or implement ConverterInfo");
}
Class sourceType = typeInfo[0];
Class targetType = typeInfo[1];
Class<?> sourceType = typeInfo[0];
Class<?> targetType = typeInfo[1];
addConverterFactory(sourceType, targetType, converterFactory);
}
......@@ -155,6 +153,7 @@ public class GenericConversionService implements ConversionService, ConverterReg
return canConvert(TypeDescriptor.valueOf(sourceType), TypeDescriptor.valueOf(targetType));
}
@SuppressWarnings("unchecked")
public <T> T convert(Object source, Class<T> targetType) {
return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType));
}
......@@ -279,46 +278,46 @@ public class GenericConversionService implements ConversionService, ConverterReg
Assert.notNull(targetType, "The targetType to convert to is required");
}
private Class[] getRequiredTypeInfo(Object converter, Class genericIfc) {
private Class<?>[] getRequiredTypeInfo(Object converter, Class<?> genericIfc) {
return GenericTypeResolver.resolveTypeArguments(converter.getClass(), genericIfc);
}
private GenericConverter findConverterByClassPair(Class sourceType, Class targetType) {
private GenericConverter findConverterByClassPair(Class<?> sourceType, Class<?> targetType) {
if (sourceType.isInterface()) {
LinkedList<Class> classQueue = new LinkedList<Class>();
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(sourceType);
while (!classQueue.isEmpty()) {
Class currentClass = classQueue.removeLast();
Map<Class, GenericConverter> converters = getConvertersForSource(currentClass);
Class<?> currentClass = classQueue.removeLast();
Map<Class<?>, GenericConverter> converters = getConvertersForSource(currentClass);
GenericConverter converter = getConverter(converters, targetType);
if (converter != null) {
return converter;
}
Class[] interfaces = currentClass.getInterfaces();
for (Class ifc : interfaces) {
Class<?>[] interfaces = currentClass.getInterfaces();
for (Class<?> ifc : interfaces) {
classQueue.addFirst(ifc);
}
}
Map<Class, GenericConverter> objectConverters = getConvertersForSource(Object.class);
Map<Class<?>, GenericConverter> objectConverters = getConvertersForSource(Object.class);
return getConverter(objectConverters, targetType);
} else {
LinkedList<Class> classQueue = new LinkedList<Class>();
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(sourceType);
while (!classQueue.isEmpty()) {
Class currentClass = classQueue.removeLast();
Map<Class, GenericConverter> converters = getConvertersForSource(currentClass);
Class<?> currentClass = classQueue.removeLast();
Map<Class<?>, GenericConverter> converters = getConvertersForSource(currentClass);
GenericConverter converter = getConverter(converters, targetType);
if (converter != null) {
return converter;
}
if (currentClass.isArray()) {
Class componentType = ClassUtils.resolvePrimitiveIfNecessary(currentClass.getComponentType());
Class<?> componentType = ClassUtils.resolvePrimitiveIfNecessary(currentClass.getComponentType());
if (componentType.getSuperclass() != null) {
classQueue.addFirst(Array.newInstance(componentType.getSuperclass(), 0).getClass());
}
} else {
Class[] interfaces = currentClass.getInterfaces();
for (Class ifc : interfaces) {
Class<?>[] interfaces = currentClass.getInterfaces();
for (Class<?> ifc : interfaces) {
classQueue.addFirst(ifc);
}
if (currentClass.getSuperclass() != null) {
......@@ -330,56 +329,56 @@ public class GenericConversionService implements ConversionService, ConverterReg
}
}
private Map<Class, GenericConverter> getSourceMap(Class sourceType) {
Map<Class, GenericConverter> sourceMap = sourceTypeConverters.get(sourceType);
private Map<Class<?>, GenericConverter> getSourceMap(Class<?> sourceType) {
Map<Class<?>, GenericConverter> sourceMap = sourceTypeConverters.get(sourceType);
if (sourceMap == null) {
sourceMap = new HashMap<Class, GenericConverter>();
sourceMap = new HashMap<Class<?>, GenericConverter>();
this.sourceTypeConverters.put(sourceType, sourceMap);
}
return sourceMap;
}
private Map<Class, GenericConverter> getConvertersForSource(Class sourceType) {
Map<Class, GenericConverter> converters = this.sourceTypeConverters.get(sourceType);
private Map<Class<?>, GenericConverter> getConvertersForSource(Class<?> sourceType) {
Map<Class<?>, GenericConverter> converters = this.sourceTypeConverters.get(sourceType);
if (converters == null) {
converters = Collections.emptyMap();
}
return converters;
}
private GenericConverter getConverter(Map<Class, GenericConverter> converters, Class targetType) {
private GenericConverter getConverter(Map<Class<?>, GenericConverter> converters, Class<?> targetType) {
if (targetType.isInterface()) {
LinkedList<Class> classQueue = new LinkedList<Class>();
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(targetType);
while (!classQueue.isEmpty()) {
Class currentClass = classQueue.removeLast();
Class<?> currentClass = classQueue.removeLast();
GenericConverter converter = converters.get(currentClass);
if (converter != null) {
return converter;
}
Class[] interfaces = currentClass.getInterfaces();
for (Class ifc : interfaces) {
Class<?>[] interfaces = currentClass.getInterfaces();
for (Class<?> ifc : interfaces) {
classQueue.addFirst(ifc);
}
}
return converters.get(Object.class);
} else {
LinkedList<Class> classQueue = new LinkedList<Class>();
LinkedList<Class<?>> classQueue = new LinkedList<Class<?>>();
classQueue.addFirst(targetType);
while (!classQueue.isEmpty()) {
Class currentClass = classQueue.removeLast();
Class<?> currentClass = classQueue.removeLast();
GenericConverter converter = converters.get(currentClass);
if (converter != null) {
return converter;
}
if (currentClass.isArray()) {
Class componentType = ClassUtils.resolvePrimitiveIfNecessary(currentClass.getComponentType());
Class<?> componentType = ClassUtils.resolvePrimitiveIfNecessary(currentClass.getComponentType());
if (componentType.getSuperclass() != null) {
classQueue.addFirst(Array.newInstance(componentType.getSuperclass(), 0).getClass());
}
} else {
Class[] interfaces = currentClass.getInterfaces();
for (Class ifc : interfaces) {
Class<?>[] interfaces = currentClass.getInterfaces();
for (Class<?> ifc : interfaces) {
classQueue.addFirst(ifc);
}
if (currentClass.getSuperclass() != null) {
......@@ -391,11 +390,12 @@ public class GenericConversionService implements ConversionService, ConverterReg
}
}
@SuppressWarnings("unchecked")
private final class ConverterAdapter implements GenericConverter {
private final Converter converter;
public ConverterAdapter(Converter converter) {
public ConverterAdapter(Converter<?, ?> converter) {
this.converter = converter;
}
......@@ -407,11 +407,12 @@ public class GenericConversionService implements ConversionService, ConverterReg
}
}
@SuppressWarnings("unchecked")
private final class ConverterFactoryAdapter implements GenericConverter {
private final ConverterFactory converterFactory;
public ConverterFactoryAdapter(ConverterFactory converterFactory) {
public ConverterFactoryAdapter(ConverterFactory<?, ?> converterFactory) {
this.converterFactory = converterFactory;
}
......
......@@ -77,8 +77,6 @@ import org.springframework.stereotype.Controller;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.ui.format.date.DateFormatter;
import org.springframework.ui.format.support.GenericFormatterRegistry;
import org.springframework.util.SerializationTestUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.MultiValueMap;
......@@ -487,37 +485,6 @@ public class ServletAnnotationControllerTests {
assertEquals("myView-String:myDefaultName-typeMismatch-tb1-myOriginalValue", response.getContentAsString());
}
@Test
public void commandProvidingFormControllerWithFormatter() throws Exception {
@SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() {
@Override
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
GenericWebApplicationContext wac = new GenericWebApplicationContext();
wac.registerBeanDefinition("controller", new RootBeanDefinition(MyCommandProvidingFormController.class));
wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(TestViewResolver.class));
RootBeanDefinition registryDef = new RootBeanDefinition(GenericFormatterRegistry.class);
registryDef.getPropertyValues().addPropertyValue("formatters", new DateFormatter("yyyy-MM-dd"));
RootBeanDefinition initializerDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
initializerDef.getPropertyValues().addPropertyValue("formatterRegistry", registryDef);
initializerDef.getPropertyValues().addPropertyValue("validator", new RootBeanDefinition(LocalValidatorFactoryBean.class));
RootBeanDefinition adapterDef = new RootBeanDefinition(AnnotationMethodHandlerAdapter.class);
adapterDef.getPropertyValues().addPropertyValue("webBindingInitializer", initializerDef);
wac.registerBeanDefinition("handlerAdapter", adapterDef);
wac.refresh();
return wac;
}
};
servlet.init(new MockServletConfig());
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath.do");
request.addParameter("defaultName", "myDefaultName");
request.addParameter("age", "value2");
request.addParameter("date", "2007-10-02");
MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response);
assertEquals("myView-String:myDefaultName-typeMismatch-tb1-myOriginalValue", response.getContentAsString());
}
@Test
public void typedCommandProvidingFormController() throws Exception {
@SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() {
......
......@@ -17,7 +17,7 @@
package org.springframework.web.bind.support;
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.ui.format.FormatterRegistry;
import org.springframework.ui.format.FormattingService;
import org.springframework.validation.BindingErrorProcessor;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
......@@ -46,7 +46,7 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer
private Validator validator;
private FormatterRegistry formatterRegistry;
private FormattingService formattingService;
private PropertyEditorRegistrar[] propertyEditorRegistrars;
......@@ -111,17 +111,17 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer
}
/**
* Specify a FormatterRegistry which will apply to every DataBinder.
* Specify a FormattingService which will apply to every DataBinder.
*/
public final void setFormatterRegistry(FormatterRegistry formatterRegistry) {
this.formatterRegistry = formatterRegistry;
public final void setFormattingService(FormattingService formattingService) {
this.formattingService = formattingService;
}
/**
* Return a FormatterRegistry which will apply to every DataBinder.
* Return the FormattingService which will apply to every DataBinder.
*/
public final FormatterRegistry getFormatterRegistry() {
return this.formatterRegistry;
public final FormattingService getFormattingService() {
return this.formattingService;
}
/**
......@@ -160,8 +160,8 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer
this.validator.supports(binder.getTarget().getClass())) {
binder.setValidator(this.validator);
}
if (this.formatterRegistry != null) {
binder.setFormatterRegistry(this.formatterRegistry);
if (this.formattingService != null) {
binder.setFormattingService(this.formattingService);
}
if (this.propertyEditorRegistrars != null) {
for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册