diff --git a/org.springframework.context/src/main/java/org/springframework/ui/format/support/MethodInvokingFormatter.java b/org.springframework.context/src/main/java/org/springframework/ui/format/support/MethodInvokingFormatter.java new file mode 100644 index 0000000000..9f17d6f148 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/ui/format/support/MethodInvokingFormatter.java @@ -0,0 +1,115 @@ +/* + * 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 ()] " + + "or [public String (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 (String)] " + + "or [public static T (String, Locale)]"); + } + } +} diff --git a/org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java b/org.springframework.context/src/test/java/org/springframework/ui/format/support/GenericFormatterRegistryTests.java similarity index 96% rename from org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java rename to org.springframework.context/src/test/java/org/springframework/ui/format/support/GenericFormatterRegistryTests.java index d4dcc8e653..e08e47fc96 100644 --- a/org.springframework.context/src/test/java/org/springframework/ui/format/GenericFormatterRegistryTests.java +++ b/org.springframework.context/src/test/java/org/springframework/ui/format/support/GenericFormatterRegistryTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ui.format; +package org.springframework.ui.format.support; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -33,6 +33,9 @@ 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; diff --git a/org.springframework.context/src/test/java/org/springframework/ui/format/support/MethodInvokingFormatterTests.java b/org.springframework.context/src/test/java/org/springframework/ui/format/support/MethodInvokingFormatterTests.java new file mode 100644 index 0000000000..6822f42ab0 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/ui/format/support/MethodInvokingFormatterTests.java @@ -0,0 +1,75 @@ +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)); + } + + } +}