Introduce FeatureSpecification support

Introduce FeatureSpecification interface and implementations

    FeatureSpecification objects decouple the configuration of
    spring container features from the concern of parsing XML
    namespaces, allowing for reuse in code-based configuration
    (see @Feature* annotations below).

    * ComponentScanSpec
    * TxAnnotationDriven
    * MvcAnnotationDriven
    * MvcDefaultServletHandler
    * MvcResources
    * MvcViewControllers

Refactor associated BeanDefinitionParsers to delegate to new impls above

    The following BeanDefinitionParser implementations now deal only
    with the concern of XML parsing.  Validation is handled by their
    corresponding FeatureSpecification object.  Bean definition creation
    and registration is handled by their corresponding
    FeatureSpecificationExecutor type.

    * ComponentScanBeanDefinitionParser
    * AnnotationDrivenBeanDefinitionParser (tx)
    * AnnotationDrivenBeanDefinitionParser (mvc)
    * DefaultServletHandlerBeanDefinitionParser
    * ResourcesBeanDefinitionParser
    * ViewControllerBeanDefinitionParser

Update AopNamespaceUtils to decouple from XML (DOM API)

    Methods necessary for executing TxAnnotationDriven specification
    (and eventually, the AspectJAutoProxy specification) have been
    added that accept boolean arguments for whether to proxy
    target classes and whether to expose the proxy via threadlocal.

    Methods that accepted and introspected DOM Element objects still
    exist but have been deprecated.

Introduce @FeatureConfiguration classes and @Feature methods

    Allow for creation and configuration of FeatureSpecification objects
    at the user level.  A companion for @Configuration classes allowing
    for completely code-driven configuration of the Spring container.

    See changes in ConfigurationClassPostProcessor for implementation
    details.

    See Feature*Tests for usage examples.

    FeatureTestSuite in .integration-tests is a JUnit test suite designed
    to aggregate all BDP and Feature* related tests for a convenient way
    to confirm that Feature-related changes don't break anything.
    Uncomment this test and execute from Eclipse / IDEA. Due to classpath
    issues, this cannot be compiled by Ant/Ivy at the command line.

Introduce @FeatureAnnotation meta-annotation and @ComponentScan impl

    @FeatureAnnotation provides an alternate mechanism for creating
    and executing FeatureSpecification objects.  See @ComponentScan
    and its corresponding ComponentScanAnnotationParser implementation
    for details.  See ComponentScanAnnotationIntegrationTests for usage
    examples

Introduce Default[Formatting]ConversionService implementations

    Allows for convenient instantiation of ConversionService objects
    containing defaults appropriate for most environments.  Replaces
    similar support originally in ConversionServiceFactory (which is now
    deprecated). This change was justified by the need to avoid use
    of FactoryBeans in @Configuration classes (such as
    FormattingConversionServiceFactoryBean). It is strongly preferred
    that users simply instantiate and configure the objects that underlie
    our FactoryBeans. In the case of the ConversionService types, the
    easiest way to do this is to create Default* subtypes. This also
    follows convention with the rest of the framework.

Minor updates to util classes

    All in service of changes above. See diffs for self-explanatory
    details.

    * BeanUtils
    * ObjectUtils
    * ReflectionUtils
This commit is contained in:
Chris Beams
2011-02-08 14:42:33 +00:00
parent b04987ccc3
commit b4fea47d5c
127 changed files with 7397 additions and 1132 deletions

View File

@@ -29,66 +29,16 @@ import org.springframework.core.convert.converter.GenericConverter;
*
* @author Keith Donald
* @author Juergen Hoeller
* @author Chris Beams
* @since 3.0
*/
public abstract class ConversionServiceFactory {
/**
* Create a new default ConversionService instance that can be safely modified.
*/
public static GenericConversionService createDefaultConversionService() {
GenericConversionService conversionService = new GenericConversionService();
addDefaultConverters(conversionService);
return conversionService;
}
/**
* Populate the given ConversionService instance with all applicable default converters.
*/
public static void addDefaultConverters(GenericConversionService conversionService) {
conversionService.addConverter(new ArrayToCollectionConverter(conversionService));
conversionService.addConverter(new CollectionToArrayConverter(conversionService));
conversionService.addConverter(new ArrayToStringConverter(conversionService));
conversionService.addConverter(new StringToArrayConverter(conversionService));
conversionService.addConverter(new ArrayToObjectConverter(conversionService));
conversionService.addConverter(new ObjectToArrayConverter(conversionService));
conversionService.addConverter(new CollectionToStringConverter(conversionService));
conversionService.addConverter(new StringToCollectionConverter(conversionService));
conversionService.addConverter(new CollectionToObjectConverter(conversionService));
conversionService.addConverter(new ObjectToCollectionConverter(conversionService));
conversionService.addConverter(new ArrayToArrayConverter(conversionService));
conversionService.addConverter(new CollectionToCollectionConverter(conversionService));
conversionService.addConverter(new MapToMapConverter(conversionService));
conversionService.addConverter(new PropertiesToStringConverter());
conversionService.addConverter(new StringToPropertiesConverter());
conversionService.addConverter(new StringToBooleanConverter());
conversionService.addConverter(new StringToCharacterConverter());
conversionService.addConverter(new StringToLocaleConverter());
conversionService.addConverterFactory(new StringToNumberConverterFactory());
conversionService.addConverterFactory(new StringToEnumConverterFactory());
conversionService.addConverter(new NumberToCharacterConverter());
conversionService.addConverterFactory(new CharacterToNumberFactory());
conversionService.addConverterFactory(new NumberToNumberConverterFactory());
conversionService.addConverter(new ObjectToStringConverter());
conversionService.addConverter(new ObjectToObjectConverter());
conversionService.addConverter(new IdToEntityConverter(conversionService));
}
/**
* Register the given converter objects with the given target registry.
* Register the given Converter objects with the given target ConverterRegistry.
* @param converters the converter objects: implementing {@link Converter},
* {@link ConverterFactory}, or {@link GenericConverter}
* @param registry the target registry to register with
* @param registry the target registry
*/
public static void registerConverters(Set<?> converters, ConverterRegistry registry) {
if (converters != null) {
@@ -110,4 +60,22 @@ public abstract class ConversionServiceFactory {
}
}
/**
* Create a new default ConversionService instance that can be safely modified.
*
* @deprecated in Spring 3.1 in favor of {@link DefaultConversionService#DefaultConversionService()}
*/
public static GenericConversionService createDefaultConversionService() {
return new DefaultConversionService();
}
/**
* Populate the given ConversionService instance with all applicable default converters.
*
* @deprecated in Spring 3.1 in favor of {@link DefaultConversionService#addDefaultConverters}
*/
public static void addDefaultConverters(GenericConversionService conversionService) {
DefaultConversionService.addDefaultConverters(conversionService);
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.convert.support;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.ConverterRegistry;
/**
* A specialization of {@link GenericConversionService} configured by default with
* converters appropriate for most applications.
*
* <p>Designed for direct instantiation but also exposes the static
* {@link #addDefaultConverters} utility method for ad hoc use against any
* {@code GenericConversionService} instance.
*
* @author Chris Beams
* @since 3.1
*/
public class DefaultConversionService extends GenericConversionService {
/**
* Create a new {@code DefaultConversionService} with the set of
* {@linkplain DefaultConversionService#addDefaultConverters default converters}.
*/
public DefaultConversionService() {
addDefaultConverters(this);
}
/**
* Add converters appropriate for most environments.
* @param conversionService the service to register default formatters against
*/
public static void addDefaultConverters(GenericConversionService conversionService) {
conversionService.addConverter(new ArrayToCollectionConverter(conversionService));
conversionService.addConverter(new CollectionToArrayConverter(conversionService));
conversionService.addConverter(new ArrayToStringConverter(conversionService));
conversionService.addConverter(new StringToArrayConverter(conversionService));
conversionService.addConverter(new ArrayToObjectConverter(conversionService));
conversionService.addConverter(new ObjectToArrayConverter(conversionService));
conversionService.addConverter(new CollectionToStringConverter(conversionService));
conversionService.addConverter(new StringToCollectionConverter(conversionService));
conversionService.addConverter(new CollectionToObjectConverter(conversionService));
conversionService.addConverter(new ObjectToCollectionConverter(conversionService));
conversionService.addConverter(new ArrayToArrayConverter(conversionService));
conversionService.addConverter(new CollectionToCollectionConverter(conversionService));
conversionService.addConverter(new MapToMapConverter(conversionService));
conversionService.addConverter(new PropertiesToStringConverter());
conversionService.addConverter(new StringToPropertiesConverter());
conversionService.addConverter(new StringToBooleanConverter());
conversionService.addConverter(new StringToCharacterConverter());
conversionService.addConverter(new StringToLocaleConverter());
conversionService.addConverterFactory(new StringToNumberConverterFactory());
conversionService.addConverterFactory(new StringToEnumConverterFactory());
conversionService.addConverter(new NumberToCharacterConverter());
conversionService.addConverterFactory(new CharacterToNumberFactory());
conversionService.addConverterFactory(new NumberToNumberConverterFactory());
conversionService.addConverter(new ObjectToStringConverter());
conversionService.addConverter(new ObjectToObjectConverter());
conversionService.addConverter(new IdToEntityConverter(conversionService));
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2011 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.
@@ -24,11 +24,10 @@ import static org.springframework.util.SystemPropertyUtils.VALUE_SEPARATOR;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
/**
* Abstract base class for resolving properties against any underlying source.
*
@@ -39,7 +38,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
protected final Log logger = LogFactory.getLog(getClass());
protected ConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
protected ConversionService conversionService = new DefaultConversionService();
private PropertyPlaceholderHelper nonStrictHelper;
private PropertyPlaceholderHelper strictHelper;

View File

@@ -69,7 +69,7 @@ public interface Environment extends PropertyResolver {
* Return the set of profiles explicitly made active for this environment. Profiles are used for
* creating logical groupings of bean definitions to be registered conditionally, often based on
* deployment environment. Profiles can be activated by setting {@linkplain
* AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME "spring.profiles.active"} as a system property
* AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME "spring.profile.active"} as a system property
* or by calling {@link ConfigurableEnvironment#setActiveProfiles(String...)}.
*
* <p>If no profiles have explicitly been specified as active, then any 'default' profiles will implicitly

View File

@@ -36,6 +36,11 @@ public interface MethodMetadata {
*/
String getMethodName();
/**
* Return the fully-qualified name of the return type of the method.
*/
String getMethodReturnType();
/**
* Return the fully-qualified name of the class that declares this method.
*/

View File

@@ -58,7 +58,11 @@ public class StandardMethodMetadata implements MethodMetadata {
public String getMethodName() {
return this.introspectedMethod.getName();
}
public String getMethodReturnType() {
return this.introspectedMethod.getReturnType().getName();
}
public String getDeclaringClassName() {
return this.introspectedMethod.getDeclaringClass().getName();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2011 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.
@@ -62,7 +62,7 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return new MethodMetadataReadingVisitor(name, access, this.getClassName(), this.classLoader, this.methodMetadataMap);
return new MethodMetadataReadingVisitor(name, getReturnTypeFromAsmMethodDescriptor(desc), access, this.getClassName(), this.classLoader, this.methodMetadataMap);
}
@Override
@@ -125,6 +125,19 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor
}
value = convArray;
}
else if (classValuesAsString) {
if (value instanceof Class) {
value = ((Class) value).getName();
}
else if (value instanceof Class[]) {
Class[] clazzArray = (Class[]) value;
String[] newValue = new String[clazzArray.length];
for (int i = 0; i < clazzArray.length; i++) {
newValue[i] = clazzArray[i].getName();
}
value = newValue;
}
}
result.put(entry.getKey(), value);
}
catch (Exception ex) {
@@ -148,4 +161,46 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor
return annotatedMethods;
}
/**
* Convert a type descriptor to a classname suitable for classloading with
* Class.forName().
*
* @param typeDescriptor see ASM guide section 2.1.3
*/
private static String convertAsmTypeDescriptorToClassName(String typeDescriptor) {
final String internalName; // See ASM guide section 2.1.2
if ("V".equals(typeDescriptor))
return Void.class.getName();
if ("I".equals(typeDescriptor))
return Integer.class.getName();
if ("Z".equals(typeDescriptor))
return Boolean.class.getName();
// strip the leading array/object/primitive identifier
if (typeDescriptor.startsWith("[["))
internalName = typeDescriptor.substring(3);
else if (typeDescriptor.startsWith("["))
internalName = typeDescriptor.substring(2);
else
internalName = typeDescriptor.substring(1);
// convert slashes to dots
String className = internalName.replace('/', '.');
// and strip trailing semicolon (if present)
if (className.endsWith(";"))
className = className.substring(0, internalName.length() - 1);
return className;
}
/**
* @param methodDescriptor see ASM guide section 2.1.4
*/
private static String getReturnTypeFromAsmMethodDescriptor(String methodDescriptor) {
String returnTypeDescriptor = methodDescriptor.substring(methodDescriptor.indexOf(')') + 1);
return convertAsmTypeDescriptorToClassName(returnTypeDescriptor);
}
}

View File

@@ -43,6 +43,8 @@ final class MethodMetadataReadingVisitor extends MethodAdapter implements Method
private final int access;
private String returnType;
private String declaringClassName;
private final ClassLoader classLoader;
@@ -51,10 +53,11 @@ final class MethodMetadataReadingVisitor extends MethodAdapter implements Method
private final Map<String, Map<String, Object>> attributeMap = new LinkedHashMap<String, Map<String, Object>>(2);
public MethodMetadataReadingVisitor(String name, int access, String declaringClassName, ClassLoader classLoader,
public MethodMetadataReadingVisitor(String name, String returnType, int access, String declaringClassName, ClassLoader classLoader,
MultiValueMap<String, MethodMetadata> methodMetadataMap) {
super(new EmptyVisitor());
this.name = name;
this.returnType = returnType;
this.access = access;
this.declaringClassName = declaringClassName;
this.classLoader = classLoader;
@@ -72,6 +75,10 @@ final class MethodMetadataReadingVisitor extends MethodAdapter implements Method
return this.name;
}
public String getMethodReturnType() {
return this.returnType;
}
public boolean isStatic() {
return ((this.access & Opcodes.ACC_STATIC) != 0);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2011 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.
@@ -32,6 +32,7 @@ import java.util.Arrays;
* @author Keith Donald
* @author Rod Johnson
* @author Rob Harrop
* @author Chris Beams
* @since 19.03.2004
* @see org.apache.commons.lang.ObjectUtils
*/
@@ -121,6 +122,54 @@ public abstract class ObjectUtils {
return false;
}
/**
* Check whether the given array of enum constants contains a constant with the given name,
* ignoring case when determining a match.
* @param enumValues the enum values to check, typically the product of a call to MyEnum.values()
* @param constant the constant name to find (must not be null or empty string)
* @return whether the constant has been found in the given array
*/
public static boolean containsConstant(Enum<?>[] enumValues, String constant) {
return containsConstant(enumValues, constant, false);
}
/**
* Check whether the given array of enum constants contains a constant with the given name.
* @param enumValues the enum values to check, typically the product of a call to MyEnum.values()
* @param constant the constant name to find (must not be null or empty string)
* @param caseSensitive whether case is significant in determining a match
* @return whether the constant has been found in the given array
*/
public static boolean containsConstant(Enum<?>[] enumValues, String constant, boolean caseSensitive) {
for (Enum<?> candidate : enumValues) {
if (caseSensitive ?
candidate.toString().equals(constant) :
candidate.toString().equalsIgnoreCase(constant)) {
return true;
}
}
return false;
}
/**
* Case insensitive alternative to {@link Enum#valueOf(Class, String)}.
* @param <E> the concrete Enum type
* @param enumValues the array of all Enum constants in question, usually per Enum.values()
* @param constant the constant to get the enum value of
* @throws IllegalArgumentException if the given constant is not found in the given array
* of enum values. Use {@link #containsConstant(Enum[], String)} as a guard to avoid this exception.
*/
public static <E extends Enum<?>> E caseInsensitiveValueOf(E[] enumValues, String constant) {
for (E candidate : enumValues) {
if(candidate.toString().equalsIgnoreCase(constant)) {
return candidate;
}
}
throw new IllegalArgumentException(
String.format("constant [%s] does not exist in enum type %s",
constant, enumValues.getClass().getComponentType().getName()));
}
/**
* Append the given object to the given array, returning a new array
* consisting of the input array contents plus the given object.

View File

@@ -372,6 +372,20 @@ public abstract class ReflectionUtils {
return (method != null && method.getName().equals("toString") && method.getParameterTypes().length == 0);
}
/**
* Determine whether the given method is originally declared by {@link java.lang.Object}.
*/
public static boolean isObjectMethod(Method method) {
try {
Object.class.getDeclaredMethod(method.getName(), method.getParameterTypes());
return true;
} catch (SecurityException ex) {
return false;
} catch (NoSuchMethodException ex) {
return false;
}
}
/**
* Make the given field accessible, explicitly setting it accessible if
* necessary. The <code>setAccessible(true)</code> method is only called

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2011 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.
@@ -55,7 +55,7 @@ import org.springframework.core.convert.converter.ConverterRegistry;
*/
public class DefaultConversionTests {
private ConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
private ConversionService conversionService = new DefaultConversionService();
@Test
public void testStringToCharacter() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2011 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.
@@ -78,6 +78,7 @@ public class GenericConversionServiceTests {
}
@Test
@SuppressWarnings("rawtypes")
public void addConverterNoSourceTargetClassInfoAvailable() {
try {
conversionService.addConverter(new Converter() {
@@ -109,7 +110,7 @@ public class GenericConversionServiceTests {
}
public void convertNullTargetClass() {
assertNull(conversionService.convert("3", (Class) null));
assertNull(conversionService.convert("3", (Class<?>) null));
assertNull(conversionService.convert("3", TypeDescriptor.NULL));
}
@@ -226,7 +227,7 @@ public class GenericConversionServiceTests {
@Test
public void testStringArrayToResourceArray() {
GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new MyStringArrayToResourceArrayConverter());
Resource[] converted = conversionService.convert(new String[] {"x1", "z3"}, Resource[].class);
assertEquals(2, converted.length);
@@ -236,7 +237,7 @@ public class GenericConversionServiceTests {
@Test
public void testStringArrayToIntegerArray() {
GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new MyStringArrayToIntegerArrayConverter());
Integer[] converted = conversionService.convert(new String[] {"x1", "z3"}, Integer[].class);
assertEquals(2, converted.length);
@@ -246,7 +247,7 @@ public class GenericConversionServiceTests {
@Test
public void testStringToIntegerArray() {
GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new MyStringToIntegerArrayConverter());
Integer[] converted = conversionService.convert("x1,z3", Integer[].class);
assertEquals(2, converted.length);
@@ -256,7 +257,7 @@ public class GenericConversionServiceTests {
@Test
public void testWildcardMap() throws Exception {
GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService conversionService = new DefaultConversionService();
Map<String, String> input = new LinkedHashMap<String, String>();
input.put("key", "value");
Object converted = conversionService.convert(input, new TypeDescriptor(getClass().getField("wildcardMap")));
@@ -265,14 +266,14 @@ public class GenericConversionServiceTests {
@Test
public void testListOfList() {
GenericConversionService service = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService service = new DefaultConversionService();
List<List<String>> list = Collections.singletonList(Collections.singletonList("Foo"));
assertNotNull(service.convert(list, String.class));
}
@Test
public void testStringToString() {
GenericConversionService service = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService service = new DefaultConversionService();
String value = "myValue";
String result = service.convert(value, String.class);
assertSame(value, result);
@@ -280,7 +281,7 @@ public class GenericConversionServiceTests {
@Test
public void testStringToObject() {
GenericConversionService service = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService service = new DefaultConversionService();
String value = "myValue";
Object result = service.convert(value, Object.class);
assertSame(value, result);
@@ -288,7 +289,7 @@ public class GenericConversionServiceTests {
@Test
public void testIgnoreCopyConstructor() {
GenericConversionService service = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService service = new DefaultConversionService();
WithCopyConstructor value = new WithCopyConstructor();
Object result = service.convert(value, WithCopyConstructor.class);
assertSame(value, result);
@@ -296,7 +297,7 @@ public class GenericConversionServiceTests {
@Test
public void testPerformance1() {
GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService conversionService = new DefaultConversionService();
StopWatch watch = new StopWatch("integer->string conversionPerformance");
watch.start("convert 4,000,000 with conversion service");
for (int i = 0; i < 4000000; i++) {
@@ -313,7 +314,7 @@ public class GenericConversionServiceTests {
@Test
public void testPerformance2() throws Exception {
GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService conversionService = new DefaultConversionService();
StopWatch watch = new StopWatch("list<string> -> list<integer> conversionPerformance");
watch.start("convert 4,000,000 with conversion service");
List<String> source = new LinkedList<String>();
@@ -340,7 +341,7 @@ public class GenericConversionServiceTests {
@Test
public void testPerformance3() throws Exception {
GenericConversionService conversionService = ConversionServiceFactory.createDefaultConversionService();
GenericConversionService conversionService = new DefaultConversionService();
StopWatch watch = new StopWatch("map<string, string> -> map<string, integer> conversionPerformance");
watch.start("convert 4,000,000 with conversion service");
Map<String, String> source = new HashMap<String, String>();
@@ -387,6 +388,7 @@ public class GenericConversionServiceTests {
TypeDescriptor sourceType = TypeDescriptor.forObject(list);
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("emptyListDifferentTarget"));
assertTrue(conversionService.canConvert(sourceType, targetType));
@SuppressWarnings("unchecked")
LinkedList<Integer> result = (LinkedList<Integer>) conversionService.convert(list, sourceType, targetType);
assertEquals(LinkedList.class, result.getClass());
assertTrue(result.isEmpty());
@@ -437,6 +439,7 @@ public class GenericConversionServiceTests {
TypeDescriptor sourceType = TypeDescriptor.forObject(map);
TypeDescriptor targetType = new TypeDescriptor(getClass().getField("emptyMapDifferentTarget"));
assertTrue(conversionService.canConvert(sourceType, targetType));
@SuppressWarnings("unchecked")
LinkedHashMap<String, String> result = (LinkedHashMap<String, String>) conversionService.convert(map, sourceType, targetType);
assertEquals(map, result);
assertEquals(LinkedHashMap.class, result.getClass());

View File

@@ -16,6 +16,9 @@
package org.springframework.util;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.sql.SQLException;
@@ -43,10 +46,10 @@ public final class ObjectUtilsTests extends TestCase {
}
public void testIsCompatibleWithThrowsClause() {
Class[] empty = new Class[0];
Class[] exception = new Class[] {Exception.class};
Class[] sqlAndIO = new Class[] {SQLException.class, IOException.class};
Class[] throwable = new Class[] {Throwable.class};
Class<?>[] empty = new Class[0];
Class<?>[] exception = new Class[] {Exception.class};
Class<?>[] sqlAndIO = new Class[] {SQLException.class, IOException.class};
Class<?>[] throwable = new Class[] {Throwable.class};
assertTrue(ObjectUtils.isCompatibleWithThrowsClause(new RuntimeException(), null));
assertTrue(ObjectUtils.isCompatibleWithThrowsClause(new RuntimeException(), empty));
@@ -617,6 +620,35 @@ public final class ObjectUtilsTests extends TestCase {
assertEquals("null", ObjectUtils.nullSafeToString((String[]) null));
}
enum Tropes { FOO, BAR, baz };
public void testContainsConstant() {
assertThat(ObjectUtils.containsConstant(Tropes.values(), "FOO"), is(true));
assertThat(ObjectUtils.containsConstant(Tropes.values(), "foo"), is(true));
assertThat(ObjectUtils.containsConstant(Tropes.values(), "BaR"), is(true));
assertThat(ObjectUtils.containsConstant(Tropes.values(), "bar"), is(true));
assertThat(ObjectUtils.containsConstant(Tropes.values(), "BAZ"), is(true));
assertThat(ObjectUtils.containsConstant(Tropes.values(), "baz"), is(true));
assertThat(ObjectUtils.containsConstant(Tropes.values(), "BOGUS"), is(false));
assertThat(ObjectUtils.containsConstant(Tropes.values(), "FOO", true), is(true));
assertThat(ObjectUtils.containsConstant(Tropes.values(), "foo", true), is(false));
}
public void testCaseInsensitiveValueOf() {
assertThat(ObjectUtils.caseInsensitiveValueOf(Tropes.values(), "foo"), is(Tropes.FOO));
assertThat(ObjectUtils.caseInsensitiveValueOf(Tropes.values(), "BAR"), is(Tropes.BAR));
try {
ObjectUtils.caseInsensitiveValueOf(Tropes.values(), "bogus");
fail("expected IllegalArgumentException");
} catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(),
is("constant [bogus] does not exist in enum type " +
"org.springframework.util.ObjectUtilsTests$Tropes"));
}
}
private void assertEqualHashCodes(int expected, Object array) {
int actual = ObjectUtils.nullSafeHashCode(array);
assertEquals(expected, actual);