diff options
Diffstat (limited to 'spring-core/src/test/java')
38 files changed, 2151 insertions, 1151 deletions
diff --git a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java index 1fed0c5b..7e7c3d5b 100644 --- a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java @@ -130,7 +130,7 @@ public class GenericTypeResolverTests { Type t = null; Type x = null; for (Map.Entry<TypeVariable, Type> entry : map.entrySet()) { - if(entry.getKey().toString().equals("T")) { + if (entry.getKey().toString().equals("T")) { t = entry.getValue(); } else { diff --git a/spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java b/spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java index ac68dace..ba669e0b 100644 --- a/spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java +++ b/spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -46,7 +46,7 @@ public class SerializableTypeWrapperTests { public void forField() throws Exception { Type type = SerializableTypeWrapper.forField(Fields.class.getField("parameterizedType")); assertThat(type.toString(), equalTo("java.util.List<java.lang.String>")); - assertSerialzable(type); + assertSerializable(type); } @Test @@ -54,7 +54,7 @@ public class SerializableTypeWrapperTests { Method method = Methods.class.getDeclaredMethod("method", Class.class, Object.class); Type type = SerializableTypeWrapper.forMethodParameter(MethodParameter.forMethodOrConstructor(method, 0)); assertThat(type.toString(), equalTo("java.lang.Class<T>")); - assertSerialzable(type); + assertSerializable(type); } @Test @@ -62,62 +62,62 @@ public class SerializableTypeWrapperTests { Constructor<?> constructor = Constructors.class.getDeclaredConstructor(List.class); Type type = SerializableTypeWrapper.forMethodParameter(MethodParameter.forMethodOrConstructor(constructor, 0)); assertThat(type.toString(), equalTo("java.util.List<java.lang.String>")); - assertSerialzable(type); + assertSerializable(type); } @Test public void forGenericSuperClass() throws Exception { Type type = SerializableTypeWrapper.forGenericSuperclass(ArrayList.class); assertThat(type.toString(), equalTo("java.util.AbstractList<E>")); - assertSerialzable(type); + assertSerializable(type); } @Test public void forGenericInterfaces() throws Exception { Type type = SerializableTypeWrapper.forGenericInterfaces(List.class)[0]; assertThat(type.toString(), equalTo("java.util.Collection<E>")); - assertSerialzable(type); + assertSerializable(type); } @Test - public void forTypeParamters() throws Exception { + public void forTypeParameters() throws Exception { Type type = SerializableTypeWrapper.forTypeParameters(List.class)[0]; assertThat(type.toString(), equalTo("E")); - assertSerialzable(type); + assertSerializable(type); } @Test public void classType() throws Exception { Type type = SerializableTypeWrapper.forField(Fields.class.getField("classType")); assertThat(type.toString(), equalTo("class java.lang.String")); - assertSerialzable(type); + assertSerializable(type); } @Test public void genericArrayType() throws Exception { GenericArrayType type = (GenericArrayType) SerializableTypeWrapper.forField(Fields.class.getField("genericArrayType")); assertThat(type.toString(), equalTo("java.util.List<java.lang.String>[]")); - assertSerialzable(type); - assertSerialzable(type.getGenericComponentType()); + assertSerializable(type); + assertSerializable(type.getGenericComponentType()); } @Test public void parameterizedType() throws Exception { ParameterizedType type = (ParameterizedType) SerializableTypeWrapper.forField(Fields.class.getField("parameterizedType")); assertThat(type.toString(), equalTo("java.util.List<java.lang.String>")); - assertSerialzable(type); - assertSerialzable(type.getOwnerType()); - assertSerialzable(type.getRawType()); - assertSerialzable(type.getActualTypeArguments()); - assertSerialzable(type.getActualTypeArguments()[0]); + assertSerializable(type); + assertSerializable(type.getOwnerType()); + assertSerializable(type.getRawType()); + assertSerializable(type.getActualTypeArguments()); + assertSerializable(type.getActualTypeArguments()[0]); } @Test public void typeVariableType() throws Exception { TypeVariable<?> type = (TypeVariable<?>) SerializableTypeWrapper.forField(Fields.class.getField("typeVariableType")); assertThat(type.toString(), equalTo("T")); - assertSerialzable(type); - assertSerialzable(type.getBounds()); + assertSerializable(type); + assertSerializable(type.getBounds()); } @Test @@ -125,13 +125,13 @@ public class SerializableTypeWrapperTests { ParameterizedType typeSource = (ParameterizedType) SerializableTypeWrapper.forField(Fields.class.getField("wildcardType")); WildcardType type = (WildcardType) typeSource.getActualTypeArguments()[0]; assertThat(type.toString(), equalTo("? extends java.lang.CharSequence")); - assertSerialzable(type); - assertSerialzable(type.getLowerBounds()); - assertSerialzable(type.getUpperBounds()); + assertSerializable(type); + assertSerializable(type.getLowerBounds()); + assertSerializable(type.getUpperBounds()); } - private void assertSerialzable(Object source) throws Exception { + private void assertSerializable(Object source) throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(source); @@ -152,19 +152,19 @@ public class SerializableTypeWrapperTests { public T typeVariableType; public List<? extends CharSequence> wildcardType; - } - static interface Methods { - <T> List<T> method(Class<T> p1, T p2); + interface Methods { + <T> List<T> method(Class<T> p1, T p2); } + static class Constructors { public Constructors(List<String> p) { } - } + } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java index 57dd9847..8230dd59 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -33,8 +33,11 @@ import javax.annotation.Resource; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; +import org.junit.internal.ArrayComparisonFailure; import org.junit.rules.ExpectedException; +import org.springframework.core.annotation.AnnotationUtilsTests.WebController; +import org.springframework.core.annotation.AnnotationUtilsTests.WebMapping; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; @@ -44,6 +47,7 @@ import static java.util.stream.Collectors.*; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.springframework.core.annotation.AnnotatedElementUtils.*; +import static org.springframework.core.annotation.AnnotationUtilsTests.*; /** * Unit tests for {@link AnnotatedElementUtils}. @@ -51,6 +55,9 @@ import static org.springframework.core.annotation.AnnotatedElementUtils.*; * @author Sam Brannen * @author Rossen Stoyanchev * @since 4.0.3 + * @see AnnotationUtilsTests + * @see MultipleComposedAnnotationsOnSingleAnnotatedElementTests + * @see ComposedRepeatableAnnotationsTests */ public class AnnotatedElementUtilsTests { @@ -63,18 +70,25 @@ public class AnnotatedElementUtilsTests { @Test public void getMetaAnnotationTypesOnNonAnnotatedClass() { assertNull(getMetaAnnotationTypes(NonAnnotatedClass.class, TransactionalComponent.class)); + assertNull(getMetaAnnotationTypes(NonAnnotatedClass.class, TransactionalComponent.class.getName())); } @Test public void getMetaAnnotationTypesOnClassWithMetaDepth1() { Set<String> names = getMetaAnnotationTypes(TransactionalComponentClass.class, TransactionalComponent.class); assertEquals(names(Transactional.class, Component.class), names); + + names = getMetaAnnotationTypes(TransactionalComponentClass.class, TransactionalComponent.class.getName()); + assertEquals(names(Transactional.class, Component.class), names); } @Test public void getMetaAnnotationTypesOnClassWithMetaDepth2() { Set<String> names = getMetaAnnotationTypes(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class); assertEquals(names(TransactionalComponent.class, Transactional.class, Component.class), names); + + names = getMetaAnnotationTypes(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class.getName()); + assertEquals(names(TransactionalComponent.class, Transactional.class, Component.class), names); } @Test @@ -294,7 +308,15 @@ public class AnnotatedElementUtilsTests { assertTrue(isAnnotated(element, name)); } - @Ignore("Disabled until SPR-13554 is addressed") + /** + * This test should never pass, simply because Spring does not support a hybrid + * approach for annotation attribute overrides with transitive implicit aliases. + * See SPR-13554 for details. + * <p>Furthermore, if you choose to execute this test, it can fail for either + * the first test class or the second one (with different exceptions), depending + * on the order in which the JVM returns the attribute methods via reflection. + */ + @Ignore("Permanently disabled but left in place for illustrative purposes") @Test public void getMergedAnnotationAttributesWithHalfConventionBasedAndHalfAliasedComposedAnnotation() { for (Class<?> clazz : asList(HalfConventionBasedAndHalfAliasedComposedContextConfigClassV1.class, @@ -380,10 +402,20 @@ public class AnnotatedElementUtilsTests { } @Test + public void getMergedAnnotationWithTransitiveImplicitAliasesWithSingleElementOverridingAnArrayViaAliasFor() { + assertGetMergedAnnotation(SingleLocationTransitiveImplicitAliasesContextConfigClass.class, "test.groovy"); + } + + @Test public void getMergedAnnotationWithTransitiveImplicitAliasesWithSkippedLevel() { assertGetMergedAnnotation(TransitiveImplicitAliasesWithSkippedLevelContextConfigClass.class, "test.xml"); } + @Test + public void getMergedAnnotationWithTransitiveImplicitAliasesWithSkippedLevelWithSingleElementOverridingAnArrayViaAliasFor() { + assertGetMergedAnnotation(SingleLocationTransitiveImplicitAliasesWithSkippedLevelContextConfigClass.class, "test.xml"); + } + private void assertGetMergedAnnotation(Class<?> element, String... expected) { String name = ContextConfig.class.getName(); ContextConfig contextConfig = getMergedAnnotation(element, ContextConfig.class); @@ -428,15 +460,15 @@ public class AnnotatedElementUtilsTests { } @Test - public void getMergedAnnotationAttributesWithInvalidAliasedComposedAnnotation() { - Class<?> element = InvalidAliasedComposedContextConfigClass.class; - exception.expect(AnnotationConfigurationException.class); - exception.expectMessage(either(containsString("attribute 'value' and its alias 'locations'")).or( - containsString("attribute 'locations' and its alias 'value'"))); - exception.expectMessage(either(containsString("values of [{duplicateDeclaration}] and [{test.xml}]")).or( - containsString("values of [{test.xml}] and [{duplicateDeclaration}]"))); - exception.expectMessage(containsString("but only one is permitted")); - getMergedAnnotationAttributes(element, ContextConfig.class); + public void getMergedAnnotationAttributesWithShadowedAliasComposedAnnotation() { + Class<?> element = ShadowedAliasComposedContextConfigClass.class; + AnnotationAttributes attributes = getMergedAnnotationAttributes(element, ContextConfig.class); + + String[] expected = asArray("test.xml"); + + assertNotNull("Should find @ContextConfig on " + element.getSimpleName(), attributes); + assertArrayEquals("locations", expected, attributes.getStringArray("locations")); + assertArrayEquals("value", expected, attributes.getStringArray("value")); } @Test @@ -561,6 +593,52 @@ public class AnnotatedElementUtilsTests { } @Test + public void findMergedAnnotationAttributesOnClassWithAttributeAliasInComposedAnnotationAndNestedAnnotationsInTargetAnnotation() { + AnnotationAttributes attributes = assertComponentScanAttributes(TestComponentScanClass.class, "com.example.app.test"); + + Filter[] excludeFilters = attributes.getAnnotationArray("excludeFilters", Filter.class); + assertNotNull(excludeFilters); + + List<String> patterns = stream(excludeFilters).map(Filter::pattern).collect(toList()); + assertEquals(asList("*Test", "*Tests"), patterns); + } + + /** + * This test ensures that {@link AnnotationUtils#postProcessAnnotationAttributes} + * uses {@code ObjectUtils.nullSafeEquals()} to check for equality between annotation + * attributes since attributes may be arrays. + */ + @Test + public void findMergedAnnotationAttributesOnClassWithBothAttributesOfAnAliasPairDeclared() { + assertComponentScanAttributes(ComponentScanWithBasePackagesAndValueAliasClass.class, "com.example.app.test"); + } + + @Test + public void findMergedAnnotationAttributesWithSingleElementOverridingAnArrayViaConvention() { + assertComponentScanAttributes(ConventionBasedSinglePackageComponentScanClass.class, "com.example.app.test"); + } + + @Test + public void findMergedAnnotationAttributesWithSingleElementOverridingAnArrayViaAliasFor() { + assertComponentScanAttributes(AliasForBasedSinglePackageComponentScanClass.class, "com.example.app.test"); + } + + private AnnotationAttributes assertComponentScanAttributes(Class<?> element, String... expected) { + AnnotationAttributes attributes = findMergedAnnotationAttributes(element, ComponentScan.class); + + assertNotNull("Should find @ComponentScan on " + element, attributes); + assertArrayEquals("value: ", expected, attributes.getStringArray("value")); + assertArrayEquals("basePackages: ", expected, attributes.getStringArray("basePackages")); + + return attributes; + } + + private AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element, Class<? extends Annotation> annotationType) { + Assert.notNull(annotationType, "annotationType must not be null"); + return AnnotatedElementUtils.findMergedAnnotationAttributes(element, annotationType.getName(), false, false); + } + + @Test public void findMergedAnnotationWithAttributeAliasesInTargetAnnotation() { Class<?> element = AliasedTransactionalComponentClass.class; AliasedTransactional annotation = findMergedAnnotation(element, AliasedTransactional.class); @@ -594,38 +672,6 @@ public class AnnotatedElementUtilsTests { } @Test - public void findMergedAnnotationAttributesOnClassWithAttributeAliasInComposedAnnotationAndNestedAnnotationsInTargetAnnotation() { - String[] expected = asArray("com.example.app.test"); - Class<?> element = TestComponentScanClass.class; - AnnotationAttributes attributes = findMergedAnnotationAttributes(element, ComponentScan.class); - - assertNotNull("Should find @ComponentScan on " + element, attributes); - assertArrayEquals("basePackages for " + element, expected, attributes.getStringArray("basePackages")); - - Filter[] excludeFilters = attributes.getAnnotationArray("excludeFilters", Filter.class); - assertNotNull(excludeFilters); - - List<String> patterns = stream(excludeFilters).map(Filter::pattern).collect(toList()); - assertEquals(asList("*Test", "*Tests"), patterns); - } - - /** - * This test ensures that {@link AnnotationUtils#postProcessAnnotationAttributes} - * uses {@code ObjectUtils.nullSafeEquals()} to check for equality between annotation - * attributes since attributes may be arrays. - */ - @Test - public void findMergedAnnotationAttributesOnClassWithBothAttributesOfAnAliasPairDeclared() { - String[] expected = asArray("com.example.app.test"); - Class<?> element = ComponentScanWithBasePackagesAndValueAliasClass.class; - AnnotationAttributes attributes = findMergedAnnotationAttributes(element, ComponentScan.class); - - assertNotNull("Should find @ComponentScan on " + element, attributes); - assertArrayEquals("value: ", expected, attributes.getStringArray("value")); - assertArrayEquals("basePackages: ", expected, attributes.getStringArray("basePackages")); - } - - @Test public void findMergedAnnotationWithLocalAliasesThatConflictWithAttributesInMetaAnnotationByConvention() { final String[] EMPTY = new String[0]; Class<?> element = SpringAppConfigClass.class; @@ -639,6 +685,24 @@ public class AnnotatedElementUtilsTests { } @Test + public void findMergedAnnotationWithSingleElementOverridingAnArrayViaConvention() throws Exception { + assertWebMapping(WebController.class.getMethod("postMappedWithPathAttribute")); + } + + @Test + public void findMergedAnnotationWithSingleElementOverridingAnArrayViaAliasFor() throws Exception { + assertWebMapping(WebController.class.getMethod("getMappedWithValueAttribute")); + assertWebMapping(WebController.class.getMethod("getMappedWithPathAttribute")); + } + + private void assertWebMapping(AnnotatedElement element) throws ArrayComparisonFailure { + WebMapping webMapping = findMergedAnnotation(element, WebMapping.class); + assertNotNull(webMapping); + assertArrayEquals("value attribute: ", asArray("/test"), webMapping.value()); + assertArrayEquals("path attribute: ", asArray("/test"), webMapping.path()); + } + + @Test public void javaLangAnnotationTypeViaFindMergedAnnotation() throws Exception { Constructor<?> deprecatedCtor = Date.class.getConstructor(String.class); assertEquals(deprecatedCtor.getAnnotation(Deprecated.class), findMergedAnnotation(deprecatedCtor, Deprecated.class)); @@ -651,28 +715,10 @@ public class AnnotatedElementUtilsTests { assertEquals(SpringAppConfigClass.class.getAnnotation(Resource.class), findMergedAnnotation(SpringAppConfigClass.class, Resource.class)); } - private Set<String> names(Class<?>... classes) { return stream(classes).map(Class::getName).collect(toSet()); } - @SafeVarargs - // The following "varargs" suppression is necessary for javac from OpenJDK - // (1.8.0_60-b27); however, Eclipse warns that it's unnecessary. See the following - // Eclipse issues for details. - // - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=344783 - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=349669#c10 - // @SuppressWarnings("varargs") - private static <T> T[] asArray(T... arr) { - return arr; - } - - private static AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element, Class<? extends Annotation> annotationType) { - Assert.notNull(annotationType, "annotationType must not be null"); - return AnnotatedElementUtils.findMergedAnnotationAttributes(element, annotationType.getName(), false, false); - } - // ------------------------------------------------------------------------- @@ -821,6 +867,10 @@ public class AnnotatedElementUtilsTests { String[] locations(); } + /** + * This hybrid approach for annotation attribute overrides with transitive implicit + * aliases is unsupported. See SPR-13554 for details. + */ @ContextConfig @Retention(RetentionPolicy.RUNTIME) @interface HalfConventionBasedAndHalfAliasedComposedContextConfig { @@ -858,10 +908,12 @@ public class AnnotatedElementUtilsTests { @AliasFor(annotation = ContextConfig.class, attribute = "locations") String[] xmlFiles() default {}; - @AliasFor(annotation = ContextConfig.class, attribute = "locations") + // intentionally omitted: attribute = "locations" + @AliasFor(annotation = ContextConfig.class) String[] locations() default {}; - @AliasFor(annotation = ContextConfig.class, attribute = "locations") + // intentionally omitted: attribute = "locations" (SPR-14069) + @AliasFor(annotation = ContextConfig.class) String[] value() default {}; } @@ -883,6 +935,17 @@ public class AnnotatedElementUtilsTests { @ImplicitAliasesContextConfig @Retention(RetentionPolicy.RUNTIME) + @interface SingleLocationTransitiveImplicitAliasesContextConfig { + + @AliasFor(annotation = ImplicitAliasesContextConfig.class, attribute = "xmlFiles") + String xml() default ""; + + @AliasFor(annotation = ImplicitAliasesContextConfig.class, attribute = "groovyScripts") + String groovy() default ""; + } + + @ImplicitAliasesContextConfig + @Retention(RetentionPolicy.RUNTIME) @interface TransitiveImplicitAliasesWithSkippedLevelContextConfig { @AliasFor(annotation = ContextConfig.class, attribute = "locations") @@ -892,15 +955,28 @@ public class AnnotatedElementUtilsTests { String[] groovy() default {}; } + @ImplicitAliasesContextConfig + @Retention(RetentionPolicy.RUNTIME) + @interface SingleLocationTransitiveImplicitAliasesWithSkippedLevelContextConfig { + + @AliasFor(annotation = ContextConfig.class, attribute = "locations") + String xml() default ""; + + @AliasFor(annotation = ImplicitAliasesContextConfig.class, attribute = "groovyScripts") + String groovy() default ""; + } + /** - * Invalid because the configuration declares a value for 'value' and - * requires a value for the aliased 'locations'. So we likely end up with - * both 'value' and 'locations' being present in {@link AnnotationAttributes} - * but with different values, which violates the contract of {@code @AliasFor}. + * Although the configuration declares an explicit value for 'value' and + * requires a value for the aliased 'locations', this does not result in + * an error since 'locations' effectively <em>shadows</em> the 'value' + * attribute (which cannot be set via the composed annotation anyway). + * + * If 'value' were not shadowed, such a declaration would not make sense. */ @ContextConfig(value = "duplicateDeclaration") @Retention(RetentionPolicy.RUNTIME) - @interface InvalidAliasedComposedContextConfig { + @interface ShadowedAliasComposedContextConfig { @AliasFor(annotation = ContextConfig.class, attribute = "locations") String[] xmlConfigFiles(); @@ -962,6 +1038,21 @@ public class AnnotatedElementUtilsTests { String[] packages(); } + @ComponentScan + @Retention(RetentionPolicy.RUNTIME) + @interface ConventionBasedSinglePackageComponentScan { + + String basePackages(); + } + + @ComponentScan + @Retention(RetentionPolicy.RUNTIME) + @interface AliasForBasedSinglePackageComponentScan { + + @AliasFor(attribute = "basePackages", annotation = ComponentScan.class) + String pkg(); + } + // ------------------------------------------------------------------------- static class NonAnnotatedClass { @@ -1132,16 +1223,24 @@ public class AnnotatedElementUtilsTests { static class TransitiveImplicitAliasesContextConfigClass { } + @SingleLocationTransitiveImplicitAliasesContextConfig(groovy = "test.groovy") + static class SingleLocationTransitiveImplicitAliasesContextConfigClass { + } + @TransitiveImplicitAliasesWithSkippedLevelContextConfig(xml = "test.xml") static class TransitiveImplicitAliasesWithSkippedLevelContextConfigClass { } + @SingleLocationTransitiveImplicitAliasesWithSkippedLevelContextConfig(xml = "test.xml") + static class SingleLocationTransitiveImplicitAliasesWithSkippedLevelContextConfigClass { + } + @ComposedImplicitAliasesContextConfig static class ComposedImplicitAliasesContextConfigClass { } - @InvalidAliasedComposedContextConfig(xmlConfigFiles = "test.xml") - static class InvalidAliasedComposedContextConfigClass { + @ShadowedAliasComposedContextConfig(xmlConfigFiles = "test.xml") + static class ShadowedAliasComposedContextConfigClass { } @AliasedComposedContextConfigAndTestPropSource(xmlConfigFiles = "test.xml") @@ -1156,6 +1255,14 @@ public class AnnotatedElementUtilsTests { static class TestComponentScanClass { } + @ConventionBasedSinglePackageComponentScan(basePackages = "com.example.app.test") + static class ConventionBasedSinglePackageComponentScanClass { + } + + @AliasForBasedSinglePackageComponentScan(pkg = "com.example.app.test") + static class AliasForBasedSinglePackageComponentScanClass { + } + @SpringAppConfig(Number.class) static class SpringAppConfigClass { } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java index 57cdd7ab..5908bf36 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAttributesTests.java @@ -25,7 +25,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.springframework.core.annotation.AnnotationUtilsTests.ContextConfig; import org.springframework.core.annotation.AnnotationUtilsTests.ImplicitAliasesContextConfig; import static org.hamcrest.CoreMatchers.*; @@ -133,21 +132,21 @@ public class AnnotationAttributesTests { @Test public void getEnumWithNullAttributeName() { exception.expect(IllegalArgumentException.class); - exception.expectMessage(containsString("attributeName must not be null or empty")); + exception.expectMessage("must not be null or empty"); attributes.getEnum(null); } @Test public void getEnumWithEmptyAttributeName() { exception.expect(IllegalArgumentException.class); - exception.expectMessage(containsString("attributeName must not be null or empty")); + exception.expectMessage("must not be null or empty"); attributes.getEnum(""); } @Test public void getEnumWithUnknownAttributeName() { exception.expect(IllegalArgumentException.class); - exception.expectMessage(containsString("Attribute 'bogus' not found")); + exception.expectMessage("Attribute 'bogus' not found"); attributes.getEnum("bogus"); } @@ -160,334 +159,66 @@ public class AnnotationAttributesTests { } @Test - public void getAliasedString() { - final String value = "metaverse"; - - attributes.clear(); - attributes.put("name", value); - assertEquals(value, getAliasedString("name")); - assertEquals(value, getAliasedString("value")); - - attributes.clear(); - attributes.put("value", value); - assertEquals(value, getAliasedString("name")); - assertEquals(value, getAliasedString("value")); - - attributes.clear(); - attributes.put("name", value); - attributes.put("value", value); - assertEquals(value, getAliasedString("name")); - assertEquals(value, getAliasedString("value")); - } - - @Test public void getAliasedStringWithImplicitAliases() { - final String value = "metaverse"; - final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript"); + String value = "metaverse"; + List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript"); attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class); attributes.put("value", value); - aliases.stream().forEach(alias -> assertEquals(value, getAliasedStringWithImplicitAliases(alias))); + AnnotationUtils.postProcessAnnotationAttributes(null, attributes, false); + aliases.stream().forEach(alias -> assertEquals(value, attributes.getString(alias))); - attributes.clear(); + attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class); attributes.put("location1", value); - aliases.stream().forEach(alias -> assertEquals(value, getAliasedStringWithImplicitAliases(alias))); + AnnotationUtils.postProcessAnnotationAttributes(null, attributes, false); + aliases.stream().forEach(alias -> assertEquals(value, attributes.getString(alias))); - attributes.clear(); + attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class); attributes.put("value", value); attributes.put("location1", value); attributes.put("xmlFile", value); attributes.put("groovyScript", value); - aliases.stream().forEach(alias -> assertEquals(value, getAliasedStringWithImplicitAliases(alias))); - } - - @Test - public void getAliasedStringWithImplicitAliasesWithMissingAliasedAttributes() { - final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript"); - attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class); - - exception.expect(IllegalArgumentException.class); - exception.expectMessage(startsWith("Neither attribute 'value' nor one of its aliases [")); - aliases.stream().forEach(alias -> exception.expectMessage(containsString(alias))); - exception.expectMessage(endsWith("] was found in attributes for annotation [" + ImplicitAliasesContextConfig.class.getName() + "]")); - getAliasedStringWithImplicitAliases("value"); - } - - @Test - public void getAliasedStringFromSynthesizedAnnotationAttributes() { - Scope scope = ScopedComponent.class.getAnnotation(Scope.class); - AnnotationAttributes scopeAttributes = AnnotationUtils.getAnnotationAttributes(ScopedComponent.class, scope); - - assertEquals("custom", getAliasedString(scopeAttributes, "name")); - assertEquals("custom", getAliasedString(scopeAttributes, "value")); - } - - @Test - public void getAliasedStringWithMissingAliasedAttributes() { - exception.expect(IllegalArgumentException.class); - exception.expectMessage(equalTo("Neither attribute 'name' nor one of its aliases [value] was found in attributes for annotation [unknown]")); - getAliasedString("name"); - } - - @Test - public void getAliasedStringWithDifferentAliasedValues() { - attributes.put("name", "request"); - attributes.put("value", "session"); - - exception.expect(AnnotationConfigurationException.class); - exception.expectMessage(containsString("In annotation [" + Scope.class.getName() + "]")); - exception.expectMessage(containsString("attribute [name] and its alias [value]")); - exception.expectMessage(containsString("[request] and [session]")); - exception.expectMessage(containsString("but only one is permitted")); - - getAliasedString("name"); - } - - private String getAliasedString(String attributeName) { - return getAliasedString(this.attributes, attributeName); - } - - private String getAliasedString(AnnotationAttributes attrs, String attributeName) { - return attrs.getAliasedString(attributeName, Scope.class, null); - } - - private String getAliasedStringWithImplicitAliases(String attributeName) { - return this.attributes.getAliasedString(attributeName, ImplicitAliasesContextConfig.class, null); - } - - @Test - public void getAliasedStringArray() { - final String[] INPUT = new String[] {"test.xml"}; - final String[] EMPTY = new String[0]; - - attributes.clear(); - attributes.put("location", INPUT); - assertArrayEquals(INPUT, getAliasedStringArray("location")); - assertArrayEquals(INPUT, getAliasedStringArray("value")); - - attributes.clear(); - attributes.put("value", INPUT); - assertArrayEquals(INPUT, getAliasedStringArray("location")); - assertArrayEquals(INPUT, getAliasedStringArray("value")); - - attributes.clear(); - attributes.put("location", INPUT); - attributes.put("value", INPUT); - assertArrayEquals(INPUT, getAliasedStringArray("location")); - assertArrayEquals(INPUT, getAliasedStringArray("value")); - - attributes.clear(); - attributes.put("location", INPUT); - attributes.put("value", EMPTY); - assertArrayEquals(INPUT, getAliasedStringArray("location")); - assertArrayEquals(INPUT, getAliasedStringArray("value")); - - attributes.clear(); - attributes.put("location", EMPTY); - attributes.put("value", INPUT); - assertArrayEquals(INPUT, getAliasedStringArray("location")); - assertArrayEquals(INPUT, getAliasedStringArray("value")); - - attributes.clear(); - attributes.put("location", EMPTY); - attributes.put("value", EMPTY); - assertArrayEquals(EMPTY, getAliasedStringArray("location")); - assertArrayEquals(EMPTY, getAliasedStringArray("value")); + AnnotationUtils.postProcessAnnotationAttributes(null, attributes, false); + aliases.stream().forEach(alias -> assertEquals(value, attributes.getString(alias))); } @Test public void getAliasedStringArrayWithImplicitAliases() { - final String[] INPUT = new String[] {"test.xml"}; - final String[] EMPTY = new String[0]; - final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript"); + String[] value = new String[] {"test.xml"}; + List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript"); attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class); + attributes.put("location1", value); + AnnotationUtils.postProcessAnnotationAttributes(null, attributes, false); + aliases.stream().forEach(alias -> assertArrayEquals(value, attributes.getStringArray(alias))); - attributes.put("location1", INPUT); - aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias))); - - attributes.clear(); - attributes.put("value", INPUT); - aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias))); - - attributes.clear(); - attributes.put("location1", INPUT); - attributes.put("value", INPUT); - aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias))); - - attributes.clear(); - attributes.put("location1", INPUT); - attributes.put("value", EMPTY); - aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias))); - - attributes.clear(); - attributes.put("location1", EMPTY); - attributes.put("value", INPUT); - aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias))); - - attributes.clear(); - attributes.put("location1", EMPTY); - attributes.put("value", EMPTY); - aliases.stream().forEach(alias -> assertArrayEquals(EMPTY, getAliasedStringArrayWithImplicitAliases(alias))); - } - - @Test - public void getAliasedStringArrayWithImplicitAliasesWithMissingAliasedAttributes() { - final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript"); attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class); - - exception.expect(IllegalArgumentException.class); - exception.expectMessage(startsWith("Neither attribute 'value' nor one of its aliases [")); - aliases.stream().forEach(alias -> exception.expectMessage(containsString(alias))); - exception.expectMessage(endsWith("] was found in attributes for annotation [" + ImplicitAliasesContextConfig.class.getName() + "]")); - getAliasedStringArrayWithImplicitAliases("value"); - } - - @Test - public void getAliasedStringArrayWithMissingAliasedAttributes() { - exception.expect(IllegalArgumentException.class); - exception.expectMessage(equalTo("Neither attribute 'location' nor one of its aliases [value] was found in attributes for annotation [unknown]")); - getAliasedStringArray("location"); - } - - @Test - public void getAliasedStringArrayWithDifferentAliasedValues() { - attributes.put("location", new String[] {"1.xml"}); - attributes.put("value", new String[] {"2.xml"}); - - exception.expect(AnnotationConfigurationException.class); - exception.expectMessage(containsString("In annotation [" + ContextConfig.class.getName() + "]")); - exception.expectMessage(containsString("attribute [location] and its alias [value]")); - exception.expectMessage(containsString("[{1.xml}] and [{2.xml}]")); - exception.expectMessage(containsString("but only one is permitted")); - - getAliasedStringArray("location"); - } - - private String[] getAliasedStringArray(String attributeName) { - // Note: even though the attributes we test against here are of type - // String instead of String[], it doesn't matter... since - // AnnotationAttributes does not validate the actual return type of - // attributes in the annotation. - return attributes.getAliasedStringArray(attributeName, ContextConfig.class, null); - } - - private String[] getAliasedStringArrayWithImplicitAliases(String attributeName) { - // Note: even though the attributes we test against here are of type - // String instead of String[], it doesn't matter... since - // AnnotationAttributes does not validate the actual return type of - // attributes in the annotation. - return this.attributes.getAliasedStringArray(attributeName, ImplicitAliasesContextConfig.class, null); - } - - @Test - public void getAliasedClassArray() { - final Class<?>[] INPUT = new Class<?>[] {String.class}; - final Class<?>[] EMPTY = new Class<?>[0]; - - attributes.clear(); - attributes.put("classes", INPUT); - assertArrayEquals(INPUT, getAliasedClassArray("classes")); - assertArrayEquals(INPUT, getAliasedClassArray("value")); - - attributes.clear(); - attributes.put("value", INPUT); - assertArrayEquals(INPUT, getAliasedClassArray("classes")); - assertArrayEquals(INPUT, getAliasedClassArray("value")); - - attributes.clear(); - attributes.put("classes", INPUT); - attributes.put("value", INPUT); - assertArrayEquals(INPUT, getAliasedClassArray("classes")); - assertArrayEquals(INPUT, getAliasedClassArray("value")); - - attributes.clear(); - attributes.put("classes", INPUT); - attributes.put("value", EMPTY); - assertArrayEquals(INPUT, getAliasedClassArray("classes")); - assertArrayEquals(INPUT, getAliasedClassArray("value")); - - attributes.clear(); - attributes.put("classes", EMPTY); - attributes.put("value", INPUT); - assertArrayEquals(INPUT, getAliasedClassArray("classes")); - assertArrayEquals(INPUT, getAliasedClassArray("value")); - - attributes.clear(); - attributes.put("classes", EMPTY); - attributes.put("value", EMPTY); - assertArrayEquals(EMPTY, getAliasedClassArray("classes")); - assertArrayEquals(EMPTY, getAliasedClassArray("value")); - } - - @Test - public void getAliasedClassArrayWithImplicitAliases() { - final Class<?>[] INPUT = new Class<?>[] {String.class}; - final Class<?>[] EMPTY = new Class<?>[0]; - final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript"); + attributes.put("value", value); + AnnotationUtils.postProcessAnnotationAttributes(null, attributes, false); + aliases.stream().forEach(alias -> assertArrayEquals(value, attributes.getStringArray(alias))); attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class); + attributes.put("location1", value); + attributes.put("value", value); + AnnotationUtils.postProcessAnnotationAttributes(null, attributes, false); + aliases.stream().forEach(alias -> assertArrayEquals(value, attributes.getStringArray(alias))); - attributes.put("location1", INPUT); - aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias))); - - attributes.clear(); - attributes.put("value", INPUT); - aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias))); - - attributes.clear(); - attributes.put("location1", INPUT); - attributes.put("value", INPUT); - aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias))); - - attributes.clear(); - attributes.put("location1", INPUT); - attributes.put("value", EMPTY); - aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias))); - - attributes.clear(); - attributes.put("location1", EMPTY); - attributes.put("value", INPUT); - aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias))); - - attributes.clear(); - attributes.put("location1", EMPTY); - attributes.put("value", EMPTY); - aliases.stream().forEach(alias -> assertArrayEquals(EMPTY, getAliasedClassArrayWithImplicitAliases(alias))); - } - - @Test - public void getAliasedClassArrayWithMissingAliasedAttributes() { - exception.expect(IllegalArgumentException.class); - exception.expectMessage(equalTo("Neither attribute 'classes' nor one of its aliases [value] was found in attributes for annotation [unknown]")); - getAliasedClassArray("classes"); - } - - @Test - public void getAliasedClassArrayWithDifferentAliasedValues() { - attributes.put("classes", new Class<?>[] {String.class}); - attributes.put("value", new Class<?>[] {Number.class}); - - exception.expect(AnnotationConfigurationException.class); - exception.expectMessage(containsString("In annotation [" + Filter.class.getName() + "]")); - exception.expectMessage(containsString("attribute [classes] and its alias [value]")); - exception.expectMessage(containsString("[{class java.lang.String}] and [{class java.lang.Number}]")); - exception.expectMessage(containsString("but only one is permitted")); - - getAliasedClassArray("classes"); - } - + attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class); + attributes.put("location1", value); + AnnotationUtils.registerDefaultValues(attributes); + AnnotationUtils.postProcessAnnotationAttributes(null, attributes, false); + aliases.stream().forEach(alias -> assertArrayEquals(value, attributes.getStringArray(alias))); - private Class<?>[] getAliasedClassArray(String attributeName) { - return attributes.getAliasedClassArray(attributeName, Filter.class, null); - } + attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class); + attributes.put("value", value); + AnnotationUtils.registerDefaultValues(attributes); + AnnotationUtils.postProcessAnnotationAttributes(null, attributes, false); + aliases.stream().forEach(alias -> assertArrayEquals(value, attributes.getStringArray(alias))); - private Class<?>[] getAliasedClassArrayWithImplicitAliases(String attributeName) { - // Note: even though the attributes we test against here are of type - // String instead of Class<?>[], it doesn't matter... since - // AnnotationAttributes does not validate the actual return type of - // attributes in the annotation. - return this.attributes.getAliasedClassArray(attributeName, ImplicitAliasesContextConfig.class, null); + attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class); + AnnotationUtils.registerDefaultValues(attributes); + AnnotationUtils.postProcessAnnotationAttributes(null, attributes, false); + aliases.stream().forEach(alias -> assertArrayEquals(new String[] {""}, attributes.getStringArray(alias))); } @@ -514,23 +245,4 @@ public class AnnotationAttributesTests { static class FilteredClass { } - - /** - * Mock of {@code org.springframework.context.annotation.Scope}. - */ - @Retention(RetentionPolicy.RUNTIME) - @interface Scope { - - @AliasFor(attribute = "name") - String value() default "singleton"; - - @AliasFor(attribute = "value") - String name() default "singleton"; - } - - - @Scope(name = "custom") - static class ScopedComponent { - } - } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java index cbf9e974..c8d5170c 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -210,8 +210,7 @@ public class AnnotationUtilsTests { /** @since 4.1.2 */ @Test public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverAnnotationsOnInterfaces() { - Component component = findAnnotation(ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, - Component.class); + Component component = findAnnotation(ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, Component.class); assertNotNull(component); assertEquals("meta2", component.value()); } @@ -330,7 +329,7 @@ public class AnnotationUtilsTests { @Test public void findAnnotationDeclaringClassForTypesWithSingleCandidateType() { // no class-level annotation - List<Class<? extends Annotation>> transactionalCandidateList = asList(Transactional.class); + List<Class<? extends Annotation>> transactionalCandidateList = Collections.singletonList(Transactional.class); assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList, NonAnnotatedInterface.class)); assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList, NonAnnotatedClass.class)); @@ -345,7 +344,7 @@ public class AnnotationUtilsTests { // non-inherited class-level annotation; note: @Order is not inherited, // but findAnnotationDeclaringClassForTypes() should still find it on classes. - List<Class<? extends Annotation>> orderCandidateList = asList(Order.class); + List<Class<? extends Annotation>> orderCandidateList = Collections.singletonList(Order.class); assertEquals(NonInheritedAnnotationInterface.class, findAnnotationDeclaringClassForTypes(orderCandidateList, NonInheritedAnnotationInterface.class)); assertNull(findAnnotationDeclaringClassForTypes(orderCandidateList, SubNonInheritedAnnotationInterface.class)); @@ -466,8 +465,8 @@ public class AnnotationUtilsTests { assertNotNull(attributes); assertEquals(WebMapping.class, attributes.annotationType()); assertEquals("name attribute: ", "foo", attributes.getString("name")); - assertEquals("value attribute: ", "/test", attributes.getString(VALUE)); - assertEquals("path attribute: ", "/test", attributes.getString("path")); + assertArrayEquals("value attribute: ", asArray("/test"), attributes.getStringArray(VALUE)); + assertArrayEquals("path attribute: ", asArray("/test"), attributes.getStringArray("path")); method = WebController.class.getMethod("handleMappedWithPathAttribute"); webMapping = method.getAnnotation(WebMapping.class); @@ -475,15 +474,18 @@ public class AnnotationUtilsTests { assertNotNull(attributes); assertEquals(WebMapping.class, attributes.annotationType()); assertEquals("name attribute: ", "bar", attributes.getString("name")); - assertEquals("value attribute: ", "/test", attributes.getString(VALUE)); - assertEquals("path attribute: ", "/test", attributes.getString("path")); + assertArrayEquals("value attribute: ", asArray("/test"), attributes.getStringArray(VALUE)); + assertArrayEquals("path attribute: ", asArray("/test"), attributes.getStringArray("path")); + } - method = WebController.class.getMethod("handleMappedWithDifferentPathAndValueAttributes"); - webMapping = method.getAnnotation(WebMapping.class); + @Test + public void getAnnotationAttributesWithAttributeAliasesWithDifferentValues() throws Exception { exception.expect(AnnotationConfigurationException.class); exception.expectMessage(containsString("attribute 'value' and its alias 'path'")); - exception.expectMessage(containsString("values of [/enigma] and [/test]")); - exception.expectMessage(endsWith("but only one is permitted.")); + exception.expectMessage(containsString("values of [{/enigma}] and [{/test}]")); + + Method method = WebController.class.getMethod("handleMappedWithDifferentPathAndValueAttributes"); + WebMapping webMapping = method.getAnnotation(WebMapping.class); getAnnotationAttributes(webMapping); } @@ -552,9 +554,10 @@ public class AnnotationUtilsTests { @Test public void getRepeatableAnnotationsDeclaredOnClassWithMissingAttributeAliasDeclaration() throws Exception { exception.expect(AnnotationConfigurationException.class); - exception.expectMessage(startsWith("Attribute [value] in")); + exception.expectMessage(startsWith("Attribute 'value' in")); exception.expectMessage(containsString(BrokenContextConfig.class.getName())); - exception.expectMessage(endsWith("must be declared as an @AliasFor [location].")); + exception.expectMessage(containsString("@AliasFor [location]")); + getRepeatableAnnotations(BrokenConfigHierarchyTestCase.class, BrokenContextConfig.class, BrokenHierarchy.class); } @@ -776,6 +779,25 @@ public class AnnotationUtilsTests { } @Test + public void getAttributeAliasNamesFromComposedAnnotationWithImplicitAliasesWithImpliedAliasNamesOmitted() + throws Exception { + + Method value = ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class.getDeclaredMethod("value"); + Method location = ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class.getDeclaredMethod("location"); + Method xmlFile = ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class.getDeclaredMethod("xmlFile"); + + // Meta-annotation attribute overrides + assertEquals("value", getAttributeOverrideName(value, ContextConfig.class)); + assertEquals("location", getAttributeOverrideName(location, ContextConfig.class)); + assertEquals("location", getAttributeOverrideName(xmlFile, ContextConfig.class)); + + // Implicit aliases + assertThat(getAttributeAliasNames(value), containsInAnyOrder("location", "xmlFile")); + assertThat(getAttributeAliasNames(location), containsInAnyOrder("value", "xmlFile")); + assertThat(getAttributeAliasNames(xmlFile), containsInAnyOrder("value", "location")); + } + + @Test public void getAttributeAliasNamesFromComposedAnnotationWithTransitiveImplicitAliases() throws Exception { Method xml = TransitiveImplicitAliasesContextConfig.class.getDeclaredMethod("xml"); Method groovy = TransitiveImplicitAliasesContextConfig.class.getDeclaredMethod("groovy"); @@ -808,6 +830,26 @@ public class AnnotationUtilsTests { } @Test + public void getAttributeAliasNamesFromComposedAnnotationWithTransitiveImplicitAliasesWithImpliedAliasNamesOmitted() + throws Exception { + + Method xml = TransitiveImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class.getDeclaredMethod("xml"); + Method groovy = TransitiveImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class.getDeclaredMethod("groovy"); + + // Meta-annotation attribute overrides + assertEquals("location", getAttributeOverrideName(xml, ContextConfig.class)); + assertEquals("location", getAttributeOverrideName(groovy, ContextConfig.class)); + + // Explicit meta-annotation attribute overrides + assertEquals("xmlFile", getAttributeOverrideName(xml, ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class)); + assertEquals("location", getAttributeOverrideName(groovy, ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class)); + + // Transitive implicit aliases + assertThat(getAttributeAliasNames(groovy), containsInAnyOrder("xml")); + assertThat(getAttributeAliasNames(xml), containsInAnyOrder("groovy")); + } + + @Test public void synthesizeAnnotationWithoutAttributeAliases() throws Exception { Component component = WebController.class.getAnnotation(Component.class); assertNotNull(component); @@ -835,17 +877,17 @@ public class AnnotationUtilsTests { assertSame(synthesizedWebMapping, synthesizedAgainWebMapping); assertEquals("name attribute: ", "foo", synthesizedAgainWebMapping.name()); - assertEquals("aliased path attribute: ", "/test", synthesizedAgainWebMapping.path()); - assertEquals("actual value attribute: ", "/test", synthesizedAgainWebMapping.value()); + assertArrayEquals("aliased path attribute: ", asArray("/test"), synthesizedAgainWebMapping.path()); + assertArrayEquals("actual value attribute: ", asArray("/test"), synthesizedAgainWebMapping.value()); } @Test public void synthesizeAnnotationWhereAliasForIsMissingAttributeDeclaration() throws Exception { AliasForWithMissingAttributeDeclaration annotation = AliasForWithMissingAttributeDeclarationClass.class.getAnnotation(AliasForWithMissingAttributeDeclaration.class); exception.expect(AnnotationConfigurationException.class); - exception.expectMessage(startsWith("@AliasFor declaration on attribute [foo] in annotation")); + exception.expectMessage(startsWith("@AliasFor declaration on attribute 'foo' in annotation")); exception.expectMessage(containsString(AliasForWithMissingAttributeDeclaration.class.getName())); - exception.expectMessage(endsWith("is missing required 'attribute' value.")); + exception.expectMessage(containsString("points to itself")); synthesizeAnnotation(annotation); } @@ -853,10 +895,9 @@ public class AnnotationUtilsTests { public void synthesizeAnnotationWhereAliasForHasDuplicateAttributeDeclaration() throws Exception { AliasForWithDuplicateAttributeDeclaration annotation = AliasForWithDuplicateAttributeDeclarationClass.class.getAnnotation(AliasForWithDuplicateAttributeDeclaration.class); exception.expect(AnnotationConfigurationException.class); - exception.expectMessage(startsWith("In @AliasFor declared on attribute [foo] in annotation")); + exception.expectMessage(startsWith("In @AliasFor declared on attribute 'foo' in annotation")); exception.expectMessage(containsString(AliasForWithDuplicateAttributeDeclaration.class.getName())); exception.expectMessage(containsString("attribute 'attribute' and its alias 'value' are present with values of [baz] and [bar]")); - exception.expectMessage(endsWith("but only one is permitted.")); synthesizeAnnotation(annotation); } @@ -864,9 +905,9 @@ public class AnnotationUtilsTests { public void synthesizeAnnotationWithAttributeAliasForNonexistentAttribute() throws Exception { AliasForNonexistentAttribute annotation = AliasForNonexistentAttributeClass.class.getAnnotation(AliasForNonexistentAttribute.class); exception.expect(AnnotationConfigurationException.class); - exception.expectMessage(startsWith("Attribute [foo] in")); + exception.expectMessage(startsWith("Attribute 'foo' in")); exception.expectMessage(containsString(AliasForNonexistentAttribute.class.getName())); - exception.expectMessage(containsString("is declared as an @AliasFor nonexistent attribute [bar]")); + exception.expectMessage(containsString("is declared as an @AliasFor nonexistent attribute 'bar'")); synthesizeAnnotation(annotation); } @@ -875,9 +916,9 @@ public class AnnotationUtilsTests { AliasForWithoutMirroredAliasFor annotation = AliasForWithoutMirroredAliasForClass.class.getAnnotation(AliasForWithoutMirroredAliasFor.class); exception.expect(AnnotationConfigurationException.class); - exception.expectMessage(startsWith("Attribute [bar] in")); + exception.expectMessage(startsWith("Attribute 'bar' in")); exception.expectMessage(containsString(AliasForWithoutMirroredAliasFor.class.getName())); - exception.expectMessage(endsWith("must be declared as an @AliasFor [foo].")); + exception.expectMessage(containsString("@AliasFor [foo]")); synthesizeAnnotation(annotation); } @@ -887,10 +928,10 @@ public class AnnotationUtilsTests { AliasForWithMirroredAliasForWrongAttributeClass.class.getAnnotation(AliasForWithMirroredAliasForWrongAttribute.class); exception.expect(AnnotationConfigurationException.class); - exception.expectMessage(startsWith("Attribute [bar] in")); + exception.expectMessage(startsWith("Attribute 'bar' in")); exception.expectMessage(containsString(AliasForWithMirroredAliasForWrongAttribute.class.getName())); exception.expectMessage(either(containsString("must be declared as an @AliasFor [foo], not [quux]")). - or(containsString("is declared as an @AliasFor nonexistent attribute [quux]"))); + or(containsString("is declared as an @AliasFor nonexistent attribute 'quux'"))); synthesizeAnnotation(annotation); } @@ -901,9 +942,9 @@ public class AnnotationUtilsTests { exception.expect(AnnotationConfigurationException.class); exception.expectMessage(startsWith("Misconfigured aliases")); exception.expectMessage(containsString(AliasForAttributeOfDifferentType.class.getName())); - exception.expectMessage(containsString("attribute [foo]")); - exception.expectMessage(containsString("attribute [bar]")); - exception.expectMessage(endsWith("must declare the same return type.")); + exception.expectMessage(containsString("attribute 'foo'")); + exception.expectMessage(containsString("attribute 'bar'")); + exception.expectMessage(containsString("same return type")); synthesizeAnnotation(annotation); } @@ -914,9 +955,9 @@ public class AnnotationUtilsTests { exception.expect(AnnotationConfigurationException.class); exception.expectMessage(startsWith("Misconfigured aliases")); exception.expectMessage(containsString(AliasForWithMissingDefaultValues.class.getName())); - exception.expectMessage(containsString("attribute [foo] in annotation")); - exception.expectMessage(containsString("attribute [bar] in annotation")); - exception.expectMessage(endsWith("must declare default values.")); + exception.expectMessage(containsString("attribute 'foo' in annotation")); + exception.expectMessage(containsString("attribute 'bar' in annotation")); + exception.expectMessage(containsString("default values")); synthesizeAnnotation(annotation); } @@ -927,21 +968,22 @@ public class AnnotationUtilsTests { exception.expect(AnnotationConfigurationException.class); exception.expectMessage(startsWith("Misconfigured aliases")); exception.expectMessage(containsString(AliasForAttributeWithDifferentDefaultValue.class.getName())); - exception.expectMessage(containsString("attribute [foo] in annotation")); - exception.expectMessage(containsString("attribute [bar] in annotation")); - exception.expectMessage(endsWith("must declare the same default value.")); + exception.expectMessage(containsString("attribute 'foo' in annotation")); + exception.expectMessage(containsString("attribute 'bar' in annotation")); + exception.expectMessage(containsString("same default value")); synthesizeAnnotation(annotation); } @Test public void synthesizeAnnotationWithAttributeAliasForMetaAnnotationThatIsNotMetaPresent() throws Exception { - AliasedComposedContextConfigNotMetaPresent annotation = AliasedComposedContextConfigNotMetaPresentClass.class.getAnnotation(AliasedComposedContextConfigNotMetaPresent.class); + AliasedComposedContextConfigNotMetaPresent annotation = + AliasedComposedContextConfigNotMetaPresentClass.class.getAnnotation(AliasedComposedContextConfigNotMetaPresent.class); exception.expect(AnnotationConfigurationException.class); - exception.expectMessage(startsWith("@AliasFor declaration on attribute [xmlConfigFile] in annotation")); + exception.expectMessage(startsWith("@AliasFor declaration on attribute 'xmlConfigFile' in annotation")); exception.expectMessage(containsString(AliasedComposedContextConfigNotMetaPresent.class.getName())); - exception.expectMessage(containsString("declares an alias for attribute [location] in meta-annotation")); + exception.expectMessage(containsString("declares an alias for attribute 'location' in meta-annotation")); exception.expectMessage(containsString(ContextConfig.class.getName())); - exception.expectMessage(endsWith("which is not meta-present.")); + exception.expectMessage(containsString("not meta-present")); synthesizeAnnotation(annotation); } @@ -956,16 +998,16 @@ public class AnnotationUtilsTests { assertNotSame(webMapping, synthesizedWebMapping1); assertEquals("name attribute: ", "foo", synthesizedWebMapping1.name()); - assertEquals("aliased path attribute: ", "/test", synthesizedWebMapping1.path()); - assertEquals("actual value attribute: ", "/test", synthesizedWebMapping1.value()); + assertArrayEquals("aliased path attribute: ", asArray("/test"), synthesizedWebMapping1.path()); + assertArrayEquals("actual value attribute: ", asArray("/test"), synthesizedWebMapping1.value()); WebMapping synthesizedWebMapping2 = synthesizeAnnotation(webMapping); assertThat(synthesizedWebMapping2, instanceOf(SynthesizedAnnotation.class)); assertNotSame(webMapping, synthesizedWebMapping2); assertEquals("name attribute: ", "foo", synthesizedWebMapping2.name()); - assertEquals("aliased path attribute: ", "/test", synthesizedWebMapping2.path()); - assertEquals("actual value attribute: ", "/test", synthesizedWebMapping2.value()); + assertArrayEquals("aliased path attribute: ", asArray("/test"), synthesizedWebMapping2.path()); + assertArrayEquals("actual value attribute: ", asArray("/test"), synthesizedWebMapping2.value()); } @Test @@ -990,6 +1032,31 @@ public class AnnotationUtilsTests { } @Test + public void synthesizeAnnotationWithImplicitAliasesWithImpliedAliasNamesOmitted() throws Exception { + assertAnnotationSynthesisWithImplicitAliasesWithImpliedAliasNamesOmitted( + ValueImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass.class, "value"); + assertAnnotationSynthesisWithImplicitAliasesWithImpliedAliasNamesOmitted( + LocationsImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass.class, "location"); + assertAnnotationSynthesisWithImplicitAliasesWithImpliedAliasNamesOmitted( + XmlFilesImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass.class, "xmlFile"); + } + + private void assertAnnotationSynthesisWithImplicitAliasesWithImpliedAliasNamesOmitted(Class<?> clazz, + String expected) throws Exception { + + ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig config = clazz.getAnnotation( + ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class); + assertNotNull(config); + + ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig synthesizedConfig = synthesizeAnnotation(config); + assertThat(synthesizedConfig, instanceOf(SynthesizedAnnotation.class)); + + assertEquals("value: ", expected, synthesizedConfig.value()); + assertEquals("locations: ", expected, synthesizedConfig.location()); + assertEquals("xmlFiles: ", expected, synthesizedConfig.xmlFile()); + } + + @Test public void synthesizeAnnotationWithImplicitAliasesForAliasPair() throws Exception { Class<?> clazz = ImplicitAliasesForAliasPairContextConfigClass.class; ImplicitAliasesForAliasPairContextConfig config = clazz.getAnnotation(ImplicitAliasesForAliasPairContextConfig.class); @@ -1037,9 +1104,9 @@ public class AnnotationUtilsTests { exception.expect(AnnotationConfigurationException.class); exception.expectMessage(startsWith("Misconfigured aliases:")); - exception.expectMessage(containsString("attribute [location1] in annotation [" + annotationType.getName() + "]")); - exception.expectMessage(containsString("attribute [location2] in annotation [" + annotationType.getName() + "]")); - exception.expectMessage(endsWith("must declare default values.")); + exception.expectMessage(containsString("attribute 'location1' in annotation [" + annotationType.getName() + "]")); + exception.expectMessage(containsString("attribute 'location2' in annotation [" + annotationType.getName() + "]")); + exception.expectMessage(containsString("default values")); synthesizeAnnotation(config, clazz); } @@ -1053,9 +1120,9 @@ public class AnnotationUtilsTests { exception.expect(AnnotationConfigurationException.class); exception.expectMessage(startsWith("Misconfigured aliases:")); - exception.expectMessage(containsString("attribute [location1] in annotation [" + annotationType.getName() + "]")); - exception.expectMessage(containsString("attribute [location2] in annotation [" + annotationType.getName() + "]")); - exception.expectMessage(endsWith("must declare the same default value.")); + exception.expectMessage(containsString("attribute 'location1' in annotation [" + annotationType.getName() + "]")); + exception.expectMessage(containsString("attribute 'location2' in annotation [" + annotationType.getName() + "]")); + exception.expectMessage(containsString("same default value")); synthesizeAnnotation(config, clazz); } @@ -1077,10 +1144,9 @@ public class AnnotationUtilsTests { exception.expectMessage(containsString(clazz.getName())); exception.expectMessage(containsString("and synthesized from")); exception.expectMessage(either(containsString("attribute 'location1' and its alias 'location2'")).or( - containsString("attribute 'location2' and its alias 'location1'"))); + containsString("attribute 'location2' and its alias 'location1'"))); exception.expectMessage(either(containsString("are present with values of [1] and [2]")).or( - containsString("are present with values of [2] and [1]"))); - exception.expectMessage(endsWith("but only one is permitted.")); + containsString("are present with values of [2] and [1]"))); synthesizedConfig.location1(); } @@ -1106,8 +1172,8 @@ public class AnnotationUtilsTests { assertNotNull(componentScan); assertEquals("value from ComponentScan: ", "*Foo", componentScan.value().pattern()); - AnnotationAttributes attributes = getAnnotationAttributes(ComponentScanSingleFilterClass.class, componentScan, - false, true); + AnnotationAttributes attributes = getAnnotationAttributes( + ComponentScanSingleFilterClass.class, componentScan, false, true); assertNotNull(attributes); assertEquals(ComponentScanSingleFilter.class, attributes.annotationType()); @@ -1119,8 +1185,8 @@ public class AnnotationUtilsTests { filterMap.put("pattern", "newFoo"); filterMap.put("enigma", 42); - ComponentScanSingleFilter synthesizedComponentScan = synthesizeAnnotation(attributes, - ComponentScanSingleFilter.class, ComponentScanSingleFilterClass.class); + ComponentScanSingleFilter synthesizedComponentScan = synthesizeAnnotation( + attributes, ComponentScanSingleFilter.class, ComponentScanSingleFilterClass.class); assertNotNull(synthesizedComponentScan); assertNotSame(componentScan, synthesizedComponentScan); @@ -1184,6 +1250,21 @@ public class AnnotationUtilsTests { } @Test + public void synthesizeAnnotationFromMapWithAttributeAliasesThatOverrideArraysWithSingleElements() throws Exception { + Map<String, Object> map = Collections.singletonMap("value", "/foo"); + Get get = synthesizeAnnotation(map, Get.class, null); + assertNotNull(get); + assertEquals("value: ", "/foo", get.value()); + assertEquals("path: ", "/foo", get.path()); + + map = Collections.singletonMap("path", "/foo"); + get = synthesizeAnnotation(map, Get.class, null); + assertNotNull(get); + assertEquals("value: ", "/foo", get.value()); + assertEquals("path: ", "/foo", get.path()); + } + + @Test public void synthesizeAnnotationFromMapWithImplicitAttributeAliases() throws Exception { assertAnnotationSynthesisFromMapWithImplicitAliases("value"); assertAnnotationSynthesisFromMapWithImplicitAliases("location1"); @@ -1220,7 +1301,7 @@ public class AnnotationUtilsTests { private void assertMissingTextAttribute(Map<String, Object> attributes) { exception.expect(IllegalArgumentException.class); exception.expectMessage(startsWith("Attributes map")); - exception.expectMessage(containsString("returned null for required attribute [text]")); + exception.expectMessage(containsString("returned null for required attribute 'text'")); exception.expectMessage(containsString("defined by annotation type [" + AnnotationWithoutDefaults.class.getName() + "]")); synthesizeAnnotation(attributes, AnnotationWithoutDefaults.class, null); } @@ -1232,15 +1313,15 @@ public class AnnotationUtilsTests { exception.expect(IllegalArgumentException.class); exception.expectMessage(startsWith("Attributes map")); exception.expectMessage(containsString("returned a value of type [java.lang.Long]")); - exception.expectMessage(containsString("for attribute [value]")); + exception.expectMessage(containsString("for attribute 'value'")); exception.expectMessage(containsString("but a value of type [java.lang.String] is required")); exception.expectMessage(containsString("as defined by annotation type [" + Component.class.getName() + "]")); + synthesizeAnnotation(map, Component.class, null); } @Test public void synthesizeAnnotationFromAnnotationAttributesWithoutAttributeAliases() throws Exception { - // 1) Get an annotation Component component = WebController.class.getAnnotation(Component.class); assertNotNull(component); @@ -1288,8 +1369,8 @@ public class AnnotationUtilsTests { private void assertToStringForWebMappingWithPathAndValue(WebMapping webMapping) { String string = webMapping.toString(); assertThat(string, startsWith("@" + WebMapping.class.getName() + "(")); - assertThat(string, containsString("value=/test")); - assertThat(string, containsString("path=/test")); + assertThat(string, containsString("value=[/test]")); + assertThat(string, containsString("path=[/test]")); assertThat(string, containsString("name=bar")); assertThat(string, containsString("method=")); assertThat(string, containsString("[GET, POST]")); @@ -1410,7 +1491,7 @@ public class AnnotationUtilsTests { ContextConfig[] configs = synthesizedHierarchy.value(); assertNotNull(configs); assertTrue("nested annotations must be synthesized", - stream(configs).allMatch(c -> c instanceof SynthesizedAnnotation)); + stream(configs).allMatch(c -> c instanceof SynthesizedAnnotation)); List<String> locations = stream(configs).map(ContextConfig::location).collect(toList()); assertThat(locations, is(expectedLocations)); @@ -1462,6 +1543,18 @@ public class AnnotationUtilsTests { assertArrayEquals(new char[] { 'x', 'y', 'z' }, chars); } + @SafeVarargs + // The following "varargs" suppression is necessary for javac from OpenJDK + // (1.8.0_60-b27); however, Eclipse warns that it's unnecessary. See the following + // Eclipse issues for details. + // + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=344783 + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=349669#c10 + // @SuppressWarnings("varargs") + static <T> T[] asArray(T... arr) { + return arr; + } + @Component("meta1") @Order @@ -1769,14 +1862,40 @@ public class AnnotationUtilsTests { String name(); @AliasFor("path") - String value() default ""; + String[] value() default ""; @AliasFor(attribute = "value") - String path() default ""; + String[] path() default ""; RequestMethod[] method() default {}; } + /** + * Mock of {@code org.springframework.web.bind.annotation.GetMapping}, except + * that the String arrays are overridden with single String elements. + */ + @Retention(RetentionPolicy.RUNTIME) + @WebMapping(method = RequestMethod.GET, name = "") + @interface Get { + + @AliasFor(annotation = WebMapping.class) + String value() default ""; + + @AliasFor(annotation = WebMapping.class) + String path() default ""; + } + + /** + * Mock of {@code org.springframework.web.bind.annotation.PostMapping}, except + * that the path is overridden by convention with single String element. + */ + @Retention(RetentionPolicy.RUNTIME) + @WebMapping(method = RequestMethod.POST, name = "") + @interface Post { + + String path() default ""; + } + @Component("webController") static class WebController { @@ -1788,6 +1907,18 @@ public class AnnotationUtilsTests { public void handleMappedWithPathAttribute() { } + @Get("/test") + public void getMappedWithValueAttribute() { + } + + @Get(path = "/test") + public void getMappedWithPathAttribute() { + } + + @Post(path = "/test") + public void postMappedWithPathAttribute() { + } + /** * mapping is logically "equal" to handleMappedWithPathAttribute(). */ @@ -1990,12 +2121,12 @@ public class AnnotationUtilsTests { @ContextConfig @Retention(RetentionPolicy.RUNTIME) - @interface ImplicitAliasesContextConfig { + public @interface ImplicitAliasesContextConfig { @AliasFor(annotation = ContextConfig.class, attribute = "location") String xmlFile() default ""; - @AliasFor(annotation = ContextConfig.class, value = "location") + @AliasFor(annotation = ContextConfig.class, attribute = "location") String groovyScript() default ""; @AliasFor(annotation = ContextConfig.class, attribute = "location") @@ -2048,6 +2179,48 @@ public class AnnotationUtilsTests { @ContextConfig @Retention(RetentionPolicy.RUNTIME) + @interface ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig { + + // intentionally omitted: attribute = "value" + @AliasFor(annotation = ContextConfig.class) + String value() default ""; + + // intentionally omitted: attribute = "locations" + @AliasFor(annotation = ContextConfig.class) + String location() default ""; + + @AliasFor(annotation = ContextConfig.class, attribute = "location") + String xmlFile() default ""; + } + + @ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig + @Retention(RetentionPolicy.RUNTIME) + @interface TransitiveImplicitAliasesWithImpliedAliasNamesOmittedContextConfig { + + @AliasFor(annotation = ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class, attribute = "xmlFile") + String xml() default ""; + + @AliasFor(annotation = ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class, attribute = "location") + String groovy() default ""; + } + + // Attribute value intentionally matches attribute name: + @ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig("value") + static class ValueImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass { + } + + // Attribute value intentionally matches attribute name: + @ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig(location = "location") + static class LocationsImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass { + } + + // Attribute value intentionally matches attribute name: + @ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig(xmlFile = "xmlFile") + static class XmlFilesImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass { + } + + @ContextConfig + @Retention(RetentionPolicy.RUNTIME) @interface ImplicitAliasesWithMissingDefaultValuesContextConfig { @AliasFor(annotation = ContextConfig.class, attribute = "location") diff --git a/spring-core/src/test/java/org/springframework/core/annotation/ComposedRepeatableAnnotationsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/ComposedRepeatableAnnotationsTests.java new file mode 100644 index 00000000..5cbc10d9 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/annotation/ComposedRepeatableAnnotationsTests.java @@ -0,0 +1,386 @@ +/* + * Copyright 2002-2016 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.AnnotatedElement; +import java.util.Iterator; +import java.util.Set; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.hamcrest.CoreMatchers.isA; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.*; +import static org.springframework.core.annotation.AnnotatedElementUtils.*; + +/** + * Unit tests that verify support for getting and finding all composed, repeatable + * annotations on a single annotated element. + * + * <p>See <a href="https://jira.spring.io/browse/SPR-13973">SPR-13973</a>. + * + * @author Sam Brannen + * @since 4.3 + * @see AnnotatedElementUtils#getMergedRepeatableAnnotations + * @see AnnotatedElementUtils#findMergedRepeatableAnnotations + * @see AnnotatedElementUtilsTests + * @see MultipleComposedAnnotationsOnSingleAnnotatedElementTests + */ +public class ComposedRepeatableAnnotationsTests { + + @Rule + public final ExpectedException exception = ExpectedException.none(); + + + @Test + public void getNonRepeatableAnnotation() { + expectNonRepeatableAnnotation(); + getMergedRepeatableAnnotations(getClass(), NonRepeatable.class); + } + + @Test + public void getInvalidRepeatableAnnotationContainerMissingValueAttribute() { + expectContainerMissingValueAttribute(); + getMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class, ContainerMissingValueAttribute.class); + } + + @Test + public void getInvalidRepeatableAnnotationContainerWithNonArrayValueAttribute() { + expectContainerWithNonArrayValueAttribute(); + getMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class, ContainerWithNonArrayValueAttribute.class); + } + + @Test + public void getInvalidRepeatableAnnotationContainerWithArrayValueAttributeButWrongComponentType() { + expectContainerWithArrayValueAttributeButWrongComponentType(); + getMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class, + ContainerWithArrayValueAttributeButWrongComponentType.class); + } + + @Test + public void getRepeatableAnnotationsOnClass() { + assertGetRepeatableAnnotations(RepeatableClass.class); + } + + @Test + public void getRepeatableAnnotationsOnSuperclass() { + assertGetRepeatableAnnotations(SubRepeatableClass.class); + } + + @Test + public void getComposedRepeatableAnnotationsOnClass() { + assertGetRepeatableAnnotations(ComposedRepeatableClass.class); + } + + @Test + public void getComposedRepeatableAnnotationsMixedWithContainerOnClass() { + assertGetRepeatableAnnotations(ComposedRepeatableMixedWithContainerClass.class); + } + + @Test + public void getComposedContainerForRepeatableAnnotationsOnClass() { + assertGetRepeatableAnnotations(ComposedContainerClass.class); + } + + @Test + public void getNoninheritedComposedRepeatableAnnotationsOnClass() { + Class<?> element = NoninheritedRepeatableClass.class; + Set<Noninherited> annotations = getMergedRepeatableAnnotations(element, Noninherited.class); + assertNoninheritedRepeatableAnnotations(annotations); + } + + @Test + public void getNoninheritedComposedRepeatableAnnotationsOnSuperclass() { + Class<?> element = SubNoninheritedRepeatableClass.class; + Set<Noninherited> annotations = getMergedRepeatableAnnotations(element, Noninherited.class); + assertNotNull(annotations); + assertEquals(0, annotations.size()); + } + + @Test + public void findNonRepeatableAnnotation() { + expectNonRepeatableAnnotation(); + findMergedRepeatableAnnotations(getClass(), NonRepeatable.class); + } + + @Test + public void findInvalidRepeatableAnnotationContainerMissingValueAttribute() { + expectContainerMissingValueAttribute(); + findMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class, ContainerMissingValueAttribute.class); + } + + @Test + public void findInvalidRepeatableAnnotationContainerWithNonArrayValueAttribute() { + expectContainerWithNonArrayValueAttribute(); + findMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class, ContainerWithNonArrayValueAttribute.class); + } + + @Test + public void findInvalidRepeatableAnnotationContainerWithArrayValueAttributeButWrongComponentType() { + expectContainerWithArrayValueAttributeButWrongComponentType(); + findMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class, + ContainerWithArrayValueAttributeButWrongComponentType.class); + } + + @Test + public void findRepeatableAnnotationsOnClass() { + assertFindRepeatableAnnotations(RepeatableClass.class); + } + + @Test + public void findRepeatableAnnotationsOnSuperclass() { + assertFindRepeatableAnnotations(SubRepeatableClass.class); + } + + @Test + public void findComposedRepeatableAnnotationsOnClass() { + assertFindRepeatableAnnotations(ComposedRepeatableClass.class); + } + + @Test + public void findComposedRepeatableAnnotationsMixedWithContainerOnClass() { + assertFindRepeatableAnnotations(ComposedRepeatableMixedWithContainerClass.class); + } + + @Test + public void findNoninheritedComposedRepeatableAnnotationsOnClass() { + Class<?> element = NoninheritedRepeatableClass.class; + Set<Noninherited> annotations = findMergedRepeatableAnnotations(element, Noninherited.class); + assertNoninheritedRepeatableAnnotations(annotations); + } + + @Test + public void findNoninheritedComposedRepeatableAnnotationsOnSuperclass() { + Class<?> element = SubNoninheritedRepeatableClass.class; + Set<Noninherited> annotations = findMergedRepeatableAnnotations(element, Noninherited.class); + assertNoninheritedRepeatableAnnotations(annotations); + } + + @Test + public void findComposedContainerForRepeatableAnnotationsOnClass() { + assertFindRepeatableAnnotations(ComposedContainerClass.class); + } + + private void expectNonRepeatableAnnotation() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage(startsWith("annotationType must be a repeatable annotation")); + exception.expectMessage(containsString("failed to resolve container type for")); + exception.expectMessage(containsString(NonRepeatable.class.getName())); + } + + private void expectContainerMissingValueAttribute() { + exception.expect(AnnotationConfigurationException.class); + exception.expectMessage(startsWith("Invalid declaration of container type")); + exception.expectMessage(containsString(ContainerMissingValueAttribute.class.getName())); + exception.expectMessage(containsString("for repeatable annotation")); + exception.expectMessage(containsString(InvalidRepeatable.class.getName())); + exception.expectCause(isA(NoSuchMethodException.class)); + } + + private void expectContainerWithNonArrayValueAttribute() { + exception.expect(AnnotationConfigurationException.class); + exception.expectMessage(startsWith("Container type")); + exception.expectMessage(containsString(ContainerWithNonArrayValueAttribute.class.getName())); + exception.expectMessage(containsString("must declare a 'value' attribute for an array of type")); + exception.expectMessage(containsString(InvalidRepeatable.class.getName())); + } + + private void expectContainerWithArrayValueAttributeButWrongComponentType() { + exception.expect(AnnotationConfigurationException.class); + exception.expectMessage(startsWith("Container type")); + exception.expectMessage(containsString(ContainerWithArrayValueAttributeButWrongComponentType.class.getName())); + exception.expectMessage(containsString("must declare a 'value' attribute for an array of type")); + exception.expectMessage(containsString(InvalidRepeatable.class.getName())); + } + + private void assertGetRepeatableAnnotations(AnnotatedElement element) { + assertNotNull(element); + + Set<PeteRepeat> peteRepeats = getMergedRepeatableAnnotations(element, PeteRepeat.class); + assertNotNull(peteRepeats); + assertEquals(3, peteRepeats.size()); + + Iterator<PeteRepeat> iterator = peteRepeats.iterator(); + assertEquals("A", iterator.next().value()); + assertEquals("B", iterator.next().value()); + assertEquals("C", iterator.next().value()); + } + + private void assertFindRepeatableAnnotations(AnnotatedElement element) { + assertNotNull(element); + + Set<PeteRepeat> peteRepeats = findMergedRepeatableAnnotations(element, PeteRepeat.class); + assertNotNull(peteRepeats); + assertEquals(3, peteRepeats.size()); + + Iterator<PeteRepeat> iterator = peteRepeats.iterator(); + assertEquals("A", iterator.next().value()); + assertEquals("B", iterator.next().value()); + assertEquals("C", iterator.next().value()); + } + + private void assertNoninheritedRepeatableAnnotations(Set<Noninherited> annotations) { + assertNotNull(annotations); + assertEquals(3, annotations.size()); + + Iterator<Noninherited> iterator = annotations.iterator(); + assertEquals("A", iterator.next().value()); + assertEquals("B", iterator.next().value()); + assertEquals("C", iterator.next().value()); + } + + + // ------------------------------------------------------------------------- + + @Retention(RetentionPolicy.RUNTIME) + @interface NonRepeatable { + } + + @Retention(RetentionPolicy.RUNTIME) + @interface ContainerMissingValueAttribute { + // InvalidRepeatable[] value(); + } + + @Retention(RetentionPolicy.RUNTIME) + @interface ContainerWithNonArrayValueAttribute { + + InvalidRepeatable value(); + } + + @Retention(RetentionPolicy.RUNTIME) + @interface ContainerWithArrayValueAttributeButWrongComponentType { + + String[] value(); + } + + @Retention(RetentionPolicy.RUNTIME) + @interface InvalidRepeatable { + } + + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @interface PeteRepeats { + + PeteRepeat[] value(); + } + + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @Repeatable(PeteRepeats.class) + @interface PeteRepeat { + + String value(); + } + + @PeteRepeat("shadowed") + @Target({ ElementType.METHOD, ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @interface ForPetesSake { + + @AliasFor(annotation = PeteRepeat.class) + String value(); + } + + @PeteRepeat("shadowed") + @Target({ ElementType.METHOD, ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @interface ForTheLoveOfFoo { + + @AliasFor(annotation = PeteRepeat.class) + String value(); + } + + @PeteRepeats({ @PeteRepeat("B"), @PeteRepeat("C") }) + @Target({ ElementType.METHOD, ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @interface ComposedContainer { + } + + @PeteRepeat("A") + @PeteRepeats({ @PeteRepeat("B"), @PeteRepeat("C") }) + static class RepeatableClass { + } + + static class SubRepeatableClass extends RepeatableClass { + } + + @ForPetesSake("B") + @ForTheLoveOfFoo("C") + @PeteRepeat("A") + static class ComposedRepeatableClass { + } + + @ForPetesSake("C") + @PeteRepeats(@PeteRepeat("A")) + @PeteRepeat("B") + static class ComposedRepeatableMixedWithContainerClass { + } + + @PeteRepeat("A") + @ComposedContainer + static class ComposedContainerClass { + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @interface Noninheriteds { + + Noninherited[] value(); + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @Repeatable(Noninheriteds.class) + @interface Noninherited { + + @AliasFor("name") + String value() default ""; + + @AliasFor("value") + String name() default ""; + } + + @Noninherited(name = "shadowed") + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @interface ComposedNoninherited { + + @AliasFor(annotation = Noninherited.class) + String name() default ""; + } + + @ComposedNoninherited(name = "C") + @Noninheriteds({ @Noninherited(value = "A"), @Noninherited(name = "B") }) + static class NoninheritedRepeatableClass { + } + + static class SubNoninheritedRepeatableClass extends NoninheritedRepeatableClass { + } + +} diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MapAnnotationAttributeExtractorTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MapAnnotationAttributeExtractorTests.java index a3811527..d3b45602 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/MapAnnotationAttributeExtractorTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/MapAnnotationAttributeExtractorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -26,11 +26,14 @@ import org.junit.Before; import org.junit.Test; import org.springframework.core.annotation.AnnotationUtilsTests.ImplicitAliasesContextConfig; +import org.springframework.core.annotation.AnnotationUtilsTests.RequestMethod; +import org.springframework.core.annotation.AnnotationUtilsTests.WebMapping; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import static org.springframework.core.annotation.AnnotationUtilsTests.*; /** * Unit tests for {@link MapAnnotationAttributeExtractor}. @@ -38,6 +41,7 @@ import static org.junit.Assert.*; * @author Sam Brannen * @since 4.2.1 */ +@SuppressWarnings("serial") public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnnotationAttributeExtractorTestCase { @Before @@ -46,7 +50,6 @@ public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnno } @Test - @SuppressWarnings("serial") public void enrichAndValidateAttributesWithImplicitAliasesAndMinimalAttributes() { Map<String, Object> attributes = new HashMap<String, Object>(); Map<String, Object> expectedAttributes = new HashMap<String, Object>() {{ @@ -64,7 +67,6 @@ public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnno } @Test - @SuppressWarnings("serial") public void enrichAndValidateAttributesWithImplicitAliases() { Map<String, Object> attributes = new HashMap<String, Object>() {{ put("groovyScript", "groovy!"); @@ -84,6 +86,31 @@ public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnno assertEnrichAndValidateAttributes(attributes, expectedAttributes); } + @Test + public void enrichAndValidateAttributesWithSingleElementThatOverridesAnArray() { + // @formatter:off + Map<String, Object> attributes = new HashMap<String, Object>() {{ + // Intentionally storing 'value' as a single String instead of an array. + // put("value", asArray("/foo")); + put("value", "/foo"); + put("name", "test"); + }}; + + Map<String, Object> expected = new HashMap<String, Object>() {{ + put("value", asArray("/foo")); + put("path", asArray("/foo")); + put("name", "test"); + put("method", new RequestMethod[0]); + }}; + // @formatter:on + + MapAnnotationAttributeExtractor extractor = new MapAnnotationAttributeExtractor(attributes, WebMapping.class, null); + Map<String, Object> enriched = extractor.getSource(); + + assertEquals("attribute map size", expected.size(), enriched.size()); + expected.forEach((attr, expectedValue) -> assertThat("for attribute '" + attr + "'", enriched.get(attr), is(expectedValue))); + } + @SuppressWarnings("unchecked") private void assertEnrichAndValidateAttributes(Map<String, Object> sourceAttributes, Map<String, Object> expected) { Class<? extends Annotation> annotationType = ImplicitAliasesContextConfig.class; @@ -114,8 +141,7 @@ public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnno Map<String, Object> enriched = extractor.getSource(); assertEquals("attribute map size", expected.size(), enriched.size()); - expected.keySet().stream().forEach( attr -> - assertThat("for attribute '" + attr + "'", enriched.get(attr), is(expected.get(attr)))); + expected.forEach((attr, expectedValue) -> assertThat("for attribute '" + attr + "'", enriched.get(attr), is(expectedValue))); } @Override diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MultipleComposedAnnotationsOnSingleAnnotatedElementTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MultipleComposedAnnotationsOnSingleAnnotatedElementTests.java new file mode 100644 index 00000000..fc2259ac --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/annotation/MultipleComposedAnnotationsOnSingleAnnotatedElementTests.java @@ -0,0 +1,368 @@ +/* + * Copyright 2002-2016 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.Iterator; +import java.util.Set; + +import org.junit.Ignore; +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.springframework.core.annotation.AnnotatedElementUtils.*; + +/** + * Unit tests that verify support for finding multiple composed annotations on + * a single annotated element. + * + * <p>See <a href="https://jira.spring.io/browse/SPR-13486">SPR-13486</a>. + * + * @author Sam Brannen + * @since 4.3 + * @see AnnotatedElementUtils + * @see AnnotatedElementUtilsTests + * @see ComposedRepeatableAnnotationsTests + */ +public class MultipleComposedAnnotationsOnSingleAnnotatedElementTests { + + @Test + public void getMultipleComposedAnnotationsOnClass() { + assertGetAllMergedAnnotationsBehavior(MultipleComposedCachesClass.class); + } + + @Test + public void getMultipleInheritedComposedAnnotationsOnSuperclass() { + assertGetAllMergedAnnotationsBehavior(SubMultipleComposedCachesClass.class); + } + + @Test + public void getMultipleNoninheritedComposedAnnotationsOnClass() { + Class<?> element = MultipleNoninheritedComposedCachesClass.class; + Set<Cacheable> cacheables = getAllMergedAnnotations(element, Cacheable.class); + assertNotNull(cacheables); + assertEquals(2, cacheables.size()); + + Iterator<Cacheable> iterator = cacheables.iterator(); + Cacheable cacheable1 = iterator.next(); + Cacheable cacheable2 = iterator.next(); + assertEquals("noninheritedCache1", cacheable1.value()); + assertEquals("noninheritedCache2", cacheable2.value()); + } + + @Test + public void getMultipleNoninheritedComposedAnnotationsOnSuperclass() { + Class<?> element = SubMultipleNoninheritedComposedCachesClass.class; + Set<Cacheable> cacheables = getAllMergedAnnotations(element, Cacheable.class); + assertNotNull(cacheables); + assertEquals(0, cacheables.size()); + } + + @Test + public void getComposedPlusLocalAnnotationsOnClass() { + assertGetAllMergedAnnotationsBehavior(ComposedPlusLocalCachesClass.class); + } + + @Test + public void getMultipleComposedAnnotationsOnInterface() { + Class<MultipleComposedCachesOnInterfaceClass> element = MultipleComposedCachesOnInterfaceClass.class; + Set<Cacheable> cacheables = getAllMergedAnnotations(element, Cacheable.class); + assertNotNull(cacheables); + assertEquals(0, cacheables.size()); + } + + @Test + public void getMultipleComposedAnnotationsOnMethod() throws Exception { + AnnotatedElement element = getClass().getDeclaredMethod("multipleComposedCachesMethod"); + assertGetAllMergedAnnotationsBehavior(element); + } + + @Test + public void getComposedPlusLocalAnnotationsOnMethod() throws Exception { + AnnotatedElement element = getClass().getDeclaredMethod("composedPlusLocalCachesMethod"); + assertGetAllMergedAnnotationsBehavior(element); + } + + @Test + @Ignore("Disabled since some Java 8 updates handle the bridge method differently") + public void getMultipleComposedAnnotationsOnBridgeMethod() throws Exception { + Set<Cacheable> cacheables = getAllMergedAnnotations(getBridgeMethod(), Cacheable.class); + assertNotNull(cacheables); + assertEquals(0, cacheables.size()); + } + + @Test + public void findMultipleComposedAnnotationsOnClass() { + assertFindAllMergedAnnotationsBehavior(MultipleComposedCachesClass.class); + } + + @Test + public void findMultipleInheritedComposedAnnotationsOnSuperclass() { + assertFindAllMergedAnnotationsBehavior(SubMultipleComposedCachesClass.class); + } + + @Test + public void findMultipleNoninheritedComposedAnnotationsOnClass() { + Class<?> element = MultipleNoninheritedComposedCachesClass.class; + Set<Cacheable> cacheables = findAllMergedAnnotations(element, Cacheable.class); + assertNotNull(cacheables); + assertEquals(2, cacheables.size()); + + Iterator<Cacheable> iterator = cacheables.iterator(); + Cacheable cacheable1 = iterator.next(); + Cacheable cacheable2 = iterator.next(); + assertEquals("noninheritedCache1", cacheable1.value()); + assertEquals("noninheritedCache2", cacheable2.value()); + } + + @Test + public void findMultipleNoninheritedComposedAnnotationsOnSuperclass() { + Class<?> element = SubMultipleNoninheritedComposedCachesClass.class; + Set<Cacheable> cacheables = findAllMergedAnnotations(element, Cacheable.class); + assertNotNull(cacheables); + assertEquals(2, cacheables.size()); + + Iterator<Cacheable> iterator = cacheables.iterator(); + Cacheable cacheable1 = iterator.next(); + Cacheable cacheable2 = iterator.next(); + assertEquals("noninheritedCache1", cacheable1.value()); + assertEquals("noninheritedCache2", cacheable2.value()); + } + + @Test + public void findComposedPlusLocalAnnotationsOnClass() { + assertFindAllMergedAnnotationsBehavior(ComposedPlusLocalCachesClass.class); + } + + @Test + public void findMultipleComposedAnnotationsOnInterface() { + assertFindAllMergedAnnotationsBehavior(MultipleComposedCachesOnInterfaceClass.class); + } + + @Test + public void findComposedCacheOnInterfaceAndLocalCacheOnClass() { + assertFindAllMergedAnnotationsBehavior(ComposedCacheOnInterfaceAndLocalCacheClass.class); + } + + @Test + public void findMultipleComposedAnnotationsOnMethod() throws Exception { + AnnotatedElement element = getClass().getDeclaredMethod("multipleComposedCachesMethod"); + assertFindAllMergedAnnotationsBehavior(element); + } + + @Test + public void findComposedPlusLocalAnnotationsOnMethod() throws Exception { + AnnotatedElement element = getClass().getDeclaredMethod("composedPlusLocalCachesMethod"); + assertFindAllMergedAnnotationsBehavior(element); + } + + @Test + public void findMultipleComposedAnnotationsOnBridgeMethod() throws Exception { + assertFindAllMergedAnnotationsBehavior(getBridgeMethod()); + } + + /** + * Bridge/bridged method setup code copied from + * {@link org.springframework.core.BridgeMethodResolverTests#testWithGenericParameter()}. + */ + public Method getBridgeMethod() throws NoSuchMethodException { + Method[] methods = StringGenericParameter.class.getMethods(); + Method bridgeMethod = null; + Method bridgedMethod = null; + + for (Method method : methods) { + if ("getFor".equals(method.getName()) && !method.getParameterTypes()[0].equals(Integer.class)) { + if (method.getReturnType().equals(Object.class)) { + bridgeMethod = method; + } + else { + bridgedMethod = method; + } + } + } + assertTrue(bridgeMethod != null && bridgeMethod.isBridge()); + assertTrue(bridgedMethod != null && !bridgedMethod.isBridge()); + + return bridgeMethod; + } + + private void assertGetAllMergedAnnotationsBehavior(AnnotatedElement element) { + assertNotNull(element); + + Set<Cacheable> cacheables = getAllMergedAnnotations(element, Cacheable.class); + assertNotNull(cacheables); + assertEquals(2, cacheables.size()); + + Iterator<Cacheable> iterator = cacheables.iterator(); + Cacheable fooCacheable = iterator.next(); + Cacheable barCacheable = iterator.next(); + assertEquals("fooKey", fooCacheable.key()); + assertEquals("fooCache", fooCacheable.value()); + assertEquals("barKey", barCacheable.key()); + assertEquals("barCache", barCacheable.value()); + } + + private void assertFindAllMergedAnnotationsBehavior(AnnotatedElement element) { + assertNotNull(element); + + Set<Cacheable> cacheables = findAllMergedAnnotations(element, Cacheable.class); + assertNotNull(cacheables); + assertEquals(2, cacheables.size()); + + Iterator<Cacheable> iterator = cacheables.iterator(); + Cacheable fooCacheable = iterator.next(); + Cacheable barCacheable = iterator.next(); + assertEquals("fooKey", fooCacheable.key()); + assertEquals("fooCache", fooCacheable.value()); + assertEquals("barKey", barCacheable.key()); + assertEquals("barCache", barCacheable.value()); + } + + + // ------------------------------------------------------------------------- + + /** + * Mock of {@code org.springframework.cache.annotation.Cacheable}. + */ + @Target({ ElementType.METHOD, ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @interface Cacheable { + + @AliasFor("cacheName") + String value() default ""; + + @AliasFor("value") + String cacheName() default ""; + + String key() default ""; + } + + @Cacheable("fooCache") + @Target({ ElementType.METHOD, ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @interface FooCache { + + @AliasFor(annotation = Cacheable.class) + String key() default ""; + } + + @Cacheable("barCache") + @Target({ ElementType.METHOD, ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @Inherited + @interface BarCache { + + @AliasFor(annotation = Cacheable.class) + String key(); + } + + @Cacheable("noninheritedCache1") + @Target({ ElementType.METHOD, ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @interface NoninheritedCache1 { + + @AliasFor(annotation = Cacheable.class) + String key() default ""; + } + + @Cacheable("noninheritedCache2") + @Target({ ElementType.METHOD, ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @interface NoninheritedCache2 { + + @AliasFor(annotation = Cacheable.class) + String key() default ""; + } + + @FooCache(key = "fooKey") + @BarCache(key = "barKey") + private static class MultipleComposedCachesClass { + } + + private static class SubMultipleComposedCachesClass extends MultipleComposedCachesClass { + } + + @NoninheritedCache1 + @NoninheritedCache2 + private static class MultipleNoninheritedComposedCachesClass { + } + + private static class SubMultipleNoninheritedComposedCachesClass extends MultipleNoninheritedComposedCachesClass { + } + + @Cacheable(cacheName = "fooCache", key = "fooKey") + @BarCache(key = "barKey") + private static class ComposedPlusLocalCachesClass { + } + + @FooCache(key = "fooKey") + @BarCache(key = "barKey") + private interface MultipleComposedCachesInterface { + } + + private static class MultipleComposedCachesOnInterfaceClass implements MultipleComposedCachesInterface { + } + + @Cacheable(cacheName = "fooCache", key = "fooKey") + private interface ComposedCacheInterface { + } + + @BarCache(key = "barKey") + private static class ComposedCacheOnInterfaceAndLocalCacheClass implements ComposedCacheInterface { + } + + + @FooCache(key = "fooKey") + @BarCache(key = "barKey") + private void multipleComposedCachesMethod() { + } + + @Cacheable(cacheName = "fooCache", key = "fooKey") + @BarCache(key = "barKey") + private void composedPlusLocalCachesMethod() { + } + + + public interface GenericParameter<T> { + + T getFor(Class<T> cls); + } + + @SuppressWarnings("unused") + private static class StringGenericParameter implements GenericParameter<String> { + + @FooCache(key = "fooKey") + @BarCache(key = "barKey") + @Override + public String getFor(Class<String> cls) { + return "foo"; + } + + public String getFor(Integer integer) { + return "foo"; + } + } + +} diff --git a/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java b/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java index 61f30c31..39184c2c 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java @@ -52,27 +52,11 @@ import static org.junit.Assert.*; * @author Andy Clement * @author Phillip Webb * @author Sam Brannen + * @author Nathan Piper */ @SuppressWarnings("rawtypes") public class TypeDescriptorTests { - public List<String> listOfString; - - public List<List<String>> listOfListOfString = new ArrayList<List<String>>(); - - public List<List> listOfListOfUnknown = new ArrayList<List>(); - - public int[] intArray; - - public List<String>[] arrayOfListOfString; - - public List<Integer> listField = new ArrayList<Integer>(); - - public Map<String, Integer> mapField = new HashMap<String, Integer>(); - - public Map<String, List<Integer>> nestedMapField = new HashMap<String, List<Integer>>(); - - @Test public void parameterPrimitive() throws Exception { TypeDescriptor desc = new TypeDescriptor(new MethodParameter(getClass().getMethod("testParameterPrimitive", int.class), 0)); @@ -86,10 +70,6 @@ public class TypeDescriptorTests { assertFalse(desc.isMap()); } - public void testParameterPrimitive(int primitive) { - - } - @Test public void parameterScalar() throws Exception { TypeDescriptor desc = new TypeDescriptor(new MethodParameter(getClass().getMethod("testParameterScalar", String.class), 0)); @@ -104,10 +84,6 @@ public class TypeDescriptorTests { assertFalse(desc.isMap()); } - public void testParameterScalar(String value) { - - } - @Test public void parameterList() throws Exception { MethodParameter methodParameter = new MethodParameter(getClass().getMethod("testParameterList", List.class), 0); @@ -129,10 +105,6 @@ public class TypeDescriptorTests { assertFalse(desc.isMap()); } - public void testParameterList(List<List<Map<Integer, Enum<?>>>> list) { - - } - @Test public void parameterListNoParamTypes() throws Exception { MethodParameter methodParameter = new MethodParameter(getClass().getMethod("testParameterListNoParamTypes", List.class), 0); @@ -149,10 +121,6 @@ public class TypeDescriptorTests { assertFalse(desc.isMap()); } - public void testParameterListNoParamTypes(List list) { - - } - @Test public void parameterArray() throws Exception { MethodParameter methodParameter = new MethodParameter(getClass().getMethod("testParameterArray", Integer[].class), 0); @@ -170,10 +138,6 @@ public class TypeDescriptorTests { assertFalse(desc.isMap()); } - public void testParameterArray(Integer[] array) { - - } - @Test public void parameterMap() throws Exception { MethodParameter methodParameter = new MethodParameter(getClass().getMethod("testParameterMap", Map.class), 0); @@ -194,10 +158,6 @@ public class TypeDescriptorTests { assertEquals(String.class, desc.getMapValueTypeDescriptor().getElementTypeDescriptor().getType()); } - public void testParameterMap(Map<Integer, List<String>> map) { - - } - @Test public void parameterAnnotated() throws Exception { TypeDescriptor t1 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0)); @@ -208,36 +168,20 @@ public class TypeDescriptorTests { assertEquals(123, t1.getAnnotation(ParameterAnnotation.class).value()); } - @Target({ElementType.PARAMETER}) - @Retention(RetentionPolicy.RUNTIME) - public @interface ParameterAnnotation { - int value(); - } - - public void testAnnotatedMethod(@ParameterAnnotation(123) String parameter) { - - } - @Test public void propertyComplex() throws Exception { - Property property = new Property(getClass(), getClass().getMethod("getComplexProperty"), getClass().getMethod("setComplexProperty", Map.class)); + Property property = new Property(getClass(), getClass().getMethod("getComplexProperty"), + getClass().getMethod("setComplexProperty", Map.class)); TypeDescriptor desc = new TypeDescriptor(property); assertEquals(String.class, desc.getMapKeyTypeDescriptor().getType()); assertEquals(Integer.class, desc.getMapValueTypeDescriptor().getElementTypeDescriptor().getElementTypeDescriptor().getType()); } - public Map<String, List<List<Integer>>> getComplexProperty() { - return null; - } - - public void setComplexProperty(Map<String, List<List<Integer>>> complexProperty) { - - } - @Test public void propertyGenericType() throws Exception { GenericType<Integer> genericBean = new IntegerType(); - Property property = new Property(getClass(), genericBean.getClass().getMethod("getProperty"), genericBean.getClass().getMethod("setProperty", Integer.class)); + Property property = new Property(getClass(), genericBean.getClass().getMethod("getProperty"), + genericBean.getClass().getMethod("setProperty", Integer.class)); TypeDescriptor desc = new TypeDescriptor(property); assertEquals(Integer.class, desc.getType()); } @@ -245,7 +189,8 @@ public class TypeDescriptorTests { @Test public void propertyTypeCovariance() throws Exception { GenericType<Number> genericBean = new NumberType(); - Property property = new Property(getClass(), genericBean.getClass().getMethod("getProperty"), genericBean.getClass().getMethod("setProperty", Number.class)); + Property property = new Property(getClass(), genericBean.getClass().getMethod("getProperty"), + genericBean.getClass().getMethod("setProperty", Number.class)); TypeDescriptor desc = new TypeDescriptor(property); assertEquals(Integer.class, desc.getType()); } @@ -253,69 +198,18 @@ public class TypeDescriptorTests { @Test public void propertyGenericTypeList() throws Exception { GenericType<Integer> genericBean = new IntegerType(); - Property property = new Property(getClass(), genericBean.getClass().getMethod("getListProperty"), genericBean.getClass().getMethod("setListProperty", List.class)); + Property property = new Property(getClass(), genericBean.getClass().getMethod("getListProperty"), + genericBean.getClass().getMethod("setListProperty", List.class)); TypeDescriptor desc = new TypeDescriptor(property); assertEquals(List.class, desc.getType()); assertEquals(Integer.class, desc.getElementTypeDescriptor().getType()); } - public interface GenericType<T> { - T getProperty(); - - void setProperty(T t); - - List<T> getListProperty(); - - void setListProperty(List<T> t); - - } - - public class IntegerType implements GenericType<Integer> { - - @Override - public Integer getProperty() { - return null; - } - - @Override - public void setProperty(Integer t) { - } - - @Override - public List<Integer> getListProperty() { - return null; - } - - @Override - public void setListProperty(List<Integer> t) { - } - } - - public class NumberType implements GenericType<Number> { - - @Override - public Integer getProperty() { - return null; - } - - @Override - public void setProperty(Number t) { - } - - @Override - public List<Number> getListProperty() { - return null; - } - - @Override - public void setListProperty(List<Number> t) { - } - } - @Test public void propertyGenericClassList() throws Exception { IntegerClass genericBean = new IntegerClass(); - Property property = new Property(genericBean.getClass(), genericBean.getClass().getMethod("getListProperty"), genericBean.getClass().getMethod("setListProperty", List.class)); + Property property = new Property(genericBean.getClass(), genericBean.getClass().getMethod("getListProperty"), + genericBean.getClass().getMethod("setListProperty", List.class)); TypeDescriptor desc = new TypeDescriptor(property); assertEquals(List.class, desc.getType()); assertEquals(Integer.class, desc.getElementTypeDescriptor().getType()); @@ -323,32 +217,10 @@ public class TypeDescriptorTests { assertTrue(desc.hasAnnotation(MethodAnnotation1.class)); } - public static class GenericClass<T> { - - public T getProperty() { - return null; - } - - public void setProperty(T t) { - } - - @MethodAnnotation1 - public List<T> getListProperty() { - return null; - } - - public void setListProperty(List<T> t) { - } - - } - - public static class IntegerClass extends GenericClass<Integer> { - - } - @Test public void property() throws Exception { - Property property = new Property(getClass(), getClass().getMethod("getProperty"), getClass().getMethod("setProperty", Map.class)); + Property property = new Property( + getClass(), getClass().getMethod("getProperty"), getClass().getMethod("setProperty", Map.class)); TypeDescriptor desc = new TypeDescriptor(property); assertEquals(Map.class, desc.getType()); assertEquals(Integer.class, desc.getMapKeyTypeDescriptor().getElementTypeDescriptor().getType()); @@ -358,60 +230,6 @@ public class TypeDescriptorTests { assertNotNull(desc.getAnnotation(MethodAnnotation3.class)); } - @MethodAnnotation1 - public Map<List<Integer>, List<Long>> getProperty() { - return property; - } - - @MethodAnnotation2 - public void setProperty(Map<List<Integer>, List<Long>> property) { - this.property = property; - } - - @MethodAnnotation3 - private Map<List<Integer>, List<Long>> property; - - - @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) - @Retention(RetentionPolicy.RUNTIME) - public @interface MethodAnnotation1 { - } - - @Target({ElementType.METHOD}) - @Retention(RetentionPolicy.RUNTIME) - public @interface MethodAnnotation2 { - } - - @Target({ElementType.FIELD}) - @Retention(RetentionPolicy.RUNTIME) - public @interface MethodAnnotation3 { - } - - @MethodAnnotation1 - @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) - @Retention(RetentionPolicy.RUNTIME) - public @interface ComposedMethodAnnotation1 {} - - @ComposedMethodAnnotation1 - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - public @interface ComposedComposedMethodAnnotation1 {} - - @MethodAnnotation1 - public void methodWithLocalAnnotation() {} - - @ComposedMethodAnnotation1 - public void methodWithComposedAnnotation() {} - - @ComposedComposedMethodAnnotation1 - public void methodWithComposedComposedAnnotation() {} - - private void assertAnnotationFoundOnMethod(Class<? extends Annotation> annotationType, String methodName) throws Exception { - TypeDescriptor typeDescriptor = new TypeDescriptor(new MethodParameter(getClass().getMethod(methodName), -1)); - assertNotNull("Should have found @" + annotationType.getSimpleName() + " on " + methodName + ".", - typeDescriptor.getAnnotation(annotationType)); - } - @Test public void getAnnotationOnMethodThatIsLocallyAnnotated() throws Exception { assertAnnotationFoundOnMethod(MethodAnnotation1.class, "methodWithLocalAnnotation"); @@ -427,6 +245,12 @@ public class TypeDescriptorTests { assertAnnotationFoundOnMethod(MethodAnnotation1.class, "methodWithComposedComposedAnnotation"); } + private void assertAnnotationFoundOnMethod(Class<? extends Annotation> annotationType, String methodName) throws Exception { + TypeDescriptor typeDescriptor = new TypeDescriptor(new MethodParameter(getClass().getMethod(methodName), -1)); + assertNotNull("Should have found @" + annotationType.getSimpleName() + " on " + methodName + ".", + typeDescriptor.getAnnotation(annotationType)); + } + @Test public void fieldScalar() throws Exception { TypeDescriptor typeDescriptor = new TypeDescriptor(getClass().getField("fieldScalar")); @@ -438,8 +262,6 @@ public class TypeDescriptorTests { assertEquals(Integer.class, typeDescriptor.getObjectType()); } - public Integer fieldScalar; - @Test public void fieldList() throws Exception { TypeDescriptor typeDescriptor = new TypeDescriptor(TypeDescriptorTests.class.getDeclaredField("listOfString")); @@ -504,8 +326,6 @@ public class TypeDescriptorTests { assertEquals(Long.class, desc.getMapValueTypeDescriptor().getElementTypeDescriptor().getType()); } - public Map<List<Integer>, List<Long>> fieldMap; - @Test public void fieldAnnotated() throws Exception { TypeDescriptor typeDescriptor = new TypeDescriptor(getClass().getField("fieldAnnotated")); @@ -513,15 +333,6 @@ public class TypeDescriptorTests { assertNotNull(typeDescriptor.getAnnotation(FieldAnnotation.class)); } - @FieldAnnotation - public List<String> fieldAnnotated; - - @Target({ElementType.FIELD}) - @Retention(RetentionPolicy.RUNTIME) - public @interface FieldAnnotation { - - } - @Test public void valueOfScalar() { TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(Integer.class); @@ -592,7 +403,7 @@ public class TypeDescriptorTests { assertEquals(String.class, t1.getType()); } - @Test(expected=IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void nestedMethodParameterNot1NestedLevel() throws Exception { TypeDescriptor.nested(new MethodParameter(getClass().getMethod("test4", List.class), 0, 2), 2); } @@ -609,31 +420,11 @@ public class TypeDescriptorTests { assertNull(t1); } - @Test(expected=IllegalArgumentException.class) + @Test(expected = IllegalArgumentException.class) public void nestedMethodParameterTypeInvalidNestingLevel() throws Exception { TypeDescriptor.nested(new MethodParameter(getClass().getMethod("test5", String.class), 0, 2), 2); } - public void test1(List<String> param1) { - - } - - public void test2(List<List<String>> param1) { - - } - - public void test3(Map<Integer, String> param1) { - - } - - public void test4(List<Map<Integer, String>> param1) { - - } - - public void test5(String param1) { - - } - @Test public void nestedNotParameterized() throws Exception { TypeDescriptor t1 = TypeDescriptor.nested(new MethodParameter(getClass().getMethod("test6", List.class), 0), 1); @@ -643,18 +434,12 @@ public class TypeDescriptorTests { assertNull(t2); } - public void test6(List<List> param1) { - - } - @Test public void nestedFieldTypeMapTwoLevels() throws Exception { TypeDescriptor t1 = TypeDescriptor.nested(getClass().getField("test4"), 2); assertEquals(String.class, t1.getType()); } - public List<Map<Integer, String>> test4; - @Test public void nestedPropertyTypeMapTwoLevels() throws Exception { Property property = new Property(getClass(), getClass().getMethod("getTest4"), getClass().getMethod("setTest4", List.class)); @@ -662,14 +447,6 @@ public class TypeDescriptorTests { assertEquals(String.class, t1.getType()); } - public List<Map<Integer, String>> getTest4() { - return null; - } - - public void setTest4(List<Map<Integer, String>> test4) { - - } - @Test public void collection() { TypeDescriptor desc = TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(Integer.class)); @@ -762,9 +539,6 @@ public class TypeDescriptorTests { assertNotNull(desc.getAnnotation(FieldAnnotation.class)); } - @FieldAnnotation - public List<List<Integer>> listPreserveContext; - @Test public void mapKeyType() { TypeDescriptor desc = TypeDescriptor.valueOf(Map.class); @@ -783,9 +557,6 @@ public class TypeDescriptorTests { assertNotNull(desc.getAnnotation(FieldAnnotation.class)); } - @FieldAnnotation - public Map<List<Integer>, List<Integer>> mapPreserveContext; - @Test public void mapValueType() { TypeDescriptor desc = TypeDescriptor.valueOf(Map.class); @@ -805,7 +576,7 @@ public class TypeDescriptorTests { } @Test - public void equals() throws Exception { + public void equality() throws Exception { TypeDescriptor t1 = TypeDescriptor.valueOf(String.class); TypeDescriptor t2 = TypeDescriptor.valueOf(String.class); TypeDescriptor t3 = TypeDescriptor.valueOf(Date.class); @@ -826,6 +597,18 @@ public class TypeDescriptorTests { TypeDescriptor t11 = new TypeDescriptor(getClass().getField("mapField")); TypeDescriptor t12 = new TypeDescriptor(getClass().getField("mapField")); assertEquals(t11, t12); + + TypeDescriptor t13 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0)); + TypeDescriptor t14 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0)); + assertEquals(t13, t14); + + TypeDescriptor t15 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0)); + TypeDescriptor t16 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethodDifferentAnnotationValue", String.class), 0)); + assertNotEquals(t15, t16); + + TypeDescriptor t17 = new TypeDescriptor(new MethodParameter(getClass().getMethod("testAnnotatedMethod", String.class), 0)); + TypeDescriptor t18 = new TypeDescriptor(new MethodParameter(getClass().getMethod("test5", String.class), 0)); + assertNotEquals(t17, t18); } @Test @@ -844,10 +627,6 @@ public class TypeDescriptorTests { assertTrue(TypeDescriptor.valueOf(List.class).isAssignableTo(new TypeDescriptor(getClass().getField("listField")))); } - public List notGenericList; - - public List<Number> isAssignableElementTypes; - @Test public void isAssignableMapKeyValueTypes() throws Exception { assertTrue(new TypeDescriptor(getClass().getField("mapField")).isAssignableTo(new TypeDescriptor(getClass().getField("mapField")))); @@ -857,10 +636,6 @@ public class TypeDescriptorTests { assertTrue(TypeDescriptor.valueOf(Map.class).isAssignableTo(new TypeDescriptor(getClass().getField("mapField")))); } - public Map notGenericMap; - - public Map<CharSequence, Number> isAssignableMapKeyValueTypes; - @Test public void multiValueMap() throws Exception { TypeDescriptor td = new TypeDescriptor(getClass().getField("multiValueMap")); @@ -871,8 +646,6 @@ public class TypeDescriptorTests { td.getMapValueTypeDescriptor().getElementTypeDescriptor().getType()); } - public MultiValueMap<String, Integer> multiValueMap = new LinkedMultiValueMap<String, Integer>(); - @Test public void passDownGeneric() throws Exception { TypeDescriptor td = new TypeDescriptor(getClass().getField("passDownGeneric")); @@ -881,12 +654,6 @@ public class TypeDescriptorTests { assertEquals(Integer.class, td.getElementTypeDescriptor().getElementTypeDescriptor().getElementTypeDescriptor().getType()); } - public PassDownGeneric<Integer> passDownGeneric = new PassDownGeneric<Integer>(); - - @SuppressWarnings("serial") - public static class PassDownGeneric<T> extends ArrayList<List<Set<T>>> { - } - @Test public void testUpCast() throws Exception { Property property = new Property(getClass(), getClass().getMethod("getProperty"), @@ -904,8 +671,9 @@ public class TypeDescriptorTests { try { typeDescriptor.upcast(Collection.class); fail("Did not throw"); - } catch(IllegalArgumentException e) { - assertEquals("interface java.util.Map is not assignable to interface java.util.Collection", e.getMessage()); + } + catch (IllegalArgumentException ex) { + assertEquals("interface java.util.Map is not assignable to interface java.util.Collection", ex.getMessage()); } } @@ -933,13 +701,13 @@ public class TypeDescriptorTests { @Test public void createMapArray() throws Exception { - TypeDescriptor mapType = TypeDescriptor.map(LinkedHashMap.class, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)); + TypeDescriptor mapType = TypeDescriptor.map( + LinkedHashMap.class, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)); TypeDescriptor arrayType = TypeDescriptor.array(mapType); assertEquals(arrayType.getType(), LinkedHashMap[].class); assertEquals(arrayType.getElementTypeDescriptor(), mapType); } - @Test public void createStringArray() throws Exception { TypeDescriptor arrayType = TypeDescriptor.array(TypeDescriptor.valueOf(String.class)); @@ -985,4 +753,268 @@ public class TypeDescriptorTests { assertThat(TypeDescriptor.valueOf(Integer.class).getSource(), equalTo((Object) Integer.class)); } + + // Methods designed for test introspection + + public void testParameterPrimitive(int primitive) { + } + + public void testParameterScalar(String value) { + } + + public void testParameterList(List<List<Map<Integer, Enum<?>>>> list) { + } + + public void testParameterListNoParamTypes(List list) { + } + + public void testParameterArray(Integer[] array) { + } + + public void testParameterMap(Map<Integer, List<String>> map) { + } + + public void test1(List<String> param1) { + } + + public void test2(List<List<String>> param1) { + } + + public void test3(Map<Integer, String> param1) { + } + + public void test4(List<Map<Integer, String>> param1) { + } + + public void test5(String param1) { + } + + public void test6(List<List> param1) { + } + + public List<Map<Integer, String>> getTest4() { + return null; + } + + public void setTest4(List<Map<Integer, String>> test4) { + } + + public Map<String, List<List<Integer>>> getComplexProperty() { + return null; + } + + @MethodAnnotation1 + public Map<List<Integer>, List<Long>> getProperty() { + return property; + } + + @MethodAnnotation2 + public void setProperty(Map<List<Integer>, List<Long>> property) { + this.property = property; + } + + @MethodAnnotation1 + public void methodWithLocalAnnotation() { + } + + @ComposedMethodAnnotation1 + public void methodWithComposedAnnotation() { + } + + @ComposedComposedMethodAnnotation1 + public void methodWithComposedComposedAnnotation() { + } + + public void setComplexProperty(Map<String, List<List<Integer>>> complexProperty) { + } + + public void testAnnotatedMethod(@ParameterAnnotation(123) String parameter) { + } + + public void testAnnotatedMethodDifferentAnnotationValue(@ParameterAnnotation(567) String parameter) { + } + + + // Fields designed for test introspection + + public Integer fieldScalar; + + public List<String> listOfString; + + public List<List<String>> listOfListOfString = new ArrayList<List<String>>(); + + public List<List> listOfListOfUnknown = new ArrayList<List>(); + + public int[] intArray; + + public List<String>[] arrayOfListOfString; + + public List<Integer> listField = new ArrayList<Integer>(); + + public Map<String, Integer> mapField = new HashMap<String, Integer>(); + + public Map<String, List<Integer>> nestedMapField = new HashMap<String, List<Integer>>(); + + public Map<List<Integer>, List<Long>> fieldMap; + + public List<Map<Integer, String>> test4; + + @FieldAnnotation + public List<String> fieldAnnotated; + + @FieldAnnotation + public List<List<Integer>> listPreserveContext; + + @FieldAnnotation + public Map<List<Integer>, List<Integer>> mapPreserveContext; + + @MethodAnnotation3 + private Map<List<Integer>, List<Long>> property; + + public List notGenericList; + + public List<Number> isAssignableElementTypes; + + public Map notGenericMap; + + public Map<CharSequence, Number> isAssignableMapKeyValueTypes; + + public MultiValueMap<String, Integer> multiValueMap = new LinkedMultiValueMap<String, Integer>(); + + public PassDownGeneric<Integer> passDownGeneric = new PassDownGeneric<Integer>(); + + + // Classes designed for test introspection + + @SuppressWarnings("serial") + public static class PassDownGeneric<T> extends ArrayList<List<Set<T>>> { + } + + + public static class GenericClass<T> { + + public T getProperty() { + return null; + } + + public void setProperty(T t) { + } + + @MethodAnnotation1 + public List<T> getListProperty() { + return null; + } + + public void setListProperty(List<T> t) { + } + } + + + public static class IntegerClass extends GenericClass<Integer> { + } + + + public interface GenericType<T> { + + T getProperty(); + + void setProperty(T t); + + List<T> getListProperty(); + + void setListProperty(List<T> t); + } + + + public class IntegerType implements GenericType<Integer> { + + @Override + public Integer getProperty() { + return null; + } + + @Override + public void setProperty(Integer t) { + } + + @Override + public List<Integer> getListProperty() { + return null; + } + + @Override + public void setListProperty(List<Integer> t) { + } + } + + + public class NumberType implements GenericType<Number> { + + @Override + public Integer getProperty() { + return null; + } + + @Override + public void setProperty(Number t) { + } + + @Override + public List<Number> getListProperty() { + return null; + } + + @Override + public void setListProperty(List<Number> t) { + } + } + + + // Annotations used on tested elements + + @Target({ElementType.PARAMETER}) + @Retention(RetentionPolicy.RUNTIME) + public @interface ParameterAnnotation { + + int value(); + } + + + @Target({ElementType.FIELD}) + @Retention(RetentionPolicy.RUNTIME) + public @interface FieldAnnotation { + } + + + @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) + public @interface MethodAnnotation1 { + } + + + @Target({ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + public @interface MethodAnnotation2 { + } + + + @Target({ElementType.FIELD}) + @Retention(RetentionPolicy.RUNTIME) + public @interface MethodAnnotation3 { + } + + + @MethodAnnotation1 + @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) + public @interface ComposedMethodAnnotation1 { + } + + + @ComposedMethodAnnotation1 + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface ComposedComposedMethodAnnotation1 { + } + } diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java index c65ba7d3..131e739e 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/CollectionToCollectionConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -75,10 +75,10 @@ public class CollectionToCollectionConverterTests { conversionService.addConverterFactory(new StringToNumberConverterFactory()); assertTrue(conversionService.canConvert(sourceType, targetType)); @SuppressWarnings("unchecked") - List<String> result = (List<String>) conversionService.convert(list, sourceType, targetType); + List<Integer> result = (List<Integer>) conversionService.convert(list, sourceType, targetType); assertFalse(list.equals(result)); - assertEquals(9, result.get(0)); - assertEquals(37, result.get(1)); + assertEquals(9, result.get(0).intValue()); + assertEquals(37, result.get(1).intValue()); } @Test @@ -262,6 +262,25 @@ public class CollectionToCollectionConverterTests { } + public ArrayList<Integer> scalarListTarget; + + public List<Integer> emptyListTarget; + + public LinkedList<Integer> emptyListDifferentTarget; + + public List<List<List<Integer>>> objectToCollection; + + public List<String> strings; + + public List<?> list = Collections.emptyList(); + + public Collection<?> wildcardCollection = Collections.emptyList(); + + public List<Resource> resources; + + public EnumSet<MyEnum> enumSet; + + public static abstract class BaseResource implements Resource { @Override @@ -330,25 +349,6 @@ public class CollectionToCollectionConverterTests { } - public static enum MyEnum {A, B, C} - - - public ArrayList<Integer> scalarListTarget; - - public List<Integer> emptyListTarget; - - public LinkedList<Integer> emptyListDifferentTarget; - - public List<List<List<Integer>>> objectToCollection; - - public List<String> strings; - - public List<?> list = Collections.emptyList(); - - public Collection<?> wildcardCollection = Collections.emptyList(); - - public List<Resource> resources; - - public EnumSet<MyEnum> enumSet; + public enum MyEnum {A, B, C} } diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionServiceTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionServiceTests.java index 5059738c..e7caf78b 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionServiceTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -243,6 +243,26 @@ public class DefaultConversionServiceTests { public void testEnumToString() { assertEquals("BAR", conversionService.convert(Foo.BAR, String.class)); } + + @Test + public void testIntegerToEnum() throws Exception { + assertEquals(Foo.BAR, conversionService.convert(0, Foo.class)); + } + + @Test + public void testIntegerToEnumWithSubclass() throws Exception { + assertEquals(SubFoo.BAZ, conversionService.convert(1, SubFoo.BAR.getClass())); + } + + @Test + public void testIntegerToEnumNull() { + assertEquals(null, conversionService.convert(null, Foo.class)); + } + + @Test + public void testEnumToInteger() { + assertEquals(Integer.valueOf(0), conversionService.convert(Foo.BAR, Integer.class)); + } @Test public void testStringToEnumSet() throws Exception { @@ -313,7 +333,7 @@ public class DefaultConversionServiceTests { @Test public void convertArrayToCollectionInterface() { - List<?> result = conversionService.convert(new String[] { "1", "2", "3" }, List.class); + List<?> result = conversionService.convert(new String[] {"1", "2", "3"}, List.class); assertEquals("1", result.get(0)); assertEquals("2", result.get(1)); assertEquals("3", result.get(2)); @@ -322,7 +342,7 @@ public class DefaultConversionServiceTests { @Test public void convertArrayToCollectionGenericTypeConversion() throws Exception { @SuppressWarnings("unchecked") - List<Integer> result = (List<Integer>) conversionService.convert(new String[] { "1", "2", "3" }, TypeDescriptor + List<Integer> result = (List<Integer>) conversionService.convert(new String[] {"1", "2", "3"}, TypeDescriptor .valueOf(String[].class), new TypeDescriptor(getClass().getDeclaredField("genericList"))); assertEquals(new Integer("1"), result.get(0)); assertEquals(new Integer("2"), result.get(1)); @@ -344,7 +364,7 @@ public class DefaultConversionServiceTests { ConverterRegistry registry = (conversionService); registry.addConverter(new ColorConverter()); @SuppressWarnings("unchecked") - List<Color> colors = (List<Color>) conversionService.convert(new String[] { "ffffff", "#000000" }, + List<Color> colors = (List<Color>) conversionService.convert(new String[] {"ffffff", "#000000"}, TypeDescriptor.valueOf(String[].class), new TypeDescriptor(new MethodParameter(getClass().getMethod("handlerMethod", List.class), 0))); assertEquals(2, colors.size()); @@ -354,7 +374,7 @@ public class DefaultConversionServiceTests { @Test public void convertArrayToCollectionImpl() { - LinkedList<?> result = conversionService.convert(new String[] { "1", "2", "3" }, LinkedList.class); + LinkedList<?> result = conversionService.convert(new String[] {"1", "2", "3"}, LinkedList.class); assertEquals("1", result.get(0)); assertEquals("2", result.get(1)); assertEquals("3", result.get(2)); @@ -371,13 +391,13 @@ public class DefaultConversionServiceTests { @Test public void convertArrayToString() { - String result = conversionService.convert(new String[] { "1", "2", "3" }, String.class); + String result = conversionService.convert(new String[] {"1", "2", "3"}, String.class); assertEquals("1,2,3", result); } @Test public void convertArrayToStringWithElementConversion() { - String result = conversionService.convert(new Integer[] { 1, 2, 3 }, String.class); + String result = conversionService.convert(new Integer[] {1, 2, 3}, String.class); assertEquals("1,2,3", result); } @@ -422,21 +442,21 @@ public class DefaultConversionServiceTests { @Test public void convertArrayToObject() { - Object[] array = new Object[] { 3L }; + Object[] array = new Object[] {3L}; Object result = conversionService.convert(array, Long.class); assertEquals(3L, result); } @Test public void convertArrayToObjectWithElementConversion() { - String[] array = new String[] { "3" }; + String[] array = new String[] {"3"}; Integer result = conversionService.convert(array, Integer.class); assertEquals(new Integer(3), result); } @Test public void convertArrayToObjectAssignableTargetType() { - Long[] array = new Long[] { 3L }; + Long[] array = new Long[] {3L}; Long[] result = (Long[]) conversionService.convert(array, Object.class); assertArrayEquals(array, result); } @@ -561,9 +581,9 @@ public class DefaultConversionServiceTests { } @Test - @SuppressWarnings("unchecked") + @SuppressWarnings("rawtypes") public void convertObjectToCollection() { - List<String> result = (List<String>) conversionService.convert(3L, List.class); + List result = conversionService.convert(3L, List.class); assertEquals(1, result.size()); assertEquals(3L, result.get(0)); } @@ -849,14 +869,14 @@ public class DefaultConversionServiceTests { } }); char[] converted = conversionService.convert("abc", char[].class); - assertThat(converted, equalTo(new char[] { 'a', 'b', 'c' })); + assertThat(converted, equalTo(new char[] {'a', 'b', 'c'})); } @Test @SuppressWarnings("unchecked") public void multidimensionalArrayToListConversionShouldConvertEntriesCorrectly() { - String[][] grid = new String[][] { new String[] { "1", "2", "3", "4" }, new String[] { "5", "6", "7", "8" }, - new String[] { "9", "10", "11", "12" } }; + String[][] grid = new String[][] {new String[] {"1", "2", "3", "4"}, new String[] {"5", "6", "7", "8"}, + new String[] {"9", "10", "11", "12"}}; List<String[]> converted = conversionService.convert(grid, List.class); String[][] convertedBack = conversionService.convert(converted, String[][].class); assertArrayEquals(grid, convertedBack); @@ -865,16 +885,15 @@ public class DefaultConversionServiceTests { @Test public void convertCannotOptimizeArray() { conversionService.addConverter(new Converter<Byte, Byte>() { - @Override public Byte convert(Byte source) { return (byte) (source + 1); } }); - byte[] byteArray = new byte[] { 1, 2, 3 }; + byte[] byteArray = new byte[] {1, 2, 3}; byte[] converted = conversionService.convert(byteArray, byte[].class); assertNotSame(byteArray, converted); - assertTrue(Arrays.equals(new byte[] { 2, 3, 4 }, converted)); + assertTrue(Arrays.equals(new byte[] {2, 3, 4}, converted)); } @Test @@ -938,6 +957,7 @@ public class DefaultConversionServiceTests { public enum Foo { + BAR, BAZ } @@ -964,7 +984,12 @@ public class DefaultConversionServiceTests { public class ColorConverter implements Converter<String, Color> { @Override - public Color convert(String source) { if (!source.startsWith("#")) source = "#" + source; return Color.decode(source); } + public Color convert(String source) { + if (!source.startsWith("#")) { + source = "#" + source; + } + return Color.decode(source); + } } @@ -1031,8 +1056,8 @@ public class DefaultConversionServiceTests { private static class SSN { static int constructorCount = 0; - static int toStringCount = 0; + static int toStringCount = 0; static void reset() { constructorCount = 0; diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java index 6b16aefe..4b3183b6 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java @@ -479,6 +479,21 @@ public class GenericConversionServiceTests { } @Test + public void conditionalConverterCachingForDifferentAnnotationAttributes() throws Exception { + conversionService.addConverter(new ColorConverter()); + conversionService.addConverter(new MyConditionalColorConverter()); + + assertEquals(Color.BLACK, conversionService.convert("000000xxxx", + new TypeDescriptor(getClass().getField("activeColor")))); + assertEquals(Color.BLACK, conversionService.convert(" #000000 ", + new TypeDescriptor(getClass().getField("inactiveColor")))); + assertEquals(Color.BLACK, conversionService.convert("000000yyyy", + new TypeDescriptor(getClass().getField("activeColor")))); + assertEquals(Color.BLACK, conversionService.convert(" #000000 ", + new TypeDescriptor(getClass().getField("inactiveColor")))); + } + + @Test public void shouldNotSupportNullConvertibleTypesFromNonConditionalGenericConverter() { GenericConverter converter = new NonConditionalGenericConverter(); try { @@ -629,14 +644,49 @@ public class GenericConversionServiceTests { } + @ExampleAnnotation(active = true) + public String annotatedString; + + @ExampleAnnotation(active = true) + public Color activeColor; + + @ExampleAnnotation(active = false) + public Color inactiveColor; + + public List<Integer> list; + + public Map<String, Integer> map; + + public Map<String, ?> wildcardMap; + + @SuppressWarnings("rawtypes") + public Collection rawCollection; + + public Collection<?> genericCollection; + + public Collection<String> stringCollection; + + public Collection<Integer> integerCollection; + + @Retention(RetentionPolicy.RUNTIME) - private static @interface ExampleAnnotation {} + private @interface ExampleAnnotation { + + boolean active(); + } + + + private interface MyBaseInterface { + } - private static interface MyBaseInterface {} - private static interface MyInterface extends MyBaseInterface {} + private interface MyInterface extends MyBaseInterface { + } + + + private static class MyInterfaceImplementer implements MyInterface { + } - private static class MyInterfaceImplementer implements MyInterface {} private static class MyBaseInterfaceToStringConverter implements Converter<MyBaseInterface, String> { @@ -646,6 +696,7 @@ public class GenericConversionServiceTests { } } + private static class MyStringArrayToResourceArrayConverter implements Converter<String[], Resource[]> { @Override @@ -654,6 +705,7 @@ public class GenericConversionServiceTests { } } + private static class MyStringArrayToIntegerArrayConverter implements Converter<String[], Integer[]> { @Override @@ -662,6 +714,7 @@ public class GenericConversionServiceTests { } } + private static class MyStringToIntegerArrayConverter implements Converter<String, Integer[]> { @Override @@ -671,6 +724,7 @@ public class GenericConversionServiceTests { } } + private static class WithCopyConstructor { WithCopyConstructor() {} @@ -679,6 +733,7 @@ public class GenericConversionServiceTests { WithCopyConstructor(WithCopyConstructor value) {} } + private static class MyConditionalConverter implements Converter<String, Color>, ConditionalConverter { private int matchAttempts = 0; @@ -699,6 +754,7 @@ public class GenericConversionServiceTests { } } + private static class NonConditionalGenericConverter implements GenericConverter { @Override @@ -712,6 +768,7 @@ public class GenericConversionServiceTests { } } + private static class MyConditionalGenericConverter implements GenericConverter, ConditionalConverter { private final List<TypeDescriptor> sourceTypes = new ArrayList<TypeDescriptor>(); @@ -737,6 +794,7 @@ public class GenericConversionServiceTests { } } + private static class MyConditionalConverterFactory implements ConverterFactory<String, Color>, ConditionalConverter { private MyConditionalConverter converter = new MyConditionalConverter(); @@ -768,11 +826,13 @@ public class GenericConversionServiceTests { String getBaseCode(); } - private static interface MyEnumInterface extends MyEnumBaseInterface { + + private interface MyEnumInterface extends MyEnumBaseInterface { String getCode(); } - private static enum MyEnum implements MyEnumInterface { + + private enum MyEnum implements MyEnumInterface { A("1"), B("2"), @@ -795,7 +855,8 @@ public class GenericConversionServiceTests { } } - private static enum EnumWithSubclass { + + private enum EnumWithSubclass { FIRST { @Override @@ -805,6 +866,7 @@ public class GenericConversionServiceTests { } } + @SuppressWarnings("rawtypes") private static class MyStringToRawCollectionConverter implements Converter<String, Collection> { @@ -814,6 +876,7 @@ public class GenericConversionServiceTests { } } + private static class MyStringToGenericCollectionConverter implements Converter<String, Collection<?>> { @Override @@ -822,6 +885,7 @@ public class GenericConversionServiceTests { } } + private static class MyEnumInterfaceToStringConverter<T extends MyEnumInterface> implements Converter<T, String> { @Override @@ -830,14 +894,16 @@ public class GenericConversionServiceTests { } } + private static class StringToMyEnumInterfaceConverterFactory implements ConverterFactory<String, MyEnumInterface> { - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({"unchecked", "rawtypes"}) public <T extends MyEnumInterface> Converter<String, T> getConverter(Class<T> targetType) { return new StringToMyEnumInterfaceConverter(targetType); } private static class StringToMyEnumInterfaceConverter<T extends Enum<?> & MyEnumInterface> implements Converter<String, T> { + private final Class<T> enumType; public StringToMyEnumInterfaceConverter(Class<T> enumType) { @@ -855,9 +921,10 @@ public class GenericConversionServiceTests { } } + private static class StringToMyEnumBaseInterfaceConverterFactory implements ConverterFactory<String, MyEnumBaseInterface> { - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({"unchecked", "rawtypes"}) public <T extends MyEnumBaseInterface> Converter<String, T> getConverter(Class<T> targetType) { return new StringToMyEnumBaseInterfaceConverter(targetType); } @@ -881,6 +948,7 @@ public class GenericConversionServiceTests { } } + private static class MyStringToStringCollectionConverter implements Converter<String, Collection<String>> { @Override @@ -889,6 +957,7 @@ public class GenericConversionServiceTests { } } + private static class MyStringToIntegerCollectionConverter implements Converter<String, Collection<Integer>> { @Override @@ -897,6 +966,7 @@ public class GenericConversionServiceTests { } } + @SuppressWarnings("rawtypes") private static class UntypedConverter implements Converter { @@ -906,28 +976,27 @@ public class GenericConversionServiceTests { } } + private static class ColorConverter implements Converter<String, Color> { + @Override - public Color convert(String source) { if (!source.startsWith("#")) source = "#" + source; return Color.decode(source); } + public Color convert(String source) { + return Color.decode(source.trim()); + } } - @ExampleAnnotation - public String annotatedString; - - public List<Integer> list; - - public Map<String, Integer> map; - - public Map<String, ?> wildcardMap; - - @SuppressWarnings("rawtypes") - public Collection rawCollection; - - public Collection<?> genericCollection; - - public Collection<String> stringCollection; + private static class MyConditionalColorConverter implements Converter<String, Color>, ConditionalConverter { - public Collection<Integer> integerCollection; + @Override + public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { + ExampleAnnotation ann = targetType.getAnnotation(ExampleAnnotation.class); + return (ann != null && ann.active()); + } + @Override + public Color convert(String source) { + return Color.decode(source.substring(0, 6)); + } + } } diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java index 510f4f53..3757f554 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/MapToMapConverterTests.java @@ -36,9 +36,14 @@ import org.springframework.util.MultiValueMap; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +/** + * @author Keith Donald + * @author Phil Webb + * @author Juergen Hoeller + */ public class MapToMapConverterTests { - private GenericConversionService conversionService = new GenericConversionService(); + private final GenericConversionService conversionService = new GenericConversionService(); @Before @@ -54,12 +59,15 @@ public class MapToMapConverterTests { map.put("2", "37"); TypeDescriptor sourceType = TypeDescriptor.forObject(map); TypeDescriptor targetType = new TypeDescriptor(getClass().getField("scalarMapTarget")); + assertTrue(conversionService.canConvert(sourceType, targetType)); try { conversionService.convert(map, sourceType, targetType); - } catch (ConversionFailedException e) { - assertTrue(e.getCause() instanceof ConverterNotFoundException); } + catch (ConversionFailedException ex) { + assertTrue(ex.getCause() instanceof ConverterNotFoundException); + } + conversionService.addConverterFactory(new StringToNumberConverterFactory()); assertTrue(conversionService.canConvert(sourceType, targetType)); @SuppressWarnings("unchecked") @@ -74,6 +82,7 @@ public class MapToMapConverterTests { Map<String, String> map = new HashMap<String, String>(); map.put("1", "9"); map.put("2", "37"); + assertTrue(conversionService.canConvert(Map.class, Map.class)); assertSame(map, conversionService.convert(map, Map.class)); } @@ -85,12 +94,15 @@ public class MapToMapConverterTests { map.put("2", "37"); TypeDescriptor sourceType = new TypeDescriptor(getClass().getField("notGenericMapSource")); TypeDescriptor targetType = new TypeDescriptor(getClass().getField("scalarMapTarget")); + assertTrue(conversionService.canConvert(sourceType, targetType)); try { conversionService.convert(map, sourceType, targetType); - } catch (ConversionFailedException e) { - assertTrue(e.getCause() instanceof ConverterNotFoundException); } + catch (ConversionFailedException ex) { + assertTrue(ex.getCause() instanceof ConverterNotFoundException); + } + conversionService.addConverterFactory(new StringToNumberConverterFactory()); assertTrue(conversionService.canConvert(sourceType, targetType)); @SuppressWarnings("unchecked") @@ -107,12 +119,15 @@ public class MapToMapConverterTests { map.put("2", Arrays.asList("37", "23")); TypeDescriptor sourceType = TypeDescriptor.forObject(map); TypeDescriptor targetType = new TypeDescriptor(getClass().getField("collectionMapTarget")); + assertTrue(conversionService.canConvert(sourceType, targetType)); try { conversionService.convert(map, sourceType, targetType); - } catch (ConversionFailedException e) { - assertTrue(e.getCause() instanceof ConverterNotFoundException); } + catch (ConversionFailedException ex) { + assertTrue(ex.getCause() instanceof ConverterNotFoundException); + } + conversionService.addConverter(new CollectionToCollectionConverter(conversionService)); conversionService.addConverterFactory(new StringToNumberConverterFactory()); assertTrue(conversionService.canConvert(sourceType, targetType)); @@ -130,6 +145,7 @@ public class MapToMapConverterTests { map.put("2", Arrays.asList("37", "23")); TypeDescriptor sourceType = new TypeDescriptor(getClass().getField("sourceCollectionMapTarget")); TypeDescriptor targetType = new TypeDescriptor(getClass().getField("collectionMapTarget")); + assertFalse(conversionService.canConvert(sourceType, targetType)); try { conversionService.convert(map, sourceType, targetType); @@ -138,6 +154,7 @@ public class MapToMapConverterTests { catch (ConverterNotFoundException ex) { // expected } + conversionService.addConverter(new CollectionToCollectionConverter(conversionService)); conversionService.addConverterFactory(new StringToNumberConverterFactory()); assertTrue(conversionService.canConvert(sourceType, targetType)); @@ -153,6 +170,7 @@ public class MapToMapConverterTests { Map<String, List<String>> map = new HashMap<String, List<String>>(); map.put("1", Arrays.asList("9", "12")); map.put("2", Arrays.asList("37", "23")); + assertTrue(conversionService.canConvert(Map.class, Map.class)); assertSame(map, conversionService.convert(map, Map.class)); } @@ -164,6 +182,7 @@ public class MapToMapConverterTests { map.put("2", Arrays.asList("37", "23")); conversionService.addConverter(new CollectionToCollectionConverter(conversionService)); conversionService.addConverter(new CollectionToObjectConverter(conversionService)); + assertTrue(conversionService.canConvert(Map.class, Map.class)); assertSame(map, conversionService.convert(map, Map.class)); } @@ -173,6 +192,7 @@ public class MapToMapConverterTests { Map<String, String> map = new HashMap<String, String>(); TypeDescriptor sourceType = TypeDescriptor.forObject(map); TypeDescriptor targetType = new TypeDescriptor(getClass().getField("emptyMapTarget")); + assertTrue(conversionService.canConvert(sourceType, targetType)); assertSame(map, conversionService.convert(map, sourceType, targetType)); } @@ -180,6 +200,7 @@ public class MapToMapConverterTests { @Test public void emptyMapNoTargetGenericInfo() throws Exception { Map<String, String> map = new HashMap<String, String>(); + assertTrue(conversionService.canConvert(Map.class, Map.class)); assertSame(map, conversionService.convert(map, Map.class)); } @@ -189,6 +210,7 @@ public class MapToMapConverterTests { Map<String, String> map = new HashMap<String, String>(); 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); @@ -205,6 +227,7 @@ public class MapToMapConverterTests { TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)); TypeDescriptor targetType = TypeDescriptor.map(NoDefaultConstructorMap.class, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)); + assertTrue(conversionService.canConvert(sourceType, targetType)); @SuppressWarnings("unchecked") Map<String, Integer> result = (Map<String, Integer>) conversionService.convert(map, sourceType, targetType); @@ -220,6 +243,7 @@ public class MapToMapConverterTests { source.put("a", Arrays.asList(1, 2, 3)); source.put("b", Arrays.asList(4, 5, 6)); TypeDescriptor targetType = new TypeDescriptor(getClass().getField("multiValueMapTarget")); + MultiValueMap<String, String> converted = (MultiValueMap<String, String>) conversionService.convert(source, targetType); assertThat(converted.size(), equalTo(2)); assertThat(converted.get("a"), equalTo(Arrays.asList("1", "2", "3"))); @@ -234,6 +258,7 @@ public class MapToMapConverterTests { source.put("a", 1); source.put("b", 2); TypeDescriptor targetType = new TypeDescriptor(getClass().getField("multiValueMapTarget")); + MultiValueMap<String, String> converted = (MultiValueMap<String, String>) conversionService.convert(source, targetType); assertThat(converted.size(), equalTo(2)); assertThat(converted.get("a"), equalTo(Arrays.asList("1"))); @@ -249,23 +274,12 @@ public class MapToMapConverterTests { EnumMap<MyEnum, Integer> result = new EnumMap<MyEnum, Integer>(MyEnum.class); result.put(MyEnum.A, 1); result.put(MyEnum.C, 2); - assertEquals(result, - conversionService.convert(source, TypeDescriptor.forObject(source), new TypeDescriptor(getClass().getField("enumMap")))); - } - - - @SuppressWarnings("serial") - public static class NoDefaultConstructorMap<K, V> extends HashMap<K, V> { - public NoDefaultConstructorMap(Map<? extends K, ? extends V> map) { - super(map); - } + assertEquals(result, conversionService.convert(source, + TypeDescriptor.forObject(source), new TypeDescriptor(getClass().getField("enumMap")))); } - public static enum MyEnum {A, B, C} - - public Map<Integer, Integer> scalarMapTarget; public Map<Integer, List<Integer>> collectionMapTarget; @@ -283,4 +297,16 @@ public class MapToMapConverterTests { public EnumMap<MyEnum, Integer> enumMap; + + @SuppressWarnings("serial") + public static class NoDefaultConstructorMap<K, V> extends HashMap<K, V> { + + public NoDefaultConstructorMap(Map<? extends K, ? extends V> map) { + super(map); + } + } + + + public enum MyEnum {A, B, C} + } diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/StreamConverterTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/StreamConverterTests.java index 6b09204e..2da31bf1 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/StreamConverterTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/StreamConverterTests.java @@ -49,6 +49,7 @@ public class StreamConverterTests { private final StreamConverter streamConverter = new StreamConverter(this.conversionService); + @Before public void setup() { this.conversionService.addConverter(new CollectionToCollectionConverter(this.conversionService)); @@ -57,13 +58,15 @@ public class StreamConverterTests { this.conversionService.addConverter(this.streamConverter); } + @Test public void convertFromStreamToList() throws NoSuchFieldException { this.conversionService.addConverter(Number.class, String.class, new ObjectToStringConverter()); Stream<Integer> stream = Arrays.asList(1, 2, 3).stream(); TypeDescriptor listOfStrings = new TypeDescriptor(Types.class.getField("listOfStrings")); ; Object result = this.conversionService.convert(stream, listOfStrings); - assertNotNull("converted object must not be null", result); + + assertNotNull("Converted object must not be null", result); assertTrue("Converted object must be a list", result instanceof List); @SuppressWarnings("unchecked") List<String> content = (List<String>) result; @@ -79,7 +82,8 @@ public class StreamConverterTests { Stream<Integer> stream = Arrays.asList(1, 2, 3).stream(); TypeDescriptor arrayOfLongs = new TypeDescriptor(Types.class.getField("arrayOfLongs")); ; Object result = this.conversionService.convert(stream, arrayOfLongs); - assertNotNull("converted object must not be null", result); + + assertNotNull("Converted object must not be null", result); assertTrue("Converted object must be an array", result.getClass().isArray()); Long[] content = (Long[]) result; assertEquals(Long.valueOf(1L), content[0]); @@ -93,7 +97,8 @@ public class StreamConverterTests { Stream<Integer> stream = Arrays.asList(1, 2, 3).stream(); TypeDescriptor listOfStrings = new TypeDescriptor(Types.class.getField("rawList")); ; Object result = this.conversionService.convert(stream, listOfStrings); - assertNotNull("converted object must not be null", result); + + assertNotNull("Converted object must not be null", result); assertTrue("Converted object must be a list", result instanceof List); @SuppressWarnings("unchecked") List<Object> content = (List<Object>) result; @@ -120,7 +125,8 @@ public class StreamConverterTests { List<String> stream = Arrays.asList("1", "2", "3"); TypeDescriptor streamOfInteger = new TypeDescriptor(Types.class.getField("streamOfIntegers")); ; Object result = this.conversionService.convert(stream, streamOfInteger); - assertNotNull("converted object must not be null", result); + + assertNotNull("Converted object must not be null", result); assertTrue("Converted object must be a stream", result instanceof Stream); @SuppressWarnings("unchecked") Stream<Integer> content = (Stream<Integer>) result; @@ -139,7 +145,8 @@ public class StreamConverterTests { }); TypeDescriptor streamOfBoolean = new TypeDescriptor(Types.class.getField("streamOfBooleans")); ; Object result = this.conversionService.convert(stream, streamOfBoolean); - assertNotNull("converted object must not be null", result); + + assertNotNull("Converted object must not be null", result); assertTrue("Converted object must be a stream", result instanceof Stream); @SuppressWarnings("unchecked") Stream<Boolean> content = (Stream<Boolean>) result; @@ -152,7 +159,8 @@ public class StreamConverterTests { List<String> stream = Arrays.asList("1", "2", "3"); TypeDescriptor streamOfInteger = new TypeDescriptor(Types.class.getField("rawStream")); ; Object result = this.conversionService.convert(stream, streamOfInteger); - assertNotNull("converted object must not be null", result); + + assertNotNull("Converted object must not be null", result); assertTrue("Converted object must be a stream", result instanceof Stream); @SuppressWarnings("unchecked") Stream<Object> content = (Stream<Object>) result; @@ -175,6 +183,7 @@ public class StreamConverterTests { new TypeDescriptor(Types.class.getField("arrayOfLongs"))); } + @SuppressWarnings({ "rawtypes" }) static class Types { diff --git a/spring-core/src/test/java/org/springframework/core/env/DummyEnvironment.java b/spring-core/src/test/java/org/springframework/core/env/DummyEnvironment.java index bcb3d225..756da6a9 100644 --- a/spring-core/src/test/java/org/springframework/core/env/DummyEnvironment.java +++ b/spring-core/src/test/java/org/springframework/core/env/DummyEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -44,6 +44,7 @@ public class DummyEnvironment implements Environment { } @Override + @Deprecated public <T> Class<T> getPropertyAsClass(String key, Class<T> targetType) { return null; } @@ -54,8 +55,7 @@ public class DummyEnvironment implements Environment { } @Override - public <T> T getRequiredProperty(String key, Class<T> targetType) - throws IllegalStateException { + public <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException { return null; } @@ -65,8 +65,7 @@ public class DummyEnvironment implements Environment { } @Override - public String resolveRequiredPlaceholders(String text) - throws IllegalArgumentException { + public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { return null; } diff --git a/spring-core/src/test/java/org/springframework/core/env/PropertySourceTests.java b/spring-core/src/test/java/org/springframework/core/env/PropertySourceTests.java index 28a51d05..fda1f06d 100644 --- a/spring-core/src/test/java/org/springframework/core/env/PropertySourceTests.java +++ b/spring-core/src/test/java/org/springframework/core/env/PropertySourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 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. @@ -30,13 +30,15 @@ import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; /** - * Unit tests for {@link AbstractPropertySource} implementations. + * Unit tests for {@link PropertySource} implementations. * * @author Chris Beams * @since 3.1 */ public class PropertySourceTests { - @Test @SuppressWarnings("serial") + + @Test + @SuppressWarnings("serial") public void equals() { Map<String, Object> map1 = new HashMap<String, Object>() {{ put("a", "b"); }}; Map<String, Object> map2 = new HashMap<String, Object>() {{ put("c", "d"); }}; @@ -59,14 +61,15 @@ public class PropertySourceTests { assertThat(new MapPropertySource("x", map1).equals(new PropertiesPropertySource("y", props2)), is(false)); } - @Test @SuppressWarnings("serial") + @Test + @SuppressWarnings("serial") public void collectionsOperations() { Map<String, Object> map1 = new HashMap<String, Object>() {{ put("a", "b"); }}; Map<String, Object> map2 = new HashMap<String, Object>() {{ put("c", "d"); }}; PropertySource<?> ps1 = new MapPropertySource("ps1", map1); ps1.getSource(); - List<PropertySource<?>> propertySources = new ArrayList<PropertySource<?>>(); + List<PropertySource<?>> propertySources = new ArrayList<>(); assertThat(propertySources.add(ps1), equalTo(true)); assertThat(propertySources.contains(ps1), is(true)); assertThat(propertySources.contains(PropertySource.named("ps1")), is(true)); @@ -110,10 +113,11 @@ public class PropertySourceTests { ps.toString(), equalTo(String.format("%s [name='%s']", ps.getClass().getSimpleName(), - name, - map.size()))); - } finally { + name))); + } + finally { logger.setLevel(original); } } + } diff --git a/spring-core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java b/spring-core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java index 84f6fce0..529bcd41 100644 --- a/spring-core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/env/PropertySourcesPropertyResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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,6 +24,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.core.convert.ConversionException; +import org.springframework.core.convert.ConverterNotFoundException; import org.springframework.mock.env.MockPropertySource; import static org.hamcrest.Matchers.*; @@ -114,9 +115,9 @@ public class PropertySourcesPropertyResolverTests { try { propertyResolver.getProperty("foo", TestType.class); - fail("Expected IllegalArgumentException due to non-convertible types"); + fail("Expected ConverterNotFoundException due to non-convertible types"); } - catch (IllegalArgumentException ex) { + catch (ConverterNotFoundException ex) { // expected } } @@ -257,6 +258,7 @@ public class PropertySourcesPropertyResolverTests { } @Test + @SuppressWarnings("deprecation") public void getPropertyAsClass() throws ClassNotFoundException, LinkageError { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("some.class", SpecificType.class.getName())); @@ -265,6 +267,7 @@ public class PropertySourcesPropertyResolverTests { } @Test + @SuppressWarnings("deprecation") public void getPropertyAsClass_withInterfaceAsTarget() throws ClassNotFoundException, LinkageError { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("some.class", SomeType.class.getName())); @@ -272,7 +275,8 @@ public class PropertySourcesPropertyResolverTests { assertTrue(resolver.getPropertyAsClass("some.class", SomeType.class).equals(SomeType.class)); } - @Test(expected=ConversionException.class) + @Test(expected = ConversionException.class) + @SuppressWarnings("deprecation") public void getPropertyAsClass_withMismatchedTypeForValue() { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("some.class", "java.lang.String")); @@ -280,7 +284,8 @@ public class PropertySourcesPropertyResolverTests { resolver.getPropertyAsClass("some.class", SomeType.class); } - @Test(expected=ConversionException.class) + @Test(expected = ConversionException.class) + @SuppressWarnings("deprecation") public void getPropertyAsClass_withNonExistentClassForValue() { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("some.class", "some.bogus.Class")); @@ -289,6 +294,7 @@ public class PropertySourcesPropertyResolverTests { } @Test + @SuppressWarnings("deprecation") public void getPropertyAsClass_withObjectForValue() { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("some.class", new SpecificType())); @@ -296,7 +302,8 @@ public class PropertySourcesPropertyResolverTests { assertTrue(resolver.getPropertyAsClass("some.class", SomeType.class).equals(SpecificType.class)); } - @Test(expected=ConversionException.class) + @Test(expected = ConversionException.class) + @SuppressWarnings("deprecation") public void getPropertyAsClass_withMismatchedObjectForValue() { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("some.class", new Integer(42))); @@ -305,6 +312,7 @@ public class PropertySourcesPropertyResolverTests { } @Test + @SuppressWarnings("deprecation") public void getPropertyAsClass_withRealClassForValue() { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("some.class", SpecificType.class)); @@ -312,7 +320,8 @@ public class PropertySourcesPropertyResolverTests { assertTrue(resolver.getPropertyAsClass("some.class", SomeType.class).equals(SpecificType.class)); } - @Test(expected=ConversionException.class) + @Test(expected = ConversionException.class) + @SuppressWarnings("deprecation") public void getPropertyAsClass_withMismatchedRealClassForValue() { MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new MockPropertySource().withProperty("some.class", Integer.class)); diff --git a/spring-core/src/test/java/org/springframework/core/env/SystemEnvironmentPropertySourceTests.java b/spring-core/src/test/java/org/springframework/core/env/SystemEnvironmentPropertySourceTests.java index 8926161a..935862b0 100644 --- a/spring-core/src/test/java/org/springframework/core/env/SystemEnvironmentPropertySourceTests.java +++ b/spring-core/src/test/java/org/springframework/core/env/SystemEnvironmentPropertySourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -93,16 +93,59 @@ public class SystemEnvironmentPropertySourceTests { @Test public void withUppercase() { envMap.put("A_KEY", "a_value"); + envMap.put("A_LONG_KEY", "a_long_value"); + envMap.put("A_DOT.KEY", "a_dot_value"); + envMap.put("A_HYPHEN-KEY", "a_hyphen_value"); assertThat(ps.containsProperty("A_KEY"), equalTo(true)); assertThat(ps.containsProperty("A.KEY"), equalTo(true)); + assertThat(ps.containsProperty("A-KEY"), equalTo(true)); assertThat(ps.containsProperty("a_key"), equalTo(true)); assertThat(ps.containsProperty("a.key"), equalTo(true)); - - assertThat(ps.getProperty("A_KEY"), equalTo((Object)"a_value")); - assertThat(ps.getProperty("A.KEY"), equalTo((Object)"a_value")); - assertThat(ps.getProperty("a_key"), equalTo((Object)"a_value")); - assertThat(ps.getProperty("a.key"), equalTo((Object)"a_value")); + assertThat(ps.containsProperty("a-key"), equalTo(true)); + assertThat(ps.containsProperty("A_LONG_KEY"), equalTo(true)); + assertThat(ps.containsProperty("A.LONG.KEY"), equalTo(true)); + assertThat(ps.containsProperty("A-LONG-KEY"), equalTo(true)); + assertThat(ps.containsProperty("A.LONG-KEY"), equalTo(true)); + assertThat(ps.containsProperty("A-LONG.KEY"), equalTo(true)); + assertThat(ps.containsProperty("A_long_KEY"), equalTo(true)); + assertThat(ps.containsProperty("A.long.KEY"), equalTo(true)); + assertThat(ps.containsProperty("A-long-KEY"), equalTo(true)); + assertThat(ps.containsProperty("A.long-KEY"), equalTo(true)); + assertThat(ps.containsProperty("A-long.KEY"), equalTo(true)); + assertThat(ps.containsProperty("A_DOT.KEY"), equalTo(true)); + assertThat(ps.containsProperty("A-DOT.KEY"), equalTo(true)); + assertThat(ps.containsProperty("A_dot.KEY"), equalTo(true)); + assertThat(ps.containsProperty("A-dot.KEY"), equalTo(true)); + assertThat(ps.containsProperty("A_HYPHEN-KEY"), equalTo(true)); + assertThat(ps.containsProperty("A.HYPHEN-KEY"), equalTo(true)); + assertThat(ps.containsProperty("A_hyphen-KEY"), equalTo(true)); + assertThat(ps.containsProperty("A.hyphen-KEY"), equalTo(true)); + + assertThat(ps.getProperty("A_KEY"), equalTo("a_value")); + assertThat(ps.getProperty("A.KEY"), equalTo("a_value")); + assertThat(ps.getProperty("A-KEY"), equalTo("a_value")); + assertThat(ps.getProperty("a_key"), equalTo("a_value")); + assertThat(ps.getProperty("a.key"), equalTo("a_value")); + assertThat(ps.getProperty("a-key"), equalTo("a_value")); + assertThat(ps.getProperty("A_LONG_KEY"), equalTo("a_long_value")); + assertThat(ps.getProperty("A.LONG.KEY"), equalTo("a_long_value")); + assertThat(ps.getProperty("A-LONG-KEY"), equalTo("a_long_value")); + assertThat(ps.getProperty("A.LONG-KEY"), equalTo("a_long_value")); + assertThat(ps.getProperty("A-LONG.KEY"), equalTo("a_long_value")); + assertThat(ps.getProperty("A_long_KEY"), equalTo("a_long_value")); + assertThat(ps.getProperty("A.long.KEY"), equalTo("a_long_value")); + assertThat(ps.getProperty("A-long-KEY"), equalTo("a_long_value")); + assertThat(ps.getProperty("A.long-KEY"), equalTo("a_long_value")); + assertThat(ps.getProperty("A-long.KEY"), equalTo("a_long_value")); + assertThat(ps.getProperty("A_DOT.KEY"), equalTo("a_dot_value")); + assertThat(ps.getProperty("A-DOT.KEY"), equalTo("a_dot_value")); + assertThat(ps.getProperty("A_dot.KEY"), equalTo("a_dot_value")); + assertThat(ps.getProperty("A-dot.KEY"), equalTo("a_dot_value")); + assertThat(ps.getProperty("A_HYPHEN-KEY"), equalTo("a_hyphen_value")); + assertThat(ps.getProperty("A.HYPHEN-KEY"), equalTo("a_hyphen_value")); + assertThat(ps.getProperty("A_hyphen-KEY"), equalTo("a_hyphen_value")); + assertThat(ps.getProperty("A.hyphen-KEY"), equalTo("a_hyphen_value")); } @Test diff --git a/spring-core/src/test/java/org/springframework/tests/BuildTests.java b/spring-core/src/test/java/org/springframework/core/io/support/DummyPackagePrivateFactory.java index ee704806..4c83bfab 100644 --- a/spring-core/src/test/java/org/springframework/tests/BuildTests.java +++ b/spring-core/src/test/java/org/springframework/core/io/support/DummyPackagePrivateFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -14,23 +14,13 @@ * limitations under the License. */ -package org.springframework.tests; - -import org.junit.Test; - -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +package org.springframework.core.io.support; /** - * General build related tests. Part of spring-core to ensure that they run early in the - * build process. - */ -public class BuildTests { + * Used by {@link SpringFactoriesLoaderTests} - @Test - public void javaVersion() throws Exception { - Assume.group(TestGroup.CI); - assertThat("Java Version", JavaVersion.runningVersion(), equalTo(JavaVersion.JAVA_18)); - } + * @author Phillip Webb + */ +class DummyPackagePrivateFactory { } diff --git a/spring-core/src/test/java/org/springframework/core/io/support/ResourceRegionTests.java b/spring-core/src/test/java/org/springframework/core/io/support/ResourceRegionTests.java new file mode 100644 index 00000000..8ee0fef2 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/io/support/ResourceRegionTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.io.support; + +import org.junit.Test; + +import org.springframework.core.io.Resource; + +import static org.mockito.Mockito.*; + +/** + * Unit tests for the {@link ResourceRegion} class. + * + * @author Brian Clozel + */ +public class ResourceRegionTests { + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionWithNullResource() { + new ResourceRegion(null, 0, 1); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionForNegativePosition() { + new ResourceRegion(mock(Resource.class), -1, 1); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionForNegativeCount() { + new ResourceRegion(mock(Resource.class), 0, -1); + } + +} diff --git a/spring-core/src/test/java/org/springframework/core/io/support/SpringFactoriesLoaderTests.java b/spring-core/src/test/java/org/springframework/core/io/support/SpringFactoriesLoaderTests.java new file mode 100644 index 00000000..4de87d2c --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/io/support/SpringFactoriesLoaderTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.io.support; + +import java.lang.reflect.Modifier; +import java.util.List; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Tests for {@link SpringFactoriesLoader}. + * + * @author Arjen Poutsma + * @author Phillip Webb + */ +public class SpringFactoriesLoaderTests { + + @Test + public void loadFactoriesInCorrectOrder() { + List<DummyFactory> factories = SpringFactoriesLoader + .loadFactories(DummyFactory.class, null); + assertEquals(2, factories.size()); + assertTrue(factories.get(0) instanceof MyDummyFactory1); + assertTrue(factories.get(1) instanceof MyDummyFactory2); + } + + @Test(expected = IllegalArgumentException.class) + public void loadInvalid() { + SpringFactoriesLoader.loadFactories(String.class, null); + } + + @Test + public void loadPackagePrivateFactory() throws Exception { + List<DummyPackagePrivateFactory> factories = SpringFactoriesLoader + .loadFactories(DummyPackagePrivateFactory.class, null); + assertEquals(1, factories.size()); + assertTrue((factories.get(0).getClass().getModifiers() & Modifier.PUBLIC) == 0); + } + +} diff --git a/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java b/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java index 5e4083e0..2f377fc0 100644 --- a/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java +++ b/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java @@ -30,6 +30,7 @@ import java.util.Set; import org.junit.Test; +import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; @@ -291,6 +292,7 @@ public class AnnotationMetadataTests { Set<MethodMetadata> methods = metadata.getAnnotatedMethods(DirectAnnotation.class.getName()); MethodMetadata method = methods.iterator().next(); assertEquals("direct", method.getAnnotationAttributes(DirectAnnotation.class.getName()).get("value")); + assertEquals("direct", method.getAnnotationAttributes(DirectAnnotation.class.getName()).get("myValue")); List<Object> allMeta = method.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("value"); assertThat(new HashSet<Object>(allMeta), is(equalTo(new HashSet<Object>(Arrays.asList("direct", "meta"))))); allMeta = method.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("additional"); @@ -416,7 +418,11 @@ public class AnnotationMetadataTests { @Retention(RetentionPolicy.RUNTIME) public @interface DirectAnnotation { - String value(); + @AliasFor("myValue") + String value() default ""; + + @AliasFor("value") + String myValue() default ""; String additional() default "direct"; } @@ -449,7 +455,7 @@ public class AnnotationMetadataTests { } // SPR-10914 - public static enum SubclassEnum { + public enum SubclassEnum { FOO { /* Do not delete! This subclassing is intentional. */ }, @@ -489,14 +495,14 @@ public class AnnotationMetadataTests { @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Component - public static @interface TestConfiguration { + public @interface TestConfiguration { String value() default ""; } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) - public static @interface TestComponentScan { + public @interface TestComponentScan { String[] value() default {}; @@ -509,7 +515,7 @@ public class AnnotationMetadataTests { @TestComponentScan(basePackages = "bogus") @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) - public static @interface ComposedConfigurationWithAttributeOverrides { + public @interface ComposedConfigurationWithAttributeOverrides { String[] basePackages() default {}; } @@ -520,19 +526,19 @@ public class AnnotationMetadataTests { @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) - public static @interface NamedAnnotation1 { + public @interface NamedAnnotation1 { String name() default ""; } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) - public static @interface NamedAnnotation2 { + public @interface NamedAnnotation2 { String name() default ""; } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) - public static @interface NamedAnnotation3 { + public @interface NamedAnnotation3 { String name() default ""; } @@ -547,7 +553,7 @@ public class AnnotationMetadataTests { @NamedAnnotation3(name = "name 3") @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) - public static @interface NamedComposedAnnotation { + public @interface NamedComposedAnnotation { } @NamedComposedAnnotation diff --git a/spring-core/src/test/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitorMemberClassTests.java b/spring-core/src/test/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitorMemberClassTests.java index cc4ca072..5553f170 100644 --- a/spring-core/src/test/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitorMemberClassTests.java +++ b/spring-core/src/test/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitorMemberClassTests.java @@ -35,7 +35,8 @@ public class ClassMetadataReadingVisitorMemberClassTests MetadataReader reader = new SimpleMetadataReaderFactory().getMetadataReader(clazz.getName()); return reader.getAnnotationMetadata(); - } catch (IOException ex) { + } + catch (IOException ex) { throw new IllegalStateException(ex); } } diff --git a/spring-core/src/test/java/org/springframework/tests/Assume.java b/spring-core/src/test/java/org/springframework/tests/Assume.java index e7923851..cd26d368 100644 --- a/spring-core/src/test/java/org/springframework/tests/Assume.java +++ b/spring-core/src/test/java/org/springframework/tests/Assume.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -21,7 +21,6 @@ import java.lang.reflect.Method; import java.util.Set; import org.apache.commons.logging.Log; - import org.junit.AssumptionViolatedException; import org.springframework.util.ClassUtils; @@ -33,38 +32,8 @@ import static org.junit.Assume.*; * conditions hold {@code true}. If the assumption fails, it means the test should be * skipped. * - * <p>For example, if a set of tests require at least JDK 1.7 it can use - * {@code Assume#atLeast(JavaVersion.JAVA_17)} as shown below: - * - * <pre class="code"> - * public void MyTests { - * - * @BeforeClass - * public static void assumptions() { - * Assume.atLeast(JavaVersion.JAVA_17); - * } - * - * // ... all the test methods that require at least JDK 1.7 - * } - * </pre> - * - * If only a single test requires at least JDK 1.7 it can use the - * {@code Assume#atLeast(JavaVersion.JAVA_17)} as shown below: - * - * <pre class="code"> - * public void MyTests { - * - * @Test - * public void requiresJdk17 { - * Assume.atLeast(JavaVersion.JAVA_17); - * // ... perform the actual test - * } - * } - * </pre> - * - * In addition to assumptions based on the JDK version, tests can be categorized into - * {@link TestGroup}s. Active groups are enabled using the 'testGroups' system property, - * usually activated from the gradle command line: + * Tests can be categorized into {@link TestGroup}s. Active groups are enabled using + * the 'testGroups' system property, usually activated from the gradle command line: * <pre> * gradle test -PtestGroups="performance" * </pre> @@ -76,7 +45,6 @@ import static org.junit.Assume.*; * @author Phillip Webb * @author Sam Brannen * @since 3.2 - * @see #atLeast(JavaVersion) * @see #group(TestGroup) * @see #group(TestGroup, Executable) */ @@ -86,26 +54,13 @@ public abstract class Assume { /** - * Assume that a minimum {@link JavaVersion} is running. - * @param version the minimum version for the test to run - * @throws AssumptionViolatedException if the assumption fails - */ - public static void atLeast(JavaVersion version) { - if (!JavaVersion.runningVersion().isAtLeast(version)) { - throw new AssumptionViolatedException("Requires JDK " + version + " but running " - + JavaVersion.runningVersion()); - } - } - - /** * Assume that a particular {@link TestGroup} has been specified. * @param group the group that must be specified * @throws AssumptionViolatedException if the assumption fails */ public static void group(TestGroup group) { if (!GROUPS.contains(group)) { - throw new AssumptionViolatedException("Requires unspecified group " + group - + " from " + GROUPS); + throw new AssumptionViolatedException("Requires unspecified group " + group + " from " + GROUPS); } } @@ -154,11 +109,12 @@ public abstract class Assume { } } + /** * @since 4.2 */ - @FunctionalInterface - public static interface Executable { + public interface Executable { + void execute() throws Exception; } diff --git a/spring-core/src/test/java/org/springframework/tests/JavaVersion.java b/spring-core/src/test/java/org/springframework/tests/JavaVersion.java deleted file mode 100644 index ac4e78ad..00000000 --- a/spring-core/src/test/java/org/springframework/tests/JavaVersion.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2002-2015 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.tests; - -/** - * Enumeration of known JDK versions. - * - * @author Phillip Webb - * @see #runningVersion() - */ -public enum JavaVersion { - - /** - * Java 1.6 - */ - JAVA_16("1.6", 16), - - /** - * Java 1.7 - */ - JAVA_17("1.7", 17), - - /** - * Java 1.8 - */ - JAVA_18("1.8", 18), - - /** - * Java 1.9 - */ - JAVA_19("1.9", 19); - - - private static final JavaVersion runningVersion = findRunningVersion(); - - private static JavaVersion findRunningVersion() { - String version = System.getProperty("java.version"); - for (JavaVersion candidate : values()) { - if (version.startsWith(candidate.version)) { - return candidate; - } - } - return JavaVersion.JAVA_16; - } - - - private String version; - - private int value; - - - private JavaVersion(String version, int value) { - this.version = version; - this.value = value; - } - - - @Override - public String toString() { - return version; - } - - /** - * Determines if the specified version is the same as or greater than this version. - * @param version the version to check - * @return {@code true} if the specified version is at least this version - */ - public boolean isAtLeast(JavaVersion version) { - return (this.value >= version.value); - } - - /** - * Returns the current running JDK version. If the current version cannot be - * determined {@link #JAVA_16} will be returned. - * @return the JDK version - */ - public static JavaVersion runningVersion() { - return runningVersion; - } - -} diff --git a/spring-core/src/test/java/org/springframework/tests/JavaVersionTests.java b/spring-core/src/test/java/org/springframework/tests/JavaVersionTests.java deleted file mode 100644 index 05c341fb..00000000 --- a/spring-core/src/test/java/org/springframework/tests/JavaVersionTests.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2002-2015 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.tests; - -import org.junit.Test; - -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; - -/** - * Tests for {@link JavaVersion}. - * - * @author Phillip Webb - */ -public class JavaVersionTests { - - @Test - public void runningVersion() { - assertNotNull(JavaVersion.runningVersion()); - assertThat(System.getProperty("java.version"), startsWith(JavaVersion.runningVersion().toString())); - } - - @Test - public void isAtLeast() throws Exception { - assertTrue(JavaVersion.JAVA_16.isAtLeast(JavaVersion.JAVA_16)); - assertFalse(JavaVersion.JAVA_16.isAtLeast(JavaVersion.JAVA_17)); - } - -} diff --git a/spring-core/src/test/java/org/springframework/tests/TestGroup.java b/spring-core/src/test/java/org/springframework/tests/TestGroup.java index f7b8525b..9a10ce12 100644 --- a/spring-core/src/test/java/org/springframework/tests/TestGroup.java +++ b/spring-core/src/test/java/org/springframework/tests/TestGroup.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -58,14 +58,7 @@ public enum TestGroup { /** * Tests that should only be run on the continuous integration server. */ - CI, - - /** - * Tests that require custom compilation beyond that of the standard JDK. This helps to - * allow running tests that will otherwise fail when using JDK > 1.8 b88. See - * <a href="https://jira.spring.io/browse/SPR-10558">SPR-10558</a> - */ - CUSTOM_COMPILATION; + CI; /** diff --git a/spring-core/src/test/java/org/springframework/tests/TestGroupTests.java b/spring-core/src/test/java/org/springframework/tests/TestGroupTests.java index f756bb31..291fba3a 100644 --- a/spring-core/src/test/java/org/springframework/tests/TestGroupTests.java +++ b/spring-core/src/test/java/org/springframework/tests/TestGroupTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -38,6 +38,7 @@ public class TestGroupTests { @Rule public ExpectedException thrown = ExpectedException.none(); + @Test public void parseNull() throws Exception { assertThat(TestGroup.parse(null), equalTo(Collections.<TestGroup> emptySet())); @@ -65,7 +66,7 @@ public class TestGroupTests { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Unable to find test group 'missing' when parsing " + "testGroups value: 'performance, missing'. Available groups include: " + - "[LONG_RUNNING,PERFORMANCE,JMXMP,CI,CUSTOM_COMPILATION]"); + "[LONG_RUNNING,PERFORMANCE,JMXMP,CI]"); TestGroup.parse("performance, missing"); } @@ -77,9 +78,8 @@ public class TestGroupTests { @Test public void parseAllExcept() throws Exception { Set<TestGroup> expected = new HashSet<TestGroup>(EnumSet.allOf(TestGroup.class)); - expected.remove(TestGroup.CUSTOM_COMPILATION); expected.remove(TestGroup.PERFORMANCE); - assertThat(TestGroup.parse("all-custom_compilation,performance"), equalTo(expected)); + assertThat(TestGroup.parse("all-performance"), equalTo(expected)); } } diff --git a/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java b/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java index 29a2486a..d810c330 100644 --- a/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java +++ b/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -53,6 +53,7 @@ public class AntPathMatcherTests { // test exact matching assertTrue(pathMatcher.match("test", "test")); assertTrue(pathMatcher.match("/test", "/test")); + assertTrue(pathMatcher.match("http://example.org", "http://example.org")); // SPR-14141 assertFalse(pathMatcher.match("/test.jpg", "test.jpg")); assertFalse(pathMatcher.match("test", "/test")); assertFalse(pathMatcher.match("/test", "test")); @@ -128,11 +129,21 @@ public class AntPathMatcherTests { assertFalse(pathMatcher.match("/x/x/**/bla", "/x/x/x/")); + assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")) ; + assertTrue(pathMatcher.match("", "")); assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); } + // SPR-14247 + @Test + public void matchWithTrimTokensEnabled() throws Exception { + pathMatcher.setTrimTokens(true); + + assertTrue(pathMatcher.match("/foo/bar", "/foo /bar")); + } + @Test public void withMatchStart() { // test exact matching @@ -418,8 +429,8 @@ public class AntPathMatcherTests { assertEquals("/*.html", pathMatcher.combine("/**", "/*.html")); assertEquals("/*.html", pathMatcher.combine("/*", "/*.html")); assertEquals("/*.html", pathMatcher.combine("/*.*", "/*.html")); - assertEquals("/{foo}/bar", pathMatcher.combine("/{foo}", "/bar")); // SPR-8858 - assertEquals("/user/user", pathMatcher.combine("/user", "/user")); // SPR-7970 + assertEquals("/{foo}/bar", pathMatcher.combine("/{foo}", "/bar")); // SPR-8858 + assertEquals("/user/user", pathMatcher.combine("/user", "/user")); // SPR-7970 assertEquals("/{foo:.*[^0-9].*}/edit/", pathMatcher.combine("/{foo:.*[^0-9].*}", "/edit/")); // SPR-10062 assertEquals("/1.0/foo/test", pathMatcher.combine("/1.0", "/foo/test")); // SPR-10554 assertEquals("/hotel", pathMatcher.combine("/", "/hotel")); // SPR-12975 @@ -454,8 +465,8 @@ public class AntPathMatcherTests { // SPR-10550 assertEquals(-1, comparator.compare("/hotels/{hotel}/bookings/{booking}/cutomers/{customer}", "/**")); - assertEquals(1, comparator.compare("/**","/hotels/{hotel}/bookings/{booking}/cutomers/{customer}")); - assertEquals(0, comparator.compare("/**","/**")); + assertEquals(1, comparator.compare("/**", "/hotels/{hotel}/bookings/{booking}/cutomers/{customer}")); + assertEquals(0, comparator.compare("/**", "/**")); assertEquals(-1, comparator.compare("/hotels/{hotel}", "/hotels/*")); assertEquals(1, comparator.compare("/hotels/*", "/hotels/{hotel}")); @@ -618,13 +629,45 @@ public class AntPathMatcherTests { assertTrue(pathMatcher.stringMatcherCache.size() > 20); for (int i = 0; i < 65536; i++) { - pathMatcher.match("test" + i, "test"); + pathMatcher.match("test" + i, "test" + i); } // Cache keeps being alive due to the explicit cache setting assertTrue(pathMatcher.stringMatcherCache.size() > 65536); } @Test + public void preventCreatingStringMatchersIfPathDoesNotStartsWithPatternPrefix() { + pathMatcher.setCachePatterns(true); + assertEquals(0, pathMatcher.stringMatcherCache.size()); + + pathMatcher.match("test?", "test"); + assertEquals(1, pathMatcher.stringMatcherCache.size()); + + pathMatcher.match("test?", "best"); + pathMatcher.match("test/*", "view/test.jpg"); + pathMatcher.match("test/**/test.jpg", "view/test.jpg"); + pathMatcher.match("test/{name}.jpg", "view/test.jpg"); + assertEquals(1, pathMatcher.stringMatcherCache.size()); + } + + @Test + public void creatingStringMatchersIfPatternPrefixCannotDetermineIfPathMatch() { + pathMatcher.setCachePatterns(true); + assertEquals(0, pathMatcher.stringMatcherCache.size()); + + pathMatcher.match("test", "testian"); + pathMatcher.match("test?", "testFf"); + pathMatcher.match("test/*", "test/dir/name.jpg"); + pathMatcher.match("test/{name}.jpg", "test/lorem.jpg"); + pathMatcher.match("bla/**/test.jpg", "bla/test.jpg"); + pathMatcher.match("**/{name}.jpg", "test/lorem.jpg"); + pathMatcher.match("/**/{name}.jpg", "/test/lorem.jpg"); + pathMatcher.match("/*/dir/{name}.jpg", "/*/dir/lorem.jpg"); + + assertEquals(7, pathMatcher.stringMatcherCache.size()); + } + + @Test public void cachePatternsSetToFalse() { pathMatcher.setCachePatterns(false); match(); diff --git a/spring-core/src/test/java/org/springframework/util/AutoPopulatingListTests.java b/spring-core/src/test/java/org/springframework/util/AutoPopulatingListTests.java index ba0980b6..21ce9562 100644 --- a/spring-core/src/test/java/org/springframework/util/AutoPopulatingListTests.java +++ b/spring-core/src/test/java/org/springframework/util/AutoPopulatingListTests.java @@ -74,9 +74,9 @@ public class AutoPopulatingListTests { private void doTestWithElementFactory(AutoPopulatingList<Object> list) { doTestWithClass(list); - for(int x = 0; x < list.size(); x++) { + for (int x = 0; x < list.size(); x++) { Object element = list.get(x); - if(element instanceof TestObject) { + if (element instanceof TestObject) { assertEquals(x, ((TestObject) element).getAge()); } } diff --git a/spring-core/src/test/java/org/springframework/util/DigestUtilsTests.java b/spring-core/src/test/java/org/springframework/util/DigestUtilsTests.java index 6842bfee..5db3ea51 100644 --- a/spring-core/src/test/java/org/springframework/util/DigestUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/DigestUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -16,6 +16,8 @@ package org.springframework.util; +import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.UnsupportedEncodingException; import org.junit.Before; @@ -25,6 +27,7 @@ import static org.junit.Assert.*; /** * @author Arjen Poutsma + * @author Juergen Hoeller */ public class DigestUtilsTests { @@ -38,24 +41,39 @@ public class DigestUtilsTests { @Test - public void md5() { - byte[] result = DigestUtils.md5Digest(bytes); + public void md5() throws IOException { byte[] expected = new byte[] {-0x4f, 0xa, -0x73, -0x4f, 0x64, -0x20, 0x75, 0x41, 0x5, -0x49, -0x57, -0x65, -0x19, 0x2e, 0x3f, -0x1b}; + + byte[] result = DigestUtils.md5Digest(bytes); + assertArrayEquals("Invalid hash", expected, result); + + result = DigestUtils.md5Digest(new ByteArrayInputStream(bytes)); assertArrayEquals("Invalid hash", expected, result); } @Test - public void md5Hex() throws UnsupportedEncodingException { + public void md5Hex() throws IOException { + String expected = "b10a8db164e0754105b7a99be72e3fe5"; + String hash = DigestUtils.md5DigestAsHex(bytes); - assertEquals("Invalid hash", "b10a8db164e0754105b7a99be72e3fe5", hash); + assertEquals("Invalid hash", expected, hash); + + hash = DigestUtils.md5DigestAsHex(new ByteArrayInputStream(bytes)); + assertEquals("Invalid hash", expected, hash); } @Test - public void md5StringBuilder() throws UnsupportedEncodingException { + public void md5StringBuilder() throws IOException { + String expected = "b10a8db164e0754105b7a99be72e3fe5"; + StringBuilder builder = new StringBuilder(); DigestUtils.appendMd5DigestAsHex(bytes, builder); - assertEquals("Invalid hash", "b10a8db164e0754105b7a99be72e3fe5", builder.toString()); + assertEquals("Invalid hash", expected, builder.toString()); + + builder = new StringBuilder(); + DigestUtils.appendMd5DigestAsHex(new ByteArrayInputStream(bytes), builder); + assertEquals("Invalid hash", expected, builder.toString()); } } diff --git a/spring-core/src/test/java/org/springframework/util/LinkedCaseInsensitiveMapTests.java b/spring-core/src/test/java/org/springframework/util/LinkedCaseInsensitiveMapTests.java index 4f4492ef..16e3e29c 100644 --- a/spring-core/src/test/java/org/springframework/util/LinkedCaseInsensitiveMapTests.java +++ b/spring-core/src/test/java/org/springframework/util/LinkedCaseInsensitiveMapTests.java @@ -74,4 +74,26 @@ public class LinkedCaseInsensitiveMapTests { assertEquals("N", map.getOrDefault(new Object(), "N")); } + @Test + @SuppressWarnings("unchecked") + public void mapClone() { + map.put("key", "value1"); + LinkedCaseInsensitiveMap<String> copy = (LinkedCaseInsensitiveMap<String>) map.clone(); + assertEquals("value1", map.get("key")); + assertEquals("value1", map.get("KEY")); + assertEquals("value1", map.get("Key")); + assertEquals("value1", copy.get("key")); + assertEquals("value1", copy.get("KEY")); + assertEquals("value1", copy.get("Key")); + copy.put("Key", "value2"); + assertEquals(1, map.size()); + assertEquals(1, copy.size()); + assertEquals("value1", map.get("key")); + assertEquals("value1", map.get("KEY")); + assertEquals("value1", map.get("Key")); + assertEquals("value2", copy.get("key")); + assertEquals("value2", copy.get("KEY")); + assertEquals("value2", copy.get("Key")); + } + } diff --git a/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java b/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java index b9d3480f..99c5a826 100644 --- a/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java +++ b/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -75,7 +75,7 @@ public class MimeTypeTests { MimeType mimeType = MimeType.valueOf(s); assertEquals("Invalid type", "text", mimeType.getType()); assertEquals("Invalid subtype", "html", mimeType.getSubtype()); - assertEquals("Invalid charset", Charset.forName("ISO-8859-1"), mimeType.getCharSet()); + assertEquals("Invalid charset", Charset.forName("ISO-8859-1"), mimeType.getCharset()); } @Test @@ -84,7 +84,7 @@ public class MimeTypeTests { MimeType mimeType = MimeType.valueOf(s); assertEquals("Invalid type", "application", mimeType.getType()); assertEquals("Invalid subtype", "xml", mimeType.getSubtype()); - assertEquals("Invalid charset", Charset.forName("UTF-8"), mimeType.getCharSet()); + assertEquals("Invalid charset", Charset.forName("UTF-8"), mimeType.getCharset()); } @Test @@ -190,6 +190,11 @@ public class MimeTypeTests { } @Test(expected = InvalidMimeTypeException.class) + public void parseMimeTypeMissingTypeAndSubtype() throws Exception { + MimeTypeUtils.parseMimeType(" ;a=b"); + } + + @Test(expected = InvalidMimeTypeException.class) public void parseMimeTypeEmptyParameterAttribute() { MimeTypeUtils.parseMimeType("audio/*;=value"); } @@ -263,13 +268,13 @@ public class MimeTypeTests { assertTrue("Invalid comparison result", audioBasicLevel.compareTo(audio) > 0); - List<MimeType> expected = new ArrayList<MimeType>(); + List<MimeType> expected = new ArrayList<>(); expected.add(audio); expected.add(audioBasic); expected.add(audioBasicLevel); expected.add(audioWave); - List<MimeType> result = new ArrayList<MimeType>(expected); + List<MimeType> result = new ArrayList<>(expected); Random rnd = new Random(); // shuffle & sort 10 times for (int i = 0; i < 10; i++) { diff --git a/spring-core/src/test/java/org/springframework/util/NumberUtilsTests.java b/spring-core/src/test/java/org/springframework/util/NumberUtilsTests.java index d6fbc9b0..2584cf42 100644 --- a/spring-core/src/test/java/org/springframework/util/NumberUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/NumberUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -328,8 +328,6 @@ public class NumberUtilsTests { assertEquals(Integer.valueOf(Integer.MIN_VALUE), NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(Integer.MAX_VALUE + 1), Integer.class)); assertEquals(Integer.valueOf(Integer.MIN_VALUE), NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(Integer.MIN_VALUE), Integer.class)); assertEquals(Integer.valueOf(Integer.MAX_VALUE), NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(Integer.MIN_VALUE - 1), Integer.class)); - assertToNumberOverflow(BigInteger.valueOf(Integer.MAX_VALUE).add(BigInteger.ONE), Integer.class); - assertToNumberOverflow(BigInteger.valueOf(Integer.MIN_VALUE).subtract(BigInteger.ONE), Integer.class); assertEquals(Integer.valueOf(Integer.valueOf(-1)), NumberUtils.convertNumberToTargetClass(Long.valueOf(-1), Integer.class)); assertEquals(Integer.valueOf(Integer.valueOf(0)), NumberUtils.convertNumberToTargetClass(Long.valueOf(0), Integer.class)); @@ -338,8 +336,6 @@ public class NumberUtilsTests { assertEquals(Integer.valueOf(Integer.MIN_VALUE), NumberUtils.convertNumberToTargetClass(Long.valueOf(Integer.MAX_VALUE + 1), Integer.class)); assertEquals(Integer.valueOf(Integer.MIN_VALUE), NumberUtils.convertNumberToTargetClass(Long.valueOf(Integer.MIN_VALUE), Integer.class)); assertEquals(Integer.valueOf(Integer.MAX_VALUE), NumberUtils.convertNumberToTargetClass(Long.valueOf(Integer.MIN_VALUE - 1), Integer.class)); - assertToNumberOverflow(Long.valueOf(Long.MAX_VALUE + 1), Integer.class); - assertToNumberOverflow(Long.valueOf(Long.MIN_VALUE - 1), Integer.class); assertEquals(Integer.valueOf(Integer.valueOf(-1)), NumberUtils.convertNumberToTargetClass(Integer.valueOf(-1), Integer.class)); assertEquals(Integer.valueOf(Integer.valueOf(0)), NumberUtils.convertNumberToTargetClass(Integer.valueOf(0), Integer.class)); @@ -364,6 +360,12 @@ public class NumberUtilsTests { assertEquals(Integer.valueOf(Byte.MIN_VALUE), NumberUtils.convertNumberToTargetClass(Byte.valueOf((byte) (Byte.MAX_VALUE + 1)), Integer.class)); assertEquals(Integer.valueOf(Byte.MIN_VALUE), NumberUtils.convertNumberToTargetClass(Byte.valueOf(Byte.MIN_VALUE), Integer.class)); assertEquals(Integer.valueOf(Byte.MAX_VALUE), NumberUtils.convertNumberToTargetClass(Byte.valueOf((byte) (Byte.MIN_VALUE - 1)), Integer.class)); + + assertToNumberOverflow(Long.valueOf(Long.MAX_VALUE + 1), Integer.class); + assertToNumberOverflow(Long.valueOf(Long.MIN_VALUE - 1), Integer.class); + assertToNumberOverflow(BigInteger.valueOf(Integer.MAX_VALUE).add(BigInteger.ONE), Integer.class); + assertToNumberOverflow(BigInteger.valueOf(Integer.MIN_VALUE).subtract(BigInteger.ONE), Integer.class); + assertToNumberOverflow(new BigDecimal("18446744073709551611"), Integer.class); } @Test @@ -376,9 +378,6 @@ public class NumberUtilsTests { assertEquals(Long.valueOf(Long.MIN_VALUE), NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(Long.MIN_VALUE), Long.class)); assertEquals(Long.valueOf(Long.MAX_VALUE), NumberUtils.convertNumberToTargetClass(BigInteger.valueOf(Long.MIN_VALUE - 1), Long.class)); - assertToNumberOverflow(BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE), Long.class); - assertToNumberOverflow(BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.ONE), Long.class); - assertEquals(Long.valueOf(Long.valueOf(-1)), NumberUtils.convertNumberToTargetClass(Long.valueOf(-1), Long.class)); assertEquals(Long.valueOf(Long.valueOf(0)), NumberUtils.convertNumberToTargetClass(Long.valueOf(0), Long.class)); assertEquals(Long.valueOf(Long.valueOf(1)), NumberUtils.convertNumberToTargetClass(Long.valueOf(1), Long.class)); @@ -410,6 +409,10 @@ public class NumberUtilsTests { assertEquals(Long.valueOf(Byte.MIN_VALUE), NumberUtils.convertNumberToTargetClass(Byte.valueOf((byte) (Byte.MAX_VALUE + 1)), Long.class)); assertEquals(Long.valueOf(Byte.MIN_VALUE), NumberUtils.convertNumberToTargetClass(Byte.valueOf(Byte.MIN_VALUE), Long.class)); assertEquals(Long.valueOf(Byte.MAX_VALUE), NumberUtils.convertNumberToTargetClass(Byte.valueOf((byte) (Byte.MIN_VALUE - 1)), Long.class)); + + assertToNumberOverflow(BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE), Long.class); + assertToNumberOverflow(BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.ONE), Long.class); + assertToNumberOverflow(new BigDecimal("18446744073709551611"), Long.class); } diff --git a/spring-core/src/test/java/org/springframework/util/ResizableByteArrayOutputStreamTests.java b/spring-core/src/test/java/org/springframework/util/ResizableByteArrayOutputStreamTests.java index ec41e815..709b3fb0 100644 --- a/spring-core/src/test/java/org/springframework/util/ResizableByteArrayOutputStreamTests.java +++ b/spring-core/src/test/java/org/springframework/util/ResizableByteArrayOutputStreamTests.java @@ -54,7 +54,7 @@ public class ResizableByteArrayOutputStreamTests { @Test public void autoGrow() { assertEquals(INITIAL_CAPACITY, this.baos.capacity()); - for(int i = 0; i < 129; i++) { + for (int i = 0; i < 129; i++) { this.baos.write(0); } assertEquals(256, this.baos.capacity()); diff --git a/spring-core/src/test/java/org/springframework/util/StreamUtilsTests.java b/spring-core/src/test/java/org/springframework/util/StreamUtilsTests.java index 93295cad..48f173f2 100644 --- a/spring-core/src/test/java/org/springframework/util/StreamUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/StreamUtilsTests.java @@ -21,6 +21,7 @@ import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; +import java.util.Arrays; import java.util.Random; import java.util.UUID; @@ -94,6 +95,15 @@ public class StreamUtilsTests { } @Test + public void copyRange() throws Exception { + ByteArrayOutputStream out = spy(new ByteArrayOutputStream()); + StreamUtils.copyRange(new ByteArrayInputStream(bytes), out, 0, 100); + byte[] range = Arrays.copyOfRange(bytes, 0, 101); + assertThat(out.toByteArray(), equalTo(range)); + verify(out, never()).close(); + } + + @Test public void nonClosingInputStream() throws Exception { InputStream source = mock(InputStream.class); InputStream nonClosing = StreamUtils.nonClosing(source); diff --git a/spring-core/src/test/java/org/springframework/util/xml/AbstractStaxHandlerTestCase.java b/spring-core/src/test/java/org/springframework/util/xml/AbstractStaxHandlerTestCase.java index 8dd82430..e23c92cc 100644 --- a/spring-core/src/test/java/org/springframework/util/xml/AbstractStaxHandlerTestCase.java +++ b/spring-core/src/test/java/org/springframework/util/xml/AbstractStaxHandlerTestCase.java @@ -82,7 +82,7 @@ public abstract class AbstractStaxHandlerTestCase { private static boolean wwwSpringframeworkOrgIsAccessible() { try { - new Socket("www.springframework.org", 80); + new Socket("www.springframework.org", 80).close(); } catch (Exception e) { return false; diff --git a/spring-core/src/test/java/org/springframework/util/xml/AbstractStaxXMLReaderTestCase.java b/spring-core/src/test/java/org/springframework/util/xml/AbstractStaxXMLReaderTestCase.java index 9dec693d..41aa5f25 100644 --- a/spring-core/src/test/java/org/springframework/util/xml/AbstractStaxXMLReaderTestCase.java +++ b/spring-core/src/test/java/org/springframework/util/xml/AbstractStaxXMLReaderTestCase.java @@ -194,8 +194,8 @@ public abstract class AbstractStaxXMLReaderTestCase { private static class SkipLocatorArgumentsAdapter implements InvocationArgumentsAdapter { @Override public Object[] adaptArguments(Object[] arguments) { - for(int i=0; i<arguments.length; i++) { - if(arguments[i] instanceof Locator) { + for (int i = 0; i < arguments.length; i++) { + if (arguments[i] instanceof Locator) { arguments[i] = null; } } @@ -206,7 +206,7 @@ public abstract class AbstractStaxXMLReaderTestCase { private static class CharArrayToStringAdapter implements InvocationArgumentsAdapter { @Override public Object[] adaptArguments(Object[] arguments) { - if(arguments.length == 3 && arguments[0] instanceof char[] + if (arguments.length == 3 && arguments[0] instanceof char[] && arguments[1] instanceof Integer && arguments[2] instanceof Integer) { return new Object[] {new String((char[]) arguments[0], (Integer) arguments[1], (Integer) arguments[2])}; } @@ -218,7 +218,7 @@ public abstract class AbstractStaxXMLReaderTestCase { @Override public Object[] adaptArguments(Object[] arguments) { for (int i = 0; i < arguments.length; i++) { - if(arguments[i] instanceof Attributes) { + if (arguments[i] instanceof Attributes) { arguments[i] = new PartialAttributes((Attributes) arguments[i]); } }; |