summaryrefslogtreecommitdiff
path: root/spring-core/src/main/java
diff options
context:
space:
mode:
authorEmmanuel Bourg <ebourg@apache.org>2014-12-03 14:31:16 +0100
committerEmmanuel Bourg <ebourg@apache.org>2014-12-03 14:31:16 +0100
commitc56370beb0a2bfa263e125fce107dceccee89fd3 (patch)
tree7ee611ceb0acbbdf7f83abcd72adb854b7d77225 /spring-core/src/main/java
parentaa5221b73661fa728dc4e62e1230e9104528c4eb (diff)
Imported Upstream version 3.2.12
Diffstat (limited to 'spring-core/src/main/java')
-rw-r--r--spring-core/src/main/java/org/springframework/asm/SpringAsmInfo.java37
-rw-r--r--spring-core/src/main/java/org/springframework/asm/package-info.java18
-rw-r--r--spring-core/src/main/java/org/springframework/asm/util/TraceClassVisitor.java37
-rw-r--r--spring-core/src/main/java/org/springframework/asm/util/package-info.java22
-rw-r--r--spring-core/src/main/java/org/springframework/cglib/SpringCglibInfo.java31
-rw-r--r--spring-core/src/main/java/org/springframework/cglib/core/SpringNamingPolicy.java39
-rw-r--r--spring-core/src/main/java/org/springframework/cglib/package-info.java30
-rw-r--r--spring-core/src/main/java/org/springframework/cglib/transform/impl/MemorySafeUndeclaredThrowableStrategy.java57
-rw-r--r--spring-core/src/main/java/org/springframework/core/AliasRegistry.java59
-rw-r--r--spring-core/src/main/java/org/springframework/core/AttributeAccessor.java67
-rw-r--r--spring-core/src/main/java/org/springframework/core/AttributeAccessorSupport.java102
-rw-r--r--spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java229
-rw-r--r--spring-core/src/main/java/org/springframework/core/CollectionFactory.java346
-rw-r--r--spring-core/src/main/java/org/springframework/core/ConcurrentMap.java46
-rw-r--r--spring-core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java149
-rw-r--r--spring-core/src/main/java/org/springframework/core/ConstantException.java50
-rw-r--r--spring-core/src/main/java/org/springframework/core/Constants.java336
-rw-r--r--spring-core/src/main/java/org/springframework/core/ControlFlow.java50
-rw-r--r--spring-core/src/main/java/org/springframework/core/ControlFlowFactory.java119
-rw-r--r--spring-core/src/main/java/org/springframework/core/Conventions.java302
-rw-r--r--spring-core/src/main/java/org/springframework/core/DecoratingClassLoader.java106
-rw-r--r--spring-core/src/main/java/org/springframework/core/ErrorCoded.java39
-rw-r--r--spring-core/src/main/java/org/springframework/core/ExceptionDepthComparator.java96
-rw-r--r--spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java483
-rw-r--r--spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java512
-rw-r--r--spring-core/src/main/java/org/springframework/core/InfrastructureProxy.java43
-rw-r--r--spring-core/src/main/java/org/springframework/core/JdkVersion.java156
-rw-r--r--spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java272
-rw-r--r--spring-core/src/main/java/org/springframework/core/MethodParameter.java455
-rw-r--r--spring-core/src/main/java/org/springframework/core/NamedInheritableThreadLocal.java48
-rw-r--r--spring-core/src/main/java/org/springframework/core/NamedThreadLocal.java48
-rw-r--r--spring-core/src/main/java/org/springframework/core/NestedCheckedException.java140
-rw-r--r--spring-core/src/main/java/org/springframework/core/NestedExceptionUtils.java55
-rw-r--r--spring-core/src/main/java/org/springframework/core/NestedIOException.java77
-rw-r--r--spring-core/src/main/java/org/springframework/core/NestedRuntimeException.java141
-rw-r--r--spring-core/src/main/java/org/springframework/core/OrderComparator.java100
-rw-r--r--spring-core/src/main/java/org/springframework/core/Ordered.java64
-rw-r--r--spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java155
-rw-r--r--spring-core/src/main/java/org/springframework/core/ParameterNameDiscoverer.java54
-rw-r--r--spring-core/src/main/java/org/springframework/core/ParameterizedTypeReference.java91
-rw-r--r--spring-core/src/main/java/org/springframework/core/PrioritizedParameterNameDiscoverer.java71
-rw-r--r--spring-core/src/main/java/org/springframework/core/PriorityOrdered.java42
-rw-r--r--spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java178
-rw-r--r--spring-core/src/main/java/org/springframework/core/SmartClassLoader.java43
-rw-r--r--spring-core/src/main/java/org/springframework/core/SpringProperties.java134
-rw-r--r--spring-core/src/main/java/org/springframework/core/SpringVersion.java44
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java158
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java89
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java518
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/Order.java53
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/package-info.java8
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java128
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java82
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/ClassDescriptor.java62
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/ConversionException.java47
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/ConversionFailedException.java74
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/ConversionService.java90
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/ConverterNotFoundException.java59
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/FieldDescriptor.java82
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java77
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/Property.java266
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java703
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/converter/ConditionalConverter.java54
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/converter/ConditionalGenericConverter.java34
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/converter/Converter.java41
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/converter/ConverterFactory.java41
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/converter/ConverterRegistry.java62
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/converter/ConvertingComparator.java143
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/converter/GenericConverter.java120
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/converter/package-info.java8
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/package-info.java8
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java68
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ArrayToCollectionConverter.java80
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ArrayToObjectConverter.java63
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ArrayToStringConverter.java58
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/CharacterToNumberFactory.java60
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/CollectionToArrayConverter.java69
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java95
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/CollectionToObjectConverter.java64
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/CollectionToStringConverter.java72
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ConfigurableConversionService.java39
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ConversionServiceFactory.java81
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java68
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ConvertingPropertyEditorAdapter.java72
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java119
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java53
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.java55
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java618
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/IdToEntityConverter.java107
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/MapToMapConverter.java134
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/NumberToCharacterConverter.java41
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/NumberToNumberConverterFactory.java67
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ObjectToArrayConverter.java60
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ObjectToCollectionConverter.java68
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java99
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ObjectToStringConverter.java33
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/PropertiesToStringConverter.java46
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToArrayConverter.java66
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToBooleanConverter.java66
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToCharacterConverter.java40
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToCollectionConverter.java78
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java60
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToLocaleConverter.java36
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToNumberConverterFactory.java63
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToPropertiesConverter.java46
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToUUIDConverter.java39
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/package-info.java8
-rw-r--r--spring-core/src/main/java/org/springframework/core/enums/AbstractCachingLabeledEnumResolver.java139
-rw-r--r--spring-core/src/main/java/org/springframework/core/enums/AbstractGenericLabeledEnum.java55
-rw-r--r--spring-core/src/main/java/org/springframework/core/enums/AbstractLabeledEnum.java77
-rw-r--r--spring-core/src/main/java/org/springframework/core/enums/LabeledEnum.java96
-rw-r--r--spring-core/src/main/java/org/springframework/core/enums/LabeledEnumResolver.java71
-rw-r--r--spring-core/src/main/java/org/springframework/core/enums/LetterCodedLabeledEnum.java66
-rw-r--r--spring-core/src/main/java/org/springframework/core/enums/ShortCodedLabeledEnum.java62
-rw-r--r--spring-core/src/main/java/org/springframework/core/enums/StaticLabeledEnum.java103
-rw-r--r--spring-core/src/main/java/org/springframework/core/enums/StaticLabeledEnumResolver.java75
-rw-r--r--spring-core/src/main/java/org/springframework/core/enums/StringCodedLabeledEnum.java67
-rw-r--r--spring-core/src/main/java/org/springframework/core/enums/package-info.java9
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java535
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java206
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/CommandLineArgs.java91
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/CommandLinePropertySource.java298
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/CompositePropertySource.java65
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java172
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java104
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/EnumerablePropertySource.java88
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/Environment.java111
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/EnvironmentCapable.java48
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/JOptCommandLinePropertySource.java107
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/MapPropertySource.java47
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/MissingRequiredPropertiesException.java56
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/MutablePropertySources.java234
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/PropertiesPropertySource.java42
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java122
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/PropertySource.java250
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/PropertySources.java39
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java167
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java105
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/SimpleCommandLineArgsParser.java86
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/SimpleCommandLinePropertySource.java113
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/StandardEnvironment.java82
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java129
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/package-info.java7
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java222
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/AbstractResource.java209
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/ByteArrayResource.java127
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java256
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/ClassRelativeResourceLoader.java75
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/ContextResource.java40
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java142
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/DescriptiveResource.java84
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java219
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/FileSystemResourceLoader.java73
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/InputStreamResource.java125
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/InputStreamSource.java56
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/Resource.java135
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/ResourceEditor.java160
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/ResourceLoader.java79
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/UrlResource.java263
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/VfsResource.java131
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/VfsUtils.java260
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/WritableResource.java52
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/package-info.java8
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java170
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/LocalizedResourceHelper.java129
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java733
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java192
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderUtils.java200
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/ResourceArrayPropertyEditor.java211
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/ResourcePatternResolver.java76
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/ResourcePatternUtils.java74
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/ResourcePropertySource.java128
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java121
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/VfsPatternUtils.java53
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/package-info.java9
-rw-r--r--spring-core/src/main/java/org/springframework/core/package-info.java9
-rw-r--r--spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java47
-rw-r--r--spring-core/src/main/java/org/springframework/core/serializer/DefaultSerializer.java47
-rw-r--r--spring-core/src/main/java/org/springframework/core/serializer/Deserializer.java42
-rw-r--r--spring-core/src/main/java/org/springframework/core/serializer/Serializer.java42
-rw-r--r--spring-core/src/main/java/org/springframework/core/serializer/package-info.java10
-rw-r--r--spring-core/src/main/java/org/springframework/core/serializer/support/DeserializingConverter.java67
-rw-r--r--spring-core/src/main/java/org/springframework/core/serializer/support/SerializationFailedException.java52
-rw-r--r--spring-core/src/main/java/org/springframework/core/serializer/support/SerializingConverter.java70
-rw-r--r--spring-core/src/main/java/org/springframework/core/serializer/support/package-info.java9
-rw-r--r--spring-core/src/main/java/org/springframework/core/style/DefaultToStringStyler.java98
-rw-r--r--spring-core/src/main/java/org/springframework/core/style/DefaultValueStyler.java148
-rw-r--r--spring-core/src/main/java/org/springframework/core/style/StylerUtils.java50
-rw-r--r--spring-core/src/main/java/org/springframework/core/style/ToStringCreator.java189
-rw-r--r--spring-core/src/main/java/org/springframework/core/style/ToStringStyler.java64
-rw-r--r--spring-core/src/main/java/org/springframework/core/style/ValueStyler.java35
-rw-r--r--spring-core/src/main/java/org/springframework/core/style/package-info.java8
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/AsyncTaskExecutor.java84
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java238
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/SyncTaskExecutor.java52
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/TaskExecutor.java50
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/TaskRejectedException.java54
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/TaskTimeoutException.java52
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/package-info.java9
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/support/ConcurrentExecutorAdapter.java57
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/support/ExecutorServiceAdapter.java87
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java110
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/support/package-info.java9
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java118
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/ClassMetadata.java106
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java79
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java205
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/StandardClassMetadata.java114
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java128
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java276
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java185
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java118
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java213
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReader.java48
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java50
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java104
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java85
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java99
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/package-info.java8
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/filter/AbstractClassTestingTypeFilter.java51
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java138
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java114
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/filter/AspectJTypeFilter.java71
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/filter/AssignableTypeFilter.java78
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/filter/RegexPatternTypeFilter.java47
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/filter/TypeFilter.java47
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/filter/package-info.java8
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/package-info.java8
-rw-r--r--spring-core/src/main/java/org/springframework/util/AntPathMatcher.java576
-rw-r--r--spring-core/src/main/java/org/springframework/util/Assert.java402
-rw-r--r--spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java277
-rw-r--r--spring-core/src/main/java/org/springframework/util/CachingMapDecorator.java311
-rw-r--r--spring-core/src/main/java/org/springframework/util/ClassUtils.java1251
-rw-r--r--spring-core/src/main/java/org/springframework/util/CollectionUtils.java500
-rw-r--r--spring-core/src/main/java/org/springframework/util/CommonsLogWriter.java78
-rw-r--r--spring-core/src/main/java/org/springframework/util/CompositeIterator.java77
-rw-r--r--spring-core/src/main/java/org/springframework/util/ConcurrencyThrottleSupport.java170
-rw-r--r--spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java1006
-rw-r--r--spring-core/src/main/java/org/springframework/util/CustomizableThreadCreator.java186
-rw-r--r--spring-core/src/main/java/org/springframework/util/DefaultPropertiesPersister.java233
-rw-r--r--spring-core/src/main/java/org/springframework/util/DigestUtils.java111
-rw-r--r--spring-core/src/main/java/org/springframework/util/ErrorHandler.java35
-rw-r--r--spring-core/src/main/java/org/springframework/util/FileCopyUtils.java236
-rw-r--r--spring-core/src/main/java/org/springframework/util/FileSystemUtils.java101
-rw-r--r--spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java153
-rw-r--r--spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java174
-rw-r--r--spring-core/src/main/java/org/springframework/util/Log4jConfigurer.java131
-rw-r--r--spring-core/src/main/java/org/springframework/util/MethodInvoker.java323
-rw-r--r--spring-core/src/main/java/org/springframework/util/MultiValueMap.java63
-rw-r--r--spring-core/src/main/java/org/springframework/util/NumberUtils.java267
-rw-r--r--spring-core/src/main/java/org/springframework/util/ObjectUtils.java880
-rw-r--r--spring-core/src/main/java/org/springframework/util/PathMatcher.java127
-rw-r--r--spring-core/src/main/java/org/springframework/util/PatternMatchUtils.java86
-rw-r--r--spring-core/src/main/java/org/springframework/util/PropertiesPersister.java121
-rw-r--r--spring-core/src/main/java/org/springframework/util/PropertyPlaceholderHelper.java224
-rw-r--r--spring-core/src/main/java/org/springframework/util/ReflectionUtils.java727
-rw-r--r--spring-core/src/main/java/org/springframework/util/ResourceUtils.java347
-rw-r--r--spring-core/src/main/java/org/springframework/util/SerializationUtils.java75
-rw-r--r--spring-core/src/main/java/org/springframework/util/StopWatch.java309
-rw-r--r--spring-core/src/main/java/org/springframework/util/StreamUtils.java183
-rw-r--r--spring-core/src/main/java/org/springframework/util/StringUtils.java1162
-rw-r--r--spring-core/src/main/java/org/springframework/util/StringValueResolver.java38
-rw-r--r--spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java113
-rw-r--r--spring-core/src/main/java/org/springframework/util/TypeUtils.java227
-rw-r--r--spring-core/src/main/java/org/springframework/util/WeakReferenceMonitor.java178
-rw-r--r--spring-core/src/main/java/org/springframework/util/comparator/BooleanComparator.java87
-rw-r--r--spring-core/src/main/java/org/springframework/util/comparator/ComparableComparator.java39
-rw-r--r--spring-core/src/main/java/org/springframework/util/comparator/CompoundComparator.java200
-rw-r--r--spring-core/src/main/java/org/springframework/util/comparator/InstanceComparator.java71
-rw-r--r--spring-core/src/main/java/org/springframework/util/comparator/InvertibleComparator.java127
-rw-r--r--spring-core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java126
-rw-r--r--spring-core/src/main/java/org/springframework/util/comparator/package-info.java9
-rw-r--r--spring-core/src/main/java/org/springframework/util/package-info.java9
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/AbstractStaxContentHandler.java175
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/AbstractStaxXMLReader.java237
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/AbstractXMLReader.java131
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/AbstractXMLStreamReader.java183
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/DomContentHandler.java137
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/DomUtils.java192
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/SimpleNamespaceContext.java159
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/SimpleSaxErrorHandler.java58
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/SimpleTransformErrorListener.java58
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/StaxEventContentHandler.java192
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/StaxEventXMLReader.java338
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/StaxResult.java113
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/StaxSource.java117
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/StaxStreamContentHandler.java113
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/StaxStreamXMLReader.java296
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java389
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/TransformerUtils.java86
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java268
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamWriter.java248
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java195
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/package-info.java9
-rw-r--r--spring-core/src/main/java/overview.html7
295 files changed, 40721 insertions, 0 deletions
diff --git a/spring-core/src/main/java/org/springframework/asm/SpringAsmInfo.java b/spring-core/src/main/java/org/springframework/asm/SpringAsmInfo.java
new file mode 100644
index 00000000..409f00c8
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/SpringAsmInfo.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2002-2014 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.asm;
+
+/**
+ * Utility class exposing constants related to Spring's internal repackaging
+ * of the ASM bytecode manipulation library (currently based on version 5.0).
+ *
+ * <p>See <a href="package-summary.html">package-level javadocs</a> for more
+ * information on {@code org.springframework.asm}.
+ *
+ * @author Chris Beams
+ * @since 3.2
+ */
+public final class SpringAsmInfo {
+
+ /**
+ * The ASM compatibility version for Spring's ASM visitor implementations:
+ * currently {@link Opcodes#ASM5}.
+ */
+ public static final int ASM_VERSION = Opcodes.ASM5;
+
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/package-info.java b/spring-core/src/main/java/org/springframework/asm/package-info.java
new file mode 100644
index 00000000..9ced53ea
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/package-info.java
@@ -0,0 +1,18 @@
+
+/**
+ * Spring's repackaging of
+ * <a href="http://asm.ow2.org">org.objectweb.asm 5.0</a>
+ * (for internal use only).
+ *
+ * <p>This repackaging technique avoids any potential conflicts with
+ * dependencies on ASM at the application level or from third-party
+ * libraries and frameworks.
+ *
+ * <p>As this repackaging happens at the class file level, sources
+ * and javadocs are not available here. See the original ObjectWeb
+ * <a href="http://asm.ow2.org/asm50/javadoc/user">ASM 5.0 javadocs</a>
+ * for details when working with these classes.
+ *
+ * @since 3.2
+ */
+package org.springframework.asm;
diff --git a/spring-core/src/main/java/org/springframework/asm/util/TraceClassVisitor.java b/spring-core/src/main/java/org/springframework/asm/util/TraceClassVisitor.java
new file mode 100644
index 00000000..bf5900fb
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/util/TraceClassVisitor.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2002-2012 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.asm.util;
+
+import java.io.PrintWriter;
+
+import org.springframework.asm.ClassVisitor;
+import org.springframework.asm.SpringAsmInfo;
+
+/**
+ * Dummy implementation of missing TraceClassVisitor from cglib-nodep's internally
+ * repackaged ASM library, added to avoid NoClassDefFoundErrors.
+ *
+ * @author Chris Beams
+ * @since 3.2
+ */
+public class TraceClassVisitor extends ClassVisitor {
+
+ public TraceClassVisitor(Object object, PrintWriter pw) {
+ super(SpringAsmInfo.ASM_VERSION);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/util/package-info.java b/spring-core/src/main/java/org/springframework/asm/util/package-info.java
new file mode 100644
index 00000000..f03a2e8d
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/util/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2002-2012 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.
+ */
+
+/**
+ * Dummy implementations of asm-util classes (for internal use only).
+ *
+ * @since 3.2
+ */
+package org.springframework.asm.util;
diff --git a/spring-core/src/main/java/org/springframework/cglib/SpringCglibInfo.java b/spring-core/src/main/java/org/springframework/cglib/SpringCglibInfo.java
new file mode 100644
index 00000000..0ed64ca5
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/cglib/SpringCglibInfo.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2002-2012 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.cglib;
+
+/**
+ * Empty class used to ensure that the {@code org.springframework.cglib} package is
+ * processed during Javadoc generation.
+ *
+ * <p>See <a href="package-summary.html">package-level Javadoc</a> for more
+ * information on {@code org.springframework.cglib}.
+ *
+ * @author Chris Beams
+ * @since 3.2
+ */
+public final class SpringCglibInfo {
+
+}
diff --git a/spring-core/src/main/java/org/springframework/cglib/core/SpringNamingPolicy.java b/spring-core/src/main/java/org/springframework/cglib/core/SpringNamingPolicy.java
new file mode 100644
index 00000000..839f36f8
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/cglib/core/SpringNamingPolicy.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2014 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.cglib.core;
+
+/**
+ * Custom extension of CGLIB's {@link DefaultNamingPolicy}, modifying
+ * the tag in generated class names from "ByCGLIB" to "BySpringCGLIB".
+ *
+ * <p>This is primarily designed to avoid clashes between a regular CGLIB
+ * version (used by some other library) and Spring's embedded variant,
+ * in case the same class happens to get proxied for different purposes.
+ *
+ * @author Juergen Hoeller
+ * @since 3.2.8
+ */
+public class SpringNamingPolicy extends DefaultNamingPolicy {
+
+ public static final SpringNamingPolicy INSTANCE = new SpringNamingPolicy();
+
+ @Override
+ protected String getTag() {
+ return "BySpringCGLIB";
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/cglib/package-info.java b/spring-core/src/main/java/org/springframework/cglib/package-info.java
new file mode 100644
index 00000000..a7f25ce2
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/cglib/package-info.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2002-2012 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.
+ */
+
+/**
+ * Spring's repackaging of <a href="http://cglib.sourceforge.net">net.sf.cglib 3</a> (for
+ * internal use only).
+ * <p>This repackaging technique avoids any potential conflicts with
+ * dependencies on CGLIB at the application level or from other third-party
+ * libraries and frameworks.
+ * <p>As this repackaging happens at the classfile level, sources and Javadoc
+ * are not available here. See the original
+ * <a href="http://cglib.sourceforge.net/apidocs">CGLIB 3 Javadoc</a>
+ * for details when working with these classes.
+ *
+ * @since 3.2
+ */
+package org.springframework.cglib;
diff --git a/spring-core/src/main/java/org/springframework/cglib/transform/impl/MemorySafeUndeclaredThrowableStrategy.java b/spring-core/src/main/java/org/springframework/cglib/transform/impl/MemorySafeUndeclaredThrowableStrategy.java
new file mode 100644
index 00000000..28f92352
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/cglib/transform/impl/MemorySafeUndeclaredThrowableStrategy.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2002-2014 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.cglib.transform.impl;
+
+import org.springframework.cglib.core.ClassGenerator;
+import org.springframework.cglib.core.DefaultGeneratorStrategy;
+import org.springframework.cglib.core.TypeUtils;
+import org.springframework.cglib.transform.ClassTransformer;
+import org.springframework.cglib.transform.MethodFilter;
+import org.springframework.cglib.transform.MethodFilterTransformer;
+import org.springframework.cglib.transform.TransformingClassGenerator;
+
+/**
+ * Memory-safe variant of {@link UndeclaredThrowableStrategy} ported from CGLIB 3.1,
+ * introduced for using it in Spring before it was officially released in CGLIB.
+ *
+ * @author Phillip Webb
+ * @since 3.2.4
+ */
+public class MemorySafeUndeclaredThrowableStrategy extends DefaultGeneratorStrategy {
+
+ private static final MethodFilter TRANSFORM_FILTER = new MethodFilter() {
+ public boolean accept(int access, String name, String desc, String signature, String[] exceptions) {
+ return (!TypeUtils.isPrivate(access) && name.indexOf('$') < 0);
+ }
+ };
+
+
+ private final Class<?> wrapper;
+
+
+ public MemorySafeUndeclaredThrowableStrategy(Class<?> wrapper) {
+ this.wrapper = wrapper;
+ }
+
+
+ protected ClassGenerator transform(ClassGenerator cg) throws Exception {
+ ClassTransformer ct = new UndeclaredThrowableTransformer(this.wrapper);
+ ct = new MethodFilterTransformer(TRANSFORM_FILTER, ct);
+ return new TransformingClassGenerator(cg, ct);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/AliasRegistry.java b/spring-core/src/main/java/org/springframework/core/AliasRegistry.java
new file mode 100644
index 00000000..0e1e18bd
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/AliasRegistry.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2002-2008 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;
+
+/**
+ * Common interface for managing aliases. Serves as super-interface for
+ * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.2
+ */
+public interface AliasRegistry {
+
+ /**
+ * Given a name, register an alias for it.
+ * @param name the canonical name
+ * @param alias the alias to be registered
+ * @throws IllegalStateException if the alias is already in use
+ * and may not be overridden
+ */
+ void registerAlias(String name, String alias);
+
+ /**
+ * Remove the specified alias from this registry.
+ * @param alias the alias to remove
+ * @throws IllegalStateException if no such alias was found
+ */
+ void removeAlias(String alias);
+
+ /**
+ * Determine whether this given name is defines as an alias
+ * (as opposed to the name of an actually registered component).
+ * @param beanName the bean name to check
+ * @return whether the given name is an alias
+ */
+ boolean isAlias(String beanName);
+
+ /**
+ * Return the aliases for the given name, if defined.
+ * @param name the name to check for aliases
+ * @return the aliases, or an empty array if none
+ */
+ String[] getAliases(String name);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/AttributeAccessor.java b/spring-core/src/main/java/org/springframework/core/AttributeAccessor.java
new file mode 100644
index 00000000..27130bce
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/AttributeAccessor.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2012 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;
+
+/**
+ * Interface defining a generic contract for attaching and accessing metadata
+ * to/from arbitrary objects.
+ *
+ * @author Rob Harrop
+ * @since 2.0
+ */
+public interface AttributeAccessor {
+
+ /**
+ * Set the attribute defined by {@code name} to the supplied {@code value}.
+ * If {@code value} is {@code null}, the attribute is {@link #removeAttribute removed}.
+ * <p>In general, users should take care to prevent overlaps with other
+ * metadata attributes by using fully-qualified names, perhaps using
+ * class or package names as prefix.
+ * @param name the unique attribute key
+ * @param value the attribute value to be attached
+ */
+ void setAttribute(String name, Object value);
+
+ /**
+ * Get the value of the attribute identified by {@code name}.
+ * Return {@code null} if the attribute doesn't exist.
+ * @param name the unique attribute key
+ * @return the current value of the attribute, if any
+ */
+ Object getAttribute(String name);
+
+ /**
+ * Remove the attribute identified by {@code name} and return its value.
+ * Return {@code null} if no attribute under {@code name} is found.
+ * @param name the unique attribute key
+ * @return the last value of the attribute, if any
+ */
+ Object removeAttribute(String name);
+
+ /**
+ * Return {@code true} if the attribute identified by {@code name} exists.
+ * Otherwise return {@code false}.
+ * @param name the unique attribute key
+ */
+ boolean hasAttribute(String name);
+
+ /**
+ * Return the names of all attributes.
+ */
+ String[] attributeNames();
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/AttributeAccessorSupport.java b/spring-core/src/main/java/org/springframework/core/AttributeAccessorSupport.java
new file mode 100644
index 00000000..cf351f42
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/AttributeAccessorSupport.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.springframework.util.Assert;
+
+/**
+ * Support class for {@link AttributeAccessor AttributeAccessors}, providing
+ * a base implementation of all methods. To be extended by subclasses.
+ *
+ * <p>{@link Serializable} if subclasses and all attribute values are {@link Serializable}.
+ *
+ * @author Rob Harrop
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+@SuppressWarnings("serial")
+public abstract class AttributeAccessorSupport implements AttributeAccessor, Serializable {
+
+ /** Map with String keys and Object values */
+ private final Map<String, Object> attributes = new LinkedHashMap<String, Object>(0);
+
+
+ public void setAttribute(String name, Object value) {
+ Assert.notNull(name, "Name must not be null");
+ if (value != null) {
+ this.attributes.put(name, value);
+ }
+ else {
+ removeAttribute(name);
+ }
+ }
+
+ public Object getAttribute(String name) {
+ Assert.notNull(name, "Name must not be null");
+ return this.attributes.get(name);
+ }
+
+ public Object removeAttribute(String name) {
+ Assert.notNull(name, "Name must not be null");
+ return this.attributes.remove(name);
+ }
+
+ public boolean hasAttribute(String name) {
+ Assert.notNull(name, "Name must not be null");
+ return this.attributes.containsKey(name);
+ }
+
+ public String[] attributeNames() {
+ return this.attributes.keySet().toArray(new String[this.attributes.size()]);
+ }
+
+
+ /**
+ * Copy the attributes from the supplied AttributeAccessor to this accessor.
+ * @param source the AttributeAccessor to copy from
+ */
+ protected void copyAttributesFrom(AttributeAccessor source) {
+ Assert.notNull(source, "Source must not be null");
+ String[] attributeNames = source.attributeNames();
+ for (String attributeName : attributeNames) {
+ setAttribute(attributeName, source.getAttribute(attributeName));
+ }
+ }
+
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof AttributeAccessorSupport)) {
+ return false;
+ }
+ AttributeAccessorSupport that = (AttributeAccessorSupport) other;
+ return this.attributes.equals(that.attributes);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.attributes.hashCode();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java b/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java
new file mode 100644
index 00000000..1027d4df
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2002-2013 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;
+
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * Helper for resolving synthetic {@link Method#isBridge bridge Methods} to the
+ * {@link Method} being bridged.
+ *
+ * <p>Given a synthetic {@link Method#isBridge bridge Method} returns the {@link Method}
+ * being bridged. A bridge method may be created by the compiler when extending a
+ * parameterized type whose methods have parameterized arguments. During runtime
+ * invocation the bridge {@link Method} may be invoked and/or used via reflection.
+ * When attempting to locate annotations on {@link Method Methods}, it is wise to check
+ * for bridge {@link Method Methods} as appropriate and find the bridged {@link Method}.
+ *
+ * <p>See <a href="http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.12.4.5">
+ * The Java Language Specification</a> for more details on the use of bridge methods.
+ *
+ * @author Rob Harrop
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public abstract class BridgeMethodResolver {
+
+ /**
+ * Find the original method for the supplied {@link Method bridge Method}.
+ * <p>It is safe to call this method passing in a non-bridge {@link Method} instance.
+ * In such a case, the supplied {@link Method} instance is returned directly to the caller.
+ * Callers are <strong>not</strong> required to check for bridging before calling this method.
+ * @param bridgeMethod the method to introspect
+ * @return the original method (either the bridged method or the passed-in method
+ * if no more specific one could be found)
+ */
+ public static Method findBridgedMethod(Method bridgeMethod) {
+ if (bridgeMethod == null || !bridgeMethod.isBridge()) {
+ return bridgeMethod;
+ }
+ // Gather all methods with matching name and parameter size.
+ List<Method> candidateMethods = new ArrayList<Method>();
+ Method[] methods = ReflectionUtils.getAllDeclaredMethods(bridgeMethod.getDeclaringClass());
+ for (Method candidateMethod : methods) {
+ if (isBridgedCandidateFor(candidateMethod, bridgeMethod)) {
+ candidateMethods.add(candidateMethod);
+ }
+ }
+ // Now perform simple quick check.
+ if (candidateMethods.size() == 1) {
+ return candidateMethods.get(0);
+ }
+ // Search for candidate match.
+ Method bridgedMethod = searchCandidates(candidateMethods, bridgeMethod);
+ if (bridgedMethod != null) {
+ // Bridged method found...
+ return bridgedMethod;
+ }
+ else {
+ // A bridge method was passed in but we couldn't find the bridged method.
+ // Let's proceed with the passed-in method and hope for the best...
+ return bridgeMethod;
+ }
+ }
+
+ /**
+ * Searches for the bridged method in the given candidates.
+ * @param candidateMethods the List of candidate Methods
+ * @param bridgeMethod the bridge method
+ * @return the bridged method, or {@code null} if none found
+ */
+ private static Method searchCandidates(List<Method> candidateMethods, Method bridgeMethod) {
+ if (candidateMethods.isEmpty()) {
+ return null;
+ }
+ Map<TypeVariable, Type> typeParameterMap = GenericTypeResolver.getTypeVariableMap(bridgeMethod.getDeclaringClass());
+ Method previousMethod = null;
+ boolean sameSig = true;
+ for (Method candidateMethod : candidateMethods) {
+ if (isBridgeMethodFor(bridgeMethod, candidateMethod, typeParameterMap)) {
+ return candidateMethod;
+ }
+ else if (previousMethod != null) {
+ sameSig = sameSig &&
+ Arrays.equals(candidateMethod.getGenericParameterTypes(), previousMethod.getGenericParameterTypes());
+ }
+ previousMethod = candidateMethod;
+ }
+ return (sameSig ? candidateMethods.get(0) : null);
+ }
+
+ /**
+ * Returns {@code true} if the supplied '{@code candidateMethod}' can be
+ * consider a validate candidate for the {@link Method} that is {@link Method#isBridge() bridged}
+ * by the supplied {@link Method bridge Method}. This method performs inexpensive
+ * checks and can be used quickly filter for a set of possible matches.
+ */
+ private static boolean isBridgedCandidateFor(Method candidateMethod, Method bridgeMethod) {
+ return (!candidateMethod.isBridge() && !candidateMethod.equals(bridgeMethod) &&
+ candidateMethod.getName().equals(bridgeMethod.getName()) &&
+ candidateMethod.getParameterTypes().length == bridgeMethod.getParameterTypes().length);
+ }
+
+ /**
+ * Determines whether or not the bridge {@link Method} is the bridge for the
+ * supplied candidate {@link Method}.
+ */
+ static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Map<TypeVariable, Type> typeVariableMap) {
+ if (isResolvedTypeMatch(candidateMethod, bridgeMethod, typeVariableMap)) {
+ return true;
+ }
+ Method method = findGenericDeclaration(bridgeMethod);
+ return (method != null && isResolvedTypeMatch(method, candidateMethod, typeVariableMap));
+ }
+
+ /**
+ * Searches for the generic {@link Method} declaration whose erased signature
+ * matches that of the supplied bridge method.
+ * @throws IllegalStateException if the generic declaration cannot be found
+ */
+ private static Method findGenericDeclaration(Method bridgeMethod) {
+ // Search parent types for method that has same signature as bridge.
+ Class superclass = bridgeMethod.getDeclaringClass().getSuperclass();
+ while (superclass != null && !Object.class.equals(superclass)) {
+ Method method = searchForMatch(superclass, bridgeMethod);
+ if (method != null && !method.isBridge()) {
+ return method;
+ }
+ superclass = superclass.getSuperclass();
+ }
+
+ // Search interfaces.
+ Class[] interfaces = ClassUtils.getAllInterfacesForClass(bridgeMethod.getDeclaringClass());
+ for (Class ifc : interfaces) {
+ Method method = searchForMatch(ifc, bridgeMethod);
+ if (method != null && !method.isBridge()) {
+ return method;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns {@code true} if the {@link Type} signature of both the supplied
+ * {@link Method#getGenericParameterTypes() generic Method} and concrete {@link Method}
+ * are equal after resolving all {@link TypeVariable TypeVariables} using the supplied
+ * TypeVariable Map, otherwise returns {@code false}.
+ */
+ private static boolean isResolvedTypeMatch(
+ Method genericMethod, Method candidateMethod, Map<TypeVariable, Type> typeVariableMap) {
+
+ Type[] genericParameters = genericMethod.getGenericParameterTypes();
+ Class[] candidateParameters = candidateMethod.getParameterTypes();
+ if (genericParameters.length != candidateParameters.length) {
+ return false;
+ }
+ for (int i = 0; i < genericParameters.length; i++) {
+ Type genericParameter = genericParameters[i];
+ Class candidateParameter = candidateParameters[i];
+ if (candidateParameter.isArray()) {
+ // An array type: compare the component type.
+ Type rawType = GenericTypeResolver.getRawType(genericParameter, typeVariableMap);
+ if (rawType instanceof GenericArrayType) {
+ if (!candidateParameter.getComponentType().equals(
+ GenericTypeResolver.resolveType(((GenericArrayType) rawType).getGenericComponentType(), typeVariableMap))) {
+ return false;
+ }
+ break;
+ }
+ }
+ // A non-array type: compare the type itself.
+ Class resolvedParameter = GenericTypeResolver.resolveType(genericParameter, typeVariableMap);
+ if (!candidateParameter.equals(resolvedParameter)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * If the supplied {@link Class} has a declared {@link Method} whose signature matches
+ * that of the supplied {@link Method}, then this matching {@link Method} is returned,
+ * otherwise {@code null} is returned.
+ */
+ private static Method searchForMatch(Class type, Method bridgeMethod) {
+ return ReflectionUtils.findMethod(type, bridgeMethod.getName(), bridgeMethod.getParameterTypes());
+ }
+
+ /**
+ * Compare the signatures of the bridge method and the method which it bridges. If
+ * the parameter and return types are the same, it is a 'visibility' bridge method
+ * introduced in Java 6 to fix http://bugs.sun.com/view_bug.do?bug_id=6342411.
+ * See also http://stas-blogspot.blogspot.com/2010/03/java-bridge-methods-explained.html
+ * @return whether signatures match as described
+ */
+ public static boolean isVisibilityBridgeMethodPair(Method bridgeMethod, Method bridgedMethod) {
+ if (bridgeMethod == bridgedMethod) {
+ return true;
+ }
+ return Arrays.equals(bridgeMethod.getParameterTypes(), bridgedMethod.getParameterTypes()) &&
+ bridgeMethod.getReturnType().equals(bridgedMethod.getReturnType());
+ }
+
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/CollectionFactory.java b/spring-core/src/main/java/org/springframework/core/CollectionFactory.java
new file mode 100644
index 00000000..23335434
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/CollectionFactory.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import org.springframework.util.ClassUtils;
+import org.springframework.util.LinkedCaseInsensitiveMap;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+/**
+ * Factory for collections, being aware of Java 5 and Java 6 collections.
+ * Mainly for internal use within the framework.
+ *
+ * <p>The goal of this class is to avoid runtime dependencies on a specific
+ * Java version, while nevertheless using the best collection implementation
+ * that is available at runtime.
+ *
+ * @author Juergen Hoeller
+ * @author Arjen Poutsma
+ * @since 1.1.1
+ */
+public abstract class CollectionFactory {
+
+ private static Class navigableSetClass = null;
+
+ private static Class navigableMapClass = null;
+
+ private static final Set<Class> approximableCollectionTypes = new HashSet<Class>(10);
+
+ private static final Set<Class> approximableMapTypes = new HashSet<Class>(6);
+
+
+ static {
+ // Standard collection interfaces
+ approximableCollectionTypes.add(Collection.class);
+ approximableCollectionTypes.add(List.class);
+ approximableCollectionTypes.add(Set.class);
+ approximableCollectionTypes.add(SortedSet.class);
+ approximableMapTypes.add(Map.class);
+ approximableMapTypes.add(SortedMap.class);
+
+ // New Java 6 collection interfaces
+ ClassLoader cl = CollectionFactory.class.getClassLoader();
+ try {
+ navigableSetClass = ClassUtils.forName("java.util.NavigableSet", cl);
+ navigableMapClass = ClassUtils.forName("java.util.NavigableMap", cl);
+ approximableCollectionTypes.add(navigableSetClass);
+ approximableMapTypes.add(navigableMapClass);
+ }
+ catch (ClassNotFoundException ex) {
+ // not running on Java 6 or above...
+ }
+
+ // Common concrete collection classes
+ approximableCollectionTypes.add(ArrayList.class);
+ approximableCollectionTypes.add(LinkedList.class);
+ approximableCollectionTypes.add(HashSet.class);
+ approximableCollectionTypes.add(LinkedHashSet.class);
+ approximableCollectionTypes.add(TreeSet.class);
+ approximableMapTypes.add(HashMap.class);
+ approximableMapTypes.add(LinkedHashMap.class);
+ approximableMapTypes.add(TreeMap.class);
+ }
+
+
+ /**
+ * Create a linked Set if possible: This implementation always
+ * creates a {@link java.util.LinkedHashSet}, since Spring 2.5
+ * requires JDK 1.4 anyway.
+ * @param initialCapacity the initial capacity of the Set
+ * @return the new Set instance
+ * @deprecated as of Spring 2.5, for usage on JDK 1.4 or higher
+ */
+ @Deprecated
+ public static <T> Set<T> createLinkedSetIfPossible(int initialCapacity) {
+ return new LinkedHashSet<T>(initialCapacity);
+ }
+
+ /**
+ * Create a copy-on-write Set (allowing for synchronization-less iteration) if possible:
+ * This implementation always creates a {@link java.util.concurrent.CopyOnWriteArraySet},
+ * since Spring 3 requires JDK 1.5 anyway.
+ * @return the new Set instance
+ * @deprecated as of Spring 3.0, for usage on JDK 1.5 or higher
+ */
+ @Deprecated
+ public static <T> Set<T> createCopyOnWriteSet() {
+ return new CopyOnWriteArraySet<T>();
+ }
+
+ /**
+ * Create a linked Map if possible: This implementation always
+ * creates a {@link java.util.LinkedHashMap}, since Spring 2.5
+ * requires JDK 1.4 anyway.
+ * @param initialCapacity the initial capacity of the Map
+ * @return the new Map instance
+ * @deprecated as of Spring 2.5, for usage on JDK 1.4 or higher
+ */
+ @Deprecated
+ public static <K,V> Map<K,V> createLinkedMapIfPossible(int initialCapacity) {
+ return new LinkedHashMap<K,V>(initialCapacity);
+ }
+
+ /**
+ * Create a linked case-insensitive Map if possible: This implementation
+ * always returns a {@link org.springframework.util.LinkedCaseInsensitiveMap}.
+ * @param initialCapacity the initial capacity of the Map
+ * @return the new Map instance
+ * @deprecated as of Spring 3.0, for usage on JDK 1.5 or higher
+ */
+ @Deprecated
+ public static Map createLinkedCaseInsensitiveMapIfPossible(int initialCapacity) {
+ return new LinkedCaseInsensitiveMap(initialCapacity);
+ }
+
+ /**
+ * Create an identity Map if possible: This implementation always
+ * creates a {@link java.util.IdentityHashMap}, since Spring 2.5
+ * requires JDK 1.4 anyway.
+ * @param initialCapacity the initial capacity of the Map
+ * @return the new Map instance
+ * @deprecated as of Spring 2.5, for usage on JDK 1.4 or higher
+ */
+ @Deprecated
+ public static Map createIdentityMapIfPossible(int initialCapacity) {
+ return new IdentityHashMap(initialCapacity);
+ }
+
+ /**
+ * Create a concurrent Map if possible: This implementation always
+ * creates a {@link java.util.concurrent.ConcurrentHashMap}, since Spring 3.0
+ * requires JDK 1.5 anyway.
+ * @param initialCapacity the initial capacity of the Map
+ * @return the new Map instance
+ * @deprecated as of Spring 3.0, for usage on JDK 1.5 or higher
+ */
+ @Deprecated
+ public static Map createConcurrentMapIfPossible(int initialCapacity) {
+ return new ConcurrentHashMap(initialCapacity);
+ }
+
+ /**
+ * Create a concurrent Map with a dedicated {@link ConcurrentMap} interface:
+ * This implementation always creates a {@link java.util.concurrent.ConcurrentHashMap},
+ * since Spring 3.0 requires JDK 1.5 anyway.
+ * @param initialCapacity the initial capacity of the Map
+ * @return the new ConcurrentMap instance
+ * @deprecated as of Spring 3.0, for usage on JDK 1.5 or higher
+ */
+ @Deprecated
+ public static ConcurrentMap createConcurrentMap(int initialCapacity) {
+ return new JdkConcurrentHashMap(initialCapacity);
+ }
+
+ /**
+ * Determine whether the given collection type is an approximable type,
+ * i.e. a type that {@link #createApproximateCollection} can approximate.
+ * @param collectionType the collection type to check
+ * @return {@code true} if the type is approximable,
+ * {@code false} if it is not
+ */
+ public static boolean isApproximableCollectionType(Class<?> collectionType) {
+ return (collectionType != null && approximableCollectionTypes.contains(collectionType));
+ }
+
+ /**
+ * Create the most approximate collection for the given collection.
+ * <p>Creates an ArrayList, TreeSet or linked Set for a List, SortedSet
+ * or Set, respectively.
+ * @param collection the original Collection object
+ * @param initialCapacity the initial capacity
+ * @return the new Collection instance
+ * @see java.util.ArrayList
+ * @see java.util.TreeSet
+ * @see java.util.LinkedHashSet
+ */
+ @SuppressWarnings("unchecked")
+ public static Collection createApproximateCollection(Object collection, int initialCapacity) {
+ if (collection instanceof LinkedList) {
+ return new LinkedList();
+ }
+ else if (collection instanceof List) {
+ return new ArrayList(initialCapacity);
+ }
+ else if (collection instanceof SortedSet) {
+ return new TreeSet(((SortedSet) collection).comparator());
+ }
+ else {
+ return new LinkedHashSet(initialCapacity);
+ }
+ }
+
+ /**
+ * Create the most appropriate collection for the given collection type.
+ * <p>Creates an ArrayList, TreeSet or linked Set for a List, SortedSet
+ * or Set, respectively.
+ * @param collectionType the desired type of the target Collection
+ * @param initialCapacity the initial capacity
+ * @return the new Collection instance
+ * @see java.util.ArrayList
+ * @see java.util.TreeSet
+ * @see java.util.LinkedHashSet
+ */
+ public static Collection createCollection(Class<?> collectionType, int initialCapacity) {
+ if (collectionType.isInterface()) {
+ if (List.class.equals(collectionType)) {
+ return new ArrayList(initialCapacity);
+ }
+ else if (SortedSet.class.equals(collectionType) || collectionType.equals(navigableSetClass)) {
+ return new TreeSet();
+ }
+ else if (Set.class.equals(collectionType) || Collection.class.equals(collectionType)) {
+ return new LinkedHashSet(initialCapacity);
+ }
+ else {
+ throw new IllegalArgumentException("Unsupported Collection interface: " + collectionType.getName());
+ }
+ }
+ else {
+ if (!Collection.class.isAssignableFrom(collectionType)) {
+ throw new IllegalArgumentException("Unsupported Collection type: " + collectionType.getName());
+ }
+ try {
+ return (Collection) collectionType.newInstance();
+ }
+ catch (Exception ex) {
+ throw new IllegalArgumentException("Could not instantiate Collection type: " +
+ collectionType.getName(), ex);
+ }
+ }
+ }
+
+ /**
+ * Determine whether the given map type is an approximable type,
+ * i.e. a type that {@link #createApproximateMap} can approximate.
+ * @param mapType the map type to check
+ * @return {@code true} if the type is approximable,
+ * {@code false} if it is not
+ */
+ public static boolean isApproximableMapType(Class<?> mapType) {
+ return (mapType != null && approximableMapTypes.contains(mapType));
+ }
+
+ /**
+ * Create the most approximate map for the given map.
+ * <p>Creates a TreeMap or linked Map for a SortedMap or Map, respectively.
+ * @param map the original Map object
+ * @param initialCapacity the initial capacity
+ * @return the new Map instance
+ * @see java.util.TreeMap
+ * @see java.util.LinkedHashMap
+ */
+ @SuppressWarnings("unchecked")
+ public static Map createApproximateMap(Object map, int initialCapacity) {
+ if (map instanceof SortedMap) {
+ return new TreeMap(((SortedMap) map).comparator());
+ }
+ else {
+ return new LinkedHashMap(initialCapacity);
+ }
+ }
+
+ /**
+ * Create the most approximate map for the given map.
+ * <p>Creates a TreeMap or linked Map for a SortedMap or Map, respectively.
+ * @param mapType the desired type of the target Map
+ * @param initialCapacity the initial capacity
+ * @return the new Map instance
+ * @see java.util.TreeMap
+ * @see java.util.LinkedHashMap
+ */
+ public static Map createMap(Class<?> mapType, int initialCapacity) {
+ if (mapType.isInterface()) {
+ if (Map.class.equals(mapType)) {
+ return new LinkedHashMap(initialCapacity);
+ }
+ else if (SortedMap.class.equals(mapType) || mapType.equals(navigableMapClass)) {
+ return new TreeMap();
+ }
+ else if (MultiValueMap.class.equals(mapType)) {
+ return new LinkedMultiValueMap();
+ }
+ else {
+ throw new IllegalArgumentException("Unsupported Map interface: " + mapType.getName());
+ }
+ }
+ else {
+ if (!Map.class.isAssignableFrom(mapType)) {
+ throw new IllegalArgumentException("Unsupported Map type: " + mapType.getName());
+ }
+ try {
+ return (Map) mapType.newInstance();
+ }
+ catch (Exception ex) {
+ throw new IllegalArgumentException("Could not instantiate Map type: " +
+ mapType.getName(), ex);
+ }
+ }
+ }
+
+
+ /**
+ * ConcurrentMap adapter for the JDK ConcurrentHashMap class.
+ */
+ @Deprecated
+ @SuppressWarnings("serial")
+ private static class JdkConcurrentHashMap extends ConcurrentHashMap implements ConcurrentMap {
+
+ private JdkConcurrentHashMap(int initialCapacity) {
+ super(initialCapacity);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/ConcurrentMap.java b/spring-core/src/main/java/org/springframework/core/ConcurrentMap.java
new file mode 100644
index 00000000..64597193
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/ConcurrentMap.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core;
+
+import java.util.Map;
+
+/**
+ * Common interface for a concurrent Map, as exposed by
+ * {@link CollectionFactory#createConcurrentMap}. Mirrors
+ * {@link java.util.concurrent.ConcurrentMap}, allowing to be backed by a
+ * JDK ConcurrentHashMap as well as a backport-concurrent ConcurrentHashMap.
+ *
+ * <p>Check out the {@link java.util.concurrent.ConcurrentMap ConcurrentMap javadoc}
+ * for details on the interface's methods.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @deprecated as of Spring 3.0, since standard {@link java.util.concurrent.ConcurrentMap}
+ * is available on Java 5+ anyway
+ */
+@Deprecated
+public interface ConcurrentMap extends Map {
+
+ Object putIfAbsent(Object key, Object value);
+
+ boolean remove(Object key, Object value);
+
+ boolean replace(Object key, Object oldValue, Object newValue);
+
+ Object replace(Object key, Object value);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java b/spring-core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java
new file mode 100644
index 00000000..4d48b044
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.NotSerializableException;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+import java.lang.reflect.Proxy;
+
+import org.springframework.util.ClassUtils;
+
+/**
+ * Special ObjectInputStream subclass that resolves class names
+ * against a specific ClassLoader. Serves as base class for
+ * {@link org.springframework.remoting.rmi.CodebaseAwareObjectInputStream}.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.5
+ */
+public class ConfigurableObjectInputStream extends ObjectInputStream {
+
+ private final ClassLoader classLoader;
+
+ private final boolean acceptProxyClasses;
+
+
+ /**
+ * Create a new ConfigurableObjectInputStream for the given InputStream and ClassLoader.
+ * @param in the InputStream to read from
+ * @param classLoader the ClassLoader to use for loading local classes
+ * @see java.io.ObjectInputStream#ObjectInputStream(java.io.InputStream)
+ */
+ public ConfigurableObjectInputStream(InputStream in, ClassLoader classLoader) throws IOException {
+ this(in, classLoader, true);
+ }
+
+ /**
+ * Create a new ConfigurableObjectInputStream for the given InputStream and ClassLoader.
+ * @param in the InputStream to read from
+ * @param classLoader the ClassLoader to use for loading local classes
+ * @param acceptProxyClasses whether to accept deserialization of proxy classes
+ * (may be deactivated as a security measure)
+ * @see java.io.ObjectInputStream#ObjectInputStream(java.io.InputStream)
+ */
+ public ConfigurableObjectInputStream(
+ InputStream in, ClassLoader classLoader, boolean acceptProxyClasses) throws IOException {
+
+ super(in);
+ this.classLoader = classLoader;
+ this.acceptProxyClasses = acceptProxyClasses;
+ }
+
+
+ @Override
+ protected Class resolveClass(ObjectStreamClass classDesc) throws IOException, ClassNotFoundException {
+ try {
+ if (this.classLoader != null) {
+ // Use the specified ClassLoader to resolve local classes.
+ return ClassUtils.forName(classDesc.getName(), this.classLoader);
+ }
+ else {
+ // Use the default ClassLoader...
+ return super.resolveClass(classDesc);
+ }
+ }
+ catch (ClassNotFoundException ex) {
+ return resolveFallbackIfPossible(classDesc.getName(), ex);
+ }
+ }
+
+ @Override
+ protected Class resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
+ if (!this.acceptProxyClasses) {
+ throw new NotSerializableException("Not allowed to accept serialized proxy classes");
+ }
+ if (this.classLoader != null) {
+ // Use the specified ClassLoader to resolve local proxy classes.
+ Class[] resolvedInterfaces = new Class[interfaces.length];
+ for (int i = 0; i < interfaces.length; i++) {
+ try {
+ resolvedInterfaces[i] = ClassUtils.forName(interfaces[i], this.classLoader);
+ }
+ catch (ClassNotFoundException ex) {
+ resolvedInterfaces[i] = resolveFallbackIfPossible(interfaces[i], ex);
+ }
+ }
+ try {
+ return Proxy.getProxyClass(this.classLoader, resolvedInterfaces);
+ }
+ catch (IllegalArgumentException ex) {
+ throw new ClassNotFoundException(null, ex);
+ }
+ }
+ else {
+ // Use ObjectInputStream's default ClassLoader...
+ try {
+ return super.resolveProxyClass(interfaces);
+ }
+ catch (ClassNotFoundException ex) {
+ Class[] resolvedInterfaces = new Class[interfaces.length];
+ for (int i = 0; i < interfaces.length; i++) {
+ resolvedInterfaces[i] = resolveFallbackIfPossible(interfaces[i], ex);
+ }
+ return Proxy.getProxyClass(getFallbackClassLoader(), resolvedInterfaces);
+ }
+ }
+ }
+
+
+ /**
+ * Resolve the given class name against a fallback class loader.
+ * <p>The default implementation simply rethrows the original exception,
+ * since there is no fallback available.
+ * @param className the class name to resolve
+ * @param ex the original exception thrown when attempting to load the class
+ * @return the newly resolved class (never {@code null})
+ */
+ protected Class resolveFallbackIfPossible(String className, ClassNotFoundException ex)
+ throws IOException, ClassNotFoundException{
+
+ throw ex;
+ }
+
+ /**
+ * Return the fallback ClassLoader to use when no ClassLoader was specified
+ * and ObjectInputStream's own default ClassLoader failed.
+ * <p>The default implementation simply returns {@code null}.
+ */
+ protected ClassLoader getFallbackClassLoader() throws IOException {
+ return null;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/ConstantException.java b/spring-core/src/main/java/org/springframework/core/ConstantException.java
new file mode 100644
index 00000000..451b46b8
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/ConstantException.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2002-2012 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;
+
+/**
+ * Exception thrown when the {@link Constants} class is asked for
+ * an invalid constant name.
+ *
+ * @author Rod Johnson
+ * @since 28.04.2003
+ * @see org.springframework.core.Constants
+ */
+@SuppressWarnings("serial")
+public class ConstantException extends IllegalArgumentException {
+
+ /**
+ * Thrown when an invalid constant name is requested.
+ * @param className name of the class containing the constant definitions
+ * @param field invalid constant name
+ * @param message description of the problem
+ */
+ public ConstantException(String className, String field, String message) {
+ super("Field '" + field + "' " + message + " in class [" + className + "]");
+ }
+
+ /**
+ * Thrown when an invalid constant value is looked up.
+ * @param className name of the class containing the constant definitions
+ * @param namePrefix prefix of the searched constant names
+ * @param value the looked up constant value
+ */
+ public ConstantException(String className, String namePrefix, Object value) {
+ super("No '" + namePrefix + "' field with value '" + value + "' found in class [" + className + "]");
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/Constants.java b/spring-core/src/main/java/org/springframework/core/Constants.java
new file mode 100644
index 00000000..3f8d9737
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/Constants.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * This class can be used to parse other classes containing constant definitions
+ * in public static final members. The {@code asXXXX} methods of this class
+ * allow these constant values to be accessed via their string names.
+ *
+ * <p>Consider class Foo containing {@code public final static int CONSTANT1 = 66;}
+ * An instance of this class wrapping {@code Foo.class} will return the constant value
+ * of 66 from its {@code asNumber} method given the argument {@code "CONSTANT1"}.
+ *
+ * <p>This class is ideal for use in PropertyEditors, enabling them to
+ * recognize the same names as the constants themselves, and freeing them
+ * from maintaining their own mapping.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @since 16.03.2003
+ */
+public class Constants {
+
+ /** The name of the introspected class */
+ private final String className;
+
+ /** Map from String field name to object value */
+ private final Map<String, Object> fieldCache = new HashMap<String, Object>();
+
+
+ /**
+ * Create a new Constants converter class wrapping the given class.
+ * <p>All <b>public</b> static final variables will be exposed, whatever their type.
+ * @param clazz the class to analyze
+ * @throws IllegalArgumentException if the supplied {@code clazz} is {@code null}
+ */
+ public Constants(Class<?> clazz) {
+ Assert.notNull(clazz);
+ this.className = clazz.getName();
+ Field[] fields = clazz.getFields();
+ for (Field field : fields) {
+ if (ReflectionUtils.isPublicStaticFinal(field)) {
+ String name = field.getName();
+ try {
+ Object value = field.get(null);
+ this.fieldCache.put(name, value);
+ }
+ catch (IllegalAccessException ex) {
+ // just leave this field and continue
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Return the name of the analyzed class.
+ */
+ public final String getClassName() {
+ return this.className;
+ }
+
+ /**
+ * Return the number of constants exposed.
+ */
+ public final int getSize() {
+ return this.fieldCache.size();
+ }
+
+ /**
+ * Exposes the field cache to subclasses:
+ * a Map from String field name to object value.
+ */
+ protected final Map<String, Object> getFieldCache() {
+ return this.fieldCache;
+ }
+
+
+ /**
+ * Return a constant value cast to a Number.
+ * @param code the name of the field (never {@code null})
+ * @return the Number value
+ * @see #asObject
+ * @throws ConstantException if the field name wasn't found
+ * or if the type wasn't compatible with Number
+ */
+ public Number asNumber(String code) throws ConstantException {
+ Object obj = asObject(code);
+ if (!(obj instanceof Number)) {
+ throw new ConstantException(this.className, code, "not a Number");
+ }
+ return (Number) obj;
+ }
+
+ /**
+ * Return a constant value as a String.
+ * @param code the name of the field (never {@code null})
+ * @return the String value
+ * Works even if it's not a string (invokes {@code toString()}).
+ * @see #asObject
+ * @throws ConstantException if the field name wasn't found
+ */
+ public String asString(String code) throws ConstantException {
+ return asObject(code).toString();
+ }
+
+ /**
+ * Parse the given String (upper or lower case accepted) and return
+ * the appropriate value if it's the name of a constant field in the
+ * class that we're analysing.
+ * @param code the name of the field (never {@code null})
+ * @return the Object value
+ * @throws ConstantException if there's no such field
+ */
+ public Object asObject(String code) throws ConstantException {
+ Assert.notNull(code, "Code must not be null");
+ String codeToUse = code.toUpperCase(Locale.ENGLISH);
+ Object val = this.fieldCache.get(codeToUse);
+ if (val == null) {
+ throw new ConstantException(this.className, codeToUse, "not found");
+ }
+ return val;
+ }
+
+
+ /**
+ * Return all names of the given group of constants.
+ * <p>Note that this method assumes that constants are named
+ * in accordance with the standard Java convention for constant
+ * values (i.e. all uppercase). The supplied {@code namePrefix}
+ * will be uppercased (in a locale-insensitive fashion) prior to
+ * the main logic of this method kicking in.
+ * @param namePrefix prefix of the constant names to search (may be {@code null})
+ * @return the set of constant names
+ */
+ public Set<String> getNames(String namePrefix) {
+ String prefixToUse = (namePrefix != null ? namePrefix.trim().toUpperCase(Locale.ENGLISH) : "");
+ Set<String> names = new HashSet<String>();
+ for (String code : this.fieldCache.keySet()) {
+ if (code.startsWith(prefixToUse)) {
+ names.add(code);
+ }
+ }
+ return names;
+ }
+
+ /**
+ * Return all names of the group of constants for the
+ * given bean property name.
+ * @param propertyName the name of the bean property
+ * @return the set of values
+ * @see #propertyToConstantNamePrefix
+ */
+ public Set<String> getNamesForProperty(String propertyName) {
+ return getNames(propertyToConstantNamePrefix(propertyName));
+ }
+
+ /**
+ * Return all names of the given group of constants.
+ * <p>Note that this method assumes that constants are named
+ * in accordance with the standard Java convention for constant
+ * values (i.e. all uppercase). The supplied {@code nameSuffix}
+ * will be uppercased (in a locale-insensitive fashion) prior to
+ * the main logic of this method kicking in.
+ * @param nameSuffix suffix of the constant names to search (may be {@code null})
+ * @return the set of constant names
+ */
+ public Set<String> getNamesForSuffix(String nameSuffix) {
+ String suffixToUse = (nameSuffix != null ? nameSuffix.trim().toUpperCase(Locale.ENGLISH) : "");
+ Set<String> names = new HashSet<String>();
+ for (String code : this.fieldCache.keySet()) {
+ if (code.endsWith(suffixToUse)) {
+ names.add(code);
+ }
+ }
+ return names;
+ }
+
+
+ /**
+ * Return all values of the given group of constants.
+ * <p>Note that this method assumes that constants are named
+ * in accordance with the standard Java convention for constant
+ * values (i.e. all uppercase). The supplied {@code namePrefix}
+ * will be uppercased (in a locale-insensitive fashion) prior to
+ * the main logic of this method kicking in.
+ * @param namePrefix prefix of the constant names to search (may be {@code null})
+ * @return the set of values
+ */
+ public Set<Object> getValues(String namePrefix) {
+ String prefixToUse = (namePrefix != null ? namePrefix.trim().toUpperCase(Locale.ENGLISH) : "");
+ Set<Object> values = new HashSet<Object>();
+ for (String code : this.fieldCache.keySet()) {
+ if (code.startsWith(prefixToUse)) {
+ values.add(this.fieldCache.get(code));
+ }
+ }
+ return values;
+ }
+
+ /**
+ * Return all values of the group of constants for the
+ * given bean property name.
+ * @param propertyName the name of the bean property
+ * @return the set of values
+ * @see #propertyToConstantNamePrefix
+ */
+ public Set<Object> getValuesForProperty(String propertyName) {
+ return getValues(propertyToConstantNamePrefix(propertyName));
+ }
+
+ /**
+ * Return all values of the given group of constants.
+ * <p>Note that this method assumes that constants are named
+ * in accordance with the standard Java convention for constant
+ * values (i.e. all uppercase). The supplied {@code nameSuffix}
+ * will be uppercased (in a locale-insensitive fashion) prior to
+ * the main logic of this method kicking in.
+ * @param nameSuffix suffix of the constant names to search (may be {@code null})
+ * @return the set of values
+ */
+ public Set<Object> getValuesForSuffix(String nameSuffix) {
+ String suffixToUse = (nameSuffix != null ? nameSuffix.trim().toUpperCase(Locale.ENGLISH) : "");
+ Set<Object> values = new HashSet<Object>();
+ for (String code : this.fieldCache.keySet()) {
+ if (code.endsWith(suffixToUse)) {
+ values.add(this.fieldCache.get(code));
+ }
+ }
+ return values;
+ }
+
+
+ /**
+ * Look up the given value within the given group of constants.
+ * <p>Will return the first match.
+ * @param value constant value to look up
+ * @param namePrefix prefix of the constant names to search (may be {@code null})
+ * @return the name of the constant field
+ * @throws ConstantException if the value wasn't found
+ */
+ public String toCode(Object value, String namePrefix) throws ConstantException {
+ String prefixToUse = (namePrefix != null ? namePrefix.trim().toUpperCase(Locale.ENGLISH) : "");
+ for (Map.Entry<String, Object> entry : this.fieldCache.entrySet()) {
+ if (entry.getKey().startsWith(prefixToUse) && entry.getValue().equals(value)) {
+ return entry.getKey();
+ }
+ }
+ throw new ConstantException(this.className, prefixToUse, value);
+ }
+
+ /**
+ * Look up the given value within the group of constants for
+ * the given bean property name. Will return the first match.
+ * @param value constant value to look up
+ * @param propertyName the name of the bean property
+ * @return the name of the constant field
+ * @throws ConstantException if the value wasn't found
+ * @see #propertyToConstantNamePrefix
+ */
+ public String toCodeForProperty(Object value, String propertyName) throws ConstantException {
+ return toCode(value, propertyToConstantNamePrefix(propertyName));
+ }
+
+ /**
+ * Look up the given value within the given group of constants.
+ * <p>Will return the first match.
+ * @param value constant value to look up
+ * @param nameSuffix suffix of the constant names to search (may be {@code null})
+ * @return the name of the constant field
+ * @throws ConstantException if the value wasn't found
+ */
+ public String toCodeForSuffix(Object value, String nameSuffix) throws ConstantException {
+ String suffixToUse = (nameSuffix != null ? nameSuffix.trim().toUpperCase(Locale.ENGLISH) : "");
+ for (Map.Entry<String, Object> entry : this.fieldCache.entrySet()) {
+ if (entry.getKey().endsWith(suffixToUse) && entry.getValue().equals(value)) {
+ return entry.getKey();
+ }
+ }
+ throw new ConstantException(this.className, suffixToUse, value);
+ }
+
+
+ /**
+ * Convert the given bean property name to a constant name prefix.
+ * <p>Uses a common naming idiom: turning all lower case characters to
+ * upper case, and prepending upper case characters with an underscore.
+ * <p>Example: "imageSize" -> "IMAGE_SIZE"<br>
+ * Example: "imagesize" -> "IMAGESIZE".<br>
+ * Example: "ImageSize" -> "_IMAGE_SIZE".<br>
+ * Example: "IMAGESIZE" -> "_I_M_A_G_E_S_I_Z_E"
+ * @param propertyName the name of the bean property
+ * @return the corresponding constant name prefix
+ * @see #getValuesForProperty
+ * @see #toCodeForProperty
+ */
+ public String propertyToConstantNamePrefix(String propertyName) {
+ StringBuilder parsedPrefix = new StringBuilder();
+ for (int i = 0; i < propertyName.length(); i++) {
+ char c = propertyName.charAt(i);
+ if (Character.isUpperCase(c)) {
+ parsedPrefix.append("_");
+ parsedPrefix.append(c);
+ }
+ else {
+ parsedPrefix.append(Character.toUpperCase(c));
+ }
+ }
+ return parsedPrefix.toString();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/ControlFlow.java b/spring-core/src/main/java/org/springframework/core/ControlFlow.java
new file mode 100644
index 00000000..bc754211
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/ControlFlow.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2002-2012 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;
+
+/**
+ * Interface to be implemented by objects that can return information about
+ * the current call stack. Useful in AOP (as in AspectJ cflow concept)
+ * but not AOP-specific.
+ *
+ * @author Rod Johnson
+ * @since 02.02.2004
+ */
+public interface ControlFlow {
+
+ /**
+ * Detect whether we're under the given class,
+ * according to the current stack trace.
+ * @param clazz the clazz to look for
+ */
+ boolean under(Class clazz);
+
+ /**
+ * Detect whether we're under the given class and method,
+ * according to the current stack trace.
+ * @param clazz the clazz to look for
+ * @param methodName the name of the method to look for
+ */
+ boolean under(Class clazz, String methodName);
+
+ /**
+ * Detect whether the current stack trace contains the given token.
+ * @param token the token to look for
+ */
+ boolean underToken(String token);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/ControlFlowFactory.java b/spring-core/src/main/java/org/springframework/core/ControlFlowFactory.java
new file mode 100644
index 00000000..6430ff7f
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/ControlFlowFactory.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2002-2008 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;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.springframework.util.Assert;
+
+/**
+ * Static factory to conceal the automatic choice of the ControlFlow
+ * implementation class.
+ *
+ * <p>This implementation always uses the efficient Java 1.4 StackTraceElement
+ * mechanism for analyzing control flows.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @since 02.02.2004
+ */
+public abstract class ControlFlowFactory {
+
+ /**
+ * Return an appropriate {@link ControlFlow} instance.
+ */
+ public static ControlFlow createControlFlow() {
+ return new Jdk14ControlFlow();
+ }
+
+
+ /**
+ * Utilities for cflow-style pointcuts. Note that such pointcuts are
+ * 5-10 times more expensive to evaluate than other pointcuts, as they require
+ * analysis of the stack trace (through constructing a new throwable).
+ * However, they are useful in some cases.
+ * <p>This implementation uses the StackTraceElement class introduced in Java 1.4.
+ * @see java.lang.StackTraceElement
+ */
+ static class Jdk14ControlFlow implements ControlFlow {
+
+ private StackTraceElement[] stack;
+
+ public Jdk14ControlFlow() {
+ this.stack = new Throwable().getStackTrace();
+ }
+
+ /**
+ * Searches for class name match in a StackTraceElement.
+ */
+ public boolean under(Class clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ String className = clazz.getName();
+ for (int i = 0; i < stack.length; i++) {
+ if (this.stack[i].getClassName().equals(className)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Searches for class name match plus method name match
+ * in a StackTraceElement.
+ */
+ public boolean under(Class clazz, String methodName) {
+ Assert.notNull(clazz, "Class must not be null");
+ Assert.notNull(methodName, "Method name must not be null");
+ String className = clazz.getName();
+ for (int i = 0; i < this.stack.length; i++) {
+ if (this.stack[i].getClassName().equals(className) &&
+ this.stack[i].getMethodName().equals(methodName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Leave it up to the caller to decide what matches.
+ * Caller must understand stack trace format, so there's less abstraction.
+ */
+ public boolean underToken(String token) {
+ if (token == null) {
+ return false;
+ }
+ StringWriter sw = new StringWriter();
+ new Throwable().printStackTrace(new PrintWriter(sw));
+ String stackTrace = sw.toString();
+ return stackTrace.indexOf(token) != -1;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("Jdk14ControlFlow: ");
+ for (int i = 0; i < this.stack.length; i++) {
+ if (i > 0) {
+ sb.append("\n\t@");
+ }
+ sb.append(this.stack[i]);
+ }
+ return sb.toString();
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/Conventions.java b/spring-core/src/main/java/org/springframework/core/Conventions.java
new file mode 100644
index 00000000..7ea7c825
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/Conventions.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.io.Externalizable;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Provides methods to support various naming and other conventions used
+ * throughout the framework. Mainly for internal use within the framework.
+ *
+ * @author Rob Harrop
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public abstract class Conventions {
+
+ /**
+ * Suffix added to names when using arrays.
+ */
+ private static final String PLURAL_SUFFIX = "List";
+
+
+ /**
+ * Set of interfaces that are supposed to be ignored
+ * when searching for the 'primary' interface of a proxy.
+ */
+ private static final Set<Class> ignoredInterfaces = new HashSet<Class>();
+
+ static {
+ ignoredInterfaces.add(Serializable.class);
+ ignoredInterfaces.add(Externalizable.class);
+ ignoredInterfaces.add(Cloneable.class);
+ ignoredInterfaces.add(Comparable.class);
+ }
+
+
+ /**
+ * Determine the conventional variable name for the supplied
+ * {@code Object} based on its concrete type. The convention
+ * used is to return the uncapitalized short name of the {@code Class},
+ * according to JavaBeans property naming rules: So,
+ * {@code com.myapp.Product} becomes {@code product};
+ * {@code com.myapp.MyProduct} becomes {@code myProduct};
+ * {@code com.myapp.UKProduct} becomes {@code UKProduct}.
+ * <p>For arrays, we use the pluralized version of the array component type.
+ * For {@code Collection}s we attempt to 'peek ahead' in the
+ * {@code Collection} to determine the component type and
+ * return the pluralized version of that component type.
+ * @param value the value to generate a variable name for
+ * @return the generated variable name
+ */
+ public static String getVariableName(Object value) {
+ Assert.notNull(value, "Value must not be null");
+ Class valueClass;
+ boolean pluralize = false;
+
+ if (value.getClass().isArray()) {
+ valueClass = value.getClass().getComponentType();
+ pluralize = true;
+ }
+ else if (value instanceof Collection) {
+ Collection collection = (Collection) value;
+ if (collection.isEmpty()) {
+ throw new IllegalArgumentException("Cannot generate variable name for an empty Collection");
+ }
+ Object valueToCheck = peekAhead(collection);
+ valueClass = getClassForValue(valueToCheck);
+ pluralize = true;
+ }
+ else {
+ valueClass = getClassForValue(value);
+ }
+
+ String name = ClassUtils.getShortNameAsProperty(valueClass);
+ return (pluralize ? pluralize(name) : name);
+ }
+
+ /**
+ * Determine the conventional variable name for the supplied parameter,
+ * taking the generic collection type (if any) into account.
+ * @param parameter the method or constructor parameter to generate a variable name for
+ * @return the generated variable name
+ */
+ public static String getVariableNameForParameter(MethodParameter parameter) {
+ Assert.notNull(parameter, "MethodParameter must not be null");
+ Class valueClass;
+ boolean pluralize = false;
+
+ if (parameter.getParameterType().isArray()) {
+ valueClass = parameter.getParameterType().getComponentType();
+ pluralize = true;
+ }
+ else if (Collection.class.isAssignableFrom(parameter.getParameterType())) {
+ valueClass = GenericCollectionTypeResolver.getCollectionParameterType(parameter);
+ if (valueClass == null) {
+ throw new IllegalArgumentException(
+ "Cannot generate variable name for non-typed Collection parameter type");
+ }
+ pluralize = true;
+ }
+ else {
+ valueClass = parameter.getParameterType();
+ }
+
+ String name = ClassUtils.getShortNameAsProperty(valueClass);
+ return (pluralize ? pluralize(name) : name);
+ }
+
+ /**
+ * Determine the conventional variable name for the return type of the supplied method,
+ * taking the generic collection type (if any) into account.
+ * @param method the method to generate a variable name for
+ * @return the generated variable name
+ */
+ public static String getVariableNameForReturnType(Method method) {
+ return getVariableNameForReturnType(method, method.getReturnType(), null);
+ }
+
+ /**
+ * Determine the conventional variable name for the return type of the supplied method,
+ * taking the generic collection type (if any) into account, falling back to the
+ * given return value if the method declaration is not specific enough (i.e. in case of
+ * the return type being declared as {@code Object} or as untyped collection).
+ * @param method the method to generate a variable name for
+ * @param value the return value (may be {@code null} if not available)
+ * @return the generated variable name
+ */
+ public static String getVariableNameForReturnType(Method method, Object value) {
+ return getVariableNameForReturnType(method, method.getReturnType(), value);
+ }
+
+ /**
+ * Determine the conventional variable name for the return type of the supplied method,
+ * taking the generic collection type (if any) into account, falling back to the
+ * given return value if the method declaration is not specific enough (i.e. in case of
+ * the return type being declared as {@code Object} or as untyped collection).
+ * @param method the method to generate a variable name for
+ * @param resolvedType the resolved return type of the method
+ * @param value the return value (may be {@code null} if not available)
+ * @return the generated variable name
+ */
+ public static String getVariableNameForReturnType(Method method, Class resolvedType, Object value) {
+ Assert.notNull(method, "Method must not be null");
+
+ if (Object.class.equals(resolvedType)) {
+ if (value == null) {
+ throw new IllegalArgumentException("Cannot generate variable name for an Object return type with null value");
+ }
+ return getVariableName(value);
+ }
+
+ Class valueClass;
+ boolean pluralize = false;
+
+ if (resolvedType.isArray()) {
+ valueClass = resolvedType.getComponentType();
+ pluralize = true;
+ }
+ else if (Collection.class.isAssignableFrom(resolvedType)) {
+ valueClass = GenericCollectionTypeResolver.getCollectionReturnType(method);
+ if (valueClass == null) {
+ if (!(value instanceof Collection)) {
+ throw new IllegalArgumentException(
+ "Cannot generate variable name for non-typed Collection return type and a non-Collection value");
+ }
+ Collection collection = (Collection) value;
+ if (collection.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Cannot generate variable name for non-typed Collection return type and an empty Collection value");
+ }
+ Object valueToCheck = peekAhead(collection);
+ valueClass = getClassForValue(valueToCheck);
+ }
+ pluralize = true;
+ }
+ else {
+ valueClass = resolvedType;
+ }
+
+ String name = ClassUtils.getShortNameAsProperty(valueClass);
+ return (pluralize ? pluralize(name) : name);
+ }
+
+ /**
+ * Convert {@code String}s in attribute name format (lowercase, hyphens separating words)
+ * into property name format (camel-cased). For example, {@code transaction-manager} is
+ * converted into {@code transactionManager}.
+ */
+ public static String attributeNameToPropertyName(String attributeName) {
+ Assert.notNull(attributeName, "'attributeName' must not be null");
+ if (!attributeName.contains("-")) {
+ return attributeName;
+ }
+ char[] chars = attributeName.toCharArray();
+ char[] result = new char[chars.length -1]; // not completely accurate but good guess
+ int currPos = 0;
+ boolean upperCaseNext = false;
+ for (char c : chars) {
+ if (c == '-') {
+ upperCaseNext = true;
+ }
+ else if (upperCaseNext) {
+ result[currPos++] = Character.toUpperCase(c);
+ upperCaseNext = false;
+ }
+ else {
+ result[currPos++] = c;
+ }
+ }
+ return new String(result, 0, currPos);
+ }
+
+ /**
+ * Return an attribute name qualified by the supplied enclosing {@link Class}. For example,
+ * the attribute name '{@code foo}' qualified by {@link Class} '{@code com.myapp.SomeClass}'
+ * would be '{@code com.myapp.SomeClass.foo}'
+ */
+ public static String getQualifiedAttributeName(Class enclosingClass, String attributeName) {
+ Assert.notNull(enclosingClass, "'enclosingClass' must not be null");
+ Assert.notNull(attributeName, "'attributeName' must not be null");
+ return enclosingClass.getName() + "." + attributeName;
+ }
+
+
+ /**
+ * Determines the class to use for naming a variable that contains
+ * the given value.
+ * <p>Will return the class of the given value, except when
+ * encountering a JDK proxy, in which case it will determine
+ * the 'primary' interface implemented by that proxy.
+ * @param value the value to check
+ * @return the class to use for naming a variable
+ */
+ private static Class getClassForValue(Object value) {
+ Class valueClass = value.getClass();
+ if (Proxy.isProxyClass(valueClass)) {
+ Class[] ifcs = valueClass.getInterfaces();
+ for (Class ifc : ifcs) {
+ if (!ignoredInterfaces.contains(ifc)) {
+ return ifc;
+ }
+ }
+ }
+ else if (valueClass.getName().lastIndexOf('$') != -1 && valueClass.getDeclaringClass() == null) {
+ // '$' in the class name but no inner class -
+ // assuming it's a special subclass (e.g. by OpenJPA)
+ valueClass = valueClass.getSuperclass();
+ }
+ return valueClass;
+ }
+
+ /**
+ * Pluralize the given name.
+ */
+ private static String pluralize(String name) {
+ return name + PLURAL_SUFFIX;
+ }
+
+ /**
+ * Retrieves the {@code Class} of an element in the {@code Collection}.
+ * The exact element for which the {@code Class} is retreived will depend
+ * on the concrete {@code Collection} implementation.
+ */
+ private static Object peekAhead(Collection collection) {
+ Iterator it = collection.iterator();
+ if (!it.hasNext()) {
+ throw new IllegalStateException(
+ "Unable to peek ahead in non-empty collection - no element found");
+ }
+ Object value = it.next();
+ if (value == null) {
+ throw new IllegalStateException(
+ "Unable to peek ahead in non-empty collection - only null element found");
+ }
+ return value;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/DecoratingClassLoader.java b/spring-core/src/main/java/org/springframework/core/DecoratingClassLoader.java
new file mode 100644
index 00000000..0e677141
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/DecoratingClassLoader.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2002-2008 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;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.springframework.util.Assert;
+
+/**
+ * Base class for decorating ClassLoaders such as {@link OverridingClassLoader}
+ * and {@link org.springframework.instrument.classloading.ShadowingClassLoader},
+ * providing common handling of excluded packages and classes.
+ *
+ * @author Juergen Hoeller
+ * @author Rod Johnson
+ * @since 2.5.2
+ */
+public abstract class DecoratingClassLoader extends ClassLoader {
+
+ private final Set<String> excludedPackages = new HashSet<String>();
+
+ private final Set<String> excludedClasses = new HashSet<String>();
+
+ private final Object exclusionMonitor = new Object();
+
+
+ /**
+ * Create a new DecoratingClassLoader with no parent ClassLoader.
+ */
+ public DecoratingClassLoader() {
+ }
+
+ /**
+ * Create a new DecoratingClassLoader using the given parent ClassLoader
+ * for delegation.
+ */
+ public DecoratingClassLoader(ClassLoader parent) {
+ super(parent);
+ }
+
+
+ /**
+ * Add a package name to exclude from decoration (e.g. overriding).
+ * <p>Any class whose fully-qualified name starts with the name registered
+ * here will be handled by the parent ClassLoader in the usual fashion.
+ * @param packageName the package name to exclude
+ */
+ public void excludePackage(String packageName) {
+ Assert.notNull(packageName, "Package name must not be null");
+ synchronized (this.exclusionMonitor) {
+ this.excludedPackages.add(packageName);
+ }
+ }
+
+ /**
+ * Add a class name to exclude from decoration (e.g. overriding).
+ * <p>Any class name registered here will be handled by the parent
+ * ClassLoader in the usual fashion.
+ * @param className the class name to exclude
+ */
+ public void excludeClass(String className) {
+ Assert.notNull(className, "Class name must not be null");
+ synchronized (this.exclusionMonitor) {
+ this.excludedClasses.add(className);
+ }
+ }
+
+ /**
+ * Determine whether the specified class is excluded from decoration
+ * by this class loader.
+ * <p>The default implementation checks against excluded packages and classes.
+ * @param className the class name to check
+ * @return whether the specified class is eligible
+ * @see #excludePackage
+ * @see #excludeClass
+ */
+ protected boolean isExcluded(String className) {
+ synchronized (this.exclusionMonitor) {
+ if (this.excludedClasses.contains(className)) {
+ return true;
+ }
+ for (String packageName : this.excludedPackages) {
+ if (className.startsWith(packageName)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/ErrorCoded.java b/spring-core/src/main/java/org/springframework/core/ErrorCoded.java
new file mode 100644
index 00000000..1425fe84
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/ErrorCoded.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2012 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;
+
+/**
+ * Interface that can be implemented by exceptions etc that are error coded.
+ * The error code is a String, rather than a number, so it can be given
+ * user-readable values, such as "object.failureDescription".
+ *
+ * <p>An error code can be resolved by a MessageSource, for example.
+ *
+ * @author Rod Johnson
+ * @see org.springframework.context.MessageSource
+ */
+public interface ErrorCoded {
+
+ /**
+ * Return the error code associated with this failure.
+ * The GUI can render this any way it pleases, allowing for localization etc.
+ * @return a String error code associated with this failure,
+ * or {@code null} if not error-coded
+ */
+ String getErrorCode();
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/ExceptionDepthComparator.java b/spring-core/src/main/java/org/springframework/core/ExceptionDepthComparator.java
new file mode 100644
index 00000000..53e3425f
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/ExceptionDepthComparator.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.springframework.util.Assert;
+
+/**
+ * Comparator capable of sorting exceptions based on their depth from the thrown exception type.
+ *
+ * @author Juergen Hoeller
+ * @author Arjen Poutsma
+ * @since 3.0.3
+ */
+public class ExceptionDepthComparator implements Comparator<Class<? extends Throwable>> {
+
+ private final Class<? extends Throwable> targetException;
+
+
+ /**
+ * Create a new ExceptionDepthComparator for the given exception.
+ * @param exception the target exception to compare to when sorting by depth
+ */
+ public ExceptionDepthComparator(Throwable exception) {
+ Assert.notNull(exception, "Target exception must not be null");
+ this.targetException = exception.getClass();
+ }
+
+ /**
+ * Create a new ExceptionDepthComparator for the given exception type.
+ * @param exceptionType the target exception type to compare to when sorting by depth
+ */
+ public ExceptionDepthComparator(Class<? extends Throwable> exceptionType) {
+ Assert.notNull(exceptionType, "Target exception type must not be null");
+ this.targetException = exceptionType;
+ }
+
+
+ public int compare(Class<? extends Throwable> o1, Class<? extends Throwable> o2) {
+ int depth1 = getDepth(o1, this.targetException, 0);
+ int depth2 = getDepth(o2, this.targetException, 0);
+ return (depth1 - depth2);
+ }
+
+ private int getDepth(Class declaredException, Class exceptionToMatch, int depth) {
+ if (declaredException.equals(exceptionToMatch)) {
+ // Found it!
+ return depth;
+ }
+ // If we've gone as far as we can go and haven't found it...
+ if (Throwable.class.equals(exceptionToMatch)) {
+ return Integer.MAX_VALUE;
+ }
+ return getDepth(declaredException, exceptionToMatch.getSuperclass(), depth + 1);
+ }
+
+
+ /**
+ * Obtain the closest match from the given exception types for the given target exception.
+ * @param exceptionTypes the collection of exception types
+ * @param targetException the target exception to find a match for
+ * @return the closest matching exception type from the given collection
+ */
+ public static Class<? extends Throwable> findClosestMatch(
+ Collection<Class<? extends Throwable>> exceptionTypes, Throwable targetException) {
+
+ Assert.notEmpty(exceptionTypes, "Exception types must not be empty");
+ if (exceptionTypes.size() == 1) {
+ return exceptionTypes.iterator().next();
+ }
+ List<Class<? extends Throwable>> handledExceptions =
+ new ArrayList<Class<? extends Throwable>>(exceptionTypes);
+ Collections.sort(handledExceptions, new ExceptionDepthComparator(targetException));
+ return handledExceptions.get(0);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java
new file mode 100644
index 00000000..8b4c280a
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright 2002-2013 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;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.MalformedParameterizedTypeException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Helper class for determining element types of collections and maps.
+ *
+ * <p>Mainly intended for usage within the framework, determining the
+ * target type of values to be added to a collection or map
+ * (to be able to attempt type conversion if appropriate).
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public abstract class GenericCollectionTypeResolver {
+
+ /**
+ * Determine the generic element type of the given Collection class
+ * (if it declares one through a generic superclass or generic interface).
+ * @param collectionClass the collection class to introspect
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getCollectionType(Class<? extends Collection> collectionClass) {
+ return extractTypeFromClass(collectionClass, Collection.class, 0);
+ }
+
+ /**
+ * Determine the generic key type of the given Map class
+ * (if it declares one through a generic superclass or generic interface).
+ * @param mapClass the map class to introspect
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getMapKeyType(Class<? extends Map> mapClass) {
+ return extractTypeFromClass(mapClass, Map.class, 0);
+ }
+
+ /**
+ * Determine the generic value type of the given Map class
+ * (if it declares one through a generic superclass or generic interface).
+ * @param mapClass the map class to introspect
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getMapValueType(Class<? extends Map> mapClass) {
+ return extractTypeFromClass(mapClass, Map.class, 1);
+ }
+
+ /**
+ * Determine the generic element type of the given Collection field.
+ * @param collectionField the collection field to introspect
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getCollectionFieldType(Field collectionField) {
+ return getGenericFieldType(collectionField, Collection.class, 0, null, 1);
+ }
+
+ /**
+ * Determine the generic element type of the given Collection field.
+ * @param collectionField the collection field to introspect
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getCollectionFieldType(Field collectionField, int nestingLevel) {
+ return getGenericFieldType(collectionField, Collection.class, 0, null, nestingLevel);
+ }
+
+ /**
+ * Determine the generic element type of the given Collection field.
+ * @param collectionField the collection field to introspect
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ * @param typeIndexesPerLevel Map keyed by nesting level, with each value
+ * expressing the type index for traversal at that level
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getCollectionFieldType(Field collectionField, int nestingLevel, Map<Integer, Integer> typeIndexesPerLevel) {
+ return getGenericFieldType(collectionField, Collection.class, 0, typeIndexesPerLevel, nestingLevel);
+ }
+
+ /**
+ * Determine the generic key type of the given Map field.
+ * @param mapField the map field to introspect
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getMapKeyFieldType(Field mapField) {
+ return getGenericFieldType(mapField, Map.class, 0, null, 1);
+ }
+
+ /**
+ * Determine the generic key type of the given Map field.
+ * @param mapField the map field to introspect
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getMapKeyFieldType(Field mapField, int nestingLevel) {
+ return getGenericFieldType(mapField, Map.class, 0, null, nestingLevel);
+ }
+
+ /**
+ * Determine the generic key type of the given Map field.
+ * @param mapField the map field to introspect
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ * @param typeIndexesPerLevel Map keyed by nesting level, with each value
+ * expressing the type index for traversal at that level
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getMapKeyFieldType(Field mapField, int nestingLevel, Map<Integer, Integer> typeIndexesPerLevel) {
+ return getGenericFieldType(mapField, Map.class, 0, typeIndexesPerLevel, nestingLevel);
+ }
+
+ /**
+ * Determine the generic value type of the given Map field.
+ * @param mapField the map field to introspect
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getMapValueFieldType(Field mapField) {
+ return getGenericFieldType(mapField, Map.class, 1, null, 1);
+ }
+
+ /**
+ * Determine the generic value type of the given Map field.
+ * @param mapField the map field to introspect
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getMapValueFieldType(Field mapField, int nestingLevel) {
+ return getGenericFieldType(mapField, Map.class, 1, null, nestingLevel);
+ }
+
+ /**
+ * Determine the generic value type of the given Map field.
+ * @param mapField the map field to introspect
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ * @param typeIndexesPerLevel Map keyed by nesting level, with each value
+ * expressing the type index for traversal at that level
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getMapValueFieldType(Field mapField, int nestingLevel, Map<Integer, Integer> typeIndexesPerLevel) {
+ return getGenericFieldType(mapField, Map.class, 1, typeIndexesPerLevel, nestingLevel);
+ }
+
+ /**
+ * Determine the generic element type of the given Collection parameter.
+ * @param methodParam the method parameter specification
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getCollectionParameterType(MethodParameter methodParam) {
+ return getGenericParameterType(methodParam, Collection.class, 0);
+ }
+
+ /**
+ * Determine the generic key type of the given Map parameter.
+ * @param methodParam the method parameter specification
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getMapKeyParameterType(MethodParameter methodParam) {
+ return getGenericParameterType(methodParam, Map.class, 0);
+ }
+
+ /**
+ * Determine the generic value type of the given Map parameter.
+ * @param methodParam the method parameter specification
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getMapValueParameterType(MethodParameter methodParam) {
+ return getGenericParameterType(methodParam, Map.class, 1);
+ }
+
+ /**
+ * Determine the generic element type of the given Collection return type.
+ * @param method the method to check the return type for
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getCollectionReturnType(Method method) {
+ return getGenericReturnType(method, Collection.class, 0, 1);
+ }
+
+ /**
+ * Determine the generic element type of the given Collection return type.
+ * <p>If the specified nesting level is higher than 1, the element type of
+ * a nested Collection/Map will be analyzed.
+ * @param method the method to check the return type for
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getCollectionReturnType(Method method, int nestingLevel) {
+ return getGenericReturnType(method, Collection.class, 0, nestingLevel);
+ }
+
+ /**
+ * Determine the generic key type of the given Map return type.
+ * @param method the method to check the return type for
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getMapKeyReturnType(Method method) {
+ return getGenericReturnType(method, Map.class, 0, 1);
+ }
+
+ /**
+ * Determine the generic key type of the given Map return type.
+ * @param method the method to check the return type for
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getMapKeyReturnType(Method method, int nestingLevel) {
+ return getGenericReturnType(method, Map.class, 0, nestingLevel);
+ }
+
+ /**
+ * Determine the generic value type of the given Map return type.
+ * @param method the method to check the return type for
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getMapValueReturnType(Method method) {
+ return getGenericReturnType(method, Map.class, 1, 1);
+ }
+
+ /**
+ * Determine the generic value type of the given Map return type.
+ * @param method the method to check the return type for
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ * @return the generic type, or {@code null} if none
+ */
+ public static Class<?> getMapValueReturnType(Method method, int nestingLevel) {
+ return getGenericReturnType(method, Map.class, 1, nestingLevel);
+ }
+
+
+ /**
+ * Extract the generic parameter type from the given method or constructor.
+ * @param methodParam the method parameter specification
+ * @param source the source class/interface defining the generic parameter types
+ * @param typeIndex the index of the type (e.g. 0 for Collections,
+ * 0 for Map keys, 1 for Map values)
+ * @return the generic type, or {@code null} if none
+ */
+ private static Class<?> getGenericParameterType(MethodParameter methodParam, Class<?> source, int typeIndex) {
+ return extractType(GenericTypeResolver.getTargetType(methodParam), source, typeIndex,
+ methodParam.typeVariableMap, methodParam.typeIndexesPerLevel, methodParam.getNestingLevel(), 1);
+ }
+
+ /**
+ * Extract the generic type from the given field.
+ * @param field the field to check the type for
+ * @param source the source class/interface defining the generic parameter types
+ * @param typeIndex the index of the type (e.g. 0 for Collections,
+ * 0 for Map keys, 1 for Map values)
+ * @param nestingLevel the nesting level of the target type
+ * @return the generic type, or {@code null} if none
+ */
+ private static Class<?> getGenericFieldType(Field field, Class<?> source, int typeIndex,
+ Map<Integer, Integer> typeIndexesPerLevel, int nestingLevel) {
+ return extractType(field.getGenericType(), source, typeIndex, null, typeIndexesPerLevel, nestingLevel, 1);
+ }
+
+ /**
+ * Extract the generic return type from the given method.
+ * @param method the method to check the return type for
+ * @param source the source class/interface defining the generic parameter types
+ * @param typeIndex the index of the type (e.g. 0 for Collections,
+ * 0 for Map keys, 1 for Map values)
+ * @param nestingLevel the nesting level of the target type
+ * @return the generic type, or {@code null} if none
+ */
+ private static Class<?> getGenericReturnType(Method method, Class<?> source, int typeIndex, int nestingLevel) {
+ return extractType(method.getGenericReturnType(), source, typeIndex, null, null, nestingLevel, 1);
+ }
+
+ /**
+ * Extract the generic type from the given Type object.
+ * @param type the Type to check
+ * @param source the source collection/map Class that we check
+ * @param typeIndex the index of the actual type argument
+ * @param nestingLevel the nesting level of the target type
+ * @param currentLevel the current nested level
+ * @return the generic type as Class, or {@code null} if none
+ */
+ private static Class<?> extractType(Type type, Class<?> source, int typeIndex,
+ Map<TypeVariable, Type> typeVariableMap, Map<Integer, Integer> typeIndexesPerLevel,
+ int nestingLevel, int currentLevel) {
+
+ Type resolvedType = type;
+ if (type instanceof TypeVariable && typeVariableMap != null) {
+ Type mappedType = typeVariableMap.get(type);
+ if (mappedType != null) {
+ resolvedType = mappedType;
+ }
+ }
+ if (resolvedType instanceof ParameterizedType) {
+ return extractTypeFromParameterizedType((ParameterizedType) resolvedType, source, typeIndex, typeVariableMap, typeIndexesPerLevel,
+ nestingLevel, currentLevel);
+ }
+ else if (resolvedType instanceof Class) {
+ return extractTypeFromClass((Class) resolvedType, source, typeIndex, typeVariableMap, typeIndexesPerLevel,
+ nestingLevel, currentLevel);
+ }
+ else if (resolvedType instanceof GenericArrayType) {
+ Type compType = ((GenericArrayType) resolvedType).getGenericComponentType();
+ return extractType(compType, source, typeIndex, typeVariableMap, typeIndexesPerLevel, nestingLevel, currentLevel + 1);
+ }
+ else {
+ return null;
+ }
+ }
+
+ /**
+ * Extract the generic type from the given ParameterizedType object.
+ * @param ptype the ParameterizedType to check
+ * @param source the expected raw source type (can be {@code null})
+ * @param typeIndex the index of the actual type argument
+ * @param nestingLevel the nesting level of the target type
+ * @param currentLevel the current nested level
+ * @return the generic type as Class, or {@code null} if none
+ */
+ private static Class<?> extractTypeFromParameterizedType(ParameterizedType ptype, Class<?> source, int typeIndex,
+ Map<TypeVariable, Type> typeVariableMap, Map<Integer, Integer> typeIndexesPerLevel,
+ int nestingLevel, int currentLevel) {
+
+ if (!(ptype.getRawType() instanceof Class)) {
+ return null;
+ }
+ Class rawType = (Class) ptype.getRawType();
+ Type[] paramTypes = ptype.getActualTypeArguments();
+ if (nestingLevel - currentLevel > 0) {
+ int nextLevel = currentLevel + 1;
+ Integer currentTypeIndex = (typeIndexesPerLevel != null ? typeIndexesPerLevel.get(nextLevel) : null);
+ // Default is last parameter type: Collection element or Map value.
+ int indexToUse = (currentTypeIndex != null ? currentTypeIndex : paramTypes.length - 1);
+ Type paramType = paramTypes[indexToUse];
+ return extractType(paramType, source, typeIndex, typeVariableMap, typeIndexesPerLevel, nestingLevel, nextLevel);
+ }
+ if (source != null && !source.isAssignableFrom(rawType)) {
+ return null;
+ }
+ Class fromSuperclassOrInterface = extractTypeFromClass(rawType, source, typeIndex, typeVariableMap, typeIndexesPerLevel,
+ nestingLevel, currentLevel);
+ if (fromSuperclassOrInterface != null) {
+ return fromSuperclassOrInterface;
+ }
+ if (paramTypes == null || typeIndex >= paramTypes.length) {
+ return null;
+ }
+ Type paramType = paramTypes[typeIndex];
+ if (paramType instanceof TypeVariable && typeVariableMap != null) {
+ Type mappedType = typeVariableMap.get(paramType);
+ if (mappedType != null) {
+ paramType = mappedType;
+ }
+ }
+ if (paramType instanceof WildcardType) {
+ WildcardType wildcardType = (WildcardType) paramType;
+ Type[] upperBounds = wildcardType.getUpperBounds();
+ if (upperBounds != null && upperBounds.length > 0 && !Object.class.equals(upperBounds[0])) {
+ paramType = upperBounds[0];
+ }
+ else {
+ Type[] lowerBounds = wildcardType.getLowerBounds();
+ if (lowerBounds != null && lowerBounds.length > 0 && !Object.class.equals(lowerBounds[0])) {
+ paramType = lowerBounds[0];
+ }
+ }
+ }
+ if (paramType instanceof ParameterizedType) {
+ paramType = ((ParameterizedType) paramType).getRawType();
+ }
+ if (paramType instanceof GenericArrayType) {
+ // A generic array type... Let's turn it into a straight array type if possible.
+ Type compType = ((GenericArrayType) paramType).getGenericComponentType();
+ if (compType instanceof Class) {
+ return Array.newInstance((Class) compType, 0).getClass();
+ }
+ }
+ else if (paramType instanceof Class) {
+ // We finally got a straight Class...
+ return (Class) paramType;
+ }
+ return null;
+ }
+
+ /**
+ * Extract the generic type from the given Class object.
+ * @param clazz the Class to check
+ * @param source the expected raw source type (can be {@code null})
+ * @param typeIndex the index of the actual type argument
+ * @return the generic type as Class, or {@code null} if none
+ */
+ private static Class<?> extractTypeFromClass(Class<?> clazz, Class<?> source, int typeIndex) {
+ return extractTypeFromClass(clazz, source, typeIndex, null, null, 1, 1);
+ }
+
+ /**
+ * Extract the generic type from the given Class object.
+ * @param clazz the Class to check
+ * @param source the expected raw source type (can be {@code null})
+ * @param typeIndex the index of the actual type argument
+ * @param nestingLevel the nesting level of the target type
+ * @param currentLevel the current nested level
+ * @return the generic type as Class, or {@code null} if none
+ */
+ private static Class<?> extractTypeFromClass(Class<?> clazz, Class<?> source, int typeIndex,
+ Map<TypeVariable, Type> typeVariableMap, Map<Integer, Integer> typeIndexesPerLevel,
+ int nestingLevel, int currentLevel) {
+
+ if (clazz.getName().startsWith("java.util.")) {
+ return null;
+ }
+ if (clazz.getSuperclass() != null && isIntrospectionCandidate(clazz.getSuperclass())) {
+ try {
+ return extractType(clazz.getGenericSuperclass(), source, typeIndex, typeVariableMap,
+ typeIndexesPerLevel, nestingLevel, currentLevel);
+ }
+ catch (MalformedParameterizedTypeException ex) {
+ // from getGenericSuperclass() - ignore and continue with interface introspection
+ }
+ }
+ Type[] ifcs = clazz.getGenericInterfaces();
+ if (ifcs != null) {
+ for (Type ifc : ifcs) {
+ Type rawType = ifc;
+ if (ifc instanceof ParameterizedType) {
+ rawType = ((ParameterizedType) ifc).getRawType();
+ }
+ if (rawType instanceof Class && isIntrospectionCandidate((Class) rawType)) {
+ return extractType(ifc, source, typeIndex, typeVariableMap, typeIndexesPerLevel, nestingLevel, currentLevel);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Determine whether the given class is a potential candidate
+ * that defines generic collection or map types.
+ * @param clazz the class to check
+ * @return whether the given class is assignable to Collection or Map
+ */
+ private static boolean isIntrospectionCandidate(Class clazz) {
+ return (Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz));
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java
new file mode 100644
index 00000000..b860aaaf
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright 2002-2013 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;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.MalformedParameterizedTypeException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ConcurrentReferenceHashMap;
+
+/**
+ * Helper class for resolving generic types against type variables.
+ *
+ * <p>Mainly intended for usage within the framework, resolving method
+ * parameter types even when they are declared generically.
+ *
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @author Sam Brannen
+ * @since 2.5.2
+ * @see GenericCollectionTypeResolver
+ */
+public abstract class GenericTypeResolver {
+
+ /** Cache from Class to TypeVariable Map */
+ private static final Map<Class, Map<TypeVariable, Type>> typeVariableCache =
+ new ConcurrentReferenceHashMap<Class, Map<TypeVariable,Type>>();
+
+
+ /**
+ * Determine the target type for the given parameter specification.
+ * @param methodParam the method parameter specification
+ * @return the corresponding generic parameter type
+ */
+ public static Type getTargetType(MethodParameter methodParam) {
+ Assert.notNull(methodParam, "MethodParameter must not be null");
+ if (methodParam.getConstructor() != null) {
+ return methodParam.getConstructor().getGenericParameterTypes()[methodParam.getParameterIndex()];
+ }
+ else {
+ if (methodParam.getParameterIndex() >= 0) {
+ return methodParam.getMethod().getGenericParameterTypes()[methodParam.getParameterIndex()];
+ }
+ else {
+ return methodParam.getMethod().getGenericReturnType();
+ }
+ }
+ }
+
+ /**
+ * Determine the target type for the given generic parameter type.
+ * @param methodParam the method parameter specification
+ * @param clazz the class to resolve type variables against
+ * @return the corresponding generic parameter or return type
+ */
+ public static Class<?> resolveParameterType(MethodParameter methodParam, Class<?> clazz) {
+ Type genericType = getTargetType(methodParam);
+ Assert.notNull(clazz, "Class must not be null");
+ Map<TypeVariable, Type> typeVariableMap = getTypeVariableMap(clazz);
+ Type rawType = getRawType(genericType, typeVariableMap);
+ Class<?> result = (rawType instanceof Class ? (Class) rawType : methodParam.getParameterType());
+ methodParam.setParameterType(result);
+ methodParam.typeVariableMap = typeVariableMap;
+ return result;
+ }
+
+ /**
+ * Determine the target type for the generic return type of the given method,
+ * where formal type variables are declared on the given class.
+ * @param method the method to introspect
+ * @param clazz the class to resolve type variables against
+ * @return the corresponding generic parameter or return type
+ * @see #resolveReturnTypeForGenericMethod
+ */
+ public static Class<?> resolveReturnType(Method method, Class<?> clazz) {
+ Assert.notNull(method, "Method must not be null");
+ Type genericType = method.getGenericReturnType();
+ Assert.notNull(clazz, "Class must not be null");
+ Map<TypeVariable, Type> typeVariableMap = getTypeVariableMap(clazz);
+ Type rawType = getRawType(genericType, typeVariableMap);
+ return (rawType instanceof Class ? (Class<?>) rawType : method.getReturnType());
+ }
+
+ /**
+ * Determine the target type for the generic return type of the given
+ * <em>generic method</em>, where formal type variables are declared on
+ * the given method itself.
+ * <p>For example, given a factory method with the following signature,
+ * if {@code resolveReturnTypeForGenericMethod()} is invoked with the reflected
+ * method for {@code creatProxy()} and an {@code Object[]} array containing
+ * {@code MyService.class}, {@code resolveReturnTypeForGenericMethod()} will
+ * infer that the target return type is {@code MyService}.
+ * <pre>{@code public static <T> T createProxy(Class<T> clazz)}</pre>
+ * <h4>Possible Return Values</h4>
+ * <ul>
+ * <li>the target return type, if it can be inferred</li>
+ * <li>the {@linkplain Method#getReturnType() standard return type}, if
+ * the given {@code method} does not declare any {@linkplain
+ * Method#getTypeParameters() formal type variables}</li>
+ * <li>the {@linkplain Method#getReturnType() standard return type}, if the
+ * target return type cannot be inferred (e.g., due to type erasure)</li>
+ * <li>{@code null}, if the length of the given arguments array is shorter
+ * than the length of the {@linkplain
+ * Method#getGenericParameterTypes() formal argument list} for the given
+ * method</li>
+ * </ul>
+ * @param method the method to introspect, never {@code null}
+ * @param args the arguments that will be supplied to the method when it is
+ * invoked, never {@code null}
+ * @return the resolved target return type, the standard return type, or {@code null}
+ * @since 3.2
+ * @deprecated in favor of resolveReturnTypeForFactoryMethod in the internal
+ * AutowireUtils class in the beans module; we do not expect other use of it!
+ */
+ @Deprecated
+ public static Class<?> resolveReturnTypeForGenericMethod(Method method, Object[] args) {
+ Assert.notNull(method, "Method must not be null");
+ Assert.notNull(args, "Argument array must not be null");
+
+ TypeVariable<Method>[] declaredTypeVariables = method.getTypeParameters();
+ Type genericReturnType = method.getGenericReturnType();
+ Type[] methodArgumentTypes = method.getGenericParameterTypes();
+
+ // No declared type variables to inspect, so just return the standard return type.
+ if (declaredTypeVariables.length == 0) {
+ return method.getReturnType();
+ }
+
+ // The supplied argument list is too short for the method's signature, so
+ // return null, since such a method invocation would fail.
+ if (args.length < methodArgumentTypes.length) {
+ return null;
+ }
+
+ // Ensure that the type variable (e.g., T) is declared directly on the method
+ // itself (e.g., via <T>), not on the enclosing class or interface.
+ boolean locallyDeclaredTypeVariableMatchesReturnType = false;
+ for (TypeVariable<Method> currentTypeVariable : declaredTypeVariables) {
+ if (currentTypeVariable.equals(genericReturnType)) {
+ locallyDeclaredTypeVariableMatchesReturnType = true;
+ break;
+ }
+ }
+
+ if (locallyDeclaredTypeVariableMatchesReturnType) {
+ for (int i = 0; i < methodArgumentTypes.length; i++) {
+ Type currentMethodArgumentType = methodArgumentTypes[i];
+ if (currentMethodArgumentType.equals(genericReturnType)) {
+ return args[i].getClass();
+ }
+ if (currentMethodArgumentType instanceof ParameterizedType) {
+ ParameterizedType parameterizedType = (ParameterizedType) currentMethodArgumentType;
+ Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
+ for (Type typeArg : actualTypeArguments) {
+ if (typeArg.equals(genericReturnType)) {
+ if (args[i] instanceof Class) {
+ return (Class<?>) args[i];
+ }
+ else {
+ // Consider adding logic to determine the class of the typeArg, if possible.
+ // For now, just fall back...
+ return method.getReturnType();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Fall back...
+ return method.getReturnType();
+ }
+
+ /**
+ * Resolve the single type argument of the given generic interface against the given
+ * target method which is assumed to return the given interface or an implementation
+ * of it.
+ * @param method the target method to check the return type of
+ * @param genericIfc the generic interface or superclass to resolve the type argument from
+ * @return the resolved parameter type of the method return type, or {@code null}
+ * if not resolvable or if the single argument is of type {@link WildcardType}.
+ */
+ public static Class<?> resolveReturnTypeArgument(Method method, Class<?> genericIfc) {
+ Assert.notNull(method, "method must not be null");
+ Type returnType = method.getReturnType();
+ Type genericReturnType = method.getGenericReturnType();
+ if (returnType.equals(genericIfc)) {
+ if (genericReturnType instanceof ParameterizedType) {
+ ParameterizedType targetType = (ParameterizedType) genericReturnType;
+ Type[] actualTypeArguments = targetType.getActualTypeArguments();
+ Type typeArg = actualTypeArguments[0];
+ if (!(typeArg instanceof WildcardType)) {
+ return (Class<?>) typeArg;
+ }
+ }
+ else {
+ return null;
+ }
+ }
+ return resolveTypeArgument((Class<?>) returnType, genericIfc);
+ }
+
+ /**
+ * Resolve the single type argument of the given generic interface against
+ * the given target class which is assumed to implement the generic interface
+ * and possibly declare a concrete type for its type variable.
+ * @param clazz the target class to check against
+ * @param genericIfc the generic interface or superclass to resolve the type argument from
+ * @return the resolved type of the argument, or {@code null} if not resolvable
+ */
+ public static Class<?> resolveTypeArgument(Class<?> clazz, Class<?> genericIfc) {
+ Class<?>[] typeArgs = resolveTypeArguments(clazz, genericIfc);
+ if (typeArgs == null) {
+ return null;
+ }
+ if (typeArgs.length != 1) {
+ throw new IllegalArgumentException("Expected 1 type argument on generic interface [" +
+ genericIfc.getName() + "] but found " + typeArgs.length);
+ }
+ return typeArgs[0];
+ }
+
+ /**
+ * Resolve the type arguments of the given generic interface against the given
+ * target class which is assumed to implement the generic interface and possibly
+ * declare concrete types for its type variables.
+ * <p>Note: In Spring 3.2, this method doesn't return {@code null} in all scenarios
+ * where it should. To be fixed in Spring 4.0; for client code, this just means it
+ * might see {@code null} in a few more cases then where it now sees an array with
+ * a single {@link Object} type.
+ * @param clazz the target class to check against
+ * @param genericIfc the generic interface or superclass to resolve the type argument from
+ * @return the resolved type of each argument, with the array size matching the
+ * number of actual type arguments, or {@code null} if not resolvable
+ */
+ public static Class<?>[] resolveTypeArguments(Class<?> clazz, Class<?> genericIfc) {
+ return doResolveTypeArguments(clazz, clazz, genericIfc);
+ }
+
+ private static Class<?>[] doResolveTypeArguments(Class<?> ownerClass, Class<?> classToIntrospect, Class<?> genericIfc) {
+ while (classToIntrospect != null) {
+ if (genericIfc.isInterface()) {
+ Type[] ifcs = classToIntrospect.getGenericInterfaces();
+ for (Type ifc : ifcs) {
+ Class<?>[] result = doResolveTypeArguments(ownerClass, ifc, genericIfc);
+ if (result != null) {
+ return result;
+ }
+ }
+ }
+ else {
+ try {
+ Class<?>[] result = doResolveTypeArguments(ownerClass, classToIntrospect.getGenericSuperclass(), genericIfc);
+ if (result != null) {
+ return result;
+ }
+ }
+ catch (MalformedParameterizedTypeException ex) {
+ // from getGenericSuperclass() - return null to skip further superclass traversal
+ return null;
+ }
+ }
+ classToIntrospect = classToIntrospect.getSuperclass();
+ }
+ return null;
+ }
+
+ private static Class<?>[] doResolveTypeArguments(Class<?> ownerClass, Type ifc, Class<?> genericIfc) {
+ if (ifc instanceof ParameterizedType) {
+ ParameterizedType paramIfc = (ParameterizedType) ifc;
+ Type rawType = paramIfc.getRawType();
+ if (genericIfc.equals(rawType)) {
+ Type[] typeArgs = paramIfc.getActualTypeArguments();
+ Class<?>[] result = new Class[typeArgs.length];
+ for (int i = 0; i < typeArgs.length; i++) {
+ Type arg = typeArgs[i];
+ result[i] = extractClass(ownerClass, arg);
+ }
+ return result;
+ }
+ else if (genericIfc.isAssignableFrom((Class) rawType)) {
+ return doResolveTypeArguments(ownerClass, (Class) rawType, genericIfc);
+ }
+ }
+ else if (ifc != null && genericIfc.isAssignableFrom((Class) ifc)) {
+ return doResolveTypeArguments(ownerClass, (Class) ifc, genericIfc);
+ }
+ return null;
+ }
+
+ /**
+ * Extract a Class from the given Type.
+ */
+ private static Class<?> extractClass(Class<?> ownerClass, Type arg) {
+ if (arg instanceof ParameterizedType) {
+ return extractClass(ownerClass, ((ParameterizedType) arg).getRawType());
+ }
+ else if (arg instanceof GenericArrayType) {
+ GenericArrayType gat = (GenericArrayType) arg;
+ Type gt = gat.getGenericComponentType();
+ Class<?> componentClass = extractClass(ownerClass, gt);
+ return Array.newInstance(componentClass, 0).getClass();
+ }
+ else if (arg instanceof TypeVariable) {
+ TypeVariable tv = (TypeVariable) arg;
+ arg = getTypeVariableMap(ownerClass).get(tv);
+ if (arg == null) {
+ arg = extractBoundForTypeVariable(tv);
+ if (arg instanceof ParameterizedType) {
+ return extractClass(ownerClass, ((ParameterizedType) arg).getRawType());
+ }
+ }
+ else {
+ return extractClass(ownerClass, arg);
+ }
+ }
+ return (arg instanceof Class ? (Class) arg : Object.class);
+ }
+
+ /**
+ * Resolve the specified generic type against the given TypeVariable map.
+ * @param genericType the generic type to resolve
+ * @param typeVariableMap the TypeVariable Map to resolved against
+ * @return the type if it resolves to a Class, or {@code Object.class} otherwise
+ */
+ public static Class<?> resolveType(Type genericType, Map<TypeVariable, Type> typeVariableMap) {
+ Type resolvedType = getRawType(genericType, typeVariableMap);
+ if (resolvedType instanceof GenericArrayType) {
+ Type componentType = ((GenericArrayType) resolvedType).getGenericComponentType();
+ Class<?> componentClass = resolveType(componentType, typeVariableMap);
+ resolvedType = Array.newInstance(componentClass, 0).getClass();
+ }
+ return (resolvedType instanceof Class ? (Class) resolvedType : Object.class);
+ }
+
+ /**
+ * Determine the raw type for the given generic parameter type.
+ * @param genericType the generic type to resolve
+ * @param typeVariableMap the TypeVariable Map to resolved against
+ * @return the resolved raw type
+ */
+ static Type getRawType(Type genericType, Map<TypeVariable, Type> typeVariableMap) {
+ Type resolvedType = genericType;
+ if (genericType instanceof TypeVariable) {
+ TypeVariable tv = (TypeVariable) genericType;
+ resolvedType = typeVariableMap.get(tv);
+ if (resolvedType == null) {
+ resolvedType = extractBoundForTypeVariable(tv);
+ }
+ }
+ if (resolvedType instanceof ParameterizedType) {
+ return ((ParameterizedType) resolvedType).getRawType();
+ }
+ else {
+ return resolvedType;
+ }
+ }
+
+ /**
+ * Build a mapping of {@link TypeVariable#getName TypeVariable names} to
+ * {@link Class concrete classes} for the specified {@link Class}. Searches
+ * all super types, enclosing types and interfaces.
+ */
+ public static Map<TypeVariable, Type> getTypeVariableMap(Class<?> clazz) {
+ Map<TypeVariable, Type> typeVariableMap = typeVariableCache.get(clazz);
+
+ if (typeVariableMap == null) {
+ typeVariableMap = new HashMap<TypeVariable, Type>();
+
+ // interfaces
+ extractTypeVariablesFromGenericInterfaces(clazz.getGenericInterfaces(), typeVariableMap);
+
+ try {
+ // super class
+ Class<?> type = clazz;
+ while (type.getSuperclass() != null && !Object.class.equals(type.getSuperclass())) {
+ Type genericType = type.getGenericSuperclass();
+ if (genericType instanceof ParameterizedType) {
+ ParameterizedType pt = (ParameterizedType) genericType;
+ populateTypeMapFromParameterizedType(pt, typeVariableMap);
+ }
+ extractTypeVariablesFromGenericInterfaces(type.getSuperclass().getGenericInterfaces(), typeVariableMap);
+ type = type.getSuperclass();
+ }
+ }
+ catch (MalformedParameterizedTypeException ex) {
+ // from getGenericSuperclass() - ignore and continue with member class check
+ }
+
+ try {
+ // enclosing class
+ Class<?> type = clazz;
+ while (type.isMemberClass()) {
+ Type genericType = type.getGenericSuperclass();
+ if (genericType instanceof ParameterizedType) {
+ ParameterizedType pt = (ParameterizedType) genericType;
+ populateTypeMapFromParameterizedType(pt, typeVariableMap);
+ }
+ type = type.getEnclosingClass();
+ }
+ }
+ catch (MalformedParameterizedTypeException ex) {
+ // from getGenericSuperclass() - ignore and preserve previously accumulated type variables
+ }
+
+ typeVariableCache.put(clazz, typeVariableMap);
+ }
+
+ return typeVariableMap;
+ }
+
+ /**
+ * Extracts the bound {@code Type} for a given {@link TypeVariable}.
+ */
+ static Type extractBoundForTypeVariable(TypeVariable typeVariable) {
+ Type[] bounds = typeVariable.getBounds();
+ if (bounds.length == 0) {
+ return Object.class;
+ }
+ Type bound = bounds[0];
+ if (bound instanceof TypeVariable) {
+ bound = extractBoundForTypeVariable((TypeVariable) bound);
+ }
+ return bound;
+ }
+
+ private static void extractTypeVariablesFromGenericInterfaces(Type[] genericInterfaces, Map<TypeVariable, Type> typeVariableMap) {
+ for (Type genericInterface : genericInterfaces) {
+ if (genericInterface instanceof ParameterizedType) {
+ ParameterizedType pt = (ParameterizedType) genericInterface;
+ populateTypeMapFromParameterizedType(pt, typeVariableMap);
+ if (pt.getRawType() instanceof Class) {
+ extractTypeVariablesFromGenericInterfaces(
+ ((Class) pt.getRawType()).getGenericInterfaces(), typeVariableMap);
+ }
+ }
+ else if (genericInterface instanceof Class) {
+ extractTypeVariablesFromGenericInterfaces(
+ ((Class) genericInterface).getGenericInterfaces(), typeVariableMap);
+ }
+ }
+ }
+
+ /**
+ * Read the {@link TypeVariable TypeVariables} from the supplied {@link ParameterizedType}
+ * and add mappings corresponding to the {@link TypeVariable#getName TypeVariable name} ->
+ * concrete type to the supplied {@link Map}.
+ * <p>Consider this case:
+ * <pre class="code>
+ * public interface Foo<S, T> {
+ * ..
+ * }
+ *
+ * public class FooImpl implements Foo<String, Integer> {
+ * ..
+ * }</pre>
+ * For '{@code FooImpl}' the following mappings would be added to the {@link Map}:
+ * {S=java.lang.String, T=java.lang.Integer}.
+ */
+ private static void populateTypeMapFromParameterizedType(ParameterizedType type, Map<TypeVariable, Type> typeVariableMap) {
+ if (type.getRawType() instanceof Class) {
+ Type[] actualTypeArguments = type.getActualTypeArguments();
+ TypeVariable[] typeVariables = ((Class) type.getRawType()).getTypeParameters();
+ for (int i = 0; i < actualTypeArguments.length; i++) {
+ Type actualTypeArgument = actualTypeArguments[i];
+ TypeVariable variable = typeVariables[i];
+ if (actualTypeArgument instanceof Class) {
+ typeVariableMap.put(variable, actualTypeArgument);
+ }
+ else if (actualTypeArgument instanceof GenericArrayType) {
+ typeVariableMap.put(variable, actualTypeArgument);
+ }
+ else if (actualTypeArgument instanceof ParameterizedType) {
+ typeVariableMap.put(variable, actualTypeArgument);
+ }
+ else if (actualTypeArgument instanceof TypeVariable) {
+ // We have a type that is parameterized at instantiation time
+ // the nearest match on the bridge method will be the bounded type.
+ TypeVariable typeVariableArgument = (TypeVariable) actualTypeArgument;
+ Type resolvedType = typeVariableMap.get(typeVariableArgument);
+ if (resolvedType == null) {
+ resolvedType = extractBoundForTypeVariable(typeVariableArgument);
+ }
+ typeVariableMap.put(variable, resolvedType);
+ }
+ }
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/InfrastructureProxy.java b/spring-core/src/main/java/org/springframework/core/InfrastructureProxy.java
new file mode 100644
index 00000000..18f90286
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/InfrastructureProxy.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2012 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;
+
+/**
+ * Interface to be implemented by transparent resource proxies that need to be
+ * considered as equal to the underlying resource, for example for consistent
+ * lookup key comparisons. Note that this interface does imply such special
+ * semantics and does not constitute a general-purpose mixin!
+ *
+ * <p>Such wrappers will automatically be unwrapped for key comparisons in
+ * {@link org.springframework.transaction.support.TransactionSynchronizationManager}.
+ *
+ * <p>Only fully transparent proxies, e.g. for redirection or service lookups,
+ * are supposed to implement this interface. Proxies that decorate the target
+ * object with new behavior, such as AOP proxies, do <i>not</i> qualify here!
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.4
+ * @see org.springframework.transaction.support.TransactionSynchronizationManager
+ */
+public interface InfrastructureProxy {
+
+ /**
+ * Return the underlying resource (never {@code null}).
+ */
+ Object getWrappedObject();
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/JdkVersion.java b/spring-core/src/main/java/org/springframework/core/JdkVersion.java
new file mode 100644
index 00000000..a77f75ff
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/JdkVersion.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2002-2013 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;
+
+/**
+ * Internal helper class used to find the Java/JVM version
+ * that Spring is operating on, to allow for automatically
+ * adapting to the present platform's capabilities.
+ *
+ * <p>Note that Spring requires JVM 1.5 or higher, as of Spring 3.0.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Rick Evans
+ */
+public abstract class JdkVersion {
+
+ /**
+ * Constant identifying the 1.3.x JVM (JDK 1.3).
+ */
+ public static final int JAVA_13 = 0;
+
+ /**
+ * Constant identifying the 1.4.x JVM (J2SE 1.4).
+ */
+ public static final int JAVA_14 = 1;
+
+ /**
+ * Constant identifying the 1.5 JVM (Java 5).
+ */
+ public static final int JAVA_15 = 2;
+
+ /**
+ * Constant identifying the 1.6 JVM (Java 6).
+ */
+ public static final int JAVA_16 = 3;
+
+ /**
+ * Constant identifying the 1.7 JVM (Java 7).
+ */
+ public static final int JAVA_17 = 4;
+
+ /**
+ * Constant identifying the 1.8 JVM (Java 8).
+ */
+ public static final int JAVA_18 = 5;
+
+
+ private static final String javaVersion;
+
+ private static final int majorJavaVersion;
+
+ static {
+ javaVersion = System.getProperty("java.version");
+ // version String should look like "1.4.2_10"
+ if (javaVersion.contains("1.8.")) {
+ majorJavaVersion = JAVA_18;
+ }
+ else if (javaVersion.contains("1.7.")) {
+ majorJavaVersion = JAVA_17;
+ }
+ else if (javaVersion.contains("1.6.")) {
+ majorJavaVersion = JAVA_16;
+ }
+ else {
+ // else leave 1.5 as default (it's either 1.5 or unknown)
+ majorJavaVersion = JAVA_15;
+ }
+ }
+
+
+ /**
+ * Return the full Java version string, as returned by
+ * {@code System.getProperty("java.version")}.
+ * @return the full Java version string
+ * @see System#getProperty(String)
+ */
+ public static String getJavaVersion() {
+ return javaVersion;
+ }
+
+ /**
+ * Get the major version code. This means we can do things like
+ * {@code if (getMajorJavaVersion() >= JAVA_17)}.
+ * @return a code comparable to the JAVA_XX codes in this class
+ * @see #JAVA_13
+ * @see #JAVA_14
+ * @see #JAVA_15
+ * @see #JAVA_16
+ * @see #JAVA_17
+ */
+ public static int getMajorJavaVersion() {
+ return majorJavaVersion;
+ }
+
+
+ /**
+ * Convenience method to determine if the current JVM is at least Java 1.4.
+ * @return {@code true} if the current JVM is at least Java 1.4
+ * @deprecated as of Spring 3.0 which requires Java 1.5+
+ * @see #getMajorJavaVersion()
+ * @see #JAVA_14
+ * @see #JAVA_15
+ * @see #JAVA_16
+ * @see #JAVA_17
+ */
+ @Deprecated
+ public static boolean isAtLeastJava14() {
+ return true;
+ }
+
+ /**
+ * Convenience method to determine if the current JVM is at least
+ * Java 1.5 (Java 5).
+ * @return {@code true} if the current JVM is at least Java 1.5
+ * @deprecated as of Spring 3.0 which requires Java 1.5+
+ * @see #getMajorJavaVersion()
+ * @see #JAVA_15
+ * @see #JAVA_16
+ * @see #JAVA_17
+ */
+ @Deprecated
+ public static boolean isAtLeastJava15() {
+ return true;
+ }
+
+ /**
+ * Convenience method to determine if the current JVM is at least
+ * Java 1.6 (Java 6).
+ * @return {@code true} if the current JVM is at least Java 1.6
+ * @deprecated as of Spring 3.0, in favor of reflective checks for
+ * the specific Java 1.6 classes of interest
+ * @see #getMajorJavaVersion()
+ * @see #JAVA_16
+ * @see #JAVA_17
+ */
+ @Deprecated
+ public static boolean isAtLeastJava16() {
+ return (majorJavaVersion >= JAVA_16);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java b/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java
new file mode 100644
index 00000000..151dcb30
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2002-2014 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;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.asm.ClassReader;
+import org.springframework.asm.ClassVisitor;
+import org.springframework.asm.Label;
+import org.springframework.asm.MethodVisitor;
+import org.springframework.asm.Opcodes;
+import org.springframework.asm.SpringAsmInfo;
+import org.springframework.asm.Type;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Implementation of {@link ParameterNameDiscoverer} that uses the LocalVariableTable
+ * information in the method attributes to discover parameter names. Returns
+ * {@code null} if the class file was compiled without debug information.
+ *
+ * <p>Uses ObjectWeb's ASM library for analyzing class files. Each discoverer instance
+ * caches the ASM discovered information for each introspected Class, in a thread-safe
+ * manner. It is recommended to reuse ParameterNameDiscoverer instances as far as possible.
+ *
+ * @author Adrian Colyer
+ * @author Costin Leau
+ * @author Juergen Hoeller
+ * @author Chris Beams
+ * @since 2.0
+ */
+public class LocalVariableTableParameterNameDiscoverer implements ParameterNameDiscoverer {
+
+ private static final Log logger = LogFactory.getLog(LocalVariableTableParameterNameDiscoverer.class);
+
+ // marker object for classes that do not have any debug info
+ private static final Map<Member, String[]> NO_DEBUG_INFO_MAP = Collections.emptyMap();
+
+ // the cache uses a nested index (value is a map) to keep the top level cache relatively small in size
+ private final Map<Class<?>, Map<Member, String[]>> parameterNamesCache =
+ new ConcurrentHashMap<Class<?>, Map<Member, String[]>>(32);
+
+
+ public String[] getParameterNames(Method method) {
+ Method originalMethod = BridgeMethodResolver.findBridgedMethod(method);
+ Class<?> declaringClass = originalMethod.getDeclaringClass();
+ Map<Member, String[]> map = this.parameterNamesCache.get(declaringClass);
+ if (map == null) {
+ map = inspectClass(declaringClass);
+ this.parameterNamesCache.put(declaringClass, map);
+ }
+ if (map != NO_DEBUG_INFO_MAP) {
+ return map.get(originalMethod);
+ }
+ return null;
+ }
+
+ public String[] getParameterNames(Constructor<?> ctor) {
+ Class<?> declaringClass = ctor.getDeclaringClass();
+ Map<Member, String[]> map = this.parameterNamesCache.get(declaringClass);
+ if (map == null) {
+ map = inspectClass(declaringClass);
+ this.parameterNamesCache.put(declaringClass, map);
+ }
+ if (map != NO_DEBUG_INFO_MAP) {
+ return map.get(ctor);
+ }
+ return null;
+ }
+
+ /**
+ * Inspects the target class. Exceptions will be logged and a maker map returned
+ * to indicate the lack of debug information.
+ */
+ private Map<Member, String[]> inspectClass(Class<?> clazz) {
+ InputStream is = clazz.getResourceAsStream(ClassUtils.getClassFileName(clazz));
+ if (is == null) {
+ // We couldn't load the class file, which is not fatal as it
+ // simply means this method of discovering parameter names won't work.
+ if (logger.isDebugEnabled()) {
+ logger.debug("Cannot find '.class' file for class [" + clazz +
+ "] - unable to determine constructor/method parameter names");
+ }
+ return NO_DEBUG_INFO_MAP;
+ }
+ try {
+ ClassReader classReader = new ClassReader(is);
+ Map<Member, String[]> map = new ConcurrentHashMap<Member, String[]>(32);
+ classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), 0);
+ return map;
+ }
+ catch (IOException ex) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Exception thrown while reading '.class' file for class [" + clazz +
+ "] - unable to determine constructor/method parameter names", ex);
+ }
+ }
+ catch (IllegalArgumentException ex) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("ASM ClassReader failed to parse class file [" + clazz +
+ "], probably due to a new Java class file version that isn't supported yet " +
+ "- unable to determine constructor/method parameter names", ex);
+ }
+ }
+ finally {
+ try {
+ is.close();
+ }
+ catch (IOException ex) {
+ // ignore
+ }
+ }
+ return NO_DEBUG_INFO_MAP;
+ }
+
+
+ /**
+ * Helper class that inspects all methods (constructor included) and then
+ * attempts to find the parameter names for that member.
+ */
+ private static class ParameterNameDiscoveringVisitor extends ClassVisitor {
+
+ private static final String STATIC_CLASS_INIT = "<clinit>";
+
+ private final Class<?> clazz;
+
+ private final Map<Member, String[]> memberMap;
+
+ public ParameterNameDiscoveringVisitor(Class<?> clazz, Map<Member, String[]> memberMap) {
+ super(SpringAsmInfo.ASM_VERSION);
+ this.clazz = clazz;
+ this.memberMap = memberMap;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ // exclude synthetic + bridged && static class initialization
+ if (!isSyntheticOrBridged(access) && !STATIC_CLASS_INIT.equals(name)) {
+ return new LocalVariableTableVisitor(clazz, memberMap, name, desc, isStatic(access));
+ }
+ return null;
+ }
+
+ private static boolean isSyntheticOrBridged(int access) {
+ return (((access & Opcodes.ACC_SYNTHETIC) | (access & Opcodes.ACC_BRIDGE)) > 0);
+ }
+
+ private static boolean isStatic(int access) {
+ return ((access & Opcodes.ACC_STATIC) > 0);
+ }
+ }
+
+
+ private static class LocalVariableTableVisitor extends MethodVisitor {
+
+ private static final String CONSTRUCTOR = "<init>";
+
+ private final Class<?> clazz;
+
+ private final Map<Member, String[]> memberMap;
+
+ private final String name;
+
+ private final Type[] args;
+
+ private final String[] parameterNames;
+
+ private final boolean isStatic;
+
+ private boolean hasLvtInfo = false;
+
+ /*
+ * The nth entry contains the slot index of the LVT table entry holding the
+ * argument name for the nth parameter.
+ */
+ private final int[] lvtSlotIndex;
+
+ public LocalVariableTableVisitor(Class<?> clazz, Map<Member, String[]> map, String name, String desc, boolean isStatic) {
+ super(SpringAsmInfo.ASM_VERSION);
+ this.clazz = clazz;
+ this.memberMap = map;
+ this.name = name;
+ this.args = Type.getArgumentTypes(desc);
+ this.parameterNames = new String[this.args.length];
+ this.isStatic = isStatic;
+ this.lvtSlotIndex = computeLvtSlotIndices(isStatic, this.args);
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String description, String signature, Label start, Label end, int index) {
+ this.hasLvtInfo = true;
+ for (int i = 0; i < this.lvtSlotIndex.length; i++) {
+ if (this.lvtSlotIndex[i] == index) {
+ this.parameterNames[i] = name;
+ }
+ }
+ }
+
+ @Override
+ public void visitEnd() {
+ if (this.hasLvtInfo || (this.isStatic && this.parameterNames.length == 0)) {
+ // visitLocalVariable will never be called for static no args methods
+ // which doesn't use any local variables.
+ // This means that hasLvtInfo could be false for that kind of methods
+ // even if the class has local variable info.
+ this.memberMap.put(resolveMember(), this.parameterNames);
+ }
+ }
+
+ private Member resolveMember() {
+ ClassLoader loader = this.clazz.getClassLoader();
+ Class<?>[] argTypes = new Class<?>[this.args.length];
+ for (int i = 0; i < this.args.length; i++) {
+ argTypes[i] = ClassUtils.resolveClassName(this.args[i].getClassName(), loader);
+ }
+ try {
+ if (CONSTRUCTOR.equals(this.name)) {
+ return this.clazz.getDeclaredConstructor(argTypes);
+ }
+ return this.clazz.getDeclaredMethod(this.name, argTypes);
+ }
+ catch (NoSuchMethodException ex) {
+ throw new IllegalStateException("Method [" + this.name +
+ "] was discovered in the .class file but cannot be resolved in the class object", ex);
+ }
+ }
+
+ private static int[] computeLvtSlotIndices(boolean isStatic, Type[] paramTypes) {
+ int[] lvtIndex = new int[paramTypes.length];
+ int nextIndex = (isStatic ? 0 : 1);
+ for (int i = 0; i < paramTypes.length; i++) {
+ lvtIndex[i] = nextIndex;
+ if (isWideType(paramTypes[i])) {
+ nextIndex += 2;
+ }
+ else {
+ nextIndex++;
+ }
+ }
+ return lvtIndex;
+ }
+
+ private static boolean isWideType(Type aType) {
+ // float is not a wide type
+ return (aType == Type.LONG_TYPE || aType == Type.DOUBLE_TYPE);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/MethodParameter.java b/spring-core/src/main/java/org/springframework/core/MethodParameter.java
new file mode 100644
index 00000000..1325db2a
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java
@@ -0,0 +1,455 @@
+/*
+ * Copyright 2002-2014 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;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.util.Assert;
+
+/**
+ * Helper class that encapsulates the specification of a method parameter, i.e.
+ * a Method or Constructor plus a parameter index and a nested type index for
+ * a declared generic type. Useful as a specification object to pass along.
+ *
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @author Andy Clement
+ * @since 2.0
+ * @see GenericCollectionTypeResolver
+ */
+public class MethodParameter {
+
+ private final Method method;
+
+ private final Constructor<?> constructor;
+
+ private final int parameterIndex;
+
+ private Class<?> parameterType;
+
+ private Type genericParameterType;
+
+ private Annotation[] parameterAnnotations;
+
+ private ParameterNameDiscoverer parameterNameDiscoverer;
+
+ private String parameterName;
+
+ private int nestingLevel = 1;
+
+ /** Map from Integer level to Integer type index */
+ Map<Integer, Integer> typeIndexesPerLevel;
+
+ Map<TypeVariable, Type> typeVariableMap;
+
+
+ /**
+ * Create a new MethodParameter for the given method, with nesting level 1.
+ * @param method the Method to specify a parameter for
+ * @param parameterIndex the index of the parameter
+ */
+ public MethodParameter(Method method, int parameterIndex) {
+ this(method, parameterIndex, 1);
+ }
+
+ /**
+ * Create a new MethodParameter for the given method.
+ * @param method the Method to specify a parameter for
+ * @param parameterIndex the index of the parameter
+ * (-1 for the method return type; 0 for the first method parameter,
+ * 1 for the second method parameter, etc)
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ */
+ public MethodParameter(Method method, int parameterIndex, int nestingLevel) {
+ Assert.notNull(method, "Method must not be null");
+ this.method = method;
+ this.parameterIndex = parameterIndex;
+ this.nestingLevel = nestingLevel;
+ this.constructor = null;
+ }
+
+ /**
+ * Create a new MethodParameter for the given constructor, with nesting level 1.
+ * @param constructor the Constructor to specify a parameter for
+ * @param parameterIndex the index of the parameter
+ */
+ public MethodParameter(Constructor<?> constructor, int parameterIndex) {
+ this(constructor, parameterIndex, 1);
+ }
+
+ /**
+ * Create a new MethodParameter for the given constructor.
+ * @param constructor the Constructor to specify a parameter for
+ * @param parameterIndex the index of the parameter
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ */
+ public MethodParameter(Constructor<?> constructor, int parameterIndex, int nestingLevel) {
+ Assert.notNull(constructor, "Constructor must not be null");
+ this.constructor = constructor;
+ this.parameterIndex = parameterIndex;
+ this.nestingLevel = nestingLevel;
+ this.method = null;
+ }
+
+ /**
+ * Copy constructor, resulting in an independent MethodParameter object
+ * based on the same metadata and cache state that the original object was in.
+ * @param original the original MethodParameter object to copy from
+ */
+ public MethodParameter(MethodParameter original) {
+ Assert.notNull(original, "Original must not be null");
+ this.method = original.method;
+ this.constructor = original.constructor;
+ this.parameterIndex = original.parameterIndex;
+ this.parameterType = original.parameterType;
+ this.genericParameterType = original.genericParameterType;
+ this.parameterAnnotations = original.parameterAnnotations;
+ this.parameterNameDiscoverer = original.parameterNameDiscoverer;
+ this.parameterName = original.parameterName;
+ this.nestingLevel = original.nestingLevel;
+ this.typeIndexesPerLevel = original.typeIndexesPerLevel;
+ this.typeVariableMap = original.typeVariableMap;
+ }
+
+
+ /**
+ * Return the wrapped Method, if any.
+ * <p>Note: Either Method or Constructor is available.
+ * @return the Method, or {@code null} if none
+ */
+ public Method getMethod() {
+ return this.method;
+ }
+
+ /**
+ * Return the wrapped Constructor, if any.
+ * <p>Note: Either Method or Constructor is available.
+ * @return the Constructor, or {@code null} if none
+ */
+ public Constructor<?> getConstructor() {
+ return this.constructor;
+ }
+
+ /**
+ * Returns the wrapped member.
+ * @return the Method or Constructor as Member
+ */
+ private Member getMember() {
+ return (this.method != null ? this.method : this.constructor);
+ }
+
+ /**
+ * Returns the wrapped annotated element.
+ * @return the Method or Constructor as AnnotatedElement
+ */
+ private AnnotatedElement getAnnotatedElement() {
+ return (this.method != null ? this.method : this.constructor);
+ }
+
+ /**
+ * Return the class that declares the underlying Method or Constructor.
+ */
+ public Class<?> getDeclaringClass() {
+ return getMember().getDeclaringClass();
+ }
+
+ /**
+ * Return the index of the method/constructor parameter.
+ * @return the parameter index (never negative)
+ */
+ public int getParameterIndex() {
+ return this.parameterIndex;
+ }
+
+ /**
+ * Set a resolved (generic) parameter type.
+ */
+ void setParameterType(Class<?> parameterType) {
+ this.parameterType = parameterType;
+ }
+
+ /**
+ * Return the type of the method/constructor parameter.
+ * @return the parameter type (never {@code null})
+ */
+ public Class<?> getParameterType() {
+ if (this.parameterType == null) {
+ if (this.parameterIndex < 0) {
+ this.parameterType = (this.method != null ? this.method.getReturnType() : null);
+ }
+ else {
+ this.parameterType = (this.method != null ?
+ this.method.getParameterTypes()[this.parameterIndex] :
+ this.constructor.getParameterTypes()[this.parameterIndex]);
+ }
+ }
+ return this.parameterType;
+ }
+
+ /**
+ * Return the generic type of the method/constructor parameter.
+ * @return the parameter type (never {@code null})
+ */
+ public Type getGenericParameterType() {
+ if (this.genericParameterType == null) {
+ if (this.parameterIndex < 0) {
+ this.genericParameterType = (this.method != null ? this.method.getGenericReturnType() : null);
+ }
+ else {
+ this.genericParameterType = (this.method != null ?
+ this.method.getGenericParameterTypes()[this.parameterIndex] :
+ this.constructor.getGenericParameterTypes()[this.parameterIndex]);
+ }
+ }
+ return this.genericParameterType;
+ }
+
+ public Class<?> getNestedParameterType() {
+ if (this.nestingLevel > 1) {
+ Type type = getGenericParameterType();
+ if (type instanceof ParameterizedType) {
+ Integer index = getTypeIndexForCurrentLevel();
+ Type arg = ((ParameterizedType) type).getActualTypeArguments()[index != null ? index : 0];
+ if (arg instanceof Class) {
+ return (Class<?>) arg;
+ }
+ else if (arg instanceof ParameterizedType) {
+ arg = ((ParameterizedType) arg).getRawType();
+ if (arg instanceof Class) {
+ return (Class<?>) arg;
+ }
+ }
+ }
+ return Object.class;
+ }
+ else {
+ return getParameterType();
+ }
+ }
+
+ /**
+ * Return the annotations associated with the target method/constructor itself.
+ */
+ public Annotation[] getMethodAnnotations() {
+ return getAnnotatedElement().getAnnotations();
+ }
+
+ /**
+ * Return the method/constructor annotation of the given type, if available.
+ * @param annotationType the annotation type to look for
+ * @return the annotation object, or {@code null} if not found
+ */
+ public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) {
+ return getAnnotatedElement().getAnnotation(annotationType);
+ }
+
+ /**
+ * Return the annotations associated with the specific method/constructor parameter.
+ */
+ public Annotation[] getParameterAnnotations() {
+ if (this.parameterAnnotations == null) {
+ Annotation[][] annotationArray = (this.method != null ?
+ this.method.getParameterAnnotations() : this.constructor.getParameterAnnotations());
+ if (this.parameterIndex >= 0 && this.parameterIndex < annotationArray.length) {
+ this.parameterAnnotations = annotationArray[this.parameterIndex];
+ }
+ else {
+ this.parameterAnnotations = new Annotation[0];
+ }
+ }
+ return this.parameterAnnotations;
+ }
+
+ /**
+ * Return the parameter annotation of the given type, if available.
+ * @param annotationType the annotation type to look for
+ * @return the annotation object, or {@code null} if not found
+ */
+ @SuppressWarnings("unchecked")
+ public <T extends Annotation> T getParameterAnnotation(Class<T> annotationType) {
+ Annotation[] anns = getParameterAnnotations();
+ for (Annotation ann : anns) {
+ if (annotationType.isInstance(ann)) {
+ return (T) ann;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return true if the parameter has at least one annotation, false if it has none.
+ */
+ public boolean hasParameterAnnotations() {
+ return (getParameterAnnotations().length != 0);
+ }
+
+ /**
+ * Return true if the parameter has the given annotation type, and false if it doesn't.
+ */
+ public <T extends Annotation> boolean hasParameterAnnotation(Class<T> annotationType) {
+ return (getParameterAnnotation(annotationType) != null);
+ }
+
+ /**
+ * Initialize parameter name discovery for this method parameter.
+ * <p>This method does not actually try to retrieve the parameter name at
+ * this point; it just allows discovery to happen when the application calls
+ * {@link #getParameterName()} (if ever).
+ */
+ public void initParameterNameDiscovery(ParameterNameDiscoverer parameterNameDiscoverer) {
+ this.parameterNameDiscoverer = parameterNameDiscoverer;
+ }
+
+ /**
+ * Return the name of the method/constructor parameter.
+ * @return the parameter name (may be {@code null} if no
+ * parameter name metadata is contained in the class file or no
+ * {@link #initParameterNameDiscovery ParameterNameDiscoverer}
+ * has been set to begin with)
+ */
+ public String getParameterName() {
+ if (this.parameterNameDiscoverer != null) {
+ String[] parameterNames = (this.method != null ?
+ this.parameterNameDiscoverer.getParameterNames(this.method) :
+ this.parameterNameDiscoverer.getParameterNames(this.constructor));
+ if (parameterNames != null) {
+ this.parameterName = parameterNames[this.parameterIndex];
+ }
+ this.parameterNameDiscoverer = null;
+ }
+ return this.parameterName;
+ }
+
+ /**
+ * Increase this parameter's nesting level.
+ * @see #getNestingLevel()
+ */
+ public void increaseNestingLevel() {
+ this.nestingLevel++;
+ }
+
+ /**
+ * Decrease this parameter's nesting level.
+ * @see #getNestingLevel()
+ */
+ public void decreaseNestingLevel() {
+ getTypeIndexesPerLevel().remove(this.nestingLevel);
+ this.nestingLevel--;
+ }
+
+ /**
+ * Return the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List).
+ */
+ public int getNestingLevel() {
+ return this.nestingLevel;
+ }
+
+ /**
+ * Set the type index for the current nesting level.
+ * @param typeIndex the corresponding type index
+ * (or {@code null} for the default type index)
+ * @see #getNestingLevel()
+ */
+ public void setTypeIndexForCurrentLevel(int typeIndex) {
+ getTypeIndexesPerLevel().put(this.nestingLevel, typeIndex);
+ }
+
+ /**
+ * Return the type index for the current nesting level.
+ * @return the corresponding type index, or {@code null}
+ * if none specified (indicating the default type index)
+ * @see #getNestingLevel()
+ */
+ public Integer getTypeIndexForCurrentLevel() {
+ return getTypeIndexForLevel(this.nestingLevel);
+ }
+
+ /**
+ * Return the type index for the specified nesting level.
+ * @param nestingLevel the nesting level to check
+ * @return the corresponding type index, or {@code null}
+ * if none specified (indicating the default type index)
+ */
+ public Integer getTypeIndexForLevel(int nestingLevel) {
+ return getTypeIndexesPerLevel().get(nestingLevel);
+ }
+
+ /**
+ * Obtain the (lazily constructed) type-indexes-per-level Map.
+ */
+ private Map<Integer, Integer> getTypeIndexesPerLevel() {
+ if (this.typeIndexesPerLevel == null) {
+ this.typeIndexesPerLevel = new HashMap<Integer, Integer>(4);
+ }
+ return this.typeIndexesPerLevel;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj != null && obj instanceof MethodParameter) {
+ MethodParameter other = (MethodParameter) obj;
+ return (this.parameterIndex == other.parameterIndex && getMember().equals(other.getMember()));
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return (getMember().hashCode() * 31 + this.parameterIndex);
+ }
+
+
+ /**
+ * Create a new MethodParameter for the given method or constructor.
+ * <p>This is a convenience constructor for scenarios where a
+ * Method or Constructor reference is treated in a generic fashion.
+ * @param methodOrConstructor the Method or Constructor to specify a parameter for
+ * @param parameterIndex the index of the parameter
+ * @return the corresponding MethodParameter instance
+ */
+ public static MethodParameter forMethodOrConstructor(Object methodOrConstructor, int parameterIndex) {
+ if (methodOrConstructor instanceof Method) {
+ return new MethodParameter((Method) methodOrConstructor, parameterIndex);
+ }
+ else if (methodOrConstructor instanceof Constructor) {
+ return new MethodParameter((Constructor<?>) methodOrConstructor, parameterIndex);
+ }
+ else {
+ throw new IllegalArgumentException(
+ "Given object [" + methodOrConstructor + "] is neither a Method nor a Constructor");
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/NamedInheritableThreadLocal.java b/spring-core/src/main/java/org/springframework/core/NamedInheritableThreadLocal.java
new file mode 100644
index 00000000..3d30f246
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/NamedInheritableThreadLocal.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2008 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;
+
+import org.springframework.util.Assert;
+
+/**
+ * {@link InheritableThreadLocal} subclass that exposes a specified name
+ * as {@link #toString()} result (allowing for introspection).
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.2
+ * @see NamedThreadLocal
+ */
+public class NamedInheritableThreadLocal<T> extends InheritableThreadLocal<T> {
+
+ private final String name;
+
+
+ /**
+ * Create a new NamedInheritableThreadLocal with the given name.
+ * @param name a descriptive name for this ThreadLocal
+ */
+ public NamedInheritableThreadLocal(String name) {
+ Assert.hasText(name, "Name must not be empty");
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return this.name;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/NamedThreadLocal.java b/spring-core/src/main/java/org/springframework/core/NamedThreadLocal.java
new file mode 100644
index 00000000..4b2b8238
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/NamedThreadLocal.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2008 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;
+
+import org.springframework.util.Assert;
+
+/**
+ * {@link ThreadLocal} subclass that exposes a specified name
+ * as {@link #toString()} result (allowing for introspection).
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.2
+ * @see NamedInheritableThreadLocal
+ */
+public class NamedThreadLocal<T> extends ThreadLocal<T> {
+
+ private final String name;
+
+
+ /**
+ * Create a new NamedThreadLocal with the given name.
+ * @param name a descriptive name for this ThreadLocal
+ */
+ public NamedThreadLocal(String name) {
+ Assert.hasText(name, "Name must not be empty");
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return this.name;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/NestedCheckedException.java b/spring-core/src/main/java/org/springframework/core/NestedCheckedException.java
new file mode 100644
index 00000000..8f72b5ea
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/NestedCheckedException.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2002-2012 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;
+
+/**
+ * Handy class for wrapping checked {@code Exceptions} with a root cause.
+ *
+ * <p>This class is {@code abstract} to force the programmer to extend
+ * the class. {@code getMessage} will include nested exception
+ * information; {@code printStackTrace} and other like methods will
+ * delegate to the wrapped exception, if any.
+ *
+ * <p>The similarity between this class and the {@link NestedRuntimeException}
+ * class is unavoidable, as Java forces these two classes to have different
+ * superclasses (ah, the inflexibility of concrete inheritance!).
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @see #getMessage
+ * @see #printStackTrace
+ * @see NestedRuntimeException
+ */
+public abstract class NestedCheckedException extends Exception {
+
+ /** Use serialVersionUID from Spring 1.2 for interoperability */
+ private static final long serialVersionUID = 7100714597678207546L;
+
+ static {
+ // Eagerly load the NestedExceptionUtils class to avoid classloader deadlock
+ // issues on OSGi when calling getMessage(). Reported by Don Brown; SPR-5607.
+ NestedExceptionUtils.class.getName();
+ }
+
+
+ /**
+ * Construct a {@code NestedCheckedException} with the specified detail message.
+ * @param msg the detail message
+ */
+ public NestedCheckedException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Construct a {@code NestedCheckedException} with the specified detail message
+ * and nested exception.
+ * @param msg the detail message
+ * @param cause the nested exception
+ */
+ public NestedCheckedException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+
+ /**
+ * Return the detail message, including the message from the nested exception
+ * if there is one.
+ */
+ @Override
+ public String getMessage() {
+ return NestedExceptionUtils.buildMessage(super.getMessage(), getCause());
+ }
+
+
+ /**
+ * Retrieve the innermost cause of this exception, if any.
+ * @return the innermost exception, or {@code null} if none
+ */
+ public Throwable getRootCause() {
+ Throwable rootCause = null;
+ Throwable cause = getCause();
+ while (cause != null && cause != rootCause) {
+ rootCause = cause;
+ cause = cause.getCause();
+ }
+ return rootCause;
+ }
+
+ /**
+ * Retrieve the most specific cause of this exception, that is,
+ * either the innermost cause (root cause) or this exception itself.
+ * <p>Differs from {@link #getRootCause()} in that it falls back
+ * to the present exception if there is no root cause.
+ * @return the most specific cause (never {@code null})
+ * @since 2.0.3
+ */
+ public Throwable getMostSpecificCause() {
+ Throwable rootCause = getRootCause();
+ return (rootCause != null ? rootCause : this);
+ }
+
+ /**
+ * Check whether this exception contains an exception of the given type:
+ * either it is of the given class itself or it contains a nested cause
+ * of the given type.
+ * @param exType the exception type to look for
+ * @return whether there is a nested exception of the specified type
+ */
+ public boolean contains(Class exType) {
+ if (exType == null) {
+ return false;
+ }
+ if (exType.isInstance(this)) {
+ return true;
+ }
+ Throwable cause = getCause();
+ if (cause == this) {
+ return false;
+ }
+ if (cause instanceof NestedCheckedException) {
+ return ((NestedCheckedException) cause).contains(exType);
+ }
+ else {
+ while (cause != null) {
+ if (exType.isInstance(cause)) {
+ return true;
+ }
+ if (cause.getCause() == cause) {
+ break;
+ }
+ cause = cause.getCause();
+ }
+ return false;
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/NestedExceptionUtils.java b/spring-core/src/main/java/org/springframework/core/NestedExceptionUtils.java
new file mode 100644
index 00000000..8b66390f
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/NestedExceptionUtils.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2008 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;
+
+/**
+ * Helper class for implementing exception classes which are capable of
+ * holding nested exceptions. Necessary because we can't share a base
+ * class among different exception types.
+ *
+ * <p>Mainly for use within the framework.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see NestedRuntimeException
+ * @see NestedCheckedException
+ * @see NestedIOException
+ * @see org.springframework.web.util.NestedServletException
+ */
+public abstract class NestedExceptionUtils {
+
+ /**
+ * Build a message for the given base message and root cause.
+ * @param message the base message
+ * @param cause the root cause
+ * @return the full exception message
+ */
+ public static String buildMessage(String message, Throwable cause) {
+ if (cause != null) {
+ StringBuilder sb = new StringBuilder();
+ if (message != null) {
+ sb.append(message).append("; ");
+ }
+ sb.append("nested exception is ").append(cause);
+ return sb.toString();
+ }
+ else {
+ return message;
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/NestedIOException.java b/spring-core/src/main/java/org/springframework/core/NestedIOException.java
new file mode 100644
index 00000000..9bd7b88e
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/NestedIOException.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.io.IOException;
+
+/**
+ * Subclass of {@link IOException} that properly handles a root cause,
+ * exposing the root cause just like NestedChecked/RuntimeException does.
+ *
+ * <p>Proper root cause handling has not been added to standard IOException before
+ * Java 6, which is why we need to do it ourselves for Java 5 compatibility purposes.
+ *
+ * <p>The similarity between this class and the NestedChecked/RuntimeException
+ * class is unavoidable, as this class needs to derive from IOException.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see #getMessage
+ * @see #printStackTrace
+ * @see org.springframework.core.NestedCheckedException
+ * @see org.springframework.core.NestedRuntimeException
+ */
+@SuppressWarnings("serial")
+public class NestedIOException extends IOException {
+
+ static {
+ // Eagerly load the NestedExceptionUtils class to avoid classloader deadlock
+ // issues on OSGi when calling getMessage(). Reported by Don Brown; SPR-5607.
+ NestedExceptionUtils.class.getName();
+ }
+
+
+ /**
+ * Construct a {@code NestedIOException} with the specified detail message.
+ * @param msg the detail message
+ */
+ public NestedIOException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Construct a {@code NestedIOException} with the specified detail message
+ * and nested exception.
+ * @param msg the detail message
+ * @param cause the nested exception
+ */
+ public NestedIOException(String msg, Throwable cause) {
+ super(msg);
+ initCause(cause);
+ }
+
+
+ /**
+ * Return the detail message, including the message from the nested exception
+ * if there is one.
+ */
+ @Override
+ public String getMessage() {
+ return NestedExceptionUtils.buildMessage(super.getMessage(), getCause());
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/NestedRuntimeException.java b/spring-core/src/main/java/org/springframework/core/NestedRuntimeException.java
new file mode 100644
index 00000000..520c8bc9
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/NestedRuntimeException.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2002-2012 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;
+
+/**
+ * Handy class for wrapping runtime {@code Exceptions} with a root cause.
+ *
+ * <p>This class is {@code abstract} to force the programmer to extend
+ * the class. {@code getMessage} will include nested exception
+ * information; {@code printStackTrace} and other like methods will
+ * delegate to the wrapped exception, if any.
+ *
+ * <p>The similarity between this class and the {@link NestedCheckedException}
+ * class is unavoidable, as Java forces these two classes to have different
+ * superclasses (ah, the inflexibility of concrete inheritance!).
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @see #getMessage
+ * @see #printStackTrace
+ * @see NestedCheckedException
+ */
+public abstract class NestedRuntimeException extends RuntimeException {
+
+ /** Use serialVersionUID from Spring 1.2 for interoperability */
+ private static final long serialVersionUID = 5439915454935047936L;
+
+ static {
+ // Eagerly load the NestedExceptionUtils class to avoid classloader deadlock
+ // issues on OSGi when calling getMessage(). Reported by Don Brown; SPR-5607.
+ NestedExceptionUtils.class.getName();
+ }
+
+
+ /**
+ * Construct a {@code NestedRuntimeException} with the specified detail message.
+ * @param msg the detail message
+ */
+ public NestedRuntimeException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Construct a {@code NestedRuntimeException} with the specified detail message
+ * and nested exception.
+ * @param msg the detail message
+ * @param cause the nested exception
+ */
+ public NestedRuntimeException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+
+ /**
+ * Return the detail message, including the message from the nested exception
+ * if there is one.
+ */
+ @Override
+ public String getMessage() {
+ return NestedExceptionUtils.buildMessage(super.getMessage(), getCause());
+ }
+
+
+ /**
+ * Retrieve the innermost cause of this exception, if any.
+ * @return the innermost exception, or {@code null} if none
+ * @since 2.0
+ */
+ public Throwable getRootCause() {
+ Throwable rootCause = null;
+ Throwable cause = getCause();
+ while (cause != null && cause != rootCause) {
+ rootCause = cause;
+ cause = cause.getCause();
+ }
+ return rootCause;
+ }
+
+ /**
+ * Retrieve the most specific cause of this exception, that is,
+ * either the innermost cause (root cause) or this exception itself.
+ * <p>Differs from {@link #getRootCause()} in that it falls back
+ * to the present exception if there is no root cause.
+ * @return the most specific cause (never {@code null})
+ * @since 2.0.3
+ */
+ public Throwable getMostSpecificCause() {
+ Throwable rootCause = getRootCause();
+ return (rootCause != null ? rootCause : this);
+ }
+
+ /**
+ * Check whether this exception contains an exception of the given type:
+ * either it is of the given class itself or it contains a nested cause
+ * of the given type.
+ * @param exType the exception type to look for
+ * @return whether there is a nested exception of the specified type
+ */
+ public boolean contains(Class exType) {
+ if (exType == null) {
+ return false;
+ }
+ if (exType.isInstance(this)) {
+ return true;
+ }
+ Throwable cause = getCause();
+ if (cause == this) {
+ return false;
+ }
+ if (cause instanceof NestedRuntimeException) {
+ return ((NestedRuntimeException) cause).contains(exType);
+ }
+ else {
+ while (cause != null) {
+ if (exType.isInstance(cause)) {
+ return true;
+ }
+ if (cause.getCause() == cause) {
+ break;
+ }
+ cause = cause.getCause();
+ }
+ return false;
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/OrderComparator.java b/spring-core/src/main/java/org/springframework/core/OrderComparator.java
new file mode 100644
index 00000000..16158ca1
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/OrderComparator.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * {@link Comparator} implementation for {@link Ordered} objects,
+ * sorting by order value ascending (resp. by priority descending).
+ *
+ * <p>Non-{@code Ordered} objects are treated as greatest order
+ * values, thus ending up at the end of the list, in arbitrary order
+ * (just like same order values of {@code Ordered} objects).
+ *
+ * @author Juergen Hoeller
+ * @since 07.04.2003
+ * @see Ordered
+ * @see java.util.Collections#sort(java.util.List, java.util.Comparator)
+ * @see java.util.Arrays#sort(Object[], java.util.Comparator)
+ */
+public class OrderComparator implements Comparator<Object> {
+
+ /**
+ * Shared default instance of OrderComparator.
+ */
+ public static final OrderComparator INSTANCE = new OrderComparator();
+
+
+ public int compare(Object o1, Object o2) {
+ boolean p1 = (o1 instanceof PriorityOrdered);
+ boolean p2 = (o2 instanceof PriorityOrdered);
+ if (p1 && !p2) {
+ return -1;
+ }
+ else if (p2 && !p1) {
+ return 1;
+ }
+
+ // Direct evaluation instead of Integer.compareTo to avoid unnecessary object creation.
+ int i1 = getOrder(o1);
+ int i2 = getOrder(o2);
+ return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
+ }
+
+ /**
+ * Determine the order value for the given object.
+ * <p>The default implementation checks against the {@link Ordered}
+ * interface. Can be overridden in subclasses.
+ * @param obj the object to check
+ * @return the order value, or {@code Ordered.LOWEST_PRECEDENCE} as fallback
+ */
+ protected int getOrder(Object obj) {
+ return (obj instanceof Ordered ? ((Ordered) obj).getOrder() : Ordered.LOWEST_PRECEDENCE);
+ }
+
+
+ /**
+ * Sort the given List with a default OrderComparator.
+ * <p>Optimized to skip sorting for lists with size 0 or 1,
+ * in order to avoid unnecessary array extraction.
+ * @param list the List to sort
+ * @see java.util.Collections#sort(java.util.List, java.util.Comparator)
+ */
+ public static void sort(List<?> list) {
+ if (list.size() > 1) {
+ Collections.sort(list, INSTANCE);
+ }
+ }
+
+ /**
+ * Sort the given array with a default OrderComparator.
+ * <p>Optimized to skip sorting for lists with size 0 or 1,
+ * in order to avoid unnecessary array extraction.
+ * @param array the array to sort
+ * @see java.util.Arrays#sort(Object[], java.util.Comparator)
+ */
+ public static void sort(Object[] array) {
+ if (array.length > 1) {
+ Arrays.sort(array, INSTANCE);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/Ordered.java b/spring-core/src/main/java/org/springframework/core/Ordered.java
new file mode 100644
index 00000000..fcabe86a
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/Ordered.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2012 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;
+
+/**
+ * Interface that can be implemented by objects that should be
+ * orderable, for example in a Collection.
+ *
+ * <p>The actual order can be interpreted as prioritization, with
+ * the first object (with the lowest order value) having the highest
+ * priority.
+ *
+ * <p>Note that there is a 'priority' marker for this interface:
+ * {@link PriorityOrdered}. Order values expressed by PriorityOrdered
+ * objects always apply before order values of 'plain' Ordered values.
+ *
+ * @author Juergen Hoeller
+ * @since 07.04.2003
+ * @see OrderComparator
+ * @see org.springframework.core.annotation.Order
+ */
+public interface Ordered {
+
+ /**
+ * Useful constant for the highest precedence value.
+ * @see java.lang.Integer#MIN_VALUE
+ */
+ int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
+
+ /**
+ * Useful constant for the lowest precedence value.
+ * @see java.lang.Integer#MAX_VALUE
+ */
+ int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
+
+
+ /**
+ * Return the order value of this object, with a
+ * higher value meaning greater in terms of sorting.
+ * <p>Normally starting with 0, with {@code Integer.MAX_VALUE}
+ * indicating the greatest value. Same order values will result
+ * in arbitrary positions for the affected objects.
+ * <p>Higher values can be interpreted as lower priority. As a
+ * consequence, the object with the lowest value has highest priority
+ * (somewhat analogous to Servlet "load-on-startup" values).
+ * @return the order value
+ */
+ int getOrder();
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java b/spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java
new file mode 100644
index 00000000..597a3923
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.springframework.util.FileCopyUtils;
+
+/**
+ * {@code ClassLoader} that does <i>not</i> always delegate to the
+ * parent loader, as normal class loaders do. This enables, for example,
+ * instrumentation to be forced in the overriding ClassLoader, or a
+ * "throwaway" class loading behavior, where selected classes are
+ * temporarily loaded in the overriding ClassLoader, in order to load
+ * an instrumented version of the class in the parent ClassLoader later on.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @since 2.0.1
+ */
+public class OverridingClassLoader extends DecoratingClassLoader {
+
+ /** Packages that are excluded by default */
+ public static final String[] DEFAULT_EXCLUDED_PACKAGES =
+ new String[] {"java.", "javax.", "sun.", "oracle."};
+
+ private static final String CLASS_FILE_SUFFIX = ".class";
+
+
+ /**
+ * Create a new OverridingClassLoader for the given class loader.
+ * @param parent the ClassLoader to build an overriding ClassLoader for
+ */
+ public OverridingClassLoader(ClassLoader parent) {
+ super(parent);
+ for (String packageName : DEFAULT_EXCLUDED_PACKAGES) {
+ excludePackage(packageName);
+ }
+ }
+
+
+ @Override
+ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ Class result = null;
+ if (isEligibleForOverriding(name)) {
+ result = loadClassForOverriding(name);
+ }
+ if (result != null) {
+ if (resolve) {
+ resolveClass(result);
+ }
+ return result;
+ }
+ else {
+ return super.loadClass(name, resolve);
+ }
+ }
+
+ /**
+ * Determine whether the specified class is eligible for overriding
+ * by this class loader.
+ * @param className the class name to check
+ * @return whether the specified class is eligible
+ * @see #isExcluded
+ */
+ protected boolean isEligibleForOverriding(String className) {
+ return !isExcluded(className);
+ }
+
+ /**
+ * Load the specified class for overriding purposes in this ClassLoader.
+ * <p>The default implementation delegates to {@link #findLoadedClass},
+ * {@link #loadBytesForClass} and {@link #defineClass}.
+ * @param name the name of the class
+ * @return the Class object, or {@code null} if no class defined for that name
+ * @throws ClassNotFoundException if the class for the given name couldn't be loaded
+ */
+ protected Class loadClassForOverriding(String name) throws ClassNotFoundException {
+ Class result = findLoadedClass(name);
+ if (result == null) {
+ byte[] bytes = loadBytesForClass(name);
+ if (bytes != null) {
+ result = defineClass(name, bytes, 0, bytes.length);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Load the defining bytes for the given class,
+ * to be turned into a Class object through a {@link #defineClass} call.
+ * <p>The default implementation delegates to {@link #openStreamForClass}
+ * and {@link #transformIfNecessary}.
+ * @param name the name of the class
+ * @return the byte content (with transformers already applied),
+ * or {@code null} if no class defined for that name
+ * @throws ClassNotFoundException if the class for the given name couldn't be loaded
+ */
+ protected byte[] loadBytesForClass(String name) throws ClassNotFoundException {
+ InputStream is = openStreamForClass(name);
+ if (is == null) {
+ return null;
+ }
+ try {
+ // Load the raw bytes.
+ byte[] bytes = FileCopyUtils.copyToByteArray(is);
+ // Transform if necessary and use the potentially transformed bytes.
+ return transformIfNecessary(name, bytes);
+ }
+ catch (IOException ex) {
+ throw new ClassNotFoundException("Cannot load resource for class [" + name + "]", ex);
+ }
+ }
+
+ /**
+ * Open an InputStream for the specified class.
+ * <p>The default implementation loads a standard class file through
+ * the parent ClassLoader's {@code getResourceAsStream} method.
+ * @param name the name of the class
+ * @return the InputStream containing the byte code for the specified class
+ */
+ protected InputStream openStreamForClass(String name) {
+ String internalName = name.replace('.', '/') + CLASS_FILE_SUFFIX;
+ return getParent().getResourceAsStream(internalName);
+ }
+
+
+ /**
+ * Transformation hook to be implemented by subclasses.
+ * <p>The default implementation simply returns the given bytes as-is.
+ * @param name the fully-qualified name of the class being transformed
+ * @param bytes the raw bytes of the class
+ * @return the transformed bytes (never {@code null};
+ * same as the input bytes if the transformation produced no changes)
+ */
+ protected byte[] transformIfNecessary(String name, byte[] bytes) {
+ return bytes;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/ParameterNameDiscoverer.java b/spring-core/src/main/java/org/springframework/core/ParameterNameDiscoverer.java
new file mode 100644
index 00000000..01356c29
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/ParameterNameDiscoverer.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/**
+ * Interface to discover parameter names for methods and constructors.
+ *
+ * <p>Parameter name discovery is not always possible, but various strategies are
+ * available to try, such as looking for debug information that may have been
+ * emitted at compile time, and looking for argname annotation values optionally
+ * accompanying AspectJ annotated methods.
+ *
+ * @author Rod Johnson
+ * @author Adrian Colyer
+ * @since 2.0
+ */
+public interface ParameterNameDiscoverer {
+
+ /**
+ * Return parameter names for this method,
+ * or {@code null} if they cannot be determined.
+ * @param method method to find parameter names for
+ * @return an array of parameter names if the names can be resolved,
+ * or {@code null} if they cannot
+ */
+ String[] getParameterNames(Method method);
+
+ /**
+ * Return parameter names for this constructor,
+ * or {@code null} if they cannot be determined.
+ * @param ctor constructor to find parameter names for
+ * @return an array of parameter names if the names can be resolved,
+ * or {@code null} if they cannot
+ */
+ String[] getParameterNames(Constructor<?> ctor);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/ParameterizedTypeReference.java b/spring-core/src/main/java/org/springframework/core/ParameterizedTypeReference.java
new file mode 100644
index 00000000..d7165b3e
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/ParameterizedTypeReference.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2002-2013 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;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+import org.springframework.util.Assert;
+
+/**
+ * The purpose of this class is to enable capturing and passing a generic
+ * {@link Type}. In order to capture the generic type and retain it at runtime,
+ * you need to create a subclass as follows:
+ *
+ * <pre class="code">
+ * ParameterizedTypeReference&lt;List&lt;String&gt;&gt; typeRef = new ParameterizedTypeReference&lt;List&lt;String&gt;&gt;() {};
+ * </pre>
+ *
+ * <p>The resulting {@code typeReference} instance can then be used to obtain a
+ * {@link Type} instance that carries parameterized type information.
+ * For more information on "super type tokens" see the link to Neal Gafter's blog post.
+ *
+ * @author Arjen Poutsma
+ * @author Rossen Stoyanchev
+ * @since 3.2
+ * @see <a href="http://gafter.blogspot.nl/2006/12/super-type-tokens.html">Neal Gafter on Super Type Tokens</a>
+ */
+public abstract class ParameterizedTypeReference<T> {
+
+ private final Type type;
+
+
+ protected ParameterizedTypeReference() {
+ Class<?> parameterizedTypeReferenceSubclass = findParameterizedTypeReferenceSubclass(getClass());
+ Type type = parameterizedTypeReferenceSubclass.getGenericSuperclass();
+ Assert.isInstanceOf(ParameterizedType.class, type);
+ ParameterizedType parameterizedType = (ParameterizedType) type;
+ Assert.isTrue(parameterizedType.getActualTypeArguments().length == 1);
+ this.type = parameterizedType.getActualTypeArguments()[0];
+ }
+
+
+ public Type getType() {
+ return this.type;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return (this == obj || (obj instanceof ParameterizedTypeReference &&
+ this.type.equals(((ParameterizedTypeReference) obj).type)));
+ }
+
+ @Override
+ public int hashCode() {
+ return this.type.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "ParameterizedTypeReference<" + this.type + ">";
+ }
+
+
+ private static Class<?> findParameterizedTypeReferenceSubclass(Class<?> child) {
+ Class<?> parent = child.getSuperclass();
+ if (Object.class.equals(parent)) {
+ throw new IllegalStateException("Expected ParameterizedTypeReference superclass");
+ }
+ else if (ParameterizedTypeReference.class.equals(parent)) {
+ return child;
+ }
+ else {
+ return findParameterizedTypeReferenceSubclass(parent);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/PrioritizedParameterNameDiscoverer.java b/spring-core/src/main/java/org/springframework/core/PrioritizedParameterNameDiscoverer.java
new file mode 100644
index 00000000..e65e88cd
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/PrioritizedParameterNameDiscoverer.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * ParameterNameDiscoverer implementation that tries several ParameterNameDiscoverers
+ * in succession. Those added first in the {@code addDiscoverer} method have
+ * highest priority. If one returns {@code null}, the next will be tried.
+ *
+ * <p>The default behavior is always to return {@code null}
+ * if no discoverer matches.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class PrioritizedParameterNameDiscoverer implements ParameterNameDiscoverer {
+
+ private final List<ParameterNameDiscoverer> parameterNameDiscoverers =
+ new LinkedList<ParameterNameDiscoverer>();
+
+
+ /**
+ * Add a further ParameterNameDiscoverer to the list of discoverers
+ * that this PrioritizedParameterNameDiscoverer checks.
+ */
+ public void addDiscoverer(ParameterNameDiscoverer pnd) {
+ this.parameterNameDiscoverers.add(pnd);
+ }
+
+
+ public String[] getParameterNames(Method method) {
+ for (ParameterNameDiscoverer pnd : this.parameterNameDiscoverers) {
+ String[] result = pnd.getParameterNames(method);
+ if (result != null) {
+ return result;
+ }
+ }
+ return null;
+ }
+
+ public String[] getParameterNames(Constructor ctor) {
+ for (ParameterNameDiscoverer pnd : this.parameterNameDiscoverers) {
+ String[] result = pnd.getParameterNames(ctor);
+ if (result != null) {
+ return result;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/PriorityOrdered.java b/spring-core/src/main/java/org/springframework/core/PriorityOrdered.java
new file mode 100644
index 00000000..1ab51087
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/PriorityOrdered.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core;
+
+/**
+ * Extension of the {@link Ordered} interface, expressing a 'priority'
+ * ordering: Order values expressed by PriorityOrdered objects always
+ * apply before order values of 'plain' Ordered values.
+ *
+ * <p>This is primarily a special-purpose interface, used for objects
+ * where it is particularly important to determine 'prioritized'
+ * objects first, without even obtaining the remaining objects.
+ * A typical example: Prioritized post-processors in a Spring
+ * {@link org.springframework.context.ApplicationContext}.
+ *
+ * <p>Note: PriorityOrdered post-processor beans are initialized in
+ * a special phase, ahead of other post-processor beans. This subtly
+ * affects their autowiring behavior: They will only be autowired against
+ * beans which do not require eager initialization for type matching.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see org.springframework.beans.factory.config.PropertyOverrideConfigurer
+ * @see org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
+ */
+public interface PriorityOrdered extends Ordered {
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java b/spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java
new file mode 100644
index 00000000..24b33f76
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+import org.springframework.util.StringValueResolver;
+
+/**
+ * Simple implementation of the {@link AliasRegistry} interface.
+ * Serves as base class for
+ * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}
+ * implementations.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.2
+ */
+public class SimpleAliasRegistry implements AliasRegistry {
+
+ /** Map from alias to canonical name */
+ private final Map<String, String> aliasMap = new ConcurrentHashMap<String, String>(16);
+
+
+ public void registerAlias(String name, String alias) {
+ Assert.hasText(name, "'name' must not be empty");
+ Assert.hasText(alias, "'alias' must not be empty");
+ if (alias.equals(name)) {
+ this.aliasMap.remove(alias);
+ }
+ else {
+ if (!allowAliasOverriding()) {
+ String registeredName = this.aliasMap.get(alias);
+ if (registeredName != null && !registeredName.equals(name)) {
+ throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" +
+ name + "': It is already registered for name '" + registeredName + "'.");
+ }
+ }
+ checkForAliasCircle(name, alias);
+ this.aliasMap.put(alias, name);
+ }
+ }
+
+ /**
+ * Return whether alias overriding is allowed.
+ * Default is {@code true}.
+ */
+ protected boolean allowAliasOverriding() {
+ return true;
+ }
+
+ public void removeAlias(String alias) {
+ String name = this.aliasMap.remove(alias);
+ if (name == null) {
+ throw new IllegalStateException("No alias '" + alias + "' registered");
+ }
+ }
+
+ public boolean isAlias(String name) {
+ return this.aliasMap.containsKey(name);
+ }
+
+ public String[] getAliases(String name) {
+ List<String> result = new ArrayList<String>();
+ synchronized (this.aliasMap) {
+ retrieveAliases(name, result);
+ }
+ return StringUtils.toStringArray(result);
+ }
+
+ /**
+ * Transitively retrieve all aliases for the given name.
+ * @param name the target name to find aliases for
+ * @param result the resulting aliases list
+ */
+ private void retrieveAliases(String name, List<String> result) {
+ for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {
+ String registeredName = entry.getValue();
+ if (registeredName.equals(name)) {
+ String alias = entry.getKey();
+ result.add(alias);
+ retrieveAliases(alias, result);
+ }
+ }
+ }
+
+ /**
+ * Resolve all alias target names and aliases registered in this
+ * factory, applying the given StringValueResolver to them.
+ * <p>The value resolver may for example resolve placeholders
+ * in target bean names and even in alias names.
+ * @param valueResolver the StringValueResolver to apply
+ */
+ public void resolveAliases(StringValueResolver valueResolver) {
+ Assert.notNull(valueResolver, "StringValueResolver must not be null");
+ synchronized (this.aliasMap) {
+ Map<String, String> aliasCopy = new HashMap<String, String>(this.aliasMap);
+ for (String alias : aliasCopy.keySet()) {
+ String registeredName = aliasCopy.get(alias);
+ String resolvedAlias = valueResolver.resolveStringValue(alias);
+ String resolvedName = valueResolver.resolveStringValue(registeredName);
+ if (resolvedAlias.equals(resolvedName)) {
+ this.aliasMap.remove(alias);
+ }
+ else if (!resolvedAlias.equals(alias)) {
+ String existingName = this.aliasMap.get(resolvedAlias);
+ if (existingName != null && !existingName.equals(resolvedName)) {
+ throw new IllegalStateException(
+ "Cannot register resolved alias '" + resolvedAlias + "' (original: '" + alias +
+ "') for name '" + resolvedName + "': It is already registered for name '" +
+ registeredName + "'.");
+ }
+ checkForAliasCircle(resolvedName, resolvedAlias);
+ this.aliasMap.remove(alias);
+ this.aliasMap.put(resolvedAlias, resolvedName);
+ }
+ else if (!registeredName.equals(resolvedName)) {
+ this.aliasMap.put(alias, resolvedName);
+ }
+ }
+ }
+ }
+
+ /**
+ * Determine the raw name, resolving aliases to canonical names.
+ * @param name the user-specified name
+ * @return the transformed name
+ */
+ public String canonicalName(String name) {
+ String canonicalName = name;
+ // Handle aliasing...
+ String resolvedName;
+ do {
+ resolvedName = this.aliasMap.get(canonicalName);
+ if (resolvedName != null) {
+ canonicalName = resolvedName;
+ }
+ }
+ while (resolvedName != null);
+ return canonicalName;
+ }
+
+ /**
+ * Check whether the given name points back to given alias as an alias
+ * in the other direction, catching a circular reference upfront and
+ * throwing a corresponding IllegalStateException.
+ * @param name the candidate name
+ * @param alias the candidate alias
+ * @see #registerAlias
+ */
+ protected void checkForAliasCircle(String name, String alias) {
+ if (alias.equals(canonicalName(name))) {
+ throw new IllegalStateException("Cannot register alias '" + alias +
+ "' for name '" + name + "': Circular reference - '" +
+ name + "' is a direct or indirect alias for '" + alias + "' already");
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/SmartClassLoader.java b/spring-core/src/main/java/org/springframework/core/SmartClassLoader.java
new file mode 100644
index 00000000..c036f27a
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/SmartClassLoader.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2012 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;
+
+/**
+ * Interface to be implemented by a reloading-aware ClassLoader
+ * (e.g. a Groovy-based ClassLoader). Detected for example by
+ * Spring's CGLIB proxy factory for making a caching decision.
+ *
+ * <p>If a ClassLoader does <i>not</i> implement this interface,
+ * then all of the classes obtained from it should be considered
+ * as not reloadable (i.e. cacheable).
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.1
+ */
+public interface SmartClassLoader {
+
+ /**
+ * Determine whether the given class is reloadable (in this ClassLoader).
+ * <p>Typically used to check whether the result may be cached (for this
+ * ClassLoader) or whether it should be reobtained every time.
+ * @param clazz the class to check (usually loaded from this ClassLoader)
+ * @return whether the class should be expected to appear in a reloaded
+ * version (with a different {@code Class} object) later on
+ */
+ boolean isClassReloadable(Class clazz);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/SpringProperties.java b/spring-core/src/main/java/org/springframework/core/SpringProperties.java
new file mode 100644
index 00000000..f0b25d6d
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/SpringProperties.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2002-2014 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;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Properties;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Static holder for local Spring properties, i.e. defined at the Spring library level.
+ *
+ * <p>Reads a {@code spring.properties} file from the root of the Spring library classpath,
+ * and also allows for programmatically setting properties through {@link #setProperty}.
+ * When checking a property, local entries are being checked first, then falling back
+ * to JVM-level system properties through a {@link System#getProperty} check.
+ *
+ * <p>This is an alternative way to set Spring-related system properties such as
+ * "spring.getenv.ignore" and "spring.beaninfo.ignore", in particular for scenarios
+ * where JVM system properties are locked on the target platform (e.g. WebSphere).
+ * See {@link #setFlag} for a convenient way to locally set such flags to "true".
+ *
+ * @author Juergen Hoeller
+ * @since 3.2.7
+ * @see org.springframework.core.env.AbstractEnvironment#IGNORE_GETENV_PROPERTY_NAME
+ * @see org.springframework.beans.CachedIntrospectionResults#IGNORE_BEANINFO_PROPERTY_NAME
+ * @see org.springframework.jdbc.core.StatementCreatorUtils#IGNORE_GETPARAMETERTYPE_PROPERTY_NAME
+ */
+public abstract class SpringProperties {
+
+ private static final String PROPERTIES_RESOURCE_LOCATION = "spring.properties";
+
+ private static final Log logger = LogFactory.getLog(SpringProperties.class);
+
+ private static final Properties localProperties = new Properties();
+
+
+ static {
+ try {
+ ClassLoader cl = SpringProperties.class.getClassLoader();
+ URL url = (cl != null ? cl.getResource(PROPERTIES_RESOURCE_LOCATION) :
+ ClassLoader.getSystemResource(PROPERTIES_RESOURCE_LOCATION));
+ if (url != null) {
+ logger.info("Found 'spring.properties' file in local classpath");
+ InputStream is = url.openStream();
+ try {
+ localProperties.load(is);
+ }
+ finally {
+ is.close();
+ }
+ }
+ }
+ catch (IOException ex) {
+ if (logger.isInfoEnabled()) {
+ logger.info("Could not load 'spring.properties' file from local classpath: " + ex);
+ }
+ }
+ }
+
+
+ /**
+ * Programmatically set a local property, overriding an entry in the
+ * {@code spring.properties} file (if any).
+ * @param key the property key
+ * @param value the associated property value, or {@code null} to reset it
+ */
+ public static void setProperty(String key, String value) {
+ if (value != null) {
+ localProperties.setProperty(key, value);
+ }
+ else {
+ localProperties.remove(key);
+ }
+ }
+
+ /**
+ * Retrieve the property value for the given key, checking local Spring
+ * properties first and falling back to JVM-level system properties.
+ * @param key the property key
+ * @return the associated property value, or {@code null} if none found
+ */
+ public static String getProperty(String key) {
+ String value = localProperties.getProperty(key);
+ if (value == null) {
+ try {
+ value = System.getProperty(key);
+ }
+ catch (Throwable ex) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Could not retrieve system property '" + key + "': " + ex);
+ }
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Programmatically set a local flag to "true", overriding an
+ * entry in the {@code spring.properties} file (if any).
+ * @param key the property key
+ */
+ public static void setFlag(String key) {
+ localProperties.put(key, Boolean.TRUE.toString());
+ }
+
+ /**
+ * Retrieve the flag for the given property key.
+ * @param key the property key
+ * @return {@code true} if the property is set to "true",
+ * {@code} false otherwise
+ */
+ public static boolean getFlag(String key) {
+ return Boolean.parseBoolean(getProperty(key));
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/SpringVersion.java b/spring-core/src/main/java/org/springframework/core/SpringVersion.java
new file mode 100644
index 00000000..38426f1b
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/SpringVersion.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2002-2012 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;
+
+/**
+ * Class that exposes the Spring version. Fetches the
+ * "Implementation-Version" manifest attribute from the jar file.
+ *
+ * <p>Note that some ClassLoaders do not expose the package metadata,
+ * hence this class might not be able to determine the Spring version
+ * in all environments. Consider using a reflection-based check instead:
+ * For example, checking for the presence of a specific Spring 2.0
+ * method that you intend to call.
+ *
+ * @author Juergen Hoeller
+ * @since 1.1
+ */
+public class SpringVersion {
+
+ /**
+ * Return the full version string of the present Spring codebase,
+ * or {@code null} if it cannot be determined.
+ * @see Package#getImplementationVersion()
+ */
+ public static String getVersion() {
+ Package pkg = SpringVersion.class.getPackage();
+ return (pkg != null ? pkg.getImplementationVersion() : null);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java
new file mode 100644
index 00000000..40041e8d
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2002-2012 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 static java.lang.String.format;
+
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link LinkedHashMap} subclass representing annotation attribute key/value pairs
+ * as read by Spring's reflection- or ASM-based {@link
+ * org.springframework.core.type.AnnotationMetadata AnnotationMetadata} implementations.
+ * Provides 'pseudo-reification' to avoid noisy Map generics in the calling code as well
+ * as convenience methods for looking up annotation attributes in a type-safe fashion.
+ *
+ * @author Chris Beams
+ * @since 3.1.1
+ */
+@SuppressWarnings("serial")
+public class AnnotationAttributes extends LinkedHashMap<String, Object> {
+
+ /**
+ * Create a new, empty {@link AnnotationAttributes} instance.
+ */
+ public AnnotationAttributes() {
+ }
+
+ /**
+ * Create a new, empty {@link AnnotationAttributes} instance with the given initial
+ * capacity to optimize performance.
+ * @param initialCapacity initial size of the underlying map
+ */
+ public AnnotationAttributes(int initialCapacity) {
+ super(initialCapacity);
+ }
+
+ /**
+ * Create a new {@link AnnotationAttributes} instance, wrapping the provided map
+ * and all its key/value pairs.
+ * @param map original source of annotation attribute key/value pairs to wrap
+ * @see #fromMap(Map)
+ */
+ public AnnotationAttributes(Map<String, Object> map) {
+ super(map);
+ }
+
+ /**
+ * Return an {@link AnnotationAttributes} instance based on the given map; if the map
+ * is already an {@code AnnotationAttributes} instance, it is casted and returned
+ * immediately without creating any new instance; otherwise create a new instance by
+ * wrapping the map with the {@link #AnnotationAttributes(Map)} constructor.
+ * @param map original source of annotation attribute key/value pairs
+ */
+ public static AnnotationAttributes fromMap(Map<String, Object> map) {
+ if (map == null) {
+ return null;
+ }
+
+ if (map instanceof AnnotationAttributes) {
+ return (AnnotationAttributes) map;
+ }
+
+ return new AnnotationAttributes(map);
+ }
+
+ public String getString(String attributeName) {
+ return doGet(attributeName, String.class);
+ }
+
+ public String[] getStringArray(String attributeName) {
+ return doGet(attributeName, String[].class);
+ }
+
+ public boolean getBoolean(String attributeName) {
+ return doGet(attributeName, Boolean.class);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <N extends Number> N getNumber(String attributeName) {
+ return (N) doGet(attributeName, Integer.class);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <E extends Enum<?>> E getEnum(String attributeName) {
+ return (E) doGet(attributeName, Enum.class);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> Class<? extends T> getClass(String attributeName) {
+ return doGet(attributeName, Class.class);
+ }
+
+ public Class<?>[] getClassArray(String attributeName) {
+ return doGet(attributeName, Class[].class);
+ }
+
+ public AnnotationAttributes getAnnotation(String attributeName) {
+ return doGet(attributeName, AnnotationAttributes.class);
+ }
+
+ public AnnotationAttributes[] getAnnotationArray(String attributeName) {
+ return doGet(attributeName, AnnotationAttributes[].class);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T> T doGet(String attributeName, Class<T> expectedType) {
+ Assert.hasText(attributeName, "attributeName must not be null or empty");
+ Object value = this.get(attributeName);
+ Assert.notNull(value, format("Attribute '%s' not found", attributeName));
+ Assert.isAssignable(expectedType, value.getClass(),
+ format("Attribute '%s' is of type [%s], but [%s] was expected. Cause: ",
+ attributeName, value.getClass().getSimpleName(), expectedType.getSimpleName()));
+ return (T) value;
+ }
+
+ public String toString() {
+ Iterator<Map.Entry<String, Object>> entries = entrySet().iterator();
+ StringBuilder sb = new StringBuilder("{");
+ while (entries.hasNext()) {
+ Map.Entry<String, Object> entry = entries.next();
+ sb.append(entry.getKey());
+ sb.append('=');
+ sb.append(valueToString(entry.getValue()));
+ sb.append(entries.hasNext() ? ", " : "");
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ private String valueToString(Object value) {
+ if (value == this) {
+ return "(this Map)";
+ }
+ if (value instanceof Object[]) {
+ return "[" + StringUtils.arrayToCommaDelimitedString((Object[]) value) + "]";
+ }
+ return String.valueOf(value);
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java
new file mode 100644
index 00000000..19ba5fec
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2002-2013 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.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.springframework.core.OrderComparator;
+import org.springframework.core.Ordered;
+
+/**
+ * {@link java.util.Comparator} implementation that checks
+ * {@link org.springframework.core.Ordered} as well as the
+ * {@link Order} annotation, with an order value provided by an
+ * {@code Ordered} instance overriding a statically defined
+ * annotation value (if any).
+ *
+ * @author Juergen Hoeller
+ * @author Oliver Gierke
+ * @since 2.0.1
+ * @see org.springframework.core.Ordered
+ * @see Order
+ */
+public class AnnotationAwareOrderComparator extends OrderComparator {
+
+ /**
+ * Shared default instance of AnnotationAwareOrderComparator.
+ */
+ public static final AnnotationAwareOrderComparator INSTANCE = new AnnotationAwareOrderComparator();
+
+
+ @Override
+ protected int getOrder(Object obj) {
+ if (obj instanceof Ordered) {
+ return ((Ordered) obj).getOrder();
+ }
+ if (obj != null) {
+ Class<?> clazz = (obj instanceof Class ? (Class) obj : obj.getClass());
+ Order order = AnnotationUtils.findAnnotation(clazz, Order.class);
+ if (order != null) {
+ return order.value();
+ }
+ }
+ return Ordered.LOWEST_PRECEDENCE;
+ }
+
+
+ /**
+ * Sort the given List with a default AnnotationAwareOrderComparator.
+ * <p>Optimized to skip sorting for lists with size 0 or 1,
+ * in order to avoid unnecessary array extraction.
+ * @param list the List to sort
+ * @see java.util.Collections#sort(java.util.List, java.util.Comparator)
+ */
+ public static void sort(List<?> list) {
+ if (list.size() > 1) {
+ Collections.sort(list, INSTANCE);
+ }
+ }
+
+ /**
+ * Sort the given array with a default AnnotationAwareOrderComparator.
+ * <p>Optimized to skip sorting for lists with size 0 or 1,
+ * in order to avoid unnecessary array extraction.
+ * @param array the array to sort
+ * @see java.util.Arrays#sort(Object[], java.util.Comparator)
+ */
+ public static void sort(Object[] array) {
+ if (array.length > 1) {
+ Arrays.sort(array, INSTANCE);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
new file mode 100644
index 00000000..3b4def63
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
@@ -0,0 +1,518 @@
+/*
+ * Copyright 2002-2014 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.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import org.springframework.core.BridgeMethodResolver;
+import org.springframework.util.Assert;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * General utility methods for working with annotations, handling bridge methods (which the compiler
+ * generates for generic declarations) as well as super methods (for optional &quot;annotation inheritance&quot;).
+ * Note that none of this is provided by the JDK's introspection facilities themselves.
+ *
+ * <p>As a general rule for runtime-retained annotations (e.g. for transaction control, authorization or service
+ * exposure), always use the lookup methods on this class (e.g., {@link #findAnnotation(Method, Class)}, {@link
+ * #getAnnotation(Method, Class)}, and {@link #getAnnotations(Method)}) instead of the plain annotation lookup
+ * methods in the JDK. You can still explicitly choose between lookup on the given class level only ({@link
+ * #getAnnotation(Method, Class)}) and lookup in the entire inheritance hierarchy of the given method ({@link
+ * #findAnnotation(Method, Class)}).
+ *
+ * @author Rob Harrop
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @author Mark Fisher
+ * @author Chris Beams
+ * @since 2.0
+ * @see java.lang.reflect.Method#getAnnotations()
+ * @see java.lang.reflect.Method#getAnnotation(Class)
+ */
+public abstract class AnnotationUtils {
+
+ /** The attribute name for annotations with a single element */
+ static final String VALUE = "value";
+
+ private static final Map<Class<?>, Boolean> annotatedInterfaceCache = new WeakHashMap<Class<?>, Boolean>();
+
+
+ /**
+ * Get a single {@link Annotation} of {@code annotationType} from the supplied
+ * Method, Constructor or Field. Meta-annotations will be searched if the annotation
+ * is not declared locally on the supplied element.
+ * @param ae the Method, Constructor or Field from which to get the annotation
+ * @param annotationType the annotation type to look for, both locally and as a meta-annotation
+ * @return the matching annotation, or {@code null} if none found
+ * @since 3.1
+ */
+ public static <T extends Annotation> T getAnnotation(AnnotatedElement ae, Class<T> annotationType) {
+ T ann = ae.getAnnotation(annotationType);
+ if (ann == null) {
+ for (Annotation metaAnn : ae.getAnnotations()) {
+ ann = metaAnn.annotationType().getAnnotation(annotationType);
+ if (ann != null) {
+ break;
+ }
+ }
+ }
+ return ann;
+ }
+
+ /**
+ * Get all {@link Annotation Annotations} from the supplied {@link Method}.
+ * <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
+ * @param method the method to look for annotations on
+ * @return the annotations found
+ * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method)
+ */
+ public static Annotation[] getAnnotations(Method method) {
+ return BridgeMethodResolver.findBridgedMethod(method).getAnnotations();
+ }
+
+ /**
+ * Get a single {@link Annotation} of {@code annotationType} from the supplied {@link Method}.
+ * <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
+ * @param method the method to look for annotations on
+ * @param annotationType the annotation type to look for
+ * @return the annotations found
+ * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method)
+ */
+ public static <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) {
+ Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
+ A ann = resolvedMethod.getAnnotation(annotationType);
+ if (ann == null) {
+ for (Annotation metaAnn : resolvedMethod.getAnnotations()) {
+ ann = metaAnn.annotationType().getAnnotation(annotationType);
+ if (ann != null) {
+ break;
+ }
+ }
+ }
+ return ann;
+ }
+
+ /**
+ * Get a single {@link Annotation} of {@code annotationType} from the supplied {@link Method},
+ * traversing its super methods if no annotation can be found on the given method itself.
+ * <p>Annotations on methods are not inherited by default, so we need to handle this explicitly.
+ * @param method the method to look for annotations on
+ * @param annotationType the annotation type to look for
+ * @return the annotation found, or {@code null} if none
+ */
+ public static <A extends Annotation> A findAnnotation(Method method, Class<A> annotationType) {
+ A annotation = getAnnotation(method, annotationType);
+ Class<?> clazz = method.getDeclaringClass();
+ if (annotation == null) {
+ annotation = searchOnInterfaces(method, annotationType, clazz.getInterfaces());
+ }
+ while (annotation == null) {
+ clazz = clazz.getSuperclass();
+ if (clazz == null || clazz.equals(Object.class)) {
+ break;
+ }
+ try {
+ Method equivalentMethod = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
+ annotation = getAnnotation(equivalentMethod, annotationType);
+ }
+ catch (NoSuchMethodException ex) {
+ // No equivalent method found
+ }
+ if (annotation == null) {
+ annotation = searchOnInterfaces(method, annotationType, clazz.getInterfaces());
+ }
+ }
+ return annotation;
+ }
+
+ private static <A extends Annotation> A searchOnInterfaces(Method method, Class<A> annotationType, Class<?>[] ifcs) {
+ A annotation = null;
+ for (Class<?> iface : ifcs) {
+ if (isInterfaceWithAnnotatedMethods(iface)) {
+ try {
+ Method equivalentMethod = iface.getMethod(method.getName(), method.getParameterTypes());
+ annotation = getAnnotation(equivalentMethod, annotationType);
+ }
+ catch (NoSuchMethodException ex) {
+ // Skip this interface - it doesn't have the method...
+ }
+ if (annotation != null) {
+ break;
+ }
+ }
+ }
+ return annotation;
+ }
+
+ private static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) {
+ synchronized (annotatedInterfaceCache) {
+ Boolean flag = annotatedInterfaceCache.get(iface);
+ if (flag != null) {
+ return flag;
+ }
+ boolean found = false;
+ for (Method ifcMethod : iface.getMethods()) {
+ if (ifcMethod.getAnnotations().length > 0) {
+ found = true;
+ break;
+ }
+ }
+ annotatedInterfaceCache.put(iface, found);
+ return found;
+ }
+ }
+
+ /**
+ * Find a single {@link Annotation} of {@code annotationType} from the supplied {@link Class},
+ * traversing its interfaces and superclasses if no annotation can be found on the given class itself.
+ * <p>This method explicitly handles class-level annotations which are not declared as
+ * {@link java.lang.annotation.Inherited inherited} <i>as well as annotations on interfaces</i>.
+ * <p>The algorithm operates as follows: Searches for an annotation on the given class and returns
+ * it if found. Else searches all interfaces that the given class declares, returning the annotation
+ * from the first matching candidate, if any. Else proceeds with introspection of the superclass
+ * of the given class, checking the superclass itself; if no annotation found there, proceeds
+ * with the interfaces that the superclass declares. Recursing up through the entire superclass
+ * hierarchy if no match is found.
+ * @param clazz the class to look for annotations on
+ * @param annotationType the annotation type to look for
+ * @return the annotation found, or {@code null} if none found
+ */
+ public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
+ Assert.notNull(clazz, "Class must not be null");
+ A annotation = clazz.getAnnotation(annotationType);
+ if (annotation != null) {
+ return annotation;
+ }
+ for (Class<?> ifc : clazz.getInterfaces()) {
+ annotation = findAnnotation(ifc, annotationType);
+ if (annotation != null) {
+ return annotation;
+ }
+ }
+ if (!Annotation.class.isAssignableFrom(clazz)) {
+ for (Annotation ann : clazz.getAnnotations()) {
+ annotation = findAnnotation(ann.annotationType(), annotationType);
+ if (annotation != null) {
+ return annotation;
+ }
+ }
+ }
+ Class<?> superclass = clazz.getSuperclass();
+ if (superclass == null || superclass.equals(Object.class)) {
+ return null;
+ }
+ return findAnnotation(superclass, annotationType);
+ }
+
+ /**
+ * Find the first {@link Class} in the inheritance hierarchy of the specified {@code clazz}
+ * (including the specified {@code clazz} itself) which declares an annotation for the
+ * specified {@code annotationType}, or {@code null} if not found. If the supplied
+ * {@code clazz} is {@code null}, {@code null} will be returned.
+ * <p>If the supplied {@code clazz} is an interface, only the interface itself will be checked;
+ * the inheritance hierarchy for interfaces will not be traversed.
+ * <p>The standard {@link Class} API does not provide a mechanism for determining which class
+ * in an inheritance hierarchy actually declares an {@link Annotation}, so we need to handle
+ * this explicitly.
+ * @param annotationType the annotation type to look for, both locally and as a meta-annotation
+ * @param clazz the class on which to check for the annotation (may be {@code null})
+ * @return the first {@link Class} in the inheritance hierarchy of the specified {@code clazz}
+ * which declares an annotation for the specified {@code annotationType}, or {@code null}
+ * if not found
+ * @see Class#isAnnotationPresent(Class)
+ * @see Class#getDeclaredAnnotations()
+ * @see #findAnnotationDeclaringClassForTypes(List, Class)
+ * @see #isAnnotationDeclaredLocally(Class, Class)
+ */
+ public static Class<?> findAnnotationDeclaringClass(Class<? extends Annotation> annotationType, Class<?> clazz) {
+ Assert.notNull(annotationType, "Annotation type must not be null");
+ if (clazz == null || clazz.equals(Object.class)) {
+ return null;
+ }
+ if (isAnnotationDeclaredLocally(annotationType, clazz)) {
+ return clazz;
+ }
+ return findAnnotationDeclaringClass(annotationType, clazz.getSuperclass());
+ }
+
+ /**
+ * Find the first {@link Class} in the inheritance hierarchy of the specified
+ * {@code clazz} (including the specified {@code clazz} itself) which declares
+ * at least one of the specified {@code annotationTypes}, or {@code null} if
+ * none of the specified annotation types could be found.
+ * <p>If the supplied {@code clazz} is {@code null}, {@code null} will be
+ * returned.
+ * <p>If the supplied {@code clazz} is an interface, only the interface itself
+ * will be checked; the inheritance hierarchy for interfaces will not be traversed.
+ * <p>The standard {@link Class} API does not provide a mechanism for determining
+ * which class in an inheritance hierarchy actually declares one of several
+ * candidate {@linkplain Annotation annotations}, so we need to handle this
+ * explicitly.
+ * @param annotationTypes the list of Class objects corresponding to the
+ * annotation types
+ * @param clazz the Class object corresponding to the class on which to check
+ * for the annotations, or {@code null}
+ * @return the first {@link Class} in the inheritance hierarchy of the specified
+ * {@code clazz} which declares an annotation of at least one of the specified
+ * {@code annotationTypes}, or {@code null} if not found
+ * @since 3.2.2
+ * @see Class#isAnnotationPresent(Class)
+ * @see Class#getDeclaredAnnotations()
+ * @see #findAnnotationDeclaringClass(Class, Class)
+ * @see #isAnnotationDeclaredLocally(Class, Class)
+ */
+ public static Class<?> findAnnotationDeclaringClassForTypes(List<Class<? extends Annotation>> annotationTypes, Class<?> clazz) {
+ Assert.notEmpty(annotationTypes, "The list of annotation types must not be empty");
+ if (clazz == null || clazz.equals(Object.class)) {
+ return null;
+ }
+ for (Class<? extends Annotation> annotationType : annotationTypes) {
+ if (isAnnotationDeclaredLocally(annotationType, clazz)) {
+ return clazz;
+ }
+ }
+ return findAnnotationDeclaringClassForTypes(annotationTypes, clazz.getSuperclass());
+ }
+
+ /**
+ * Determine whether an annotation for the specified {@code annotationType} is
+ * declared locally on the supplied {@code clazz}. The supplied {@link Class}
+ * may represent any type.
+ * <p>Note: This method does <strong>not</strong> determine if the annotation is
+ * {@linkplain java.lang.annotation.Inherited inherited}. For greater clarity
+ * regarding inherited annotations, consider using
+ * {@link #isAnnotationInherited(Class, Class)} instead.
+ * @param annotationType the Class object corresponding to the annotation type
+ * @param clazz the Class object corresponding to the class on which to check for the annotation
+ * @return {@code true} if an annotation for the specified {@code annotationType}
+ * is declared locally on the supplied {@code clazz}
+ * @see Class#getDeclaredAnnotations()
+ * @see #isAnnotationInherited(Class, Class)
+ */
+ public static boolean isAnnotationDeclaredLocally(Class<? extends Annotation> annotationType, Class<?> clazz) {
+ Assert.notNull(annotationType, "Annotation type must not be null");
+ Assert.notNull(clazz, "Class must not be null");
+ boolean declaredLocally = false;
+ for (Annotation annotation : clazz.getDeclaredAnnotations()) {
+ if (annotation.annotationType().equals(annotationType)) {
+ declaredLocally = true;
+ break;
+ }
+ }
+ return declaredLocally;
+ }
+
+ /**
+ * Determine whether an annotation for the specified {@code annotationType} is present
+ * on the supplied {@code clazz} and is {@linkplain java.lang.annotation.Inherited inherited}
+ * (i.e., not declared locally for the class).
+ * <p>If the supplied {@code clazz} is an interface, only the interface itself will be checked.
+ * In accordance with standard meta-annotation semantics, the inheritance hierarchy for interfaces
+ * will not be traversed. See the {@linkplain java.lang.annotation.Inherited Javadoc} for the
+ * {@code @Inherited} meta-annotation for further details regarding annotation inheritance.
+ * @param annotationType the Class object corresponding to the annotation type
+ * @param clazz the Class object corresponding to the class on which to check for the annotation
+ * @return {@code true} if an annotation for the specified {@code annotationType} is present
+ * on the supplied {@code clazz} and is <em>inherited</em>
+ * @see Class#isAnnotationPresent(Class)
+ * @see #isAnnotationDeclaredLocally(Class, Class)
+ */
+ public static boolean isAnnotationInherited(Class<? extends Annotation> annotationType, Class<?> clazz) {
+ Assert.notNull(annotationType, "Annotation type must not be null");
+ Assert.notNull(clazz, "Class must not be null");
+ return (clazz.isAnnotationPresent(annotationType) && !isAnnotationDeclaredLocally(annotationType, clazz));
+ }
+
+ /**
+ * Retrieve the given annotation's attributes as a Map, preserving all attribute types
+ * as-is.
+ * <p>Note: As of Spring 3.1.1, the returned map is actually an
+ * {@link AnnotationAttributes} instance, however the Map signature of this method has
+ * been preserved for binary compatibility.
+ * @param annotation the annotation to retrieve the attributes for
+ * @return the Map of annotation attributes, with attribute names as keys and
+ * corresponding attribute values as values
+ */
+ public static Map<String, Object> getAnnotationAttributes(Annotation annotation) {
+ return getAnnotationAttributes(annotation, false, false);
+ }
+
+ /**
+ * Retrieve the given annotation's attributes as a Map. Equivalent to calling
+ * {@link #getAnnotationAttributes(Annotation, boolean, boolean)} with
+ * the {@code nestedAnnotationsAsMap} parameter set to {@code false}.
+ * <p>Note: As of Spring 3.1.1, the returned map is actually an
+ * {@link AnnotationAttributes} instance, however the Map signature of this method has
+ * been preserved for binary compatibility.
+ * @param annotation the annotation to retrieve the attributes for
+ * @param classValuesAsString whether to turn Class references into Strings (for
+ * compatibility with {@link org.springframework.core.type.AnnotationMetadata} or to
+ * preserve them as Class references
+ * @return the Map of annotation attributes, with attribute names as keys and
+ * corresponding attribute values as values
+ */
+ public static Map<String, Object> getAnnotationAttributes(Annotation annotation, boolean classValuesAsString) {
+ return getAnnotationAttributes(annotation, classValuesAsString, false);
+ }
+
+ /**
+ * Retrieve the given annotation's attributes as an {@link AnnotationAttributes}
+ * map structure. Implemented in Spring 3.1.1 to provide fully recursive annotation
+ * reading capabilities on par with that of the reflection-based
+ * {@link org.springframework.core.type.StandardAnnotationMetadata}.
+ * @param annotation the annotation to retrieve the attributes for
+ * @param classValuesAsString whether to turn Class references into Strings (for
+ * compatibility with {@link org.springframework.core.type.AnnotationMetadata} or to
+ * preserve them as Class references
+ * @param nestedAnnotationsAsMap whether to turn nested Annotation instances into
+ * {@link AnnotationAttributes} maps (for compatibility with
+ * {@link org.springframework.core.type.AnnotationMetadata} or to preserve them as
+ * Annotation instances
+ * @return the annotation attributes (a specialized Map) with attribute names as keys
+ * and corresponding attribute values as values
+ * @since 3.1.1
+ */
+ public static AnnotationAttributes getAnnotationAttributes(Annotation annotation, boolean classValuesAsString,
+ boolean nestedAnnotationsAsMap) {
+
+ AnnotationAttributes attrs = new AnnotationAttributes();
+ Method[] methods = annotation.annotationType().getDeclaredMethods();
+ for (Method method : methods) {
+ if (method.getParameterTypes().length == 0 && method.getReturnType() != void.class) {
+ try {
+ Object value = method.invoke(annotation);
+ if (classValuesAsString) {
+ if (value instanceof Class) {
+ value = ((Class<?>) value).getName();
+ }
+ else if (value instanceof Class[]) {
+ Class<?>[] clazzArray = (Class[]) value;
+ String[] newValue = new String[clazzArray.length];
+ for (int i = 0; i < clazzArray.length; i++) {
+ newValue[i] = clazzArray[i].getName();
+ }
+ value = newValue;
+ }
+ }
+ if (nestedAnnotationsAsMap && value instanceof Annotation) {
+ attrs.put(method.getName(),
+ getAnnotationAttributes((Annotation) value, classValuesAsString, true));
+ }
+ else if (nestedAnnotationsAsMap && value instanceof Annotation[]) {
+ Annotation[] realAnnotations = (Annotation[]) value;
+ AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length];
+ for (int i = 0; i < realAnnotations.length; i++) {
+ mappedAnnotations[i] = getAnnotationAttributes(realAnnotations[i], classValuesAsString, true);
+ }
+ attrs.put(method.getName(), mappedAnnotations);
+ }
+ else {
+ attrs.put(method.getName(), value);
+ }
+ }
+ catch (Exception ex) {
+ throw new IllegalStateException("Could not obtain annotation attribute values", ex);
+ }
+ }
+ }
+ return attrs;
+ }
+
+ /**
+ * Retrieve the <em>value</em> of the {@code &quot;value&quot;} attribute of a
+ * single-element Annotation, given an annotation instance.
+ * @param annotation the annotation instance from which to retrieve the value
+ * @return the attribute value, or {@code null} if not found
+ * @see #getValue(Annotation, String)
+ */
+ public static Object getValue(Annotation annotation) {
+ return getValue(annotation, VALUE);
+ }
+
+ /**
+ * Retrieve the <em>value</em> of a named Annotation attribute, given an annotation instance.
+ * @param annotation the annotation instance from which to retrieve the value
+ * @param attributeName the name of the attribute value to retrieve
+ * @return the attribute value, or {@code null} if not found
+ * @see #getValue(Annotation)
+ */
+ public static Object getValue(Annotation annotation, String attributeName) {
+ try {
+ Method method = annotation.annotationType().getDeclaredMethod(attributeName);
+ ReflectionUtils.makeAccessible(method);
+ return method.invoke(annotation);
+ }
+ catch (Exception ex) {
+ return null;
+ }
+ }
+
+ /**
+ * Retrieve the <em>default value</em> of the {@code &quot;value&quot;} attribute
+ * of a single-element Annotation, given an annotation instance.
+ * @param annotation the annotation instance from which to retrieve the default value
+ * @return the default value, or {@code null} if not found
+ * @see #getDefaultValue(Annotation, String)
+ */
+ public static Object getDefaultValue(Annotation annotation) {
+ return getDefaultValue(annotation, VALUE);
+ }
+
+ /**
+ * Retrieve the <em>default value</em> of a named Annotation attribute, given an annotation instance.
+ * @param annotation the annotation instance from which to retrieve the default value
+ * @param attributeName the name of the attribute value to retrieve
+ * @return the default value of the named attribute, or {@code null} if not found
+ * @see #getDefaultValue(Class, String)
+ */
+ public static Object getDefaultValue(Annotation annotation, String attributeName) {
+ return getDefaultValue(annotation.annotationType(), attributeName);
+ }
+
+ /**
+ * Retrieve the <em>default value</em> of the {@code &quot;value&quot;} attribute
+ * of a single-element Annotation, given the {@link Class annotation type}.
+ * @param annotationType the <em>annotation type</em> for which the default value should be retrieved
+ * @return the default value, or {@code null} if not found
+ * @see #getDefaultValue(Class, String)
+ */
+ public static Object getDefaultValue(Class<? extends Annotation> annotationType) {
+ return getDefaultValue(annotationType, VALUE);
+ }
+
+ /**
+ * Retrieve the <em>default value</em> of a named Annotation attribute, given the {@link Class annotation type}.
+ * @param annotationType the <em>annotation type</em> for which the default value should be retrieved
+ * @param attributeName the name of the attribute value to retrieve.
+ * @return the default value of the named attribute, or {@code null} if not found
+ * @see #getDefaultValue(Annotation, String)
+ */
+ public static Object getDefaultValue(Class<? extends Annotation> annotationType, String attributeName) {
+ try {
+ return annotationType.getDeclaredMethod(attributeName).getDefaultValue();
+ }
+ catch (Exception ex) {
+ return null;
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/Order.java b/spring-core/src/main/java/org/springframework/core/annotation/Order.java
new file mode 100644
index 00000000..54f81677
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/annotation/Order.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2002-2012 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.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.Ordered;
+
+/**
+ * Annotation that defines ordering. The value is optional, and represents order value
+ * as defined in the {@link Ordered} interface. Lower values have higher priority.
+ * The default value is {@code Ordered.LOWEST_PRECEDENCE}, indicating
+ * lowest priority (losing to any other specified order value).
+ *
+ * <p><b>NOTE:</b> Annotation-based ordering is supported for specific kinds of
+ * components only, e.g. for annotation-based AspectJ aspects. Spring container
+ * strategies, on the other hand, are typically based on the {@link Ordered}
+ * interface in order to allow for configurable ordering of each <i>instance</i>.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see org.springframework.core.Ordered
+ * @see AnnotationAwareOrderComparator
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
+public @interface Order {
+
+ /**
+ * The order value. Default is {@link Ordered#LOWEST_PRECEDENCE}.
+ * @see Ordered#getOrder()
+ */
+ int value() default Ordered.LOWEST_PRECEDENCE;
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/package-info.java b/spring-core/src/main/java/org/springframework/core/annotation/package-info.java
new file mode 100644
index 00000000..435f79ae
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/annotation/package-info.java
@@ -0,0 +1,8 @@
+
+/**
+ *
+ * Core support package for Java 5 annotations.
+ *
+ */
+package org.springframework.core.annotation;
+
diff --git a/spring-core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java
new file mode 100644
index 00000000..86995f32
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert;
+
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.Map;
+
+import org.springframework.util.Assert;
+
+/**
+ * @author Keith Donald
+ * @since 3.1
+ */
+abstract class AbstractDescriptor {
+
+ private final Class<?> type;
+
+
+ protected AbstractDescriptor(Class<?> type) {
+ Assert.notNull(type, "Type must not be null");
+ this.type = type;
+ }
+
+
+ public Class<?> getType() {
+ return this.type;
+ }
+
+ public TypeDescriptor getElementTypeDescriptor() {
+ if (isCollection()) {
+ Class<?> elementType = resolveCollectionElementType();
+ return (elementType != null ? new TypeDescriptor(nested(elementType, 0)) : null);
+ }
+ else if (isArray()) {
+ Class<?> elementType = getType().getComponentType();
+ return new TypeDescriptor(nested(elementType, 0));
+ }
+ else {
+ return null;
+ }
+ }
+
+ public TypeDescriptor getMapKeyTypeDescriptor() {
+ if (isMap()) {
+ Class<?> keyType = resolveMapKeyType();
+ return keyType != null ? new TypeDescriptor(nested(keyType, 0)) : null;
+ }
+ else {
+ return null;
+ }
+ }
+
+ public TypeDescriptor getMapValueTypeDescriptor() {
+ if (isMap()) {
+ Class<?> valueType = resolveMapValueType();
+ return valueType != null ? new TypeDescriptor(nested(valueType, 1)) : null;
+ }
+ else {
+ return null;
+ }
+ }
+
+ public AbstractDescriptor nested() {
+ if (isCollection()) {
+ Class<?> elementType = resolveCollectionElementType();
+ return (elementType != null ? nested(elementType, 0) : null);
+ }
+ else if (isArray()) {
+ return nested(getType().getComponentType(), 0);
+ }
+ else if (isMap()) {
+ Class<?> mapValueType = resolveMapValueType();
+ return (mapValueType != null ? nested(mapValueType, 1) : null);
+ }
+ else if (Object.class.equals(getType())) {
+ // could be a collection type but we don't know about its element type,
+ // so let's just assume there is an element type of type Object
+ return this;
+ }
+ else {
+ throw new IllegalStateException("Not a collection, array, or map: cannot resolve nested value types");
+ }
+ }
+
+
+ // subclassing hooks
+
+ public abstract Annotation[] getAnnotations();
+
+ protected abstract Class<?> resolveCollectionElementType();
+
+ protected abstract Class<?> resolveMapKeyType();
+
+ protected abstract Class<?> resolveMapValueType();
+
+ protected abstract AbstractDescriptor nested(Class<?> type, int typeIndex);
+
+
+ // internal helpers
+
+ private boolean isCollection() {
+ return Collection.class.isAssignableFrom(getType());
+ }
+
+ private boolean isArray() {
+ return getType().isArray();
+ }
+
+ private boolean isMap() {
+ return Map.class.isAssignableFrom(getType());
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java
new file mode 100644
index 00000000..8be6b0e2
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.core.convert;
+
+import java.lang.annotation.Annotation;
+
+import org.springframework.core.GenericCollectionTypeResolver;
+import org.springframework.core.MethodParameter;
+
+/**
+ * @author Keith Donald
+ * @since 3.1
+ */
+class BeanPropertyDescriptor extends AbstractDescriptor {
+
+ private final Property property;
+
+ private final MethodParameter methodParameter;
+
+ private final Annotation[] annotations;
+
+
+ public BeanPropertyDescriptor(Property property) {
+ super(property.getType());
+ this.property = property;
+ this.methodParameter = property.getMethodParameter();
+ this.annotations = property.getAnnotations();
+ }
+
+
+ @Override
+ public Annotation[] getAnnotations() {
+ return this.annotations;
+ }
+
+ @Override
+ protected Class<?> resolveCollectionElementType() {
+ return GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter);
+ }
+
+ @Override
+ protected Class<?> resolveMapKeyType() {
+ return GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter);
+ }
+
+ @Override
+ protected Class<?> resolveMapValueType() {
+ return GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter);
+ }
+
+ @Override
+ protected AbstractDescriptor nested(Class<?> type, int typeIndex) {
+ MethodParameter methodParameter = new MethodParameter(this.methodParameter);
+ methodParameter.increaseNestingLevel();
+ methodParameter.setTypeIndexForCurrentLevel(typeIndex);
+ return new BeanPropertyDescriptor(type, this.property, methodParameter, this.annotations);
+ }
+
+
+ // internal
+
+ private BeanPropertyDescriptor(Class<?> type, Property propertyDescriptor, MethodParameter methodParameter, Annotation[] annotations) {
+ super(type);
+ this.property = propertyDescriptor;
+ this.methodParameter = methodParameter;
+ this.annotations = annotations;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/ClassDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/ClassDescriptor.java
new file mode 100644
index 00000000..87f8e066
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/ClassDescriptor.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert;
+
+import java.lang.annotation.Annotation;
+
+import org.springframework.core.GenericCollectionTypeResolver;
+
+/**
+ * @author Keith Donald
+ * @author Phillip Webb
+ * @since 3.1
+ */
+class ClassDescriptor extends AbstractDescriptor {
+
+ ClassDescriptor(Class<?> type) {
+ super(type);
+ }
+
+ @Override
+ public Annotation[] getAnnotations() {
+ return TypeDescriptor.EMPTY_ANNOTATION_ARRAY;
+ }
+
+ @Override
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ protected Class<?> resolveCollectionElementType() {
+ return GenericCollectionTypeResolver.getCollectionType((Class) getType());
+ }
+
+ @Override
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ protected Class<?> resolveMapKeyType() {
+ return GenericCollectionTypeResolver.getMapKeyType((Class) getType());
+ }
+
+ @Override
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ protected Class<?> resolveMapValueType() {
+ return GenericCollectionTypeResolver.getMapValueType((Class) getType());
+ }
+
+ @Override
+ protected AbstractDescriptor nested(Class<?> type, int typeIndex) {
+ return new ClassDescriptor(type);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/ConversionException.java b/spring-core/src/main/java/org/springframework/core/convert/ConversionException.java
new file mode 100644
index 00000000..f93e2e67
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/ConversionException.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert;
+
+import org.springframework.core.NestedRuntimeException;
+
+/**
+ * Base class for exceptions thrown by the conversion system.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public abstract class ConversionException extends NestedRuntimeException {
+
+ /**
+ * Construct a new conversion exception.
+ * @param message the exception message
+ */
+ public ConversionException(String message) {
+ super(message);
+ }
+
+ /**
+ * Construct a new conversion exception.
+ * @param message the exception message
+ * @param cause the cause
+ */
+ public ConversionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/ConversionFailedException.java b/spring-core/src/main/java/org/springframework/core/convert/ConversionFailedException.java
new file mode 100644
index 00000000..63f4678f
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/ConversionFailedException.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2002-2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert;
+
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Exception to be thrown when an actual type conversion attempt fails.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public final class ConversionFailedException extends ConversionException {
+
+ private final TypeDescriptor sourceType;
+
+ private final TypeDescriptor targetType;
+
+ private final Object value;
+
+
+ /**
+ * Create a new conversion exception.
+ * @param sourceType the value's original type
+ * @param targetType the value's target type
+ * @param value the value we tried to convert
+ * @param cause the cause of the conversion failure
+ */
+ public ConversionFailedException(TypeDescriptor sourceType, TypeDescriptor targetType, Object value, Throwable cause) {
+ super("Failed to convert from type " + sourceType + " to type " + targetType + " for value '" + ObjectUtils.nullSafeToString(value) + "'", cause);
+ this.sourceType = sourceType;
+ this.targetType = targetType;
+ this.value = value;
+ }
+
+
+ /**
+ * Return the source type we tried to convert the value from.
+ */
+ public TypeDescriptor getSourceType() {
+ return this.sourceType;
+ }
+
+ /**
+ * Return the target type we tried to convert the value to.
+ */
+ public TypeDescriptor getTargetType() {
+ return this.targetType;
+ }
+
+ /**
+ * Return the offending value.
+ */
+ public Object getValue() {
+ return this.value;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/ConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/ConversionService.java
new file mode 100644
index 00000000..75f9a276
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/ConversionService.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert;
+
+/**
+ * A service interface for type conversion. This is the entry point into the convert system.
+ * Call {@link #convert(Object, Class)} to perform a thread-safe type conversion using this system.
+ *
+ * @author Keith Donald
+ * @author Phillip Webb
+ * @since 3.0
+ */
+public interface ConversionService {
+
+ /**
+ * Return {@code true} if objects of {@code sourceType} can be converted to the {@code targetType}.
+ * <p>If this method returns {@code true}, it means {@link #convert(Object, Class)} is capable
+ * of converting an instance of {@code sourceType} to {@code targetType}.
+ * <p>Special note on collections, arrays, and maps types:
+ * For conversion between collection, array, and map types, this method will return {@code true}
+ * even though a convert invocation may still generate a {@link ConversionException} if the
+ * underlying elements are not convertible. Callers are expected to handle this exceptional case
+ * when working with collections and maps.
+ * @param sourceType the source type to convert from (may be {@code null} if source is {@code null})
+ * @param targetType the target type to convert to (required)
+ * @return {@code true} if a conversion can be performed, {@code false} if not
+ * @throws IllegalArgumentException if {@code targetType} is {@code null}
+ */
+ boolean canConvert(Class<?> sourceType, Class<?> targetType);
+
+ /**
+ * Return {@code true} if objects of {@code sourceType} can be converted to the {@code targetType}.
+ * The TypeDescriptors provide additional context about the source and target locations
+ * where conversion would occur, often object fields or property locations.
+ * <p>If this method returns {@code true}, it means {@link #convert(Object, TypeDescriptor, TypeDescriptor)}
+ * is capable of converting an instance of {@code sourceType} to {@code targetType}.
+ * <p>Special note on collections, arrays, and maps types:
+ * For conversion between collection, array, and map types, this method will return {@code true}
+ * even though a convert invocation may still generate a {@link ConversionException} if the
+ * underlying elements are not convertible. Callers are expected to handle this exceptional case
+ * when working with collections and maps.
+ * @param sourceType context about the source type to convert from
+ * (may be {@code null} if source is {@code null})
+ * @param targetType context about the target type to convert to (required)
+ * @return {@code true} if a conversion can be performed between the source and target types,
+ * {@code false} if not
+ * @throws IllegalArgumentException if {@code targetType} is {@code null}
+ */
+ boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
+
+ /**
+ * Convert the given {@code source} to the specified {@code targetType}.
+ * @param source the source object to convert (may be null)
+ * @param targetType the target type to convert to (required)
+ * @return the converted object, an instance of targetType
+ * @throws ConversionException if a conversion exception occurred
+ * @throws IllegalArgumentException if targetType is null
+ */
+ <T> T convert(Object source, Class<T> targetType);
+
+ /**
+ * Convert the given {@code source} to the specified {@code targetType}.
+ * The TypeDescriptors provide additional context about the source and target locations
+ * where conversion will occur, often object fields or property locations.
+ * @param source the source object to convert (may be null)
+ * @param sourceType context about the source type to convert from
+ * (may be {@code null} if source is {@code null})
+ * @param targetType context about the target type to convert to (required)
+ * @return the converted object, an instance of {@link TypeDescriptor#getObjectType() targetType}
+ * @throws ConversionException if a conversion exception occurred
+ * @throws IllegalArgumentException if targetType is {@code null},
+ * or {@code sourceType} is {@code null} but source is not {@code null}
+ */
+ Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/ConverterNotFoundException.java b/spring-core/src/main/java/org/springframework/core/convert/ConverterNotFoundException.java
new file mode 100644
index 00000000..ec966b70
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/ConverterNotFoundException.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2002-2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert;
+
+/**
+ * Thrown when a suitable converter could not be found in a conversion service.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public final class ConverterNotFoundException extends ConversionException {
+
+ private final TypeDescriptor sourceType;
+
+ private final TypeDescriptor targetType;
+
+
+ /**
+ * Creates a new conversion executor not found exception.
+ * @param sourceType the source type requested to convert from
+ * @param targetType the target type requested to convert to
+ */
+ public ConverterNotFoundException(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ super("No converter found capable of converting from type " + sourceType + " to type " + targetType);
+ this.sourceType = sourceType;
+ this.targetType = targetType;
+ }
+
+
+ /**
+ * Returns the source type that was requested to convert from.
+ */
+ public TypeDescriptor getSourceType() {
+ return this.sourceType;
+ }
+
+ /**
+ * Returns the target type that was requested to convert to.
+ */
+ public TypeDescriptor getTargetType() {
+ return this.targetType;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/FieldDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/FieldDescriptor.java
new file mode 100644
index 00000000..9d951bb3
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/FieldDescriptor.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.core.GenericCollectionTypeResolver;
+
+/**
+ * @author Keith Donald
+ * @since 3.1
+ */
+class FieldDescriptor extends AbstractDescriptor {
+
+ private final Field field;
+
+ private final int nestingLevel;
+
+ private Map<Integer, Integer> typeIndexesPerLevel;
+
+
+ public FieldDescriptor(Field field) {
+ super(field.getType());
+ this.field = field;
+ this.nestingLevel = 1;
+ }
+
+ private FieldDescriptor(Class<?> type, Field field, int nestingLevel, int typeIndex, Map<Integer, Integer> typeIndexesPerLevel) {
+ super(type);
+ this.field = field;
+ this.nestingLevel = nestingLevel;
+ this.typeIndexesPerLevel = typeIndexesPerLevel;
+ this.typeIndexesPerLevel.put(nestingLevel, typeIndex);
+ }
+
+
+ @Override
+ public Annotation[] getAnnotations() {
+ return TypeDescriptor.nullSafeAnnotations(this.field.getAnnotations());
+ }
+
+ @Override
+ protected Class<?> resolveCollectionElementType() {
+ return GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.nestingLevel, this.typeIndexesPerLevel);
+ }
+
+ @Override
+ protected Class<?> resolveMapKeyType() {
+ return GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.nestingLevel, this.typeIndexesPerLevel);
+ }
+
+ @Override
+ protected Class<?> resolveMapValueType() {
+ return GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.nestingLevel, this.typeIndexesPerLevel);
+ }
+
+ @Override
+ protected AbstractDescriptor nested(Class<?> type, int typeIndex) {
+ if (this.typeIndexesPerLevel == null) {
+ this.typeIndexesPerLevel = new HashMap<Integer, Integer>(4);
+ }
+ return new FieldDescriptor(type, this.field, this.nestingLevel + 1, typeIndex, this.typeIndexesPerLevel);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java
new file mode 100644
index 00000000..c4ccebe7
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert;
+
+import java.lang.annotation.Annotation;
+
+import org.springframework.core.GenericCollectionTypeResolver;
+import org.springframework.core.MethodParameter;
+
+/**
+ * @author Keith Donald
+ * @since 3.1
+ */
+class ParameterDescriptor extends AbstractDescriptor {
+
+ private final MethodParameter methodParameter;
+
+
+ public ParameterDescriptor(MethodParameter methodParameter) {
+ super(methodParameter.getParameterType());
+ this.methodParameter = methodParameter;
+ }
+
+ private ParameterDescriptor(Class<?> type, MethodParameter methodParameter) {
+ super(type);
+ this.methodParameter = methodParameter;
+ }
+
+
+ @Override
+ public Annotation[] getAnnotations() {
+ if (this.methodParameter.getParameterIndex() == -1) {
+ return TypeDescriptor.nullSafeAnnotations(this.methodParameter.getMethodAnnotations());
+ }
+ else {
+ return TypeDescriptor.nullSafeAnnotations(this.methodParameter.getParameterAnnotations());
+ }
+ }
+
+ @Override
+ protected Class<?> resolveCollectionElementType() {
+ return GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter);
+ }
+
+ @Override
+ protected Class<?> resolveMapKeyType() {
+ return GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter);
+ }
+
+ @Override
+ protected Class<?> resolveMapValueType() {
+ return GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter);
+ }
+
+ @Override
+ protected AbstractDescriptor nested(Class<?> type, int typeIndex) {
+ MethodParameter methodParameter = new MethodParameter(this.methodParameter);
+ methodParameter.increaseNestingLevel();
+ methodParameter.setTypeIndexForCurrentLevel(typeIndex);
+ return new ParameterDescriptor(type, methodParameter);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/Property.java b/spring-core/src/main/java/org/springframework/core/convert/Property.java
new file mode 100644
index 00000000..84258e9d
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/Property.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.springframework.core.GenericTypeResolver;
+import org.springframework.core.MethodParameter;
+import org.springframework.util.ConcurrentReferenceHashMap;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * A description of a JavaBeans Property that allows us to avoid a dependency on
+ * {@code java.beans.PropertyDescriptor}. The {@code java.beans} package
+ * is not available in a number of environments (e.g. Android, Java ME), so this is
+ * desirable for portability of Spring's core conversion facility.
+ *
+ * <p>Used to build a TypeDescriptor from a property location.
+ * The built TypeDescriptor can then be used to convert from/to the property type.
+ *
+ * @author Keith Donald
+ * @author Phillip Webb
+ * @since 3.1
+ * @see TypeDescriptor#TypeDescriptor(Property)
+ * @see TypeDescriptor#nested(Property, int)
+ */
+public final class Property {
+
+ private static Map<Property, Annotation[]> annotationCache =
+ new ConcurrentReferenceHashMap<Property, Annotation[]>();
+
+ private final Class<?> objectType;
+
+ private final Method readMethod;
+
+ private final Method writeMethod;
+
+ private final String name;
+
+ private final MethodParameter methodParameter;
+
+ private Annotation[] annotations;
+
+
+ public Property(Class<?> objectType, Method readMethod, Method writeMethod) {
+ this(objectType, readMethod, writeMethod, null);
+ }
+
+ public Property(Class<?> objectType, Method readMethod, Method writeMethod, String name) {
+ this.objectType = objectType;
+ this.readMethod = readMethod;
+ this.writeMethod = writeMethod;
+ this.methodParameter = resolveMethodParameter();
+ this.name = (name == null ? resolveName() : name);
+ }
+
+
+ /**
+ * The object declaring this property, either directly or in a superclass the object extends.
+ */
+ public Class<?> getObjectType() {
+ return this.objectType;
+ }
+
+ /**
+ * The name of the property: e.g. 'foo'
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * The property type: e.g. {@code java.lang.String}
+ */
+ public Class<?> getType() {
+ return this.methodParameter.getParameterType();
+ }
+
+ /**
+ * The property getter method: e.g. {@code getFoo()}
+ */
+ public Method getReadMethod() {
+ return this.readMethod;
+ }
+
+ /**
+ * The property setter method: e.g. {@code setFoo(String)}
+ */
+ public Method getWriteMethod() {
+ return this.writeMethod;
+ }
+
+
+ // package private
+
+ MethodParameter getMethodParameter() {
+ return this.methodParameter;
+ }
+
+ Annotation[] getAnnotations() {
+ if (this.annotations == null) {
+ this.annotations = resolveAnnotations();
+ }
+ return this.annotations;
+ }
+
+
+ // internal helpers
+
+ private String resolveName() {
+ if (this.readMethod != null) {
+ int index = this.readMethod.getName().indexOf("get");
+ if (index != -1) {
+ index += 3;
+ }
+ else {
+ index = this.readMethod.getName().indexOf("is");
+ if (index == -1) {
+ throw new IllegalArgumentException("Not a getter method");
+ }
+ index += 2;
+ }
+ return StringUtils.uncapitalize(this.readMethod.getName().substring(index));
+ }
+ else {
+ int index = this.writeMethod.getName().indexOf("set") + 3;
+ if (index == -1) {
+ throw new IllegalArgumentException("Not a setter method");
+ }
+ return StringUtils.uncapitalize(this.writeMethod.getName().substring(index));
+ }
+ }
+
+ private MethodParameter resolveMethodParameter() {
+ MethodParameter read = resolveReadMethodParameter();
+ MethodParameter write = resolveWriteMethodParameter();
+ if (write == null) {
+ if (read == null) {
+ throw new IllegalStateException("Property is neither readable nor writeable");
+ }
+ return read;
+ }
+ if (read != null) {
+ Class<?> readType = read.getParameterType();
+ Class<?> writeType = write.getParameterType();
+ if (!writeType.equals(readType) && writeType.isAssignableFrom(readType)) {
+ return read;
+ }
+ }
+ return write;
+ }
+
+ private MethodParameter resolveReadMethodParameter() {
+ if (getReadMethod() == null) {
+ return null;
+ }
+ return resolveParameterType(new MethodParameter(getReadMethod(), -1));
+ }
+
+ private MethodParameter resolveWriteMethodParameter() {
+ if (getWriteMethod() == null) {
+ return null;
+ }
+ return resolveParameterType(new MethodParameter(getWriteMethod(), 0));
+ }
+
+ private MethodParameter resolveParameterType(MethodParameter parameter) {
+ // needed to resolve generic property types that parameterized by sub-classes e.g. T getFoo();
+ GenericTypeResolver.resolveParameterType(parameter, getObjectType());
+ return parameter;
+ }
+
+ private Annotation[] resolveAnnotations() {
+ Annotation[] annotations = annotationCache.get(this);
+ if (annotations == null) {
+ Map<Class<? extends Annotation>, Annotation> annotationMap = new LinkedHashMap<Class<? extends Annotation>, Annotation>();
+ addAnnotationsToMap(annotationMap, getReadMethod());
+ addAnnotationsToMap(annotationMap, getWriteMethod());
+ addAnnotationsToMap(annotationMap, getField());
+ annotations = annotationMap.values().toArray(new Annotation[annotationMap.size()]);
+ annotationCache.put(this, annotations);
+ }
+ return annotations;
+ }
+
+ private void addAnnotationsToMap(
+ Map<Class<? extends Annotation>, Annotation> annotationMap,
+ AnnotatedElement object) {
+ if (object != null) {
+ for (Annotation annotation : object.getAnnotations()) {
+ annotationMap.put(annotation.annotationType(), annotation);
+ }
+ }
+ }
+
+ private Field getField() {
+ String name = getName();
+ if (!StringUtils.hasLength(name)) {
+ return null;
+ }
+ Class<?> declaringClass = declaringClass();
+ Field field = ReflectionUtils.findField(declaringClass, name);
+ if (field == null) {
+ // Same lenient fallback checking as in CachedIntrospectionResults...
+ field = ReflectionUtils.findField(declaringClass,
+ name.substring(0, 1).toLowerCase() + name.substring(1));
+ if (field == null) {
+ field = ReflectionUtils.findField(declaringClass,
+ name.substring(0, 1).toUpperCase() + name.substring(1));
+ }
+ }
+ return field;
+ }
+
+ private Class<?> declaringClass() {
+ if (getReadMethod() != null) {
+ return getReadMethod().getDeclaringClass();
+ }
+ else {
+ return getWriteMethod().getDeclaringClass();
+ }
+ }
+
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof Property)) {
+ return false;
+ }
+ Property otherProperty = (Property) other;
+ return (ObjectUtils.nullSafeEquals(this.objectType, otherProperty.objectType) &&
+ ObjectUtils.nullSafeEquals(this.name, otherProperty.name) &&
+ ObjectUtils.nullSafeEquals(this.readMethod, otherProperty.readMethod) &&
+ ObjectUtils.nullSafeEquals(this.writeMethod, otherProperty.writeMethod));
+ }
+
+ @Override
+ public int hashCode() {
+ return (ObjectUtils.nullSafeHashCode(this.objectType) * 31 + ObjectUtils.nullSafeHashCode(this.name));
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java
new file mode 100644
index 00000000..39d0f847
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java
@@ -0,0 +1,703 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert;
+
+import java.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Context about a type to convert from or to.
+ *
+ * @author Keith Donald
+ * @author Andy Clement
+ * @author Juergen Hoeller
+ * @author Phillip Webb
+ * @author Sam Brannen
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class TypeDescriptor implements Serializable {
+
+ static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
+
+ private static final Map<Class<?>, TypeDescriptor> typeDescriptorCache = new HashMap<Class<?>, TypeDescriptor>(18);
+
+ static {
+ typeDescriptorCache.put(boolean.class, new TypeDescriptor(boolean.class));
+ typeDescriptorCache.put(Boolean.class, new TypeDescriptor(Boolean.class));
+ typeDescriptorCache.put(byte.class, new TypeDescriptor(byte.class));
+ typeDescriptorCache.put(Byte.class, new TypeDescriptor(Byte.class));
+ typeDescriptorCache.put(char.class, new TypeDescriptor(char.class));
+ typeDescriptorCache.put(Character.class, new TypeDescriptor(Character.class));
+ typeDescriptorCache.put(double.class, new TypeDescriptor(double.class));
+ typeDescriptorCache.put(Double.class, new TypeDescriptor(Double.class));
+ typeDescriptorCache.put(int.class, new TypeDescriptor(int.class));
+ typeDescriptorCache.put(Integer.class, new TypeDescriptor(Integer.class));
+ typeDescriptorCache.put(long.class, new TypeDescriptor(long.class));
+ typeDescriptorCache.put(Long.class, new TypeDescriptor(Long.class));
+ typeDescriptorCache.put(float.class, new TypeDescriptor(float.class));
+ typeDescriptorCache.put(Float.class, new TypeDescriptor(Float.class));
+ typeDescriptorCache.put(short.class, new TypeDescriptor(short.class));
+ typeDescriptorCache.put(Short.class, new TypeDescriptor(Short.class));
+ typeDescriptorCache.put(String.class, new TypeDescriptor(String.class));
+ typeDescriptorCache.put(Object.class, new TypeDescriptor(Object.class));
+ }
+
+
+ private final Class<?> type;
+
+ private final TypeDescriptor elementTypeDescriptor;
+
+ private final TypeDescriptor mapKeyTypeDescriptor;
+
+ private final TypeDescriptor mapValueTypeDescriptor;
+
+ private final Annotation[] annotations;
+
+
+ /**
+ * Create a new type descriptor from a {@link MethodParameter}.
+ * <p>Use this constructor when a source or target conversion point is a
+ * constructor parameter, method parameter, or method return value.
+ * @param methodParameter the method parameter
+ */
+ public TypeDescriptor(MethodParameter methodParameter) {
+ this(new ParameterDescriptor(methodParameter));
+ }
+
+ /**
+ * Create a new type descriptor from a {@link Field}.
+ * <p>Use this constructor when a source or target conversion point is a field.
+ * @param field the field
+ */
+ public TypeDescriptor(Field field) {
+ this(new FieldDescriptor(field));
+ }
+
+ /**
+ * Create a new type descriptor from a {@link Property}.
+ * <p>Use this constructor when a source or target conversion point is a
+ * property on a Java class.
+ * @param property the property
+ */
+ public TypeDescriptor(Property property) {
+ this(new BeanPropertyDescriptor(property));
+ }
+
+
+ /**
+ * Create a new type descriptor from the given type.
+ * <p>Use this to instruct the conversion system to convert an object to a
+ * specific target type, when no type location such as a method parameter or
+ * field is available to provide additional conversion context.
+ * <p>Generally prefer use of {@link #forObject(Object)} for constructing type
+ * descriptors from source objects, as it handles the {@code null} object case.
+ * @param type the class
+ * @return the type descriptor
+ */
+ public static TypeDescriptor valueOf(Class<?> type) {
+ if (type == null) {
+ type = Object.class;
+ }
+ TypeDescriptor desc = typeDescriptorCache.get(type);
+ return (desc != null ? desc : new TypeDescriptor(type));
+ }
+
+ /**
+ * Create a new type descriptor from a {@link java.util.Collection} type.
+ * <p>Useful for converting to typed Collections.
+ * <p>For example, a {@code List<String>} could be converted to a
+ * {@code List<EmailAddress>} by converting to a targetType built with this method.
+ * The method call to construct such a {@code TypeDescriptor} would look something
+ * like: {@code collection(List.class, TypeDescriptor.valueOf(EmailAddress.class));}
+ * @param collectionType the collection type, which must implement {@link Collection}.
+ * @param elementTypeDescriptor a descriptor for the collection's element type,
+ * used to convert collection elements
+ * @return the collection type descriptor
+ */
+ public static TypeDescriptor collection(Class<?> collectionType, TypeDescriptor elementTypeDescriptor) {
+ if (!Collection.class.isAssignableFrom(collectionType)) {
+ throw new IllegalArgumentException("collectionType must be a java.util.Collection");
+ }
+ return new TypeDescriptor(collectionType, elementTypeDescriptor);
+ }
+
+ /**
+ * Create a new type descriptor from a {@link java.util.Map} type.
+ * <p>Useful for converting to typed Maps.
+ * <p>For example, a Map&lt;String, String&gt; could be converted to a Map&lt;Id, EmailAddress&gt;
+ * by converting to a targetType built with this method:
+ * The method call to construct such a TypeDescriptor would look something like:
+ * map(Map.class, TypeDescriptor.valueOf(Id.class), TypeDescriptor.valueOf(EmailAddress.class));
+ * @param mapType the map type, which must implement {@link Map}
+ * @param keyTypeDescriptor a descriptor for the map's key type, used to convert map keys
+ * @param valueTypeDescriptor the map's value type, used to convert map values
+ * @return the map type descriptor
+ */
+ public static TypeDescriptor map(Class<?> mapType, TypeDescriptor keyTypeDescriptor, TypeDescriptor valueTypeDescriptor) {
+ if (!Map.class.isAssignableFrom(mapType)) {
+ throw new IllegalArgumentException("mapType must be a java.util.Map");
+ }
+ return new TypeDescriptor(mapType, keyTypeDescriptor, valueTypeDescriptor);
+ }
+
+ /**
+ * Create a new type descriptor as an array of the specified type.
+ * <p>For example to create a {@code Map<String,String>[]} use
+ * {@code TypeDescriptor.array(TypeDescriptor.map(Map.class, TypeDescriptor.value(String.class), TypeDescriptor.value(String.class)))}.
+ * @param elementTypeDescriptor the {@link TypeDescriptor} of the array element or {@code null}
+ * @return an array {@link TypeDescriptor} or {@code null} if {@code elementTypeDescriptor} is {@code null}
+ * @since 3.2.1
+ */
+ public static TypeDescriptor array(TypeDescriptor elementTypeDescriptor) {
+ if (elementTypeDescriptor == null) {
+ return null;
+ }
+ Class<?> type = Array.newInstance(elementTypeDescriptor.getType(), 0).getClass();
+ return new TypeDescriptor(type, elementTypeDescriptor, null, null, elementTypeDescriptor.getAnnotations());
+ }
+
+ /**
+ * Creates a type descriptor for a nested type declared within the method parameter.
+ * <p>For example, if the methodParameter is a {@code List<String>} and the
+ * nesting level is 1, the nested type descriptor will be String.class.
+ * <p>If the methodParameter is a {@code List<List<String>>} and the nesting
+ * level is 2, the nested type descriptor will also be a String.class.
+ * <p>If the methodParameter is a {@code Map<Integer, String>} and the nesting
+ * level is 1, the nested type descriptor will be String, derived from the map value.
+ * <p>If the methodParameter is a {@code List<Map<Integer, String>>} and the
+ * nesting level is 2, the nested type descriptor will be String, derived from the map value.
+ * <p>Returns {@code null} if a nested type cannot be obtained because it was not declared.
+ * For example, if the method parameter is a {@code List<?>}, the nested type
+ * descriptor returned will be {@code null}.
+ * @param methodParameter the method parameter with a nestingLevel of 1
+ * @param nestingLevel the nesting level of the collection/array element or
+ * map key/value declaration within the method parameter
+ * @return the nested type descriptor at the specified nesting level, or null
+ * if it could not be obtained
+ * @throws IllegalArgumentException if the nesting level of the input
+ * {@link MethodParameter} argument is not 1, or if the types up to the
+ * specified nesting level are not of collection, array, or map types
+ */
+ public static TypeDescriptor nested(MethodParameter methodParameter, int nestingLevel) {
+ if (methodParameter.getNestingLevel() != 1) {
+ throw new IllegalArgumentException("methodParameter nesting level must be 1: " +
+ "use the nestingLevel parameter to specify the desired nestingLevel for nested type traversal");
+ }
+ return nested(new ParameterDescriptor(methodParameter), nestingLevel);
+ }
+
+ /**
+ * Creates a type descriptor for a nested type declared within the field.
+ * <p>For example, if the field is a {@code List<String>} and the nesting
+ * level is 1, the nested type descriptor will be {@code String.class}.
+ * <p>If the field is a {@code List<List<String>>} and the nesting level is
+ * 2, the nested type descriptor will also be a {@code String.class}.
+ * <p>If the field is a {@code Map<Integer, String>} and the nesting level
+ * is 1, the nested type descriptor will be String, derived from the map value.
+ * <p>If the field is a {@code List<Map<Integer, String>>} and the nesting
+ * level is 2, the nested type descriptor will be String, derived from the map value.
+ * <p>Returns {@code null} if a nested type cannot be obtained because it was not declared.
+ * For example, if the field is a {@code List<?>}, the nested type descriptor returned will be {@code null}.
+ * @param field the field
+ * @param nestingLevel the nesting level of the collection/array element or
+ * map key/value declaration within the field
+ * @return the nested type descriptor at the specified nesting level, or null
+ * if it could not be obtained
+ * @throws IllegalArgumentException if the types up to the specified nesting
+ * level are not of collection, array, or map types
+ */
+ public static TypeDescriptor nested(Field field, int nestingLevel) {
+ return nested(new FieldDescriptor(field), nestingLevel);
+ }
+
+ /**
+ * Creates a type descriptor for a nested type declared within the property.
+ * <p>For example, if the property is a {@code List<String>} and the nesting
+ * level is 1, the nested type descriptor will be {@code String.class}.
+ * <p>If the property is a {@code List<List<String>>} and the nesting level
+ * is 2, the nested type descriptor will also be a {@code String.class}.
+ * <p>If the property is a {@code Map<Integer, String>} and the nesting level
+ * is 1, the nested type descriptor will be String, derived from the map value.
+ * <p>If the property is a {@code List<Map<Integer, String>>} and the nesting
+ * level is 2, the nested type descriptor will be String, derived from the map value.
+ * <p>Returns {@code null} if a nested type cannot be obtained because it was not declared.
+ * For example, if the property is a {@code List<?>}, the nested type descriptor
+ * returned will be {@code null}.
+ * @param property the property
+ * @param nestingLevel the nesting level of the collection/array element or
+ * map key/value declaration within the property
+ * @return the nested type descriptor at the specified nesting level, or
+ * {@code null} if it could not be obtained
+ * @throws IllegalArgumentException if the types up to the specified nesting
+ * level are not of collection, array, or map types
+ */
+ public static TypeDescriptor nested(Property property, int nestingLevel) {
+ return nested(new BeanPropertyDescriptor(property), nestingLevel);
+ }
+
+ /**
+ * Create a new type descriptor for an object.
+ * <p>Use this factory method to introspect a source object before asking the
+ * conversion system to convert it to some another type.
+ * <p>If the provided object is null, returns null, else calls {@link #valueOf(Class)}
+ * to build a TypeDescriptor from the object's class.
+ * @param source the source object
+ * @return the type descriptor
+ */
+ public static TypeDescriptor forObject(Object source) {
+ return (source != null ? valueOf(source.getClass()) : null);
+ }
+
+
+ /**
+ * The type of the backing class, method parameter, field, or property described by this TypeDescriptor.
+ * <p>Returns primitive types as-is.
+ * <p>See {@link #getObjectType()} for a variation of this operation that resolves primitive types
+ * to their corresponding Object types if necessary.
+ * @return the type, or {@code null}
+ * @see #getObjectType()
+ */
+ public Class<?> getType() {
+ return this.type;
+ }
+
+ /**
+ * Variation of {@link #getType()} that accounts for a primitive type by returning its object wrapper type.
+ * <p>This is useful for conversion service implementations that wish to normalize to object-based types
+ * and not work with primitive types directly.
+ */
+ public Class<?> getObjectType() {
+ return ClassUtils.resolvePrimitiveIfNecessary(getType());
+ }
+
+ /**
+ * Narrows this {@link TypeDescriptor} by setting its type to the class of the provided value.
+ * <p>If the value is {@code null}, no narrowing is performed and this TypeDescriptor is returned unchanged.
+ * <p>Designed to be called by binding frameworks when they read property, field, or method return values.
+ * Allows such frameworks to narrow a TypeDescriptor built from a declared property, field, or method return
+ * value type. For example, a field declared as {@code java.lang.Object} would be narrowed to
+ * {@code java.util.HashMap} if it was set to a {@code java.util.HashMap} value. The narrowed
+ * TypeDescriptor can then be used to convert the HashMap to some other type. Annotation and
+ * nested type context is preserved by the narrowed copy.
+ * @param value the value to use for narrowing this type descriptor
+ * @return this TypeDescriptor narrowed (returns a copy with its type updated to the class of the provided value)
+ */
+ public TypeDescriptor narrow(Object value) {
+ if (value == null) {
+ return this;
+ }
+ return new TypeDescriptor(value.getClass(), this.elementTypeDescriptor,
+ this.mapKeyTypeDescriptor, this.mapValueTypeDescriptor, this.annotations);
+ }
+
+ /**
+ * Cast this {@link TypeDescriptor} to a superclass or implemented interface
+ * preserving annotations and nested type context.
+ * @param superType the super type to cast to (can be {@code null}
+ * @return a new TypeDescriptor for the up-cast type
+ * @throws IllegalArgumentException if this type is not assignable to the super-type
+ * @since 3.2
+ */
+ public TypeDescriptor upcast(Class<?> superType) {
+ if (superType == null) {
+ return null;
+ }
+ Assert.isAssignable(superType, getType());
+ return new TypeDescriptor(superType, this.elementTypeDescriptor,
+ this.mapKeyTypeDescriptor, this.mapValueTypeDescriptor, this.annotations);
+ }
+
+ /**
+ * Returns the name of this type: the fully qualified class name.
+ */
+ public String getName() {
+ return ClassUtils.getQualifiedName(getType());
+ }
+
+ /**
+ * Is this type a primitive type?
+ */
+ public boolean isPrimitive() {
+ return getType().isPrimitive();
+ }
+
+ /**
+ * The annotations associated with this type descriptor, if any.
+ * @return the annotations, or an empty array if none
+ */
+ public Annotation[] getAnnotations() {
+ return this.annotations;
+ }
+
+ /**
+ * Determine if this type descriptor has the specified annotation.
+ * @param annotationType the annotation type
+ * @return <tt>true</tt> if the annotation is present
+ */
+ public boolean hasAnnotation(Class<? extends Annotation> annotationType) {
+ return getAnnotation(annotationType) != null;
+ }
+
+ /**
+ * Obtain the annotation associated with this type descriptor of the specified type.
+ * @param annotationType the annotation type
+ * @return the annotation, or {@code null} if no such annotation exists on this type descriptor
+ */
+ @SuppressWarnings("unchecked")
+ public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
+ for (Annotation annotation : this.annotations) {
+ if (annotation.annotationType().equals(annotationType)) {
+ return (T) annotation;
+ }
+ }
+ for (Annotation metaAnn : this.annotations) {
+ T ann = metaAnn.annotationType().getAnnotation(annotationType);
+ if (ann != null) {
+ return ann;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if an object of this type descriptor can be assigned to the location described by the
+ * given type descriptor.
+ * <p>For example, valueOf(String.class).isAssignableTo(valueOf(CharSequence.class)) returns true
+ * because a String value can be assigned to a CharSequence variable. On the other hand,
+ * valueOf(Number.class).isAssignableTo(valueOf(Integer.class)) returns false because,
+ * while all Integers are Numbers, not all Numbers are Integers.
+ * <p>For arrays, collections, and maps, element and key/value types are checked if declared.
+ * For example, a List&lt;String&gt; field value is assignable to a Collection&lt;CharSequence&gt;
+ * field, but List&lt;Number&gt; is not assignable to List&lt;Integer&gt;.
+ * @return true if this type is assignable to the type represented by the provided type descriptor
+ * @see #getObjectType()
+ */
+ public boolean isAssignableTo(TypeDescriptor typeDescriptor) {
+ boolean typesAssignable = typeDescriptor.getObjectType().isAssignableFrom(getObjectType());
+ if (!typesAssignable) {
+ return false;
+ }
+ if (isArray() && typeDescriptor.isArray()) {
+ return getElementTypeDescriptor().isAssignableTo(typeDescriptor.getElementTypeDescriptor());
+ }
+ else if (isCollection() && typeDescriptor.isCollection()) {
+ return isNestedAssignable(getElementTypeDescriptor(), typeDescriptor.getElementTypeDescriptor());
+ }
+ else if (isMap() && typeDescriptor.isMap()) {
+ return isNestedAssignable(getMapKeyTypeDescriptor(), typeDescriptor.getMapKeyTypeDescriptor()) &&
+ isNestedAssignable(getMapValueTypeDescriptor(), typeDescriptor.getMapValueTypeDescriptor());
+ }
+ else {
+ return true;
+ }
+ }
+
+
+ // indexable type descriptor operations
+
+ /**
+ * Is this type a {@link Collection} type?
+ */
+ public boolean isCollection() {
+ return Collection.class.isAssignableFrom(getType());
+ }
+
+ /**
+ * Is this type an array type?
+ */
+ public boolean isArray() {
+ return getType().isArray();
+ }
+
+ /**
+ * If this type is an array, returns the array's component type.
+ * If this type is a {@link Collection} and it is parameterized, returns the Collection's element type.
+ * If the Collection is not parameterized, returns null indicating the element type is not declared.
+ * @return the array component type or Collection element type, or {@code null} if this type is a
+ * Collection but its element type is not parameterized
+ * @throws IllegalStateException if this type is not a java.util.Collection or Array type
+ */
+ public TypeDescriptor getElementTypeDescriptor() {
+ assertCollectionOrArray();
+ return this.elementTypeDescriptor;
+ }
+
+ /**
+ * If this type is a {@link Collection} or an Array, creates a element TypeDescriptor from the provided
+ * collection or array element.
+ * <p>Narrows the {@link #getElementTypeDescriptor() elementType} property to the class of the provided
+ * collection or array element. For example, if this describes a java.util.List&lt;java.lang.Number&lt;
+ * and the element argument is a java.lang.Integer, the returned TypeDescriptor will be java.lang.Integer.
+ * If this describes a java.util.List&lt;?&gt; and the element argument is a java.lang.Integer, the returned
+ * TypeDescriptor will be java.lang.Integer as well.
+ * <p>Annotation and nested type context will be preserved in the narrowed TypeDescriptor that is returned.
+ * @param element the collection or array element
+ * @return a element type descriptor, narrowed to the type of the provided element
+ * @throws IllegalStateException if this type is not a java.util.Collection or Array type
+ * @see #narrow(Object)
+ */
+ public TypeDescriptor elementTypeDescriptor(Object element) {
+ return narrow(element, getElementTypeDescriptor());
+ }
+
+
+ // map type descriptor operations
+
+ /**
+ * Is this type a {@link Map} type?
+ */
+ public boolean isMap() {
+ return Map.class.isAssignableFrom(getType());
+ }
+
+ /**
+ * If this type is a {@link Map} and its key type is parameterized, returns the map's key type.
+ * If the Map's key type is not parameterized, returns null indicating the key type is not declared.
+ * @return the Map key type, or {@code null} if this type is a Map but its key type is not parameterized
+ * @throws IllegalStateException if this type is not a java.util.Map
+ */
+ public TypeDescriptor getMapKeyTypeDescriptor() {
+ assertMap();
+ return this.mapKeyTypeDescriptor;
+ }
+
+ /**
+ * If this type is a {@link Map}, creates a mapKey {@link TypeDescriptor} from the provided map key.
+ * <p>Narrows the {@link #getMapKeyTypeDescriptor() mapKeyType} property to the class of the provided map key.
+ * For example, if this describes a java.util.Map&lt;java.lang.Number, java.lang.String&lt; and the key argument
+ * is a java.lang.Integer, the returned TypeDescriptor will be java.lang.Integer.
+ * <p>If this describes a java.util.Map&lt;?, ?&gt; and the key argument is a java.lang.Integer, the returned
+ * TypeDescriptor will be java.lang.Integer as well.
+ * <p>Annotation and nested type context will be preserved in the narrowed TypeDescriptor that is returned.
+ * @param mapKey the map key
+ * @return the map key type descriptor
+ * @throws IllegalStateException if this type is not a java.util.Map
+ * @see #narrow(Object)
+ */
+ public TypeDescriptor getMapKeyTypeDescriptor(Object mapKey) {
+ return narrow(mapKey, getMapKeyTypeDescriptor());
+ }
+
+ /**
+ * If this type is a {@link Map} and its value type is parameterized, returns the map's value type.
+ * <p>If the Map's value type is not parameterized, returns null indicating the value type is not declared.
+ * @return the Map value type, or {@code null} if this type is a Map but its value type is not parameterized
+ * @throws IllegalStateException if this type is not a java.util.Map
+ */
+ public TypeDescriptor getMapValueTypeDescriptor() {
+ assertMap();
+ return this.mapValueTypeDescriptor;
+ }
+
+ /**
+ * If this type is a {@link Map}, creates a mapValue {@link TypeDescriptor} from the provided map value.
+ * <p>Narrows the {@link #getMapValueTypeDescriptor() mapValueType} property to the class of the provided
+ * map value. For example, if this describes a java.util.Map&lt;java.lang.String, java.lang.Number&lt;
+ * and the value argument is a java.lang.Integer, the returned TypeDescriptor will be java.lang.Integer.
+ * If this describes a java.util.Map&lt;?, ?&gt; and the value argument is a java.lang.Integer, the
+ * returned TypeDescriptor will be java.lang.Integer as well.
+ * <p>Annotation and nested type context will be preserved in the narrowed TypeDescriptor that is returned.
+ * @param mapValue the map value
+ * @return the map value type descriptor
+ * @throws IllegalStateException if this type is not a java.util.Map
+ */
+ public TypeDescriptor getMapValueTypeDescriptor(Object mapValue) {
+ return narrow(mapValue, getMapValueTypeDescriptor());
+ }
+
+
+ // deprecations in Spring 3.1
+
+ /**
+ * Returns the value of {@link TypeDescriptor#getType() getType()} for the
+ * {@link #getElementTypeDescriptor() elementTypeDescriptor}.
+ * @deprecated in Spring 3.1 in favor of {@link #getElementTypeDescriptor()}
+ * @throws IllegalStateException if this type is not a java.util.Collection or Array type
+ */
+ @Deprecated
+ public Class<?> getElementType() {
+ return getElementTypeDescriptor().getType();
+ }
+
+ /**
+ * Returns the value of {@link TypeDescriptor#getType() getType()} for the
+ * {@link #getMapKeyTypeDescriptor() getMapKeyTypeDescriptor}.
+ * @deprecated in Spring 3.1 in favor of {@link #getMapKeyTypeDescriptor()}
+ * @throws IllegalStateException if this type is not a java.util.Map
+ */
+ @Deprecated
+ public Class<?> getMapKeyType() {
+ return getMapKeyTypeDescriptor().getType();
+ }
+
+ /**
+ * Returns the value of {@link TypeDescriptor#getType() getType()} for the
+ * {@link #getMapValueTypeDescriptor() getMapValueTypeDescriptor}.
+ * @deprecated in Spring 3.1 in favor of {@link #getMapValueTypeDescriptor()}
+ * @throws IllegalStateException if this type is not a java.util.Map
+ */
+ @Deprecated
+ public Class<?> getMapValueType() {
+ return getMapValueTypeDescriptor().getType();
+ }
+
+
+ // internal constructors
+
+ private TypeDescriptor(Class<?> type) {
+ this(new ClassDescriptor(type));
+ }
+
+ private TypeDescriptor(Class<?> collectionType, TypeDescriptor elementTypeDescriptor) {
+ this(collectionType, elementTypeDescriptor, null, null, EMPTY_ANNOTATION_ARRAY);
+ }
+
+ private TypeDescriptor(Class<?> mapType, TypeDescriptor keyTypeDescriptor, TypeDescriptor valueTypeDescriptor) {
+ this(mapType, null, keyTypeDescriptor, valueTypeDescriptor, EMPTY_ANNOTATION_ARRAY);
+ }
+
+ private TypeDescriptor(Class<?> type, TypeDescriptor elementTypeDescriptor, TypeDescriptor mapKeyTypeDescriptor,
+ TypeDescriptor mapValueTypeDescriptor, Annotation[] annotations) {
+
+ this.type = type;
+ this.elementTypeDescriptor = elementTypeDescriptor;
+ this.mapKeyTypeDescriptor = mapKeyTypeDescriptor;
+ this.mapValueTypeDescriptor = mapValueTypeDescriptor;
+ this.annotations = annotations;
+ }
+
+ TypeDescriptor(AbstractDescriptor descriptor) {
+ this.type = descriptor.getType();
+ this.elementTypeDescriptor = descriptor.getElementTypeDescriptor();
+ this.mapKeyTypeDescriptor = descriptor.getMapKeyTypeDescriptor();
+ this.mapValueTypeDescriptor = descriptor.getMapValueTypeDescriptor();
+ this.annotations = descriptor.getAnnotations();
+ }
+
+
+ // internal helpers
+
+ static Annotation[] nullSafeAnnotations(Annotation[] annotations) {
+ return (annotations != null ? annotations : EMPTY_ANNOTATION_ARRAY);
+ }
+
+ private static TypeDescriptor nested(AbstractDescriptor descriptor, int nestingLevel) {
+ for (int i = 0; i < nestingLevel; i++) {
+ descriptor = descriptor.nested();
+ if (descriptor == null) {
+ return null;
+ }
+ }
+ return new TypeDescriptor(descriptor);
+ }
+
+ private void assertCollectionOrArray() {
+ if (!isCollection() && !isArray()) {
+ throw new IllegalStateException("Not a java.util.Collection or Array");
+ }
+ }
+
+ private void assertMap() {
+ if (!isMap()) {
+ throw new IllegalStateException("Not a java.util.Map");
+ }
+ }
+
+ private TypeDescriptor narrow(Object value, TypeDescriptor typeDescriptor) {
+ if (typeDescriptor != null) {
+ return typeDescriptor.narrow(value);
+ }
+ else {
+ return (value != null ? new TypeDescriptor(value.getClass(), null, null, null, this.annotations) : null);
+ }
+ }
+
+ private boolean isNestedAssignable(TypeDescriptor nestedTypeDescriptor, TypeDescriptor otherNestedTypeDescriptor) {
+ if (nestedTypeDescriptor == null || otherNestedTypeDescriptor == null) {
+ return true;
+ }
+ return nestedTypeDescriptor.isAssignableTo(otherNestedTypeDescriptor);
+ }
+
+ private String wildcard(TypeDescriptor typeDescriptor) {
+ return (typeDescriptor != null ? typeDescriptor.toString() : "?");
+ }
+
+
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof TypeDescriptor)) {
+ return false;
+ }
+ TypeDescriptor other = (TypeDescriptor) obj;
+ if (!ObjectUtils.nullSafeEquals(this.type, other.type)) {
+ return false;
+ }
+ if (this.annotations.length != other.annotations.length) {
+ return false;
+ }
+ for (Annotation ann : this.annotations) {
+ if (other.getAnnotation(ann.annotationType()) == null) {
+ return false;
+ }
+ }
+ if (isCollection() || isArray()) {
+ return ObjectUtils.nullSafeEquals(this.elementTypeDescriptor, other.elementTypeDescriptor);
+ }
+ else if (isMap()) {
+ return ObjectUtils.nullSafeEquals(this.mapKeyTypeDescriptor, other.mapKeyTypeDescriptor) &&
+ ObjectUtils.nullSafeEquals(this.mapValueTypeDescriptor, other.mapValueTypeDescriptor);
+ }
+ else {
+ return true;
+ }
+ }
+
+ public int hashCode() {
+ return getType().hashCode();
+ }
+
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ for (Annotation ann : this.annotations) {
+ builder.append("@").append(ann.annotationType().getName()).append(' ');
+ }
+ builder.append(ClassUtils.getQualifiedName(getType()));
+ if (isMap()) {
+ builder.append("<").append(wildcard(this.mapKeyTypeDescriptor));
+ builder.append(", ").append(wildcard(this.mapValueTypeDescriptor)).append(">");
+ }
+ else if (isCollection()) {
+ builder.append("<").append(wildcard(this.elementTypeDescriptor)).append(">");
+ }
+ return builder.toString();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/converter/ConditionalConverter.java b/spring-core/src/main/java/org/springframework/core/convert/converter/ConditionalConverter.java
new file mode 100644
index 00000000..964139e0
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/converter/ConditionalConverter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.converter;
+
+import org.springframework.core.convert.TypeDescriptor;
+
+/**
+ * Allows a {@link Converter}, {@link GenericConverter} or {@link ConverterFactory} to
+ * conditionally execute based on attributes of the {@code source} and {@code target}
+ * {@link TypeDescriptor}.
+ *
+ * <p>Often used to selectively match custom conversion logic based on the presence of a
+ * field or class-level characteristic, such as an annotation or method. For example, when
+ * converting from a String field to a Date field, an implementation might return
+ * {@code true} if the target field has also been annotated with {@code @DateTimeFormat}.
+ *
+ * <p>As another example, when converting from a String field to an {@code Account} field,
+ * an implementation might return {@code true} if the target Account class defines a
+ * {@code public static findAccount(String)} method.
+ *
+ * @author Phillip Webb
+ * @author Keith Donald
+ * @since 3.2
+ * @see Converter
+ * @see GenericConverter
+ * @see ConverterFactory
+ * @see ConditionalGenericConverter
+ */
+public interface ConditionalConverter {
+
+ /**
+ * Should the conversion from {@code sourceType} to {@code targetType} currently under
+ * consideration be selected?
+ * @param sourceType the type descriptor of the field we are converting from
+ * @param targetType the type descriptor of the field we are converting to
+ * @return true if conversion should be performed, false otherwise
+ */
+ boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/converter/ConditionalGenericConverter.java b/spring-core/src/main/java/org/springframework/core/convert/converter/ConditionalGenericConverter.java
new file mode 100644
index 00000000..56dadbfc
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/converter/ConditionalGenericConverter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.converter;
+
+import org.springframework.core.convert.TypeDescriptor;
+
+/**
+ * A {@link GenericConverter} that may conditionally execute based on attributes
+ * of the {@code source} and {@code target} {@link TypeDescriptor}.
+ * See {@link ConditionalConverter} for details.
+ *
+ * @author Keith Donald
+ * @author Phillip Webb
+ * @since 3.0
+ * @see GenericConverter
+ * @see ConditionalConverter
+ */
+public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/converter/Converter.java b/spring-core/src/main/java/org/springframework/core/convert/converter/Converter.java
new file mode 100644
index 00000000..6710b666
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/converter/Converter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.converter;
+
+/**
+ * A converter converts a source object of type S to a target of type T.
+ * Implementations of this interface are thread-safe and can be shared.
+ *
+ * <p>Implementations may additionally implement {@link ConditionalConverter}.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ * @see ConditionalConverter
+ * @param <S> The source type
+ * @param <T> The target type
+ */
+public interface Converter<S, T> {
+
+ /**
+ * Convert the source of type S to target type T.
+ * @param source the source object to convert, which must be an instance of S
+ * @return the converted object, which must be an instance of T
+ * @throws IllegalArgumentException if the source could not be converted to the desired target type
+ */
+ T convert(S source);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/converter/ConverterFactory.java b/spring-core/src/main/java/org/springframework/core/convert/converter/ConverterFactory.java
new file mode 100644
index 00000000..97ae018d
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/converter/ConverterFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.converter;
+
+/**
+ * A factory for "ranged" converters that can convert objects from S to subtypes of R.
+ *
+ * <p>Implementations may additionally implement {@link ConditionalConverter}.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ * @see ConditionalConverter
+ * @param <S> The source type converters created by this factory can convert from
+ * @param <R> The target range (or base) type converters created by this factory can convert to;
+ * for example {@link Number} for a set of number subtypes.
+ */
+public interface ConverterFactory<S, R> {
+
+ /**
+ * Get the converter to convert from S to target type T, where T is also an instance of R.
+ * @param <T> the target type
+ * @param targetType the target type to convert to
+ * @return A converter from S to T
+ */
+ <T extends R> Converter<S, T> getConverter(Class<T> targetType);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/converter/ConverterRegistry.java b/spring-core/src/main/java/org/springframework/core/convert/converter/ConverterRegistry.java
new file mode 100644
index 00000000..9280c4ea
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/converter/ConverterRegistry.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.converter;
+
+/**
+ * For registering converters with a type conversion system.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public interface ConverterRegistry {
+
+ /**
+ * Add a plain converter to this registry.
+ * The convertible sourceType/targetType pair is derived from the Converter's parameterized types.
+ * @throws IllegalArgumentException if the parameterized types could not be resolved
+ */
+ void addConverter(Converter<?, ?> converter);
+
+ /**
+ * Add a plain converter to this registry.
+ * The convertible sourceType/targetType pair is specified explicitly.
+ * Allows for a Converter to be reused for multiple distinct pairs without having to create a Converter class for each pair.
+ * @since 3.1
+ */
+ void addConverter(Class<?> sourceType, Class<?> targetType, Converter<?, ?> converter);
+
+ /**
+ * Add a generic converter to this registry.
+ */
+ void addConverter(GenericConverter converter);
+
+ /**
+ * Add a ranged converter factory to this registry.
+ * The convertible sourceType/rangeType pair is derived from the ConverterFactory's parameterized types.
+ * @throws IllegalArgumentException if the parameterized types could not be resolved.
+ */
+ void addConverterFactory(ConverterFactory<?, ?> converterFactory);
+
+ /**
+ * Remove any converters from sourceType to targetType.
+ * @param sourceType the source type
+ * @param targetType the target type
+ */
+ void removeConvertible(Class<?> sourceType, Class<?> targetType);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/converter/ConvertingComparator.java b/spring-core/src/main/java/org/springframework/core/convert/converter/ConvertingComparator.java
new file mode 100644
index 00000000..3e2d7415
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/converter/ConvertingComparator.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.converter;
+
+import java.util.Comparator;
+import java.util.Map;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.util.Assert;
+import org.springframework.util.comparator.ComparableComparator;
+
+/**
+ * A {@link Comparator} that converts values before they are compared. The specified
+ * {@link Converter} will be used to convert each value before it passed to the underlying
+ * {@code Comparator}.
+ *
+ * @author Phillip Webb
+ * @param <S> the source type
+ * @param <T> the target type
+ * @since 3.2
+ */
+public class ConvertingComparator<S, T> implements Comparator<S> {
+
+ private Comparator<T> comparator;
+
+ private Converter<S, T> converter;
+
+
+ /**
+ * Create a new {@link ConvertingComparator} instance.
+ *
+ * @param converter the converter
+ */
+ @SuppressWarnings("unchecked")
+ public ConvertingComparator(Converter<S, T> converter) {
+ this(ComparableComparator.INSTANCE, converter);
+ }
+
+ /**
+ * Create a new {@link ConvertingComparator} instance.
+ *
+ * @param comparator the underlying comparator used to compare the converted values
+ * @param converter the converter
+ */
+ public ConvertingComparator(Comparator<T> comparator, Converter<S, T> converter) {
+ Assert.notNull(comparator, "Comparator must not be null");
+ Assert.notNull(converter, "Converter must not be null");
+ this.comparator = comparator;
+ this.converter = converter;
+ }
+
+ /**
+ * Create a new {@link ComparableComparator} instance.
+ *
+ * @param comparator the underlying comparator
+ * @param conversionService the conversion service
+ * @param targetType the target type
+ */
+ public ConvertingComparator(Comparator<T> comparator,
+ ConversionService conversionService, Class<? extends T> targetType) {
+ this(comparator, new ConversionServiceConverter<S, T>(
+ conversionService, targetType));
+ }
+
+
+ public int compare(S o1, S o2) {
+ T c1 = this.converter.convert(o1);
+ T c2 = this.converter.convert(o2);
+ return this.comparator.compare(c1, c2);
+ }
+
+ /**
+ * Create a new {@link ConvertingComparator} that compares {@link java.util.Map.Entry
+ * map * entries} based on their {@link java.util.Map.Entry#getKey() keys}.
+ *
+ * @param comparator the underlying comparator used to compare keys
+ * @return a new {@link ConvertingComparator} instance
+ */
+ public static <K, V> ConvertingComparator<Map.Entry<K, V>, K> mapEntryKeys(
+ Comparator<K> comparator) {
+ return new ConvertingComparator<Map.Entry<K,V>, K>(comparator, new Converter<Map.Entry<K, V>, K>() {
+
+ public K convert(Map.Entry<K, V> source) {
+ return source.getKey();
+ }
+ });
+ }
+
+ /**
+ * Create a new {@link ConvertingComparator} that compares {@link java.util.Map.Entry
+ * map entries} based on their {@link java.util.Map.Entry#getValue() values}.
+ *
+ * @param comparator the underlying comparator used to compare values
+ * @return a new {@link ConvertingComparator} instance
+ */
+ public static <K, V> ConvertingComparator<Map.Entry<K, V>, V> mapEntryValues(
+ Comparator<V> comparator) {
+ return new ConvertingComparator<Map.Entry<K,V>, V>(comparator, new Converter<Map.Entry<K, V>, V>() {
+
+ public V convert(Map.Entry<K, V> source) {
+ return source.getValue();
+ }
+ });
+ }
+
+
+ /**
+ * Adapts a {@link ConversionService} and <tt>targetType</tt> to a {@link Converter}.
+ */
+ private static class ConversionServiceConverter<S, T> implements Converter<S, T> {
+
+ private final ConversionService conversionService;
+
+ private final Class<? extends T> targetType;
+
+ public ConversionServiceConverter(ConversionService conversionService,
+ Class<? extends T> targetType) {
+ Assert.notNull(conversionService, "ConversionService must not be null");
+ Assert.notNull(targetType, "TargetType must not be null");
+ this.conversionService = conversionService;
+ this.targetType = targetType;
+ }
+
+ public T convert(S source) {
+ return this.conversionService.convert(source, this.targetType);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/converter/GenericConverter.java b/spring-core/src/main/java/org/springframework/core/convert/converter/GenericConverter.java
new file mode 100644
index 00000000..5258497b
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/converter/GenericConverter.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.converter;
+
+import java.util.Set;
+
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.util.Assert;
+
+/**
+ * Generic converter interface for converting between two or more types.
+ *
+ * <p>This is the most flexible of the Converter SPI interfaces, but also the most complex.
+ * It is flexible in that a GenericConverter may support converting between multiple source/target
+ * type pairs (see {@link #getConvertibleTypes()}. In addition, GenericConverter implementations
+ * have access to source/target {@link TypeDescriptor field context} during the type conversion process.
+ * This allows for resolving source and target field metadata such as annotations and generics
+ * information, which can be used influence the conversion logic.
+ *
+ * <p>This interface should generally not be used when the simpler {@link Converter} or
+ * {@link ConverterFactory} interfaces are sufficient.
+ *
+ * <p>Implementations may additionally implement {@link ConditionalConverter}.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 3.0
+ * @see TypeDescriptor
+ * @see Converter
+ * @see ConverterFactory
+ * @see ConditionalConverter
+ */
+public interface GenericConverter {
+
+ /**
+ * Return the source and target types which this converter can convert between. Each
+ * entry is a convertible source-to-target type pair.
+ * <p>
+ * For {@link ConditionalConverter conditional} converters this method may return
+ * {@code null} to indicate all source-to-target pairs should be considered. *
+ */
+ Set<ConvertiblePair> getConvertibleTypes();
+
+ /**
+ * Convert the source to the targetType described by the TypeDescriptor.
+ * @param source the source object to convert (may be null)
+ * @param sourceType the type descriptor of the field we are converting from
+ * @param targetType the type descriptor of the field we are converting to
+ * @return the converted object
+ */
+ Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
+
+
+ /**
+ * Holder for a source-to-target class pair.
+ */
+ public static final class ConvertiblePair {
+
+ private final Class<?> sourceType;
+
+ private final Class<?> targetType;
+
+ /**
+ * Create a new source-to-target pair.
+ * @param sourceType the source type
+ * @param targetType the target type
+ */
+ public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
+ Assert.notNull(sourceType, "Source type must not be null");
+ Assert.notNull(targetType, "Target type must not be null");
+ this.sourceType = sourceType;
+ this.targetType = targetType;
+ }
+
+ public Class<?> getSourceType() {
+ return this.sourceType;
+ }
+
+ public Class<?> getTargetType() {
+ return this.targetType;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || obj.getClass() != ConvertiblePair.class) {
+ return false;
+ }
+ ConvertiblePair other = (ConvertiblePair) obj;
+ return this.sourceType.equals(other.sourceType) && this.targetType.equals(other.targetType);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.sourceType.hashCode() * 31 + this.targetType.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return this.sourceType.getName() + " -> " + this.targetType.getName();
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/converter/package-info.java b/spring-core/src/main/java/org/springframework/core/convert/converter/package-info.java
new file mode 100644
index 00000000..a6d95c86
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/converter/package-info.java
@@ -0,0 +1,8 @@
+
+/**
+ *
+ * SPI to implement Converters for the type conversion system.
+ *
+ */
+package org.springframework.core.convert.converter;
+
diff --git a/spring-core/src/main/java/org/springframework/core/convert/package-info.java b/spring-core/src/main/java/org/springframework/core/convert/package-info.java
new file mode 100644
index 00000000..8d8bd415
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/package-info.java
@@ -0,0 +1,8 @@
+
+/**
+ *
+ * Type conversion system API.
+ *
+ */
+package org.springframework.core.convert;
+
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java
new file mode 100644
index 00000000..b2098494
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Converts an Array to another Array. First adapts the source array to a List, then
+ * delegates to {@link CollectionToArrayConverter} to perform the target array conversion.
+ *
+ * @author Keith Donald
+ * @author Phillip Webb
+ * @since 3.0
+ */
+final class ArrayToArrayConverter implements ConditionalGenericConverter {
+
+ private final CollectionToArrayConverter helperConverter;
+
+ private final ConversionService conversionService;
+
+
+ public ArrayToArrayConverter(ConversionService conversionService) {
+ this.helperConverter = new CollectionToArrayConverter(conversionService);
+ this.conversionService = conversionService;
+ }
+
+
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return Collections.singleton(new ConvertiblePair(Object[].class, Object[].class));
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return this.helperConverter.matches(sourceType, targetType);
+ }
+
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (this.conversionService instanceof GenericConversionService &&
+ ((GenericConversionService) this.conversionService).canBypassConvert(
+ sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor())) {
+ return source;
+ }
+ List<Object> sourceList = Arrays.asList(ObjectUtils.toObjectArray(source));
+ return this.helperConverter.convert(sourceList, sourceType, targetType);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToCollectionConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToCollectionConverter.java
new file mode 100644
index 00000000..4e028b49
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToCollectionConverter.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+import org.springframework.core.CollectionFactory;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+
+/**
+ * Converts an Array to a Collection.
+ *
+ * <p>First, creates a new Collection of the requested targetType.
+ * Then adds each array element to the target collection.
+ * Will perform an element conversion from the source component type to the collection's parameterized type if necessary.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ */
+final class ArrayToCollectionConverter implements ConditionalGenericConverter {
+
+ private final ConversionService conversionService;
+
+ public ArrayToCollectionConverter(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return Collections.singleton(new ConvertiblePair(Object[].class, Collection.class));
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return ConversionUtils.canConvertElements(
+ sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor(), this.conversionService);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (source == null) {
+ return null;
+ }
+ int length = Array.getLength(source);
+ Collection<Object> target = CollectionFactory.createCollection(targetType.getType(), length);
+ if (targetType.getElementTypeDescriptor() == null) {
+ for (int i = 0; i < length; i++) {
+ Object sourceElement = Array.get(source, i);
+ target.add(sourceElement);
+ }
+ }
+ else {
+ for (int i = 0; i < length; i++) {
+ Object sourceElement = Array.get(source, i);
+ Object targetElement = this.conversionService.convert(sourceElement,
+ sourceType.elementTypeDescriptor(sourceElement), targetType.getElementTypeDescriptor());
+ target.add(targetElement);
+ }
+ }
+ return target;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToObjectConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToObjectConverter.java
new file mode 100644
index 00000000..1db3b2ec
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToObjectConverter.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.lang.reflect.Array;
+import java.util.Collections;
+import java.util.Set;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+
+/**
+ * Converts an Array to an Object by returning the first array element after converting it to the desired targetType.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ */
+final class ArrayToObjectConverter implements ConditionalGenericConverter {
+
+ private final ConversionService conversionService;
+
+ public ArrayToObjectConverter(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return Collections.singleton(new ConvertiblePair(Object[].class, Object.class));
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType, this.conversionService);
+ }
+
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (source == null) {
+ return null;
+ }
+ if (sourceType.isAssignableTo(targetType)) {
+ return source;
+ }
+ if (Array.getLength(source) == 0) {
+ return null;
+ }
+ Object firstElement = Array.get(source, 0);
+ return this.conversionService.convert(firstElement, sourceType.elementTypeDescriptor(firstElement), targetType);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToStringConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToStringConverter.java
new file mode 100644
index 00000000..7ffdb862
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/ArrayToStringConverter.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Converts an Array to a comma-delimited String.
+ * This implementation first adapts the source Array to a List,
+ * then delegates to {@link CollectionToStringConverter} to perform the target String conversion.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ */
+final class ArrayToStringConverter implements ConditionalGenericConverter {
+
+ private final CollectionToStringConverter helperConverter;
+
+
+ public ArrayToStringConverter(ConversionService conversionService) {
+ this.helperConverter = new CollectionToStringConverter(conversionService);
+ }
+
+
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return Collections.singleton(new ConvertiblePair(Object[].class, String.class));
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return this.helperConverter.matches(sourceType, targetType);
+ }
+
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return this.helperConverter.convert(Arrays.asList(ObjectUtils.toObjectArray(source)), sourceType, targetType);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/CharacterToNumberFactory.java b/spring-core/src/main/java/org/springframework/core/convert/support/CharacterToNumberFactory.java
new file mode 100644
index 00000000..8f9642b0
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/CharacterToNumberFactory.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.core.convert.converter.ConverterFactory;
+import org.springframework.util.NumberUtils;
+
+/**
+ * Converts from a Character to any JDK-standard Number implementation.
+ *
+ * <p>Support Number classes including Byte, Short, Integer, Float, Double, Long, BigInteger, BigDecimal. This class
+ * delegates to {@link NumberUtils#convertNumberToTargetClass(Number, Class)} to perform the conversion.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ * @see java.lang.Byte
+ * @see java.lang.Short
+ * @see java.lang.Integer
+ * @see java.lang.Long
+ * @see java.math.BigInteger
+ * @see java.lang.Float
+ * @see java.lang.Double
+ * @see java.math.BigDecimal
+ * @see NumberUtils
+ */
+final class CharacterToNumberFactory implements ConverterFactory<Character, Number> {
+
+ public <T extends Number> Converter<Character, T> getConverter(Class<T> targetType) {
+ return new CharacterToNumber<T>(targetType);
+ }
+
+ private static final class CharacterToNumber<T extends Number> implements Converter<Character, T> {
+
+ private final Class<T> targetType;
+
+ public CharacterToNumber(Class<T> targetType) {
+ this.targetType = targetType;
+ }
+
+ public T convert(Character source) {
+ return NumberUtils.convertNumberToTargetClass((short) source.charValue(), this.targetType);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToArrayConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToArrayConverter.java
new file mode 100644
index 00000000..d616eeb0
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToArrayConverter.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+
+/**
+ * Converts a Collection to an array.
+ *
+ * <p>First, creates a new array of the requested targetType with a length equal to the
+ * size of the source Collection. Then sets each collection element into the array.
+ * Will perform an element conversion from the collection's parameterized type to the
+ * array's component type if necessary.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ */
+final class CollectionToArrayConverter implements ConditionalGenericConverter {
+
+ private final ConversionService conversionService;
+
+ public CollectionToArrayConverter(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return Collections.singleton(new ConvertiblePair(Collection.class, Object[].class));
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor(), this.conversionService);
+ }
+
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (source == null) {
+ return null;
+ }
+ Collection<?> sourceCollection = (Collection<?>) source;
+ Object array = Array.newInstance(targetType.getElementTypeDescriptor().getType(), sourceCollection.size());
+ int i = 0;
+ for (Object sourceElement : sourceCollection) {
+ Object targetElement = this.conversionService.convert(sourceElement, sourceType.elementTypeDescriptor(sourceElement), targetType.getElementTypeDescriptor());
+ Array.set(array, i++, targetElement);
+ }
+ return array;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java
new file mode 100644
index 00000000..f0dfd3d6
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+import org.springframework.core.CollectionFactory;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+
+/**
+ * Converts from a Collection to another Collection.
+ *
+ * <p>First, creates a new Collection of the requested targetType with a size equal to the
+ * size of the source Collection. Then copies each element in the source collection to the
+ * target collection. Will perform an element conversion from the source collection's
+ * parameterized type to the target collection's parameterized type if necessary.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+final class CollectionToCollectionConverter implements ConditionalGenericConverter {
+
+ private final ConversionService conversionService;
+
+
+ public CollectionToCollectionConverter(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return Collections.singleton(new ConvertiblePair(Collection.class, Collection.class));
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return ConversionUtils.canConvertElements(
+ sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor(), this.conversionService);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (source == null) {
+ return null;
+ }
+ Collection<?> sourceCollection = (Collection<?>) source;
+
+ // Shortcut if possible...
+ boolean copyRequired = !targetType.getType().isInstance(source);
+ if (!copyRequired && sourceCollection.isEmpty()) {
+ return source;
+ }
+ TypeDescriptor elementDesc = targetType.getElementTypeDescriptor();
+ if (elementDesc == null && !copyRequired) {
+ return source;
+ }
+
+ // At this point, we need a collection copy in any case, even if just for finding out about element copies...
+ Collection<Object> target = CollectionFactory.createCollection(targetType.getType(), sourceCollection.size());
+ if (elementDesc == null) {
+ target.addAll(sourceCollection);
+ }
+ else {
+ for (Object sourceElement : sourceCollection) {
+ Object targetElement = this.conversionService.convert(sourceElement,
+ sourceType.elementTypeDescriptor(sourceElement), elementDesc);
+ target.add(targetElement);
+ if (sourceElement != targetElement) {
+ copyRequired = true;
+ }
+ }
+ }
+
+ return (copyRequired ? target : source);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToObjectConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToObjectConverter.java
new file mode 100644
index 00000000..b9913ef6
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToObjectConverter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+
+/**
+ * Converts a Collection to an Object by returning the first collection element after converting it to the desired targetType.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ */
+final class CollectionToObjectConverter implements ConditionalGenericConverter {
+
+ private final ConversionService conversionService;
+
+ public CollectionToObjectConverter(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return Collections.singleton(new ConvertiblePair(Collection.class, Object.class));
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType, this.conversionService);
+ }
+
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (source == null) {
+ return null;
+ }
+ if (sourceType.isAssignableTo(targetType)) {
+ return source;
+ }
+ Collection<?> sourceCollection = (Collection<?>) source;
+ if (sourceCollection.size() == 0) {
+ return null;
+ }
+ Object firstElement = sourceCollection.iterator().next();
+ return this.conversionService.convert(firstElement, sourceType.elementTypeDescriptor(firstElement), targetType);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToStringConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToStringConverter.java
new file mode 100644
index 00000000..02b9d0eb
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/CollectionToStringConverter.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+
+/**
+ * Converts a Collection to a comma-delimited String.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ */
+final class CollectionToStringConverter implements ConditionalGenericConverter {
+
+ private static final String DELIMITER = ",";
+
+ private final ConversionService conversionService;
+
+ public CollectionToStringConverter(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return Collections.singleton(new ConvertiblePair(Collection.class, String.class));
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType, this.conversionService);
+ }
+
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (source == null) {
+ return null;
+ }
+ Collection<?> sourceCollection = (Collection<?>) source;
+ if (sourceCollection.size() == 0) {
+ return "";
+ }
+ StringBuilder sb = new StringBuilder();
+ int i = 0;
+ for (Object sourceElement : sourceCollection) {
+ if (i > 0) {
+ sb.append(DELIMITER);
+ }
+ Object targetElement = this.conversionService.convert(sourceElement, sourceType.elementTypeDescriptor(sourceElement), targetType);
+ sb.append(targetElement);
+ i++;
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ConfigurableConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/support/ConfigurableConversionService.java
new file mode 100644
index 00000000..a6123c0b
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/ConfigurableConversionService.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.converter.ConverterRegistry;
+
+/**
+ * Configuration interface to be implemented by most if not all {@link ConversionService}
+ * types. Consolidates the read-only operations exposed by {@link ConversionService} and
+ * the mutating operations of {@link ConverterRegistry} to allow for convenient ad-hoc
+ * addition and removal of {@link org.springframework.core.convert.converter.Converter
+ * Converters} through. The latter is particularly useful when working against a
+ * {@link org.springframework.core.env.ConfigurableEnvironment ConfigurableEnvironment}
+ * instance in application context bootstrapping code.
+ *
+ * @author Chris Beams
+ * @since 3.1
+ * @see org.springframework.core.env.ConfigurablePropertyResolver#getConversionService()
+ * @see org.springframework.core.env.ConfigurableEnvironment
+ * @see org.springframework.context.ConfigurableApplicationContext#getEnvironment()
+ */
+public interface ConfigurableConversionService extends ConversionService, ConverterRegistry {
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ConversionServiceFactory.java b/spring-core/src/main/java/org/springframework/core/convert/support/ConversionServiceFactory.java
new file mode 100644
index 00000000..8a45f037
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/ConversionServiceFactory.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.util.Set;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.core.convert.converter.ConverterFactory;
+import org.springframework.core.convert.converter.ConverterRegistry;
+import org.springframework.core.convert.converter.GenericConverter;
+
+/**
+ * A factory for common {@link org.springframework.core.convert.ConversionService}
+ * configurations.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @author Chris Beams
+ * @since 3.0
+ */
+public abstract class ConversionServiceFactory {
+
+ /**
+ * Register the given Converter objects with the given target ConverterRegistry.
+ * @param converters the converter objects: implementing {@link Converter},
+ * {@link ConverterFactory}, or {@link GenericConverter}
+ * @param registry the target registry
+ */
+ public static void registerConverters(Set<?> converters, ConverterRegistry registry) {
+ if (converters != null) {
+ for (Object converter : converters) {
+ if (converter instanceof GenericConverter) {
+ registry.addConverter((GenericConverter) converter);
+ }
+ else if (converter instanceof Converter<?, ?>) {
+ registry.addConverter((Converter<?, ?>) converter);
+ }
+ else if (converter instanceof ConverterFactory<?, ?>) {
+ registry.addConverterFactory((ConverterFactory<?, ?>) converter);
+ }
+ else {
+ throw new IllegalArgumentException("Each converter object must implement one of the " +
+ "Converter, ConverterFactory, or GenericConverter interfaces");
+ }
+ }
+ }
+ }
+
+ /**
+ * Create a new default GenericConversionService instance that can be safely modified.
+ * @deprecated in Spring 3.1 in favor of {@link DefaultConversionService#DefaultConversionService()}
+ */
+ @Deprecated
+ public static GenericConversionService createDefaultConversionService() {
+ return new DefaultConversionService();
+ }
+
+ /**
+ * Populate the given GenericConversionService instance with the set of default converters.
+ * @deprecated in Spring 3.1 in favor of {@link DefaultConversionService#addDefaultConverters(ConverterRegistry)}
+ */
+ @Deprecated
+ public static void addDefaultConverters(GenericConversionService conversionService) {
+ DefaultConversionService.addDefaultConverters(conversionService);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java b/spring-core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java
new file mode 100644
index 00000000..c4d41ec3
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import org.springframework.core.convert.ConversionFailedException;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.GenericConverter;
+
+/**
+ * Internal utilities for the conversion package.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ */
+abstract class ConversionUtils {
+
+ public static Object invokeConverter(GenericConverter converter, Object source, TypeDescriptor sourceType,
+ TypeDescriptor targetType) {
+ try {
+ return converter.convert(source, sourceType, targetType);
+ }
+ catch (ConversionFailedException ex) {
+ throw ex;
+ }
+ catch (Exception ex) {
+ throw new ConversionFailedException(sourceType, targetType, source, ex);
+ }
+ }
+
+ public static boolean canConvertElements(TypeDescriptor sourceElementType, TypeDescriptor targetElementType, ConversionService conversionService) {
+ if (targetElementType == null) {
+ // yes
+ return true;
+ }
+ if (sourceElementType == null) {
+ // maybe
+ return true;
+ }
+ if (conversionService.canConvert(sourceElementType, targetElementType)) {
+ // yes
+ return true;
+ }
+ else if (sourceElementType.getType().isAssignableFrom(targetElementType.getType())) {
+ // maybe;
+ return true;
+ }
+ else {
+ // no;
+ return false;
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ConvertingPropertyEditorAdapter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ConvertingPropertyEditorAdapter.java
new file mode 100644
index 00000000..47cadd66
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/ConvertingPropertyEditorAdapter.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.beans.PropertyEditorSupport;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.util.Assert;
+
+/**
+ * Adapter that exposes a {@link java.beans.PropertyEditor} for any given
+ * {@link org.springframework.core.convert.ConversionService} and specific target type.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class ConvertingPropertyEditorAdapter extends PropertyEditorSupport {
+
+ private final ConversionService conversionService;
+
+ private final TypeDescriptor targetDescriptor;
+
+ private final boolean canConvertToString;
+
+
+ /**
+ * Create a new ConvertingPropertyEditorAdapter for a given
+ * {@link org.springframework.core.convert.ConversionService}
+ * and the given target type.
+ * @param conversionService the ConversionService to delegate to
+ * @param targetDescriptor the target type to convert to
+ */
+ public ConvertingPropertyEditorAdapter(ConversionService conversionService, TypeDescriptor targetDescriptor) {
+ Assert.notNull(conversionService, "ConversionService must not be null");
+ Assert.notNull(targetDescriptor, "TypeDescriptor must not be null");
+ this.conversionService = conversionService;
+ this.targetDescriptor = targetDescriptor;
+ this.canConvertToString = conversionService.canConvert(this.targetDescriptor, TypeDescriptor.valueOf(String.class));
+ }
+
+
+ @Override
+ public void setAsText(String text) throws IllegalArgumentException {
+ setValue(this.conversionService.convert(text, TypeDescriptor.valueOf(String.class), this.targetDescriptor));
+ }
+
+ @Override
+ public String getAsText() {
+ if (this.canConvertToString) {
+ return (String) this.conversionService.convert(getValue(), this.targetDescriptor, TypeDescriptor.valueOf(String.class));
+ }
+ else {
+ return null;
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java
new file mode 100644
index 00000000..86e58098
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.util.Locale;
+import java.util.UUID;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.converter.ConverterRegistry;
+
+/**
+ * A specialization of {@link GenericConversionService} configured by default with
+ * converters appropriate for most environments.
+ *
+ * <p>Designed for direct instantiation but also exposes the static
+ * {@link #addDefaultConverters(ConverterRegistry)} utility method for ad hoc use against any
+ * {@code ConverterRegistry} instance.
+ *
+ * @author Chris Beams
+ * @since 3.1
+ */
+public class DefaultConversionService extends GenericConversionService {
+
+ /**
+ * Create a new {@code DefaultConversionService} with the set of
+ * {@linkplain DefaultConversionService#addDefaultConverters(ConverterRegistry) default converters}.
+ */
+ public DefaultConversionService() {
+ addDefaultConverters(this);
+ }
+
+ // static utility methods
+
+ /**
+ * Add converters appropriate for most environments.
+ * @param converterRegistry the registry of converters to add to (must also be castable to ConversionService)
+ * @throws ClassCastException if the converterRegistry could not be cast to a ConversionService
+ */
+ public static void addDefaultConverters(ConverterRegistry converterRegistry) {
+ addScalarConverters(converterRegistry);
+ addCollectionConverters(converterRegistry);
+ addFallbackConverters(converterRegistry);
+ }
+
+ // internal helpers
+
+ private static void addScalarConverters(ConverterRegistry converterRegistry) {
+ ConversionService conversionService = (ConversionService) converterRegistry;
+ converterRegistry.addConverter(new StringToBooleanConverter());
+ converterRegistry.addConverter(Boolean.class, String.class, new ObjectToStringConverter());
+
+ converterRegistry.addConverterFactory(new StringToNumberConverterFactory());
+ converterRegistry.addConverter(Number.class, String.class, new ObjectToStringConverter());
+
+ converterRegistry.addConverterFactory(new NumberToNumberConverterFactory());
+
+ converterRegistry.addConverter(new StringToCharacterConverter());
+ converterRegistry.addConverter(Character.class, String.class, new ObjectToStringConverter());
+
+ converterRegistry.addConverter(new NumberToCharacterConverter());
+ converterRegistry.addConverterFactory(new CharacterToNumberFactory());
+
+ converterRegistry.addConverterFactory(new StringToEnumConverterFactory());
+ converterRegistry.addConverter(Enum.class, String.class, new EnumToStringConverter(conversionService));
+
+ converterRegistry.addConverter(new StringToLocaleConverter());
+ converterRegistry.addConverter(Locale.class, String.class, new ObjectToStringConverter());
+
+ converterRegistry.addConverter(new PropertiesToStringConverter());
+ converterRegistry.addConverter(new StringToPropertiesConverter());
+
+ converterRegistry.addConverter(new StringToUUIDConverter());
+ converterRegistry.addConverter(UUID.class, String.class, new ObjectToStringConverter());
+ }
+
+ private static void addCollectionConverters(ConverterRegistry converterRegistry) {
+ ConversionService conversionService = (ConversionService) converterRegistry;
+ converterRegistry.addConverter(new ArrayToCollectionConverter(conversionService));
+ converterRegistry.addConverter(new CollectionToArrayConverter(conversionService));
+
+ converterRegistry.addConverter(new ArrayToArrayConverter(conversionService));
+ converterRegistry.addConverter(new CollectionToCollectionConverter(conversionService));
+ converterRegistry.addConverter(new MapToMapConverter(conversionService));
+
+ converterRegistry.addConverter(new ArrayToStringConverter(conversionService));
+ converterRegistry.addConverter(new StringToArrayConverter(conversionService));
+
+ converterRegistry.addConverter(new ArrayToObjectConverter(conversionService));
+ converterRegistry.addConverter(new ObjectToArrayConverter(conversionService));
+
+ converterRegistry.addConverter(new CollectionToStringConverter(conversionService));
+ converterRegistry.addConverter(new StringToCollectionConverter(conversionService));
+
+ converterRegistry.addConverter(new CollectionToObjectConverter(conversionService));
+ converterRegistry.addConverter(new ObjectToCollectionConverter(conversionService));
+ }
+
+ private static void addFallbackConverters(ConverterRegistry converterRegistry) {
+ ConversionService conversionService = (ConversionService) converterRegistry;
+ converterRegistry.addConverter(new ObjectToObjectConverter());
+ converterRegistry.addConverter(new IdToEntityConverter(conversionService));
+ converterRegistry.addConverter(new FallbackObjectToStringConverter());
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java
new file mode 100644
index 00000000..e391b75b
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalConverter;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Calls {@link Enum#name()} to convert a source Enum to a String. This converter will
+ * not match enums with interfaces that can be converterd.
+ * @author Keith Donald
+ * @author Phillip Webb
+ * @since 3.0
+ */
+final class EnumToStringConverter implements Converter<Enum<?>, String>, ConditionalConverter {
+
+ private final ConversionService conversionService;
+
+ public EnumToStringConverter(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ for (Class<?> interfaceType : ClassUtils.getAllInterfacesForClass(sourceType.getType())) {
+ if (conversionService.canConvert(TypeDescriptor.valueOf(interfaceType), targetType)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public String convert(Enum<?> source) {
+ return source.name();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.java
new file mode 100644
index 00000000..071ad3b6
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.core.convert.support;
+
+import java.io.StringWriter;
+import java.util.Collections;
+import java.util.Set;
+
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+
+/**
+ * Simply calls {@link Object#toString()} to convert any supported Object to a String.
+ * Supports CharSequence, StringWriter, and any class with a String constructor or {@code valueOf(String)} method.
+ *
+ * <p>Used by the default ConversionService as a fallback if there are no other explicit
+ * to-String converters registered.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+final class FallbackObjectToStringConverter implements ConditionalGenericConverter {
+
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return Collections.singleton(new ConvertiblePair(Object.class, String.class));
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ Class<?> sourceClass = sourceType.getObjectType();
+ if (String.class.equals(sourceClass)) {
+ return false;
+ }
+ return CharSequence.class.isAssignableFrom(sourceClass) || StringWriter.class.isAssignableFrom(sourceClass) ||
+ ObjectToObjectConverter.hasValueOfMethodOrConstructor(sourceClass, String.class);
+ }
+
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return (source != null ? source.toString() : null);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java
new file mode 100644
index 00000000..9f78f660
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java
@@ -0,0 +1,618 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.springframework.core.GenericTypeResolver;
+import org.springframework.core.convert.ConversionException;
+import org.springframework.core.convert.ConversionFailedException;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.ConverterNotFoundException;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalConverter;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.core.convert.converter.ConverterFactory;
+import org.springframework.core.convert.converter.ConverterRegistry;
+import org.springframework.core.convert.converter.GenericConverter;
+import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Base {@link ConversionService} implementation suitable for use in most environments.
+ * Indirectly implements {@link ConverterRegistry} as registration API through the
+ * {@link ConfigurableConversionService} interface.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @author Chris Beams
+ * @author Phillip Webb
+ * @since 3.0
+ */
+public class GenericConversionService implements ConfigurableConversionService {
+
+ /**
+ * General NO-OP converter used when conversion is not required.
+ */
+ private static final GenericConverter NO_OP_CONVERTER = new NoOpConverter("NO_OP");
+
+ /**
+ * Used as a cache entry when no converter is available. This converter is never
+ * returned.
+ */
+ private static final GenericConverter NO_MATCH = new NoOpConverter("NO_MATCH");
+
+
+ private final Converters converters = new Converters();
+
+ private final Map<ConverterCacheKey, GenericConverter> converterCache =
+ new ConcurrentHashMap<ConverterCacheKey, GenericConverter>(64);
+
+
+ // implementing ConverterRegistry
+
+ public void addConverter(Converter<?, ?> converter) {
+ GenericConverter.ConvertiblePair typeInfo = getRequiredTypeInfo(converter, Converter.class);
+ Assert.notNull(typeInfo, "Unable to the determine sourceType <S> and targetType " +
+ "<T> which your Converter<S, T> converts between; declare these generic types.");
+ addConverter(new ConverterAdapter(converter, typeInfo));
+ }
+
+ public void addConverter(Class<?> sourceType, Class<?> targetType, Converter<?, ?> converter) {
+ GenericConverter.ConvertiblePair typeInfo = new GenericConverter.ConvertiblePair(sourceType, targetType);
+ addConverter(new ConverterAdapter(converter, typeInfo));
+ }
+
+ public void addConverter(GenericConverter converter) {
+ this.converters.add(converter);
+ invalidateCache();
+ }
+
+ public void addConverterFactory(ConverterFactory<?, ?> converterFactory) {
+ GenericConverter.ConvertiblePair typeInfo = getRequiredTypeInfo(converterFactory, ConverterFactory.class);
+ if (typeInfo == null) {
+ throw new IllegalArgumentException("Unable to the determine sourceType <S> and " +
+ "targetRangeType R which your ConverterFactory<S, R> converts between; " +
+ "declare these generic types.");
+ }
+ addConverter(new ConverterFactoryAdapter(converterFactory, typeInfo));
+ }
+
+ public void removeConvertible(Class<?> sourceType, Class<?> targetType) {
+ this.converters.remove(sourceType, targetType);
+ invalidateCache();
+ }
+
+ // implementing ConversionService
+
+ public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
+ Assert.notNull(targetType, "targetType to convert to cannot be null");
+ return canConvert((sourceType != null ? TypeDescriptor.valueOf(sourceType) : null),
+ TypeDescriptor.valueOf(targetType));
+ }
+
+ public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ Assert.notNull(targetType, "targetType to convert to cannot be null");
+ if (sourceType == null) {
+ return true;
+ }
+ GenericConverter converter = getConverter(sourceType, targetType);
+ return (converter != null);
+ }
+
+ /**
+ * Returns true if conversion between the sourceType and targetType can be bypassed.
+ * More precisely this method will return true if objects of sourceType can be
+ * converted to the targetType by returning the source object unchanged.
+ * @param sourceType context about the source type to convert from (may be null if source is null)
+ * @param targetType context about the target type to convert to (required)
+ * @return true if conversion can be bypassed
+ * @throws IllegalArgumentException if targetType is null
+ * @since 3.2
+ */
+ public boolean canBypassConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ Assert.notNull(targetType, "The targetType to convert to cannot be null");
+ if (sourceType == null) {
+ return true;
+ }
+ GenericConverter converter = getConverter(sourceType, targetType);
+ return (converter == NO_OP_CONVERTER);
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T convert(Object source, Class<T> targetType) {
+ Assert.notNull(targetType,"The targetType to convert to cannot be null");
+ return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType));
+ }
+
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ Assert.notNull(targetType,"The targetType to convert to cannot be null");
+ if (sourceType == null) {
+ Assert.isTrue(source == null, "The source must be [null] if sourceType == [null]");
+ return handleResult(sourceType, targetType, convertNullSource(sourceType, targetType));
+ }
+ if (source != null && !sourceType.getObjectType().isInstance(source)) {
+ throw new IllegalArgumentException("The source to convert from must be an instance of " +
+ sourceType + "; instead it was a " + source.getClass().getName());
+ }
+ GenericConverter converter = getConverter(sourceType, targetType);
+ if (converter != null) {
+ Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
+ return handleResult(sourceType, targetType, result);
+ }
+ return handleConverterNotFound(source, sourceType, targetType);
+ }
+
+ /**
+ * Convenience operation for converting a source object to the specified targetType,
+ * where the targetType is a descriptor that provides additional conversion context.
+ * Simply delegates to {@link #convert(Object, TypeDescriptor, TypeDescriptor)} and
+ * encapsulates the construction of the sourceType descriptor using
+ * {@link TypeDescriptor#forObject(Object)}.
+ * @param source the source object
+ * @param targetType the target type
+ * @return the converted value
+ * @throws ConversionException if a conversion exception occurred
+ * @throws IllegalArgumentException if targetType is null,
+ * or sourceType is null but source is not null
+ */
+ public Object convert(Object source, TypeDescriptor targetType) {
+ return convert(source, TypeDescriptor.forObject(source), targetType);
+ }
+
+ @Override
+ public String toString() {
+ return this.converters.toString();
+ }
+
+
+ // Protected template methods
+
+ /**
+ * Template method to convert a null source.
+ * <p>Default implementation returns {@code null}.
+ * Subclasses may override to return custom null objects for specific target types.
+ * @param sourceType the sourceType to convert from
+ * @param targetType the targetType to convert to
+ * @return the converted null object
+ */
+ protected Object convertNullSource(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return null;
+ }
+
+ /**
+ * Hook method to lookup the converter for a given sourceType/targetType pair.
+ * First queries this ConversionService's converter cache.
+ * On a cache miss, then performs an exhaustive search for a matching converter.
+ * If no converter matches, returns the default converter.
+ * Subclasses may override.
+ * @param sourceType the source type to convert from
+ * @param targetType the target type to convert to
+ * @return the generic converter that will perform the conversion, or {@code null} if
+ * no suitable converter was found
+ * @see #getDefaultConverter(TypeDescriptor, TypeDescriptor)
+ */
+ protected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);
+ GenericConverter converter = this.converterCache.get(key);
+ if (converter != null) {
+ return (converter != NO_MATCH ? converter : null);
+ }
+
+ converter = this.converters.find(sourceType, targetType);
+ if (converter == null) {
+ converter = getDefaultConverter(sourceType, targetType);
+ }
+
+ if (converter != null) {
+ this.converterCache.put(key, converter);
+ return converter;
+ }
+
+ this.converterCache.put(key, NO_MATCH);
+ return null;
+ }
+
+ /**
+ * Return the default converter if no converter is found for the given sourceType/targetType pair.
+ * Returns a NO_OP Converter if the sourceType is assignable to the targetType.
+ * Returns {@code null} otherwise, indicating no suitable converter could be found.
+ * Subclasses may override.
+ * @param sourceType the source type to convert from
+ * @param targetType the target type to convert to
+ * @return the default generic converter that will perform the conversion
+ */
+ protected GenericConverter getDefaultConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return (sourceType.isAssignableTo(targetType) ? NO_OP_CONVERTER : null);
+ }
+
+ // internal helpers
+
+ private GenericConverter.ConvertiblePair getRequiredTypeInfo(Object converter, Class<?> genericIfc) {
+ Class<?>[] args = GenericTypeResolver.resolveTypeArguments(converter.getClass(), genericIfc);
+ return (args != null ? new GenericConverter.ConvertiblePair(args[0], args[1]) : null);
+ }
+
+ private void invalidateCache() {
+ this.converterCache.clear();
+ }
+
+ private Object handleConverterNotFound(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (source == null) {
+ assertNotPrimitiveTargetType(sourceType, targetType);
+ return source;
+ }
+ if (sourceType.isAssignableTo(targetType) && targetType.getObjectType().isInstance(source)) {
+ return source;
+ }
+ throw new ConverterNotFoundException(sourceType, targetType);
+ }
+
+ private Object handleResult(TypeDescriptor sourceType, TypeDescriptor targetType, Object result) {
+ if (result == null) {
+ assertNotPrimitiveTargetType(sourceType, targetType);
+ }
+ return result;
+ }
+
+ private void assertNotPrimitiveTargetType(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (targetType.isPrimitive()) {
+ throw new ConversionFailedException(sourceType, targetType, null,
+ new IllegalArgumentException("A null value cannot be assigned to a primitive type"));
+ }
+ }
+
+
+ /**
+ * Adapts a {@link Converter} to a {@link GenericConverter}.
+ */
+ @SuppressWarnings("unchecked")
+ private final class ConverterAdapter implements ConditionalGenericConverter {
+
+ private final Converter<Object, Object> converter;
+
+ private final ConvertiblePair typeInfo;
+
+ public ConverterAdapter(Converter<?, ?> converter, ConvertiblePair typeInfo) {
+ this.converter = (Converter<Object, Object>) converter;
+ this.typeInfo = typeInfo;
+ }
+
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return Collections.singleton(this.typeInfo);
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (!this.typeInfo.getTargetType().equals(targetType.getObjectType())) {
+ return false;
+ }
+ if (this.converter instanceof ConditionalConverter) {
+ return ((ConditionalConverter) this.converter).matches(sourceType, targetType);
+ }
+ return true;
+ }
+
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (source == null) {
+ return convertNullSource(sourceType, targetType);
+ }
+ return this.converter.convert(source);
+ }
+
+ @Override
+ public String toString() {
+ return this.typeInfo + " : " + this.converter;
+ }
+ }
+
+
+ /**
+ * Adapts a {@link ConverterFactory} to a {@link GenericConverter}.
+ */
+ @SuppressWarnings("unchecked")
+ private final class ConverterFactoryAdapter implements ConditionalGenericConverter {
+
+ private final ConverterFactory<Object, Object> converterFactory;
+
+ private final ConvertiblePair typeInfo;
+
+ public ConverterFactoryAdapter(ConverterFactory<?, ?> converterFactory, ConvertiblePair typeInfo) {
+ this.converterFactory = (ConverterFactory<Object, Object>) converterFactory;
+ this.typeInfo = typeInfo;
+ }
+
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return Collections.singleton(this.typeInfo);
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ boolean matches = true;
+ if (this.converterFactory instanceof ConditionalConverter) {
+ matches = ((ConditionalConverter) this.converterFactory).matches(sourceType, targetType);
+ }
+ if (matches) {
+ Converter<?, ?> converter = this.converterFactory.getConverter(targetType.getType());
+ if (converter instanceof ConditionalConverter) {
+ matches = ((ConditionalConverter) converter).matches(sourceType, targetType);
+ }
+ }
+ return matches;
+ }
+
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (source == null) {
+ return convertNullSource(sourceType, targetType);
+ }
+ return this.converterFactory.getConverter(targetType.getObjectType()).convert(source);
+ }
+
+ @Override
+ public String toString() {
+ return this.typeInfo + " : " + this.converterFactory;
+ }
+ }
+
+
+ /**
+ * Key for use with the converter cache.
+ */
+ private static final class ConverterCacheKey {
+
+ private final TypeDescriptor sourceType;
+
+ private final TypeDescriptor targetType;
+
+ public ConverterCacheKey(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ this.sourceType = sourceType;
+ this.targetType = targetType;
+ }
+
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof ConverterCacheKey)) {
+ return false;
+ }
+ ConverterCacheKey otherKey = (ConverterCacheKey) other;
+ return ObjectUtils.nullSafeEquals(this.sourceType, otherKey.sourceType) &&
+ ObjectUtils.nullSafeEquals(this.targetType, otherKey.targetType);
+ }
+
+ @Override
+ public int hashCode() {
+ return ObjectUtils.nullSafeHashCode(this.sourceType) * 29 +
+ ObjectUtils.nullSafeHashCode(this.targetType);
+ }
+
+ @Override
+ public String toString() {
+ return "ConverterCacheKey [sourceType = " + this.sourceType +
+ ", targetType = " + this.targetType + "]";
+ }
+ }
+
+
+ /**
+ * Manages all converters registered with the service.
+ */
+ private static class Converters {
+
+ private final Set<GenericConverter> globalConverters = new LinkedHashSet<GenericConverter>();
+
+ private final Map<ConvertiblePair, ConvertersForPair> converters =
+ new LinkedHashMap<ConvertiblePair, ConvertersForPair>(36);
+
+ public void add(GenericConverter converter) {
+ Set<ConvertiblePair> convertibleTypes = converter.getConvertibleTypes();
+ if (convertibleTypes == null) {
+ Assert.state(converter instanceof ConditionalConverter,
+ "Only conditional converters may return null convertible types");
+ this.globalConverters.add(converter);
+ }
+ else {
+ for (ConvertiblePair convertiblePair : convertibleTypes) {
+ ConvertersForPair convertersForPair = getMatchableConverters(convertiblePair);
+ convertersForPair.add(converter);
+ }
+ }
+ }
+
+ private ConvertersForPair getMatchableConverters(ConvertiblePair convertiblePair) {
+ ConvertersForPair convertersForPair = this.converters.get(convertiblePair);
+ if (convertersForPair == null) {
+ convertersForPair = new ConvertersForPair();
+ this.converters.put(convertiblePair, convertersForPair);
+ }
+ return convertersForPair;
+ }
+
+ public void remove(Class<?> sourceType, Class<?> targetType) {
+ this.converters.remove(new ConvertiblePair(sourceType, targetType));
+ }
+
+ /**
+ * Find a {@link GenericConverter} given a source and target type.
+ * <p>This method will attempt to match all possible converters by working
+ * through the class and interface hierarchy of the types.
+ * @param sourceType the source type
+ * @param targetType the target type
+ * @return a matching {@link GenericConverter}, or {@code null} if none found
+ */
+ public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ // Search the full type hierarchy
+ List<Class<?>> sourceCandidates = getClassHierarchy(sourceType.getType());
+ List<Class<?>> targetCandidates = getClassHierarchy(targetType.getType());
+ for (Class<?> sourceCandidate : sourceCandidates) {
+ for (Class<?> targetCandidate : targetCandidates) {
+ ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate);
+ GenericConverter converter = getRegisteredConverter(sourceType, targetType, convertiblePair);
+ if (converter != null) {
+ return converter;
+ }
+ }
+ }
+ return null;
+ }
+
+ private GenericConverter getRegisteredConverter(TypeDescriptor sourceType,
+ TypeDescriptor targetType, ConvertiblePair convertiblePair) {
+
+ // Check specifically registered converters
+ ConvertersForPair convertersForPair = this.converters.get(convertiblePair);
+ if (convertersForPair != null) {
+ GenericConverter converter = convertersForPair.getConverter(sourceType, targetType);
+ if (converter != null) {
+ return converter;
+ }
+ }
+ // Check ConditionalGenericConverter that match all types
+ for (GenericConverter globalConverter : this.globalConverters) {
+ if (((ConditionalConverter)globalConverter).matches(sourceType, targetType)) {
+ return globalConverter;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns an ordered class hierarchy for the given type.
+ * @param type the type
+ * @return an ordered list of all classes that the given type extends or implements
+ */
+ private List<Class<?>> getClassHierarchy(Class<?> type) {
+ List<Class<?>> hierarchy = new ArrayList<Class<?>>(20);
+ Set<Class<?>> visited = new HashSet<Class<?>>(20);
+ addToClassHierarchy(0, ClassUtils.resolvePrimitiveIfNecessary(type), false, hierarchy, visited);
+ boolean array = type.isArray();
+ int i = 0;
+ while (i < hierarchy.size()) {
+ Class<?> candidate = hierarchy.get(i);
+ candidate = (array ? candidate.getComponentType() : ClassUtils.resolvePrimitiveIfNecessary(candidate));
+ Class<?> superclass = candidate.getSuperclass();
+ if (candidate.getSuperclass() != null && superclass != Object.class) {
+ addToClassHierarchy(i + 1, candidate.getSuperclass(), array, hierarchy, visited);
+ }
+ for (Class<?> implementedInterface : candidate.getInterfaces()) {
+ addToClassHierarchy(hierarchy.size(), implementedInterface, array, hierarchy, visited);
+ }
+ i++;
+ }
+ addToClassHierarchy(hierarchy.size(), Object.class, array, hierarchy, visited);
+ addToClassHierarchy(hierarchy.size(), Object.class, false, hierarchy, visited);
+ return hierarchy;
+ }
+
+ private void addToClassHierarchy(int index, Class<?> type, boolean asArray,
+ List<Class<?>> hierarchy, Set<Class<?>> visited) {
+ if (asArray) {
+ type = Array.newInstance(type, 0).getClass();
+ }
+ if (visited.add(type)) {
+ hierarchy.add(index, type);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("ConversionService converters =\n");
+ for (String converterString : getConverterStrings()) {
+ builder.append('\t').append(converterString).append('\n');
+ }
+ return builder.toString();
+ }
+
+ private List<String> getConverterStrings() {
+ List<String> converterStrings = new ArrayList<String>();
+ for (ConvertersForPair convertersForPair : converters.values()) {
+ converterStrings.add(convertersForPair.toString());
+ }
+ Collections.sort(converterStrings);
+ return converterStrings;
+ }
+ }
+
+
+ /**
+ * Manages converters registered with a specific {@link ConvertiblePair}.
+ */
+ private static class ConvertersForPair {
+
+ private final LinkedList<GenericConverter> converters = new LinkedList<GenericConverter>();
+
+ public void add(GenericConverter converter) {
+ this.converters.addFirst(converter);
+ }
+
+ public GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ for (GenericConverter converter : this.converters) {
+ if (!(converter instanceof ConditionalGenericConverter) ||
+ ((ConditionalGenericConverter) converter).matches(sourceType, targetType)) {
+ return converter;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return StringUtils.collectionToCommaDelimitedString(this.converters);
+ }
+ }
+
+
+ /**
+ * Internal converter that performs no operation.
+ */
+ private static class NoOpConverter implements GenericConverter {
+
+ private final String name;
+
+ public NoOpConverter(String name) {
+ this.name = name;
+ }
+
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return null;
+ }
+
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return source;
+ }
+
+ @Override
+ public String toString() {
+ return this.name;
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/IdToEntityConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/IdToEntityConverter.java
new file mode 100644
index 00000000..da30e0d3
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/IdToEntityConverter.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.Set;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * Converts an entity identifier to a entity reference by calling a static finder method
+ * on the target entity type.
+ *
+ * <p>For this converter to match, the finder method must be static, have the signature
+ * {@code find[EntityName]([IdType])}, and return an instance of the desired entity type.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+final class IdToEntityConverter implements ConditionalGenericConverter {
+
+ private final ConversionService conversionService;
+
+
+ public IdToEntityConverter(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return Collections.singleton(new ConvertiblePair(Object.class, Object.class));
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ Method finder = getFinder(targetType.getType());
+ return (finder != null &&
+ this.conversionService.canConvert(sourceType, TypeDescriptor.valueOf(finder.getParameterTypes()[0])));
+ }
+
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (source == null) {
+ return null;
+ }
+ Method finder = getFinder(targetType.getType());
+ Object id = this.conversionService.convert(
+ source, sourceType, TypeDescriptor.valueOf(finder.getParameterTypes()[0]));
+ return ReflectionUtils.invokeMethod(finder, source, id);
+ }
+
+
+ private Method getFinder(Class<?> entityClass) {
+ String finderMethod = "find" + getEntityName(entityClass);
+ Method[] methods;
+ boolean localOnlyFiltered;
+ try {
+ methods = entityClass.getDeclaredMethods();
+ localOnlyFiltered = true;
+ }
+ catch (SecurityException ex) {
+ // Not allowed to access non-public methods...
+ // Fallback: check locally declared public methods only.
+ methods = entityClass.getMethods();
+ localOnlyFiltered = false;
+ }
+ for (Method method : methods) {
+ if (Modifier.isStatic(method.getModifiers()) && method.getName().equals(finderMethod) &&
+ method.getParameterTypes().length == 1 && method.getReturnType().equals(entityClass) &&
+ (localOnlyFiltered || method.getDeclaringClass().equals(entityClass))) {
+ return method;
+ }
+ }
+ return null;
+ }
+
+ private String getEntityName(Class<?> entityClass) {
+ String shortName = ClassUtils.getShortName(entityClass);
+ int lastDot = shortName.lastIndexOf('.');
+ if (lastDot != -1) {
+ return shortName.substring(lastDot + 1);
+ }
+ else {
+ return shortName;
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/MapToMapConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/MapToMapConverter.java
new file mode 100644
index 00000000..c9f7f9a5
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/MapToMapConverter.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.core.CollectionFactory;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+
+/**
+ * Converts a Map to another Map.
+ *
+ * <p>First, creates a new Map of the requested targetType with a size equal to the
+ * size of the source Map. Then copies each element in the source map to the target map.
+ * Will perform a conversion from the source maps's parameterized K,V types to the target
+ * map's parameterized types K,V if necessary.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ */
+final class MapToMapConverter implements ConditionalGenericConverter {
+
+ private final ConversionService conversionService;
+
+
+ public MapToMapConverter(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return Collections.singleton(new ConvertiblePair(Map.class, Map.class));
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return canConvertKey(sourceType, targetType) && canConvertValue(sourceType, targetType);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (source == null) {
+ return null;
+ }
+ boolean copyRequired = !targetType.getType().isInstance(source);
+ Map<Object, Object> sourceMap = (Map<Object, Object>) source;
+ if (!copyRequired && sourceMap.isEmpty()) {
+ return sourceMap;
+ }
+ List<MapEntry> targetEntries = new ArrayList<MapEntry>(sourceMap.size());
+ for (Map.Entry<Object, Object> entry : sourceMap.entrySet()) {
+ Object sourceKey = entry.getKey();
+ Object sourceValue = entry.getValue();
+ Object targetKey = convertKey(sourceKey, sourceType, targetType.getMapKeyTypeDescriptor());
+ Object targetValue = convertValue(sourceValue, sourceType, targetType.getMapValueTypeDescriptor());
+ targetEntries.add(new MapEntry(targetKey, targetValue));
+ if (sourceKey != targetKey || sourceValue != targetValue) {
+ copyRequired = true;
+ }
+ }
+ if (!copyRequired) {
+ return sourceMap;
+ }
+ Map<Object, Object> targetMap = CollectionFactory.createMap(targetType.getType(), sourceMap.size());
+ for (MapEntry entry : targetEntries) {
+ entry.addToMap(targetMap);
+ }
+ return targetMap;
+ }
+
+
+ // internal helpers
+
+ private boolean canConvertKey(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return ConversionUtils.canConvertElements(sourceType.getMapKeyTypeDescriptor(),
+ targetType.getMapKeyTypeDescriptor(), this.conversionService);
+ }
+
+ private boolean canConvertValue(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return ConversionUtils.canConvertElements(sourceType.getMapValueTypeDescriptor(),
+ targetType.getMapValueTypeDescriptor(), this.conversionService);
+ }
+
+ private Object convertKey(Object sourceKey, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (targetType == null) {
+ return sourceKey;
+ }
+ return this.conversionService.convert(sourceKey, sourceType.getMapKeyTypeDescriptor(sourceKey), targetType);
+ }
+
+ private Object convertValue(Object sourceValue, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (targetType == null) {
+ return sourceValue;
+ }
+ return this.conversionService.convert(sourceValue, sourceType.getMapValueTypeDescriptor(sourceValue), targetType);
+ }
+
+
+ private static class MapEntry {
+
+ private final Object key;
+
+ private final Object value;
+
+ public MapEntry(Object key, Object value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ public void addToMap(Map<Object, Object> map) {
+ map.put(this.key, this.value);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/NumberToCharacterConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/NumberToCharacterConverter.java
new file mode 100644
index 00000000..b272abc6
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/NumberToCharacterConverter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import org.springframework.core.convert.converter.Converter;
+
+/**
+ * Converts from any JDK-standard Number implementation to a Character.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ * @see java.lang.Character
+ * @see java.lang.Short
+ * @see java.lang.Integer
+ * @see java.lang.Long
+ * @see java.math.BigInteger
+ * @see java.lang.Float
+ * @see java.lang.Double
+ * @see java.math.BigDecimal
+ */
+final class NumberToCharacterConverter implements Converter<Number, Character> {
+
+ public Character convert(Number source) {
+ return (char) source.shortValue();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/NumberToNumberConverterFactory.java b/spring-core/src/main/java/org/springframework/core/convert/support/NumberToNumberConverterFactory.java
new file mode 100644
index 00000000..240880d6
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/NumberToNumberConverterFactory.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalConverter;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.core.convert.converter.ConverterFactory;
+import org.springframework.util.NumberUtils;
+
+/**
+ * Converts from any JDK-standard Number implementation to any other JDK-standard Number implementation.
+ *
+ * <p>Support Number classes including Byte, Short, Integer, Float, Double, Long, BigInteger, BigDecimal. This class
+ * delegates to {@link NumberUtils#convertNumberToTargetClass(Number, Class)} to perform the conversion.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ * @see java.lang.Byte
+ * @see java.lang.Short
+ * @see java.lang.Integer
+ * @see java.lang.Long
+ * @see java.math.BigInteger
+ * @see java.lang.Float
+ * @see java.lang.Double
+ * @see java.math.BigDecimal
+ * @see NumberUtils
+ */
+final class NumberToNumberConverterFactory implements ConverterFactory<Number, Number>,
+ ConditionalConverter {
+
+ public <T extends Number> Converter<Number, T> getConverter(Class<T> targetType) {
+ return new NumberToNumber<T>(targetType);
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return !sourceType.equals(targetType);
+ }
+
+ private final static class NumberToNumber<T extends Number> implements Converter<Number, T> {
+
+ private final Class<T> targetType;
+
+ public NumberToNumber(Class<T> targetType) {
+ this.targetType = targetType;
+ }
+
+ public T convert(Number source) {
+ return NumberUtils.convertNumberToTargetClass(source, this.targetType);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToArrayConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToArrayConverter.java
new file mode 100644
index 00000000..2e8d850d
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToArrayConverter.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.lang.reflect.Array;
+import java.util.Collections;
+import java.util.Set;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+
+/**
+ * Converts an Object to a single-element Array containing the Object.
+ * Will convert the Object to the target Array's component type if necessary.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ */
+final class ObjectToArrayConverter implements ConditionalGenericConverter {
+
+ private final ConversionService conversionService;
+
+ public ObjectToArrayConverter(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return Collections.singleton(new ConvertiblePair(Object.class, Object[].class));
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return ConversionUtils.canConvertElements(sourceType, targetType.getElementTypeDescriptor(), this.conversionService);
+ }
+
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (source == null) {
+ return null;
+ }
+ Object target = Array.newInstance(targetType.getElementTypeDescriptor().getType(), 1);
+ Object targetElement = this.conversionService.convert(source, sourceType, targetType.getElementTypeDescriptor());
+ Array.set(target, 0, targetElement);
+ return target;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToCollectionConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToCollectionConverter.java
new file mode 100644
index 00000000..7741128b
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToCollectionConverter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+import org.springframework.core.CollectionFactory;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+
+/**
+ * Converts an Object to a single-element Collection containing the Object.
+ * Will convert the Object to the target Collection's parameterized type if necessary.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+final class ObjectToCollectionConverter implements ConditionalGenericConverter {
+
+ private final ConversionService conversionService;
+
+ public ObjectToCollectionConverter(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return Collections.singleton(new ConvertiblePair(Object.class, Collection.class));
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return ConversionUtils.canConvertElements(sourceType, targetType.getElementTypeDescriptor(), this.conversionService);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (source == null) {
+ return null;
+ }
+ Collection<Object> target = CollectionFactory.createCollection(targetType.getType(), 1);
+ if (targetType.getElementTypeDescriptor() == null || targetType.getElementTypeDescriptor().isCollection()) {
+ target.add(source);
+ }
+ else {
+ Object singleElement = this.conversionService.convert(source, sourceType, targetType.getElementTypeDescriptor());
+ target.add(singleElement);
+ }
+ return target;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java
new file mode 100644
index 00000000..0448e582
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Set;
+
+import org.springframework.core.convert.ConversionFailedException;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * Generic Converter that attempts to convert a source Object to a target type
+ * by delegating to methods on the target type.
+ *
+ * <p>Calls the static {@code valueOf(sourceType)} method on the target type
+ * to perform the conversion, if such a method exists. Else calls the target type's
+ * Constructor that accepts a single sourceType argument, if such a Constructor exists.
+ * Else throws a ConversionFailedException.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+final class ObjectToObjectConverter implements ConditionalGenericConverter {
+
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return Collections.singleton(new ConvertiblePair(Object.class, Object.class));
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (sourceType.getType().equals(targetType.getType())) {
+ // no conversion required
+ return false;
+ }
+ return hasValueOfMethodOrConstructor(targetType.getType(), sourceType.getType());
+ }
+
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (source == null) {
+ return null;
+ }
+ Class<?> sourceClass = sourceType.getType();
+ Class<?> targetClass = targetType.getType();
+ Method method = getValueOfMethodOn(targetClass, sourceClass);
+ try {
+ if (method != null) {
+ ReflectionUtils.makeAccessible(method);
+ return method.invoke(null, source);
+ }
+ else {
+ Constructor<?> constructor = getConstructor(targetClass, sourceClass);
+ if (constructor != null) {
+ return constructor.newInstance(source);
+ }
+ }
+ }
+ catch (InvocationTargetException ex) {
+ throw new ConversionFailedException(sourceType, targetType, source, ex.getTargetException());
+ }
+ catch (Throwable ex) {
+ throw new ConversionFailedException(sourceType, targetType, source, ex);
+ }
+ throw new IllegalStateException("No static valueOf(" + sourceClass.getName() +
+ ") method or Constructor(" + sourceClass.getName() + ") exists on " + targetClass.getName());
+ }
+
+ static boolean hasValueOfMethodOrConstructor(Class<?> clazz, Class<?> sourceParameterType) {
+ return getValueOfMethodOn(clazz, sourceParameterType) != null || getConstructor(clazz, sourceParameterType) != null;
+ }
+
+ private static Method getValueOfMethodOn(Class<?> clazz, Class<?> sourceParameterType) {
+ return ClassUtils.getStaticMethod(clazz, "valueOf", sourceParameterType);
+ }
+
+ private static Constructor<?> getConstructor(Class<?> clazz, Class<?> sourceParameterType) {
+ return ClassUtils.getConstructorIfAvailable(clazz, sourceParameterType);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToStringConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToStringConverter.java
new file mode 100644
index 00000000..420183b3
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToStringConverter.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import org.springframework.core.convert.converter.Converter;
+
+/**
+ * Simply calls {@link Object#toString()} to convert a source Object to a String.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ */
+final class ObjectToStringConverter implements Converter<Object, String> {
+
+ public String convert(Object source) {
+ return source.toString();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/PropertiesToStringConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/PropertiesToStringConverter.java
new file mode 100644
index 00000000..f4962d01
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/PropertiesToStringConverter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+import org.springframework.core.convert.converter.Converter;
+
+/**
+ * Converts from a Properties to a String by calling {@link Properties#store(java.io.OutputStream, String)}.
+ * Decodes with the ISO-8859-1 charset before returning the String.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ */
+final class PropertiesToStringConverter implements Converter<Properties, String> {
+
+ public String convert(Properties source) {
+ try {
+ ByteArrayOutputStream os = new ByteArrayOutputStream(256);
+ source.store(os, null);
+ return os.toString("ISO-8859-1");
+ }
+ catch (IOException ex) {
+ // Should never happen.
+ throw new IllegalArgumentException("Failed to store [" + source + "] into String", ex);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToArrayConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToArrayConverter.java
new file mode 100644
index 00000000..4bf32af3
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToArrayConverter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.lang.reflect.Array;
+import java.util.Collections;
+import java.util.Set;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+import org.springframework.util.StringUtils;
+
+/**
+ * Converts a comma-delimited String to an Array.
+ * Only matches if String.class can be converted to the target array element type.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ */
+final class StringToArrayConverter implements ConditionalGenericConverter {
+
+ private final ConversionService conversionService;
+
+ public StringToArrayConverter(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return Collections.singleton(new ConvertiblePair(String.class, Object[].class));
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor());
+ }
+
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (source == null) {
+ return null;
+ }
+ String string = (String) source;
+ String[] fields = StringUtils.commaDelimitedListToStringArray(string);
+ Object target = Array.newInstance(targetType.getElementTypeDescriptor().getType(), fields.length);
+ for (int i = 0; i < fields.length; i++) {
+ String sourceElement = fields[i];
+ Object targetElement = this.conversionService.convert(sourceElement.trim(), sourceType, targetType.getElementTypeDescriptor());
+ Array.set(target, i, targetElement);
+ }
+ return target;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToBooleanConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToBooleanConverter.java
new file mode 100644
index 00000000..1e23ee61
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToBooleanConverter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.springframework.core.convert.converter.Converter;
+
+/**
+ * Converts String to a Boolean.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+final class StringToBooleanConverter implements Converter<String, Boolean> {
+
+ private static final Set<String> trueValues = new HashSet<String>(4);
+
+ private static final Set<String> falseValues = new HashSet<String>(4);
+
+ static {
+ trueValues.add("true");
+ trueValues.add("on");
+ trueValues.add("yes");
+ trueValues.add("1");
+
+ falseValues.add("false");
+ falseValues.add("off");
+ falseValues.add("no");
+ falseValues.add("0");
+ }
+
+ public Boolean convert(String source) {
+ String value = source.trim();
+ if ("".equals(value)) {
+ return null;
+ }
+ value = value.toLowerCase();
+ if (trueValues.contains(value)) {
+ return Boolean.TRUE;
+ }
+ else if (falseValues.contains(value)) {
+ return Boolean.FALSE;
+ }
+ else {
+ throw new IllegalArgumentException("Invalid boolean value '" + source + "'");
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToCharacterConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToCharacterConverter.java
new file mode 100644
index 00000000..3e379164
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToCharacterConverter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import org.springframework.core.convert.converter.Converter;
+
+/**
+ * Converts a String to a Character.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ */
+final class StringToCharacterConverter implements Converter<String, Character> {
+
+ public Character convert(String source) {
+ if (source.length() == 0) {
+ return null;
+ }
+ if (source.length() > 1) {
+ throw new IllegalArgumentException(
+ "Can only convert a [String] with length of 1 to a [Character]; string value '" + source + "' has length of " + source.length());
+ }
+ return source.charAt(0);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToCollectionConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToCollectionConverter.java
new file mode 100644
index 00000000..74f912c4
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToCollectionConverter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+import org.springframework.core.CollectionFactory;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalGenericConverter;
+import org.springframework.util.StringUtils;
+
+/**
+ * Converts a comma-delimited String to a Collection.
+ * If the target collection element type is declared, only matches if
+ * {@code String.class} can be converted to it.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ */
+final class StringToCollectionConverter implements ConditionalGenericConverter {
+
+ private final ConversionService conversionService;
+
+
+ public StringToCollectionConverter(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return Collections.singleton(new ConvertiblePair(String.class, Collection.class));
+ }
+
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ return (targetType.getElementTypeDescriptor() == null ||
+ this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor()));
+ }
+
+ @SuppressWarnings("unchecked")
+ public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (source == null) {
+ return null;
+ }
+ String string = (String) source;
+ String[] fields = StringUtils.commaDelimitedListToStringArray(string);
+ Collection<Object> target = CollectionFactory.createCollection(targetType.getType(), fields.length);
+ if (targetType.getElementTypeDescriptor() == null) {
+ for (String field : fields) {
+ target.add(field.trim());
+ }
+ }
+ else {
+ for (String field : fields) {
+ Object targetElement = this.conversionService.convert(field.trim(), sourceType, targetType.getElementTypeDescriptor());
+ target.add(targetElement);
+ }
+ }
+ return target;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java
new file mode 100644
index 00000000..d22ed96b
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2002-2014 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.core.convert.converter.ConverterFactory;
+
+/**
+ * Converts from a String to a java.lang.Enum by calling {@link Enum#valueOf(Class, String)}.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ */
+@SuppressWarnings({ "unchecked", "rawtypes" })
+final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
+
+ public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
+ Class<?> enumType = targetType;
+ while (enumType != null && !enumType.isEnum()) {
+ enumType = enumType.getSuperclass();
+ }
+ if (enumType == null) {
+ throw new IllegalArgumentException("The target type " + targetType.getName() + " does not refer to an enum");
+ }
+ return new StringToEnum(enumType);
+ }
+
+
+ private class StringToEnum<T extends Enum> implements Converter<String, T> {
+
+ private final Class<T> enumType;
+
+ public StringToEnum(Class<T> enumType) {
+ this.enumType = enumType;
+ }
+
+ public T convert(String source) {
+ if (source.length() == 0) {
+ // It's an empty enum identifier: reset the enum value to null.
+ return null;
+ }
+ return (T) Enum.valueOf(this.enumType, source.trim());
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToLocaleConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToLocaleConverter.java
new file mode 100644
index 00000000..3ca91385
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToLocaleConverter.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.util.Locale;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.util.StringUtils;
+
+/**
+ * Converts a String to a Locale.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ */
+final class StringToLocaleConverter implements Converter<String, Locale> {
+
+ public Locale convert(String source) {
+ return StringUtils.parseLocaleString(source);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToNumberConverterFactory.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToNumberConverterFactory.java
new file mode 100644
index 00000000..8bb0d646
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToNumberConverterFactory.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.core.convert.converter.ConverterFactory;
+import org.springframework.util.NumberUtils;
+
+/**
+ * Converts from a String any JDK-standard Number implementation.
+ *
+ * <p>Support Number classes including Byte, Short, Integer, Float, Double, Long, BigInteger, BigDecimal. This class
+ * delegates to {@link NumberUtils#parseNumber(String, Class)} to perform the conversion.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ * @see java.lang.Byte
+ * @see java.lang.Short
+ * @see java.lang.Integer
+ * @see java.lang.Long
+ * @see java.math.BigInteger
+ * @see java.lang.Float
+ * @see java.lang.Double
+ * @see java.math.BigDecimal
+ * @see NumberUtils
+ */
+final class StringToNumberConverterFactory implements ConverterFactory<String, Number> {
+
+ public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
+ return new StringToNumber<T>(targetType);
+ }
+
+ private static final class StringToNumber<T extends Number> implements Converter<String, T> {
+
+ private final Class<T> targetType;
+
+ public StringToNumber(Class<T> targetType) {
+ this.targetType = targetType;
+ }
+
+ public T convert(String source) {
+ if (source.length() == 0) {
+ return null;
+ }
+ return NumberUtils.parseNumber(source, this.targetType);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToPropertiesConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToPropertiesConverter.java
new file mode 100644
index 00000000..e97cae67
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToPropertiesConverter.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.io.ByteArrayInputStream;
+import java.util.Properties;
+
+import org.springframework.core.convert.converter.Converter;
+
+/**
+ * Converts a String to a Properties by calling Properties#load(java.io.InputStream).
+ * Uses ISO-8559-1 encoding required by Properties.
+ *
+ * @author Keith Donald
+ * @since 3.0
+ */
+final class StringToPropertiesConverter implements Converter<String, Properties> {
+
+ public Properties convert(String source) {
+ try {
+ Properties props = new Properties();
+ // Must use the ISO-8859-1 encoding because Properties.load(stream) expects it.
+ props.load(new ByteArrayInputStream(source.getBytes("ISO-8859-1")));
+ return props;
+ }
+ catch (Exception ex) {
+ // Should never happen.
+ throw new IllegalArgumentException("Failed to parse [" + source + "] into Properties", ex);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToUUIDConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToUUIDConverter.java
new file mode 100644
index 00000000..db5a3ebf
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToUUIDConverter.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import java.util.UUID;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.util.StringUtils;
+
+/**
+ * Converts from a String to a java.util.UUID by calling {@link UUID#fromString(String)}.
+ *
+ * @author Phillip Webb
+ * @since 3.2
+ */
+final class StringToUUIDConverter implements Converter<String, UUID> {
+
+ public UUID convert(String source) {
+ if (StringUtils.hasLength(source)) {
+ return UUID.fromString(source.trim());
+ }
+ return null;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/package-info.java b/spring-core/src/main/java/org/springframework/core/convert/support/package-info.java
new file mode 100644
index 00000000..3c0c0ded
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/package-info.java
@@ -0,0 +1,8 @@
+
+/**
+ *
+ * Default implementation of the type conversion system.
+ *
+ */
+package org.springframework.core.convert.support;
+
diff --git a/spring-core/src/main/java/org/springframework/core/enums/AbstractCachingLabeledEnumResolver.java b/spring-core/src/main/java/org/springframework/core/enums/AbstractCachingLabeledEnumResolver.java
new file mode 100644
index 00000000..4a278f92
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/enums/AbstractCachingLabeledEnumResolver.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2002-2012 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.enums;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.util.Assert;
+import org.springframework.util.CachingMapDecorator;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Abstract base class for {@link LabeledEnumResolver} implementations,
+ * caching all retrieved {@link LabeledEnum} instances.
+ *
+ * <p>Subclasses need to implement the template method
+ * {@link #findLabeledEnums(Class)}.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ * @see #findLabeledEnums(Class)
+ * @deprecated as of Spring 3.0, in favor of Java 5 enums.
+ */
+@Deprecated
+public abstract class AbstractCachingLabeledEnumResolver implements LabeledEnumResolver {
+
+ protected transient final Log logger = LogFactory.getLog(getClass());
+
+ private final LabeledEnumCache labeledEnumCache = new LabeledEnumCache();
+
+
+ public Set<LabeledEnum> getLabeledEnumSet(Class type) throws IllegalArgumentException {
+ return new TreeSet<LabeledEnum>(getLabeledEnumMap(type).values());
+ }
+
+ public Map<Comparable, LabeledEnum> getLabeledEnumMap(Class type) throws IllegalArgumentException {
+ Assert.notNull(type, "No type specified");
+ return this.labeledEnumCache.get(type);
+ }
+
+ public LabeledEnum getLabeledEnumByCode(Class type, Comparable code) throws IllegalArgumentException {
+ Assert.notNull(code, "No enum code specified");
+ Map<Comparable, LabeledEnum> typeEnums = getLabeledEnumMap(type);
+ LabeledEnum codedEnum = typeEnums.get(code);
+ if (codedEnum == null) {
+ throw new IllegalArgumentException(
+ "No enumeration with code '" + code + "'" + " of type [" + type.getName() +
+ "] exists: this is likely a configuration error. " +
+ "Make sure the code value matches a valid instance's code property!");
+ }
+ return codedEnum;
+ }
+
+ public LabeledEnum getLabeledEnumByLabel(Class type, String label) throws IllegalArgumentException {
+ Map<Comparable, LabeledEnum> typeEnums = getLabeledEnumMap(type);
+ for (LabeledEnum value : typeEnums.values()) {
+ if (value.getLabel().equalsIgnoreCase(label)) {
+ return value;
+ }
+ }
+ throw new IllegalArgumentException(
+ "No enumeration with label '" + label + "' of type [" + type +
+ "] exists: this is likely a configuration error. " +
+ "Make sure the label string matches a valid instance's label property!");
+ }
+
+
+ /**
+ * Template method to be implemented by subclasses.
+ * Supposed to find all LabeledEnum instances for the given type.
+ * @param type the enum type
+ * @return the Set of LabeledEnum instances
+ * @see org.springframework.core.enums.LabeledEnum
+ */
+ protected abstract Set<LabeledEnum> findLabeledEnums(Class type);
+
+
+ /**
+ * Inner cache class that implements lazy building of LabeledEnum Maps.
+ */
+ @SuppressWarnings("serial")
+ private class LabeledEnumCache extends CachingMapDecorator<Class, Map<Comparable, LabeledEnum>> {
+
+ public LabeledEnumCache() {
+ super(true);
+ }
+
+ @Override
+ protected Map<Comparable, LabeledEnum> create(Class key) {
+ Set<LabeledEnum> typeEnums = findLabeledEnums(key);
+ if (typeEnums == null || typeEnums.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Unsupported labeled enumeration type '" + key + "': " +
+ "make sure you've properly defined this enumeration! " +
+ "If it is static, are the class and its fields public/static/final?");
+ }
+ Map<Comparable, LabeledEnum> typeEnumMap = new HashMap<Comparable, LabeledEnum>(typeEnums.size());
+ for (LabeledEnum labeledEnum : typeEnums) {
+ typeEnumMap.put(labeledEnum.getCode(), labeledEnum);
+ }
+ return Collections.unmodifiableMap(typeEnumMap);
+ }
+
+ @Override
+ protected boolean useWeakValue(Class key, Map<Comparable, LabeledEnum> value) {
+ if (!ClassUtils.isCacheSafe(key, AbstractCachingLabeledEnumResolver.this.getClass().getClassLoader())) {
+ if (logger != null && logger.isDebugEnabled()) {
+ logger.debug("Not strongly caching class [" + key.getName() + "] because it is not cache-safe");
+ }
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/enums/AbstractGenericLabeledEnum.java b/spring-core/src/main/java/org/springframework/core/enums/AbstractGenericLabeledEnum.java
new file mode 100644
index 00000000..cee9535c
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/enums/AbstractGenericLabeledEnum.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2012 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.enums;
+
+/**
+ * Base class for labeled enum instances that aren't static.
+ *
+ * @author Keith Donald
+ * @since 1.2.6
+ * @deprecated as of Spring 3.0, in favor of Java 5 enums.
+ */
+@Deprecated
+@SuppressWarnings("serial")
+public abstract class AbstractGenericLabeledEnum extends AbstractLabeledEnum {
+
+ /**
+ * A descriptive label for the enum.
+ */
+ private final String label;
+
+
+ /**
+ * Create a new StaticLabeledEnum instance.
+ * @param label the label; if {@code null}), the enum's code
+ * will be used as label
+ */
+ protected AbstractGenericLabeledEnum(String label) {
+ this.label = label;
+ }
+
+
+ public String getLabel() {
+ if (this.label != null) {
+ return label;
+ }
+ else {
+ return getCode().toString();
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/enums/AbstractLabeledEnum.java b/spring-core/src/main/java/org/springframework/core/enums/AbstractLabeledEnum.java
new file mode 100644
index 00000000..3c2860f7
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/enums/AbstractLabeledEnum.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2002-2012 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.enums;
+
+/**
+ * Abstract base superclass for LabeledEnum implementations.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @since 1.2.2
+ * @deprecated as of Spring 3.0, in favor of Java 5 enums.
+ */
+@Deprecated
+@SuppressWarnings("serial")
+public abstract class AbstractLabeledEnum implements LabeledEnum {
+
+ /**
+ * Create a new AbstractLabeledEnum instance.
+ */
+ protected AbstractLabeledEnum() {
+ }
+
+ public Class getType() {
+ // Could be coded as getClass().isAnonymousClass() on JDK 1.5
+ boolean isAnonymous = (getClass().getDeclaringClass() == null && getClass().getName().indexOf('$') != -1);
+ return (isAnonymous ? getClass().getSuperclass() : getClass());
+ }
+
+ public int compareTo(Object obj) {
+ if (!(obj instanceof LabeledEnum)) {
+ throw new ClassCastException("You may only compare LabeledEnums");
+ }
+ LabeledEnum that = (LabeledEnum) obj;
+ if (!this.getType().equals(that.getType())) {
+ throw new ClassCastException("You may only compare LabeledEnums of the same type");
+ }
+ return this.getCode().compareTo(that.getCode());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof LabeledEnum)) {
+ return false;
+ }
+ LabeledEnum other = (LabeledEnum) obj;
+ return (this.getType().equals(other.getType()) && this.getCode().equals(other.getCode()));
+ }
+
+ @Override
+ public int hashCode() {
+ return (getType().hashCode() * 29 + getCode().hashCode());
+ }
+
+ @Override
+ public String toString() {
+ return getLabel();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/enums/LabeledEnum.java b/spring-core/src/main/java/org/springframework/core/enums/LabeledEnum.java
new file mode 100644
index 00000000..dc2325bf
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/enums/LabeledEnum.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2002-2012 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.enums;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+import org.springframework.util.comparator.CompoundComparator;
+import org.springframework.util.comparator.NullSafeComparator;
+
+/**
+ * An interface for objects that represent a labeled enumeration.
+ * Each such enum instance has the following characteristics:
+ *
+ * <ul>
+ * <li>A type that identifies the enum's class.
+ * For example: {@code com.mycompany.util.FileFormat}.</li>
+ *
+ * <li>A code that uniquely identifies the enum within the context of its type.
+ * For example: &quot;CSV&quot;. Different classes of codes are possible
+ * (e.g., Character, Integer, String).</li>
+ *
+ * <li>A descriptive label. For example: "the CSV File Format".</li>
+ * </ul>
+ *
+ * @author Keith Donald
+ * @since 1.2.2
+ * @deprecated as of Spring 3.0, in favor of Java 5 enums.
+ */
+@Deprecated
+public interface LabeledEnum extends Comparable, Serializable {
+
+ /**
+ * Return this enumeration's type.
+ */
+ Class getType();
+
+ /**
+ * Return this enumeration's code.
+ * <p>Each code should be unique within enumerations of the same type.
+ */
+ Comparable getCode();
+
+ /**
+ * Return a descriptive, optional label.
+ */
+ String getLabel();
+
+
+ // Constants for standard enum ordering (Comparator implementations)
+
+ /**
+ * Shared Comparator instance that sorts enumerations by {@code CODE_ORDER}.
+ */
+ Comparator CODE_ORDER = new Comparator() {
+ public int compare(Object o1, Object o2) {
+ Comparable c1 = ((LabeledEnum) o1).getCode();
+ Comparable c2 = ((LabeledEnum) o2).getCode();
+ return c1.compareTo(c2);
+ }
+ };
+
+ /**
+ * Shared Comparator instance that sorts enumerations by {@code LABEL_ORDER}.
+ */
+ Comparator LABEL_ORDER = new Comparator() {
+ public int compare(Object o1, Object o2) {
+ LabeledEnum e1 = (LabeledEnum) o1;
+ LabeledEnum e2 = (LabeledEnum) o2;
+ Comparator comp = new NullSafeComparator(String.CASE_INSENSITIVE_ORDER, true);
+ return comp.compare(e1.getLabel(), e2.getLabel());
+ }
+ };
+
+ /**
+ * Shared Comparator instance that sorts enumerations by {@code LABEL_ORDER},
+ * then {@code CODE_ORDER}.
+ */
+ Comparator DEFAULT_ORDER =
+ new CompoundComparator(new Comparator[] { LABEL_ORDER, CODE_ORDER });
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/enums/LabeledEnumResolver.java b/spring-core/src/main/java/org/springframework/core/enums/LabeledEnumResolver.java
new file mode 100644
index 00000000..7b374a70
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/enums/LabeledEnumResolver.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2002-2012 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.enums;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Interface for looking up {@code LabeledEnum} instances.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ * @deprecated as of Spring 3.0, in favor of Java 5 enums.
+ */
+@Deprecated
+public interface LabeledEnumResolver {
+
+ /**
+ * Return a set of enumerations of a particular type. Each element in the
+ * set should be an instance of LabeledEnum.
+ * @param type the enum type
+ * @return a set of localized enumeration instances for the provided type
+ * @throws IllegalArgumentException if the type is not supported
+ */
+ public Set getLabeledEnumSet(Class type) throws IllegalArgumentException;
+
+ /**
+ * Return a map of enumerations of a particular type. Each element in the
+ * map should be a key/value pair, where the key is the enum code, and the
+ * value is the {@code LabeledEnum} instance.
+ * @param type the enum type
+ * @return a Map of localized enumeration instances,
+ * with enum code as key and {@code LabeledEnum} instance as value
+ * @throws IllegalArgumentException if the type is not supported
+ */
+ public Map getLabeledEnumMap(Class type) throws IllegalArgumentException;
+
+ /**
+ * Resolve a single {@code LabeledEnum} by its identifying code.
+ * @param type the enum type
+ * @param code the enum code
+ * @return the enum
+ * @throws IllegalArgumentException if the code did not map to a valid instance
+ */
+ public LabeledEnum getLabeledEnumByCode(Class type, Comparable code) throws IllegalArgumentException;
+
+ /**
+ * Resolve a single {@code LabeledEnum} by its identifying code.
+ * @param type the enum type
+ * @param label the enum label
+ * @return the enum
+ * @throws IllegalArgumentException if the label did not map to a valid instance
+ */
+ public LabeledEnum getLabeledEnumByLabel(Class type, String label) throws IllegalArgumentException;
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/enums/LetterCodedLabeledEnum.java b/spring-core/src/main/java/org/springframework/core/enums/LetterCodedLabeledEnum.java
new file mode 100644
index 00000000..cb70971a
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/enums/LetterCodedLabeledEnum.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2012 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.enums;
+
+import org.springframework.util.Assert;
+
+/**
+ * Implementation of LabeledEnum which uses a letter as the code type.
+ *
+ * <p>Should almost always be subclassed, but for some simple situations it may be
+ * used directly. Note that you will not be able to use unique type-based functionality
+ * like {@code LabeledEnumResolver.getLabeledEnumSet(type)} in this case.
+ *
+ * @author Keith Donald
+ * @since 1.2.2
+ * @deprecated as of Spring 3.0, in favor of Java 5 enums.
+ */
+@Deprecated
+@SuppressWarnings("serial")
+public class LetterCodedLabeledEnum extends AbstractGenericLabeledEnum {
+
+ /**
+ * The unique code of this enum.
+ */
+ private final Character code;
+
+
+ /**
+ * Create a new LetterCodedLabeledEnum instance.
+ * @param code the letter code
+ * @param label the label (can be {@code null})
+ */
+ public LetterCodedLabeledEnum(char code, String label) {
+ super(label);
+ Assert.isTrue(Character.isLetter(code),
+ "The code '" + code + "' is invalid: it must be a letter");
+ this.code = new Character(code);
+ }
+
+
+ public Comparable getCode() {
+ return code;
+ }
+
+ /**
+ * Return the letter code of this LabeledEnum instance.
+ */
+ public char getLetterCode() {
+ return ((Character) getCode()).charValue();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/enums/ShortCodedLabeledEnum.java b/spring-core/src/main/java/org/springframework/core/enums/ShortCodedLabeledEnum.java
new file mode 100644
index 00000000..0c5e3fae
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/enums/ShortCodedLabeledEnum.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2012 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.enums;
+
+/**
+ * Implementation of LabeledEnum which uses Short as the code type.
+ *
+ * <p>Should almost always be subclassed, but for some simple situations it may be
+ * used directly. Note that you will not be able to use unique type-based functionality
+ * like {@code LabeledEnumResolver.getLabeledEnumSet(type)} in this case.
+ *
+ * @author Keith Donald
+ * @since 1.2.2
+ * @deprecated as of Spring 3.0, in favor of Java 5 enums.
+ */
+@Deprecated
+@SuppressWarnings("serial")
+public class ShortCodedLabeledEnum extends AbstractGenericLabeledEnum {
+
+ /**
+ * The unique code of this enum.
+ */
+ private final Short code;
+
+
+ /**
+ * Create a new ShortCodedLabeledEnum instance.
+ * @param code the short code
+ * @param label the label (can be {@code null})
+ */
+ public ShortCodedLabeledEnum(int code, String label) {
+ super(label);
+ this.code = new Short((short) code);
+ }
+
+
+ public Comparable getCode() {
+ return code;
+ }
+
+ /**
+ * Return the short code of this LabeledEnum instance.
+ */
+ public short getShortCode() {
+ return ((Short) getCode()).shortValue();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/enums/StaticLabeledEnum.java b/spring-core/src/main/java/org/springframework/core/enums/StaticLabeledEnum.java
new file mode 100644
index 00000000..e6440cc3
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/enums/StaticLabeledEnum.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2002-2012 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.enums;
+
+/**
+ * Base class for static type-safe labeled enum instances.
+ *
+ * Usage example:
+ *
+ * <pre>
+ * public class FlowSessionStatus extends StaticLabeledEnum {
+ *
+ * // public static final instances!
+ * public static FlowSessionStatus CREATED = new FlowSessionStatus(0, &quot;Created&quot;);
+ * public static FlowSessionStatus ACTIVE = new FlowSessionStatus(1, &quot;Active&quot;);
+ * public static FlowSessionStatus PAUSED = new FlowSessionStatus(2, &quot;Paused&quot;);
+ * public static FlowSessionStatus SUSPENDED = new FlowSessionStatus(3, &quot;Suspended&quot;);
+ * public static FlowSessionStatus ENDED = new FlowSessionStatus(4, &quot;Ended&quot;);
+ *
+ * // private constructor!
+ * private FlowSessionStatus(int code, String label) {
+ * super(code, label);
+ * }
+ *
+ * // custom behavior
+ * }</pre>
+ *
+ * @author Keith Donald
+ * @since 1.2.6
+ * @deprecated as of Spring 3.0, in favor of Java 5 enums.
+ */
+@Deprecated
+@SuppressWarnings("serial")
+public abstract class StaticLabeledEnum extends AbstractLabeledEnum {
+
+ /**
+ * The unique code of the enum.
+ */
+ private final Short code;
+
+ /**
+ * A descriptive label for the enum.
+ */
+ private final transient String label;
+
+
+ /**
+ * Create a new StaticLabeledEnum instance.
+ * @param code the short code
+ * @param label the label (can be {@code null})
+ */
+ protected StaticLabeledEnum(int code, String label) {
+ this.code = new Short((short) code);
+ if (label != null) {
+ this.label = label;
+ }
+ else {
+ this.label = this.code.toString();
+ }
+ }
+
+ public Comparable getCode() {
+ return code;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ /**
+ * Return the code of this LabeledEnum instance as a short.
+ */
+ public short shortValue() {
+ return ((Number) getCode()).shortValue();
+ }
+
+
+ //---------------------------------------------------------------------
+ // Serialization support
+ //---------------------------------------------------------------------
+
+ /**
+ * Return the resolved type safe static enum instance.
+ */
+ protected Object readResolve() {
+ return StaticLabeledEnumResolver.instance().getLabeledEnumByCode(getType(), getCode());
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/enums/StaticLabeledEnumResolver.java b/spring-core/src/main/java/org/springframework/core/enums/StaticLabeledEnumResolver.java
new file mode 100644
index 00000000..3d9f78a4
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/enums/StaticLabeledEnumResolver.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2002-2012 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.enums;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.springframework.util.Assert;
+
+/**
+ * {@link LabeledEnumResolver} that resolves statically defined enumerations.
+ * Static implies all enum instances were defined within Java code,
+ * implementing the type-safe enum pattern.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ * @deprecated as of Spring 3.0, in favor of Java 5 enums.
+ */
+@Deprecated
+public class StaticLabeledEnumResolver extends AbstractCachingLabeledEnumResolver {
+
+ /**
+ * Shared {@code StaticLabeledEnumResolver} singleton instance.
+ */
+ private static final StaticLabeledEnumResolver INSTANCE = new StaticLabeledEnumResolver();
+
+
+ /**
+ * Return the shared {@code StaticLabeledEnumResolver} singleton instance.
+ * Mainly for resolving unique StaticLabeledEnum references on deserialization.
+ * @see StaticLabeledEnum
+ */
+ public static StaticLabeledEnumResolver instance() {
+ return INSTANCE;
+ }
+
+
+ @Override
+ protected Set<LabeledEnum> findLabeledEnums(Class type) {
+ Set<LabeledEnum> typeEnums = new TreeSet<LabeledEnum>();
+ for (Field field : type.getFields()) {
+ if (Modifier.isStatic(field.getModifiers()) && Modifier.isPublic(field.getModifiers())) {
+ if (type.isAssignableFrom(field.getType())) {
+ try {
+ Object value = field.get(null);
+ Assert.isTrue(value instanceof LabeledEnum, "Field value must be a LabeledEnum instance");
+ typeEnums.add((LabeledEnum) value);
+ }
+ catch (IllegalAccessException ex) {
+ logger.warn("Unable to access field value: " + field, ex);
+ }
+ }
+ }
+ }
+ return typeEnums;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/enums/StringCodedLabeledEnum.java b/spring-core/src/main/java/org/springframework/core/enums/StringCodedLabeledEnum.java
new file mode 100644
index 00000000..d0a936ef
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/enums/StringCodedLabeledEnum.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2012 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.enums;
+
+import org.springframework.util.Assert;
+
+/**
+ * Implementation of LabeledEnum which uses a String as the code type.
+ *
+ * <p>Should almost always be subclassed, but for some simple situations it may be
+ * used directly. Note that you will not be able to use unique type-based
+ * functionality like {@code LabeledEnumResolver.getLabeledEnumSet(type)} in this case.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ * @see org.springframework.core.enums.LabeledEnumResolver#getLabeledEnumSet(Class)
+ * @deprecated as of Spring 3.0, in favor of Java 5 enums.
+ */
+@Deprecated
+@SuppressWarnings("serial")
+public class StringCodedLabeledEnum extends AbstractGenericLabeledEnum {
+
+ /**
+ * The unique code of this enum.
+ */
+ private final String code;
+
+
+ /**
+ * Create a new StringCodedLabeledEnum instance.
+ * @param code the String code
+ * @param label the label (can be {@code null})
+ */
+ public StringCodedLabeledEnum(String code, String label) {
+ super(label);
+ Assert.notNull(code, "'code' must not be null");
+ this.code = code;
+ }
+
+
+ public Comparable getCode() {
+ return this.code;
+ }
+
+ /**
+ * Return the String code of this LabeledEnum instance.
+ */
+ public String getStringCode() {
+ return (String) getCode();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/enums/package-info.java b/spring-core/src/main/java/org/springframework/core/enums/package-info.java
new file mode 100644
index 00000000..ac720538
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/enums/package-info.java
@@ -0,0 +1,9 @@
+
+/**
+ *
+ * Interfaces and classes for type-safe enum support on JDK >= 1.3.
+ * This enum abstraction support codes and labels.
+ *
+ */
+package org.springframework.core.enums;
+
diff --git a/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java b/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java
new file mode 100644
index 00000000..786443be
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java
@@ -0,0 +1,535 @@
+/*
+ * Copyright 2002-2014 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.env;
+
+import java.security.AccessControlException;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.core.SpringProperties;
+import org.springframework.core.convert.support.ConfigurableConversionService;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import static java.lang.String.*;
+import static org.springframework.util.StringUtils.*;
+
+/**
+ * Abstract base class for {@link Environment} implementations. Supports the notion of
+ * reserved default profile names and enables specifying active and default profiles
+ * through the {@link #ACTIVE_PROFILES_PROPERTY_NAME} and
+ * {@link #DEFAULT_PROFILES_PROPERTY_NAME} properties.
+ *
+ * <p>Concrete subclasses differ primarily on which {@link PropertySource} objects they
+ * add by default. {@code AbstractEnvironment} adds none. Subclasses should contribute
+ * property sources through the protected {@link #customizePropertySources(MutablePropertySources)}
+ * hook, while clients should customize using {@link ConfigurableEnvironment#getPropertySources()}
+ * and working against the {@link MutablePropertySources} API.
+ * See {@link ConfigurableEnvironment} javadoc for usage examples.
+ *
+ * @author Chris Beams
+ * @author Juergen Hoeller
+ * @since 3.1
+ * @see ConfigurableEnvironment
+ * @see StandardEnvironment
+ */
+public abstract class AbstractEnvironment implements ConfigurableEnvironment {
+
+ /**
+ * System property that instructs Spring to ignore system environment variables,
+ * i.e. to never attempt to retrieve such a variable via {@link System#getenv()}.
+ * <p>The default is "false", falling back to system environment variable checks if a
+ * Spring environment property (e.g. a placeholder in a configuration String) isn't
+ * resolvable otherwise. Consider switching this flag to "true" if you experience
+ * log warnings from {@code getenv} calls coming from Spring, e.g. on WebSphere
+ * with strict SecurityManager settings and AccessControlExceptions warnings.
+ * @see #suppressGetenvAccess()
+ */
+ public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";
+
+ /**
+ * Name of property to set to specify active profiles: {@value}. Value may be comma
+ * delimited.
+ * <p>Note that certain shell environments such as Bash disallow the use of the period
+ * character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource}
+ * is in use, this property may be specified as an environment variable as
+ * {@code SPRING_PROFILES_ACTIVE}.
+ * @see ConfigurableEnvironment#setActiveProfiles
+ */
+ public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
+
+ /**
+ * Name of property to set to specify profiles active by default: {@value}. Value may
+ * be comma delimited.
+ * <p>Note that certain shell environments such as Bash disallow the use of the period
+ * character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource}
+ * is in use, this property may be specified as an environment variable as
+ * {@code SPRING_PROFILES_DEFAULT}.
+ * @see ConfigurableEnvironment#setDefaultProfiles
+ */
+ public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
+
+ /**
+ * Name of reserved default profile name: {@value}. If no default profile names are
+ * explicitly and no active profile names are explicitly set, this profile will
+ * automatically be activated by default.
+ * @see #getReservedDefaultProfiles
+ * @see ConfigurableEnvironment#setDefaultProfiles
+ * @see ConfigurableEnvironment#setActiveProfiles
+ * @see AbstractEnvironment#DEFAULT_PROFILES_PROPERTY_NAME
+ * @see AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME
+ */
+ protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";
+
+
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ private Set<String> activeProfiles = new LinkedHashSet<String>();
+
+ private Set<String> defaultProfiles = new LinkedHashSet<String>(getReservedDefaultProfiles());
+
+ private final MutablePropertySources propertySources = new MutablePropertySources(this.logger);
+
+ private final ConfigurablePropertyResolver propertyResolver =
+ new PropertySourcesPropertyResolver(this.propertySources);
+
+
+ /**
+ * Create a new {@code Environment} instance, calling back to
+ * {@link #customizePropertySources(MutablePropertySources)} during construction to
+ * allow subclasses to contribute or manipulate {@link PropertySource} instances as
+ * appropriate.
+ * @see #customizePropertySources(MutablePropertySources)
+ */
+ public AbstractEnvironment() {
+ customizePropertySources(this.propertySources);
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(format(
+ "Initialized %s with PropertySources %s", getClass().getSimpleName(), this.propertySources));
+ }
+ }
+
+
+ /**
+ * Customize the set of {@link PropertySource} objects to be searched by this
+ * {@code Environment} during calls to {@link #getProperty(String)} and related
+ * methods.
+ *
+ * <p>Subclasses that override this method are encouraged to add property
+ * sources using {@link MutablePropertySources#addLast(PropertySource)} such that
+ * further subclasses may call {@code super.customizePropertySources()} with
+ * predictable results. For example:
+ * <pre class="code">
+ * public class Level1Environment extends AbstractEnvironment {
+ * &#064;Override
+ * protected void customizePropertySources(MutablePropertySources propertySources) {
+ * super.customizePropertySources(propertySources); // no-op from base class
+ * propertySources.addLast(new PropertySourceA(...));
+ * propertySources.addLast(new PropertySourceB(...));
+ * }
+ * }
+ *
+ * public class Level2Environment extends Level1Environment {
+ * &#064;Override
+ * protected void customizePropertySources(MutablePropertySources propertySources) {
+ * super.customizePropertySources(propertySources); // add all from superclass
+ * propertySources.addLast(new PropertySourceC(...));
+ * propertySources.addLast(new PropertySourceD(...));
+ * }
+ * }
+ * </pre>
+ * In this arrangement, properties will be resolved against sources A, B, C, D in that
+ * order. That is to say that property source "A" has precedence over property source
+ * "D". If the {@code Level2Environment} subclass wished to give property sources C
+ * and D higher precedence than A and B, it could simply call
+ * {@code super.customizePropertySources} after, rather than before adding its own:
+ * <pre class="code">
+ * public class Level2Environment extends Level1Environment {
+ * &#064;Override
+ * protected void customizePropertySources(MutablePropertySources propertySources) {
+ * propertySources.addLast(new PropertySourceC(...));
+ * propertySources.addLast(new PropertySourceD(...));
+ * super.customizePropertySources(propertySources); // add all from superclass
+ * }
+ * }
+ * </pre>
+ * The search order is now C, D, A, B as desired.
+ *
+ * <p>Beyond these recommendations, subclasses may use any of the {@code add&#42;},
+ * {@code remove}, or {@code replace} methods exposed by {@link MutablePropertySources}
+ * in order to create the exact arrangement of property sources desired.
+ *
+ * <p>The base implementation registers no property sources.
+ *
+ * <p>Note that clients of any {@link ConfigurableEnvironment} may further customize
+ * property sources via the {@link #getPropertySources()} accessor, typically within
+ * an {@link org.springframework.context.ApplicationContextInitializer
+ * ApplicationContextInitializer}. For example:
+ * <pre class="code">
+ * ConfigurableEnvironment env = new StandardEnvironment();
+ * env.getPropertySources().addLast(new PropertySourceX(...));
+ * </pre>
+ *
+ * <h2>A warning about instance variable access</h2>
+ * Instance variables declared in subclasses and having default initial values should
+ * <em>not</em> be accessed from within this method. Due to Java object creation
+ * lifecycle constraints, any initial value will not yet be assigned when this
+ * callback is invoked by the {@link #AbstractEnvironment()} constructor, which may
+ * lead to a {@code NullPointerException} or other problems. If you need to access
+ * default values of instance variables, leave this method as a no-op and perform
+ * property source manipulation and instance variable access directly within the
+ * subclass constructor. Note that <em>assigning</em> values to instance variables is
+ * not problematic; it is only attempting to read default values that must be avoided.
+ *
+ * @see MutablePropertySources
+ * @see PropertySourcesPropertyResolver
+ * @see org.springframework.context.ApplicationContextInitializer
+ */
+ protected void customizePropertySources(MutablePropertySources propertySources) {
+ }
+
+ /**
+ * Return the set of reserved default profile names. This implementation returns
+ * {@value #RESERVED_DEFAULT_PROFILE_NAME}. Subclasses may override in order to
+ * customize the set of reserved names.
+ * @see #RESERVED_DEFAULT_PROFILE_NAME
+ * @see #doGetDefaultProfiles()
+ */
+ protected Set<String> getReservedDefaultProfiles() {
+ return Collections.singleton(RESERVED_DEFAULT_PROFILE_NAME);
+ }
+
+
+ //---------------------------------------------------------------------
+ // Implementation of ConfigurableEnvironment interface
+ //---------------------------------------------------------------------
+
+ public String[] getActiveProfiles() {
+ return StringUtils.toStringArray(doGetActiveProfiles());
+ }
+
+ /**
+ * Return the set of active profiles as explicitly set through
+ * {@link #setActiveProfiles} or if the current set of active profiles
+ * is empty, check for the presence of the {@value #ACTIVE_PROFILES_PROPERTY_NAME}
+ * property and assign its value to the set of active profiles.
+ * @see #getActiveProfiles()
+ * @see #ACTIVE_PROFILES_PROPERTY_NAME
+ */
+ protected Set<String> doGetActiveProfiles() {
+ if (this.activeProfiles.isEmpty()) {
+ String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
+ if (StringUtils.hasText(profiles)) {
+ setActiveProfiles(commaDelimitedListToStringArray(trimAllWhitespace(profiles)));
+ }
+ }
+ return this.activeProfiles;
+ }
+
+ public void setActiveProfiles(String... profiles) {
+ Assert.notNull(profiles, "Profile array must not be null");
+ this.activeProfiles.clear();
+ for (String profile : profiles) {
+ validateProfile(profile);
+ this.activeProfiles.add(profile);
+ }
+ }
+
+ public void addActiveProfile(String profile) {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug(format("Activating profile '%s'", profile));
+ }
+ validateProfile(profile);
+ doGetActiveProfiles();
+ this.activeProfiles.add(profile);
+ }
+
+
+ public String[] getDefaultProfiles() {
+ return StringUtils.toStringArray(doGetDefaultProfiles());
+ }
+
+ /**
+ * Return the set of default profiles explicitly set via
+ * {@link #setDefaultProfiles(String...)} or if the current set of default profiles
+ * consists only of {@linkplain #getReservedDefaultProfiles() reserved default
+ * profiles}, then check for the presence of the
+ * {@value #DEFAULT_PROFILES_PROPERTY_NAME} property and assign its value (if any)
+ * to the set of default profiles.
+ * @see #AbstractEnvironment()
+ * @see #getDefaultProfiles()
+ * @see #DEFAULT_PROFILES_PROPERTY_NAME
+ * @see #getReservedDefaultProfiles()
+ */
+ protected Set<String> doGetDefaultProfiles() {
+ if (this.defaultProfiles.equals(getReservedDefaultProfiles())) {
+ String profiles = getProperty(DEFAULT_PROFILES_PROPERTY_NAME);
+ if (StringUtils.hasText(profiles)) {
+ setDefaultProfiles(commaDelimitedListToStringArray(trimAllWhitespace(profiles)));
+ }
+ }
+ return this.defaultProfiles;
+ }
+
+ /**
+ * Specify the set of profiles to be made active by default if no other profiles
+ * are explicitly made active through {@link #setActiveProfiles}.
+ * <p>Calling this method removes overrides any reserved default profiles
+ * that may have been added during construction of the environment.
+ * @see #AbstractEnvironment()
+ * @see #getReservedDefaultProfiles()
+ */
+ public void setDefaultProfiles(String... profiles) {
+ Assert.notNull(profiles, "Profile array must not be null");
+ this.defaultProfiles.clear();
+ for (String profile : profiles) {
+ validateProfile(profile);
+ this.defaultProfiles.add(profile);
+ }
+ }
+
+ public boolean acceptsProfiles(String... profiles) {
+ Assert.notEmpty(profiles, "Must specify at least one profile");
+ for (String profile : profiles) {
+ if (profile != null && profile.length() > 0 && profile.charAt(0) == '!') {
+ if (!isProfileActive(profile.substring(1))) {
+ return true;
+ }
+ }
+ else if (isProfileActive(profile)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return whether the given profile is active, or if active profiles are empty
+ * whether the profile should be active by default.
+ * @throws IllegalArgumentException per {@link #validateProfile(String)}
+ */
+ protected boolean isProfileActive(String profile) {
+ validateProfile(profile);
+ return doGetActiveProfiles().contains(profile) ||
+ (doGetActiveProfiles().isEmpty() && doGetDefaultProfiles().contains(profile));
+ }
+
+ /**
+ * Validate the given profile, called internally prior to adding to the set of
+ * active or default profiles.
+ * <p>Subclasses may override to impose further restrictions on profile syntax.
+ * @throws IllegalArgumentException if the profile is null, empty, whitespace-only or
+ * begins with the profile NOT operator (!).
+ * @see #acceptsProfiles
+ * @see #addActiveProfile
+ * @see #setDefaultProfiles
+ */
+ protected void validateProfile(String profile) {
+ if (!StringUtils.hasText(profile)) {
+ throw new IllegalArgumentException("Invalid profile [" + profile + "]: must contain text");
+ }
+ if (profile.charAt(0) == '!') {
+ throw new IllegalArgumentException("Invalid profile [" + profile + "]: must not begin with ! operator");
+ }
+ }
+
+ public MutablePropertySources getPropertySources() {
+ return this.propertySources;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Map<String, Object> getSystemEnvironment() {
+ if (suppressGetenvAccess()) {
+ return Collections.emptyMap();
+ }
+ try {
+ return (Map) System.getenv();
+ }
+ catch (AccessControlException ex) {
+ return (Map) new ReadOnlySystemAttributesMap() {
+ @Override
+ protected String getSystemAttribute(String attributeName) {
+ try {
+ return System.getenv(attributeName);
+ }
+ catch (AccessControlException ex) {
+ if (logger.isInfoEnabled()) {
+ logger.info(format("Caught AccessControlException when accessing system " +
+ "environment variable [%s]; its value will be returned [null]. Reason: %s",
+ attributeName, ex.getMessage()));
+ }
+ return null;
+ }
+ }
+ };
+ }
+ }
+
+ /**
+ * Determine whether to suppress {@link System#getenv()}/{@link System#getenv(String)}
+ * access for the purposes of {@link #getSystemEnvironment()}.
+ * <p>If this method returns {@code true}, an empty dummy Map will be used instead
+ * of the regular system environment Map, never even trying to call {@code getenv}
+ * and therefore avoiding security manager warnings (if any).
+ * <p>The default implementation checks for the "spring.getenv.ignore" system property,
+ * returning {@code true} if its value equals "true" in any case.
+ * @see #IGNORE_GETENV_PROPERTY_NAME
+ * @see SpringProperties#getFlag
+ */
+ protected boolean suppressGetenvAccess() {
+ return SpringProperties.getFlag(IGNORE_GETENV_PROPERTY_NAME);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Map<String, Object> getSystemProperties() {
+ try {
+ return (Map) System.getProperties();
+ }
+ catch (AccessControlException ex) {
+ return (Map) new ReadOnlySystemAttributesMap() {
+ @Override
+ protected String getSystemAttribute(String attributeName) {
+ try {
+ return System.getProperty(attributeName);
+ }
+ catch (AccessControlException ex) {
+ if (logger.isInfoEnabled()) {
+ logger.info(format("Caught AccessControlException when accessing system " +
+ "property [%s]; its value will be returned [null]. Reason: %s",
+ attributeName, ex.getMessage()));
+ }
+ return null;
+ }
+ }
+ };
+ }
+ }
+
+ public void merge(ConfigurableEnvironment parent) {
+ for (PropertySource<?> ps : parent.getPropertySources()) {
+ if (!this.propertySources.contains(ps.getName())) {
+ this.propertySources.addLast(ps);
+ }
+ }
+ for (String profile : parent.getActiveProfiles()) {
+ this.activeProfiles.add(profile);
+ }
+ if (parent.getDefaultProfiles().length > 0) {
+ this.defaultProfiles.remove(RESERVED_DEFAULT_PROFILE_NAME);
+ for (String profile : parent.getDefaultProfiles()) {
+ this.defaultProfiles.add(profile);
+ }
+ }
+ }
+
+
+ //---------------------------------------------------------------------
+ // Implementation of ConfigurablePropertyResolver interface
+ //---------------------------------------------------------------------
+
+ public ConfigurableConversionService getConversionService() {
+ return this.propertyResolver.getConversionService();
+ }
+
+ public void setConversionService(ConfigurableConversionService conversionService) {
+ this.propertyResolver.setConversionService(conversionService);
+ }
+
+ public void setPlaceholderPrefix(String placeholderPrefix) {
+ this.propertyResolver.setPlaceholderPrefix(placeholderPrefix);
+ }
+
+ public void setPlaceholderSuffix(String placeholderSuffix) {
+ this.propertyResolver.setPlaceholderSuffix(placeholderSuffix);
+ }
+
+ public void setValueSeparator(String valueSeparator) {
+ this.propertyResolver.setValueSeparator(valueSeparator);
+ }
+
+ public void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders) {
+ this.propertyResolver.setIgnoreUnresolvableNestedPlaceholders(ignoreUnresolvableNestedPlaceholders);
+ }
+
+ public void setRequiredProperties(String... requiredProperties) {
+ this.propertyResolver.setRequiredProperties(requiredProperties);
+ }
+
+ public void validateRequiredProperties() throws MissingRequiredPropertiesException {
+ this.propertyResolver.validateRequiredProperties();
+ }
+
+
+ //---------------------------------------------------------------------
+ // Implementation of PropertyResolver interface
+ //---------------------------------------------------------------------
+
+ @Override
+ public boolean containsProperty(String key) {
+ return this.propertyResolver.containsProperty(key);
+ }
+
+ public String getProperty(String key) {
+ return this.propertyResolver.getProperty(key);
+ }
+
+ public String getProperty(String key, String defaultValue) {
+ return this.propertyResolver.getProperty(key, defaultValue);
+ }
+
+ public <T> T getProperty(String key, Class<T> targetType) {
+ return this.propertyResolver.getProperty(key, targetType);
+ }
+
+ public <T> T getProperty(String key, Class<T> targetType, T defaultValue) {
+ return this.propertyResolver.getProperty(key, targetType, defaultValue);
+ }
+
+ public <T> Class<T> getPropertyAsClass(String key, Class<T> targetType) {
+ return this.propertyResolver.getPropertyAsClass(key, targetType);
+ }
+
+ public String getRequiredProperty(String key) throws IllegalStateException {
+ return this.propertyResolver.getRequiredProperty(key);
+ }
+
+ public <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException {
+ return this.propertyResolver.getRequiredProperty(key, targetType);
+ }
+
+ public String resolvePlaceholders(String text) {
+ return this.propertyResolver.resolvePlaceholders(text);
+ }
+
+ public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
+ return this.propertyResolver.resolveRequiredPlaceholders(text);
+ }
+
+
+ @Override
+ public String toString() {
+ return format("%s {activeProfiles=%s, defaultProfiles=%s, propertySources=%s}",
+ getClass().getSimpleName(), this.activeProfiles, this.defaultProfiles,
+ this.propertySources);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java b/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java
new file mode 100644
index 00000000..4b02991a
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2002-2014 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.env;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.core.convert.support.ConfigurableConversionService;
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.util.PropertyPlaceholderHelper;
+import org.springframework.util.SystemPropertyUtils;
+
+/**
+ * Abstract base class for resolving properties against any underlying source.
+ *
+ * @author Chris Beams
+ * @author Juergen Hoeller
+ * @since 3.1
+ */
+public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
+
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ protected ConfigurableConversionService conversionService = new DefaultConversionService();
+
+ private PropertyPlaceholderHelper nonStrictHelper;
+
+ private PropertyPlaceholderHelper strictHelper;
+
+ private boolean ignoreUnresolvableNestedPlaceholders = false;
+
+ private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;
+
+ private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;
+
+ private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;
+
+ private final Set<String> requiredProperties = new LinkedHashSet<String>();
+
+
+ public ConfigurableConversionService getConversionService() {
+ return this.conversionService;
+ }
+
+ public void setConversionService(ConfigurableConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+ /**
+ * Set the prefix that placeholders replaced by this resolver must begin with.
+ * <p>The default is "${".
+ * @see org.springframework.util.SystemPropertyUtils#PLACEHOLDER_PREFIX
+ */
+ public void setPlaceholderPrefix(String placeholderPrefix) {
+ this.placeholderPrefix = placeholderPrefix;
+ }
+
+ /**
+ * Set the suffix that placeholders replaced by this resolver must end with.
+ * <p>The default is "}".
+ * @see org.springframework.util.SystemPropertyUtils#PLACEHOLDER_SUFFIX
+ */
+ public void setPlaceholderSuffix(String placeholderSuffix) {
+ this.placeholderSuffix = placeholderSuffix;
+ }
+
+ /**
+ * Specify the separating character between the placeholders replaced by this
+ * resolver and their associated default value, or {@code null} if no such
+ * special character should be processed as a value separator.
+ * <p>The default is ":".
+ * @see org.springframework.util.SystemPropertyUtils#VALUE_SEPARATOR
+ */
+ public void setValueSeparator(String valueSeparator) {
+ this.valueSeparator = valueSeparator;
+ }
+
+ /**
+ * Set whether to throw an exception when encountering an unresolvable placeholder
+ * nested within the value of a given property. A {@code false} value indicates strict
+ * resolution, i.e. that an exception will be thrown. A {@code true} value indicates
+ * that unresolvable nested placeholders should be passed through in their unresolved
+ * ${...} form.
+ * <p>The default is {@code false}.
+ * @since 3.2
+ */
+ public void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders) {
+ this.ignoreUnresolvableNestedPlaceholders = ignoreUnresolvableNestedPlaceholders;
+ }
+
+ public void setRequiredProperties(String... requiredProperties) {
+ for (String key : requiredProperties) {
+ this.requiredProperties.add(key);
+ }
+ }
+
+ public void validateRequiredProperties() {
+ MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
+ for (String key : this.requiredProperties) {
+ if (this.getProperty(key) == null) {
+ ex.addMissingRequiredProperty(key);
+ }
+ }
+ if (!ex.getMissingRequiredProperties().isEmpty()) {
+ throw ex;
+ }
+ }
+
+
+ public String getProperty(String key, String defaultValue) {
+ String value = getProperty(key);
+ return (value != null ? value : defaultValue);
+ }
+
+ public <T> T getProperty(String key, Class<T> targetType, T defaultValue) {
+ T value = getProperty(key, targetType);
+ return (value != null ? value : defaultValue);
+ }
+
+ public String getRequiredProperty(String key) throws IllegalStateException {
+ String value = getProperty(key);
+ if (value == null) {
+ throw new IllegalStateException(String.format("required key [%s] not found", key));
+ }
+ return value;
+ }
+
+ public <T> T getRequiredProperty(String key, Class<T> valueType) throws IllegalStateException {
+ T value = getProperty(key, valueType);
+ if (value == null) {
+ throw new IllegalStateException(String.format("required key [%s] not found", key));
+ }
+ return value;
+ }
+
+ public String resolvePlaceholders(String text) {
+ if (this.nonStrictHelper == null) {
+ this.nonStrictHelper = createPlaceholderHelper(true);
+ }
+ return doResolvePlaceholders(text, this.nonStrictHelper);
+ }
+
+ public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
+ if (this.strictHelper == null) {
+ this.strictHelper = createPlaceholderHelper(false);
+ }
+ return doResolvePlaceholders(text, this.strictHelper);
+ }
+
+ /**
+ * Resolve placeholders within the given string, deferring to the value of
+ * {@link #setIgnoreUnresolvableNestedPlaceholders} to determine whether any
+ * unresolvable placeholders should raise an exception or be ignored.
+ * <p>Invoked from {@link #getProperty} and its variants, implicitly resolving
+ * nested placeholders. In contrast, {@link #resolvePlaceholders} and
+ * {@link #resolveRequiredPlaceholders} do <emphasis>not</emphasis> delegate
+ * to this method but rather perform their own handling of unresolvable
+ * placeholders, as specified by each of those methods.
+ * @since 3.2
+ * @see #setIgnoreUnresolvableNestedPlaceholders
+ */
+ protected String resolveNestedPlaceholders(String value) {
+ return (this.ignoreUnresolvableNestedPlaceholders ?
+ resolvePlaceholders(value) : resolveRequiredPlaceholders(value));
+ }
+
+ private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
+ return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
+ this.valueSeparator, ignoreUnresolvablePlaceholders);
+ }
+
+ private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
+ return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
+ public String resolvePlaceholder(String placeholderName) {
+ return getPropertyAsRawString(placeholderName);
+ }
+ });
+ }
+
+
+ /**
+ * Retrieve the specified property as a raw String,
+ * i.e. without resolution of nested placeholders.
+ * @param key the property name to resolve
+ * @return the property value or {@code null} if none found
+ */
+ protected abstract String getPropertyAsRawString(String key);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/CommandLineArgs.java b/spring-core/src/main/java/org/springframework/core/env/CommandLineArgs.java
new file mode 100644
index 00000000..189484a6
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/CommandLineArgs.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.env;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A simple representation of command line arguments, broken into "option arguments" and
+ * "non-option arguments".
+ *
+ * @author Chris Beams
+ * @since 3.1
+ * @see SimpleCommandLineArgsParser
+ */
+class CommandLineArgs {
+
+ private final Map<String, List<String>> optionArgs = new HashMap<String, List<String>>();
+ private final List<String> nonOptionArgs = new ArrayList<String>();
+
+ /**
+ * Add an option argument for the given option name and add the given value to the
+ * list of values associated with this option (of which there may be zero or more).
+ * The given value may be {@code null}, indicating that the option was specified
+ * without an associated value (e.g. "--foo" vs. "--foo=bar").
+ */
+ public void addOptionArg(String optionName, String optionValue) {
+ if (!this.optionArgs.containsKey(optionName)) {
+ this.optionArgs.put(optionName, new ArrayList<String>());
+ }
+ if (optionValue != null) {
+ this.optionArgs.get(optionName).add(optionValue);
+ }
+ }
+
+ /**
+ * Return the set of all option arguments present on the command line.
+ */
+ public Set<String> getOptionNames() {
+ return Collections.unmodifiableSet(this.optionArgs.keySet());
+ }
+
+ /**
+ * Return whether the option with the given name was present on the command line.
+ */
+ public boolean containsOption(String optionName) {
+ return this.optionArgs.containsKey(optionName);
+ }
+
+ /**
+ * Return the list of values associated with the given option. {@code null} signifies
+ * that the option was not present; empty list signifies that no values were associated
+ * with this option.
+ */
+ public List<String> getOptionValues(String optionName) {
+ return this.optionArgs.get(optionName);
+ }
+
+ /**
+ * Add the given value to the list of non-option arguments.
+ */
+ public void addNonOptionArg(String value) {
+ this.nonOptionArgs.add(value);
+ }
+
+ /**
+ * Return the list of non-option arguments specified on the command line.
+ */
+ public List<String> getNonOptionArgs() {
+ return Collections.unmodifiableList(this.nonOptionArgs);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/CommandLinePropertySource.java b/spring-core/src/main/java/org/springframework/core/env/CommandLinePropertySource.java
new file mode 100644
index 00000000..575cd359
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/CommandLinePropertySource.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2002-2014 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.env;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * Abstract base class for {@link PropertySource} implementations backed by command line
+ * arguments. The parameterized type {@code T} represents the underlying source of command
+ * line options. This may be as simple as a String array in the case of
+ * {@link SimpleCommandLinePropertySource}, or specific to a particular API such as JOpt's
+ * {@code OptionSet} in the case of {@link JOptCommandLinePropertySource}.
+ *
+ * <h3>Purpose and General Usage</h3>
+ * For use in standalone Spring-based applications, i.e. those that are bootstrapped via
+ * a traditional {@code main} method accepting a {@code String[]} of arguments from the
+ * command line. In many cases, processing command-line arguments directly within the
+ * {@code main} method may be sufficient, but in other cases, it may be desirable to
+ * inject arguments as values into Spring beans. It is this latter set of cases in which
+ * a {@code CommandLinePropertySource} becomes useful. A {@code CommandLinePropertySource}
+ * will typically be added to the {@link Environment} of the Spring
+ * {@code ApplicationContext}, at which point all command line arguments become available
+ * through the {@link Environment#getProperty(String)} family of methods. For example:
+ * <pre class="code">
+ * public static void main(String[] args) {
+ * CommandLinePropertySource clps = ...;
+ * AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ * ctx.getEnvironment().getPropertySources().addFirst(clps);
+ * ctx.register(AppConfig.class);
+ * ctx.refresh();
+ * }</pre>
+ * With the bootstrap logic above, the {@code AppConfig} class may {@code @Inject} the
+ * Spring {@code Environment} and query it directly for properties:
+ * <pre class="code">
+ * &#064;Configuration
+ * public class AppConfig {
+ * &#064;Inject Environment env;
+ *
+ * &#064;Bean
+ * public void DataSource dataSource() {
+ * MyVendorDataSource dataSource = new MyVendorDataSource();
+ * dataSource.setHostname(env.getProperty("db.hostname", "localhost"));
+ * dataSource.setUsername(env.getRequiredProperty("db.username"));
+ * dataSource.setPassword(env.getRequiredProperty("db.password"));
+ * // ...
+ * return dataSource;
+ * }
+ * }</pre>
+ * Because the {@code CommandLinePropertySource} was added to the {@code Environment}'s
+ * set of {@link MutablePropertySources} using the {@code #addFirst} method, it has
+ * highest search precedence, meaning that while "db.hostname" and other properties may
+ * exist in other property sources such as the system environment variables, it will be
+ * chosen from the command line property source first. This is a reasonable approach
+ * given that arguments specified on the command line are naturally more specific than
+ * those specified as environment variables.
+ *
+ * <p>As an alternative to injecting the {@code Environment}, Spring's {@code @Value}
+ * annotation may be used to inject these properties, given that a {@link
+ * PropertySourcesPropertyResolver} bean has been registered, either directly or through
+ * using the {@code <context:property-placeholder>} element. For example:
+ * <pre class="code">
+ * &#064;Component
+ * public class MyComponent {
+ * &#064;Value("my.property:defaultVal")
+ * private String myProperty;
+ *
+ * public void getMyProperty() {
+ * return this.myProperty;
+ * }
+ *
+ * // ...
+ * }</pre>
+ *
+ * <h3>Working with option arguments</h3>
+ *
+ * <p>Individual command line arguments are represented as properties through the usual
+ * {@link PropertySource#getProperty(String)} and
+ * {@link PropertySource#containsProperty(String)} methods. For example, given the
+ * following command line:
+ * <pre class="code">
+ * --o1=v1 --o2</pre>
+ * 'o1' and 'o2' are treated as "option arguments", and the following assertions would
+ * evaluate true:
+ * <pre class="code">
+ * CommandLinePropertySource<?> ps = ...
+ * assert ps.containsProperty("o1") == true;
+ * assert ps.containsProperty("o2") == true;
+ * assert ps.containsProperty("o3") == false;
+ * assert ps.getProperty("o1").equals("v1");
+ * assert ps.getProperty("o2").equals("");
+ * assert ps.getProperty("o3") == null;</pre>
+ *
+ * Note that the 'o2' option has no argument, but {@code getProperty("o2")} resolves to
+ * empty string ({@code ""}) as opposed to {@code null}, while {@code getProperty("o3")}
+ * resolves to {@code null} because it was not specified. This behavior is consistent with
+ * the general contract to be followed by all {@code PropertySource} implementations.
+ *
+ * <p>Note also that while "--" was used in the examples above to denote an option
+ * argument, this syntax may vary across individual command line argument libraries. For
+ * example, a JOpt- or Commons CLI-based implementation may allow for single dash ("-")
+ * "short" option arguments, etc.
+ *
+ * <h3>Working with non-option arguments</h3>
+ *
+ * <p>Non-option arguments are also supported through this abstraction. Any arguments
+ * supplied without an option-style prefix such as "-" or "--" are considered "non-option
+ * arguments" and available through the special {@linkplain
+ * #DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME "nonOptionArgs"} property. If multiple
+ * non-option arguments are specified, the value of this property will be a
+ * comma-delimited string containing all of the arguments. This approach ensures a simple
+ * and consistent return type (String) for all properties from a {@code
+ * CommandLinePropertySource} and at the same time lends itself to conversion when used
+ * in conjunction with the Spring {@link Environment} and its built-in {@code
+ * ConversionService}. Consider the following example:
+ * <pre class="code">
+ * --o1=v1 --o2=v2 /path/to/file1 /path/to/file2</pre>
+ * In this example, "o1" and "o2" would be considered "option arguments", while the two
+ * filesystem paths qualify as "non-option arguments". As such, the following assertions
+ * will evaluate true:
+ * <pre class="code">
+ * CommandLinePropertySource<?> ps = ...
+ * assert ps.containsProperty("o1") == true;
+ * assert ps.containsProperty("o2") == true;
+ * assert ps.containsProperty("nonOptionArgs") == true;
+ * assert ps.getProperty("o1").equals("v1");
+ * assert ps.getProperty("o2").equals("v2");
+ * assert ps.getProperty("nonOptionArgs").equals("/path/to/file1,/path/to/file2");</pre>
+ *
+ * <p>As mentioned above, when used in conjunction with the Spring {@code Environment}
+ * abstraction, this comma-delimited string may easily be converted to a String array or
+ * list:
+ * <pre class="code">
+ * Environment env = applicationContext.getEnvironment();
+ * String[] nonOptionArgs = env.getProperty("nonOptionArgs", String[].class);
+ * assert nonOptionArgs[0].equals("/path/to/file1");
+ * assert nonOptionArgs[1].equals("/path/to/file2");</pre>
+ *
+ * <p>The name of the special "non-option arguments" property may be customized through
+ * the {@link #setNonOptionArgsPropertyName(String)} method. Doing so is recommended as
+ * it gives proper semantic value to non-option arguments. For example, if filesystem
+ * paths are being specified as non-option arguments, it is likely preferable to refer to
+ * these as something like "file.locations" than the default of "nonOptionArgs":
+ * <pre class="code">
+ * public static void main(String[] args) {
+ * CommandLinePropertySource clps = ...;
+ * clps.setNonOptionArgsPropertyName("file.locations");
+ *
+ * AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ * ctx.getEnvironment().getPropertySources().addFirst(clps);
+ * ctx.register(AppConfig.class);
+ * ctx.refresh();
+ * }</pre>
+ *
+ * <h3>Limitations</h3>
+ * This abstraction is not intended to expose the full power of underlying command line
+ * parsing APIs such as JOpt or Commons CLI. It's intent is rather just the opposite: to
+ * provide the simplest possible abstraction for accessing command line arguments
+ * <em>after</em> they have been parsed. So the typical case will involve fully configuring
+ * the underlying command line parsing API, parsing the {@code String[]} of arguments
+ * coming into the main method, and then simply providing the parsing results to an
+ * implementation of {@code CommandLinePropertySource}. At that point, all arguments can
+ * be considered either 'option' or 'non-option' arguments and as described above can be
+ * accessed through the normal {@code PropertySource} and {@code Environment} APIs.
+ *
+ * @author Chris Beams
+ * @since 3.1
+ * @see PropertySource
+ * @see SimpleCommandLinePropertySource
+ * @see JOptCommandLinePropertySource
+ */
+public abstract class CommandLinePropertySource<T> extends PropertySource<T> {
+
+ /** The default name given to {@link CommandLinePropertySource} instances: {@value} */
+ public static final String COMMAND_LINE_PROPERTY_SOURCE_NAME = "commandLineArgs";
+
+ /** The default name of the property representing non-option arguments: {@value} */
+ public static final String DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME = "nonOptionArgs";
+
+
+ private String nonOptionArgsPropertyName = DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME;
+
+
+ /**
+ * Create a new {@code CommandLinePropertySource} having the default name
+ * {@value #COMMAND_LINE_PROPERTY_SOURCE_NAME} and backed by the given source object.
+ */
+ public CommandLinePropertySource(T source) {
+ super(COMMAND_LINE_PROPERTY_SOURCE_NAME, source);
+ }
+
+ /**
+ * Create a new {@link CommandLinePropertySource} having the given name
+ * and backed by the given source object.
+ */
+ public CommandLinePropertySource(String name, T source) {
+ super(name, source);
+ }
+
+
+ /**
+ * Specify the name of the special "non-option arguments" property.
+ * The default is {@value #DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME}.
+ */
+ public void setNonOptionArgsPropertyName(String nonOptionArgsPropertyName) {
+ this.nonOptionArgsPropertyName = nonOptionArgsPropertyName;
+ }
+
+ /**
+ * This implementation first checks to see if the name specified is the special
+ * {@linkplain #setNonOptionArgsPropertyName(String) "non-option arguments" property},
+ * and if so delegates to the abstract {@link #getNonOptionArgs()} method
+ * checking to see whether it returns an empty collection. Otherwise delegates to and
+ * returns the value of the abstract {@link #containsOption(String)} method.
+ */
+ @Override
+ public final boolean containsProperty(String name) {
+ if (this.nonOptionArgsPropertyName.equals(name)) {
+ return !this.getNonOptionArgs().isEmpty();
+ }
+ return this.containsOption(name);
+ }
+
+ /**
+ * This implementation first checks to see if the name specified is the special
+ * {@linkplain #setNonOptionArgsPropertyName(String) "non-option arguments" property},
+ * and if so delegates to the abstract {@link #getNonOptionArgs()} method. If so
+ * and the collection of non-option arguments is empty, this method returns {@code
+ * null}. If not empty, it returns a comma-separated String of all non-option
+ * arguments. Otherwise delegates to and returns the result of the abstract {@link
+ * #getOptionValues(String)} method.
+ */
+ @Override
+ public final String getProperty(String name) {
+ if (this.nonOptionArgsPropertyName.equals(name)) {
+ Collection<String> nonOptionArguments = this.getNonOptionArgs();
+ if (nonOptionArguments.isEmpty()) {
+ return null;
+ }
+ else {
+ return StringUtils.collectionToCommaDelimitedString(nonOptionArguments);
+ }
+ }
+ Collection<String> optionValues = this.getOptionValues(name);
+ if (optionValues == null) {
+ return null;
+ }
+ else {
+ return StringUtils.collectionToCommaDelimitedString(optionValues);
+ }
+ }
+
+
+ /**
+ * Return whether the set of option arguments parsed from the command line contains
+ * an option with the given name.
+ */
+ protected abstract boolean containsOption(String name);
+
+ /**
+ * Return the collection of values associated with the command line option having the
+ * given name.
+ * <ul>
+ * <li>if the option is present and has no argument (e.g.: "--foo"), return an empty
+ * collection ({@code []})</li>
+ * <li>if the option is present and has a single value (e.g. "--foo=bar"), return a
+ * collection having one element ({@code ["bar"]})</li>
+ * <li>if the option is present and the underlying command line parsing library
+ * supports multiple arguments (e.g. "--foo=bar --foo=baz"), return a collection
+ * having elements for each value ({@code ["bar", "baz"]})</li>
+ * <li>if the option is not present, return {@code null}</li>
+ * </ul>
+ */
+ protected abstract List<String> getOptionValues(String name);
+
+ /**
+ * Return the collection of non-option arguments parsed from the command line.
+ * Never {@code null}.
+ */
+ protected abstract List<String> getNonOptionArgs();
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/CompositePropertySource.java b/spring-core/src/main/java/org/springframework/core/env/CompositePropertySource.java
new file mode 100644
index 00000000..f05d0ce4
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/CompositePropertySource.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2002-2014 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.env;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Composite {@link PropertySource} implementation that iterates over a set of
+ * {@link PropertySource} instances. Necessary in cases where multiple property sources
+ * share the same name, e.g. when multiple values are supplied to {@code @PropertySource}.
+ *
+ * @author Chris Beams
+ * @since 3.1.1
+ */
+public class CompositePropertySource extends PropertySource<Object> {
+
+ private final Set<PropertySource<?>> propertySources = new LinkedHashSet<PropertySource<?>>();
+
+
+ /**
+ * Create a new {@code CompositePropertySource}.
+ * @param name the name of the property source
+ */
+ public CompositePropertySource(String name) {
+ super(name);
+ }
+
+
+ @Override
+ public Object getProperty(String name) {
+ for (PropertySource<?> propertySource : this.propertySources) {
+ Object candidate = propertySource.getProperty(name);
+ if (candidate != null) {
+ return candidate;
+ }
+ }
+ return null;
+ }
+
+ public void addPropertySource(PropertySource<?> propertySource) {
+ this.propertySources.add(propertySource);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s [name='%s', propertySources=%s]",
+ getClass().getSimpleName(), this.name, this.propertySources);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java b/spring-core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java
new file mode 100644
index 00000000..9e7cb58e
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2002-2012 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.env;
+
+import java.util.Map;
+
+/**
+ * Configuration interface to be implemented by most if not all {@link Environment} types.
+ * Provides facilities for setting active and default profiles and manipulating underlying
+ * property sources. Allows clients to set and validate required properties, customize the
+ * conversion service and more through the {@link ConfigurablePropertyResolver}
+ * superinterface.
+ *
+ * <h2>Manipulating property sources</h2>
+ * <p>Property sources may be removed, reordered, or replaced; and additional
+ * property sources may be added using the {@link MutablePropertySources}
+ * instance returned from {@link #getPropertySources()}. The following examples
+ * are against the {@link StandardEnvironment} implementation of
+ * {@code ConfigurableEnvironment}, but are generally applicable to any implementation,
+ * though particular default property sources may differ.
+ *
+ * <h4>Example: adding a new property source with highest search priority</h4>
+ * <pre class="code">
+ * ConfigurableEnvironment environment = new StandardEnvironment();
+ * MutablePropertySources propertySources = environment.getPropertySources();
+ * Map<String, String> myMap = new HashMap<String, String>();
+ * myMap.put("xyz", "myValue");
+ * propertySources.addFirst(new MapPropertySource("MY_MAP", myMap));
+ * </pre>
+ *
+ * <h4>Example: removing the default system properties property source</h4>
+ * <pre class="code">
+ * MutablePropertySources propertySources = environment.getPropertySources();
+ * propertySources.remove(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)
+ * </pre>
+ *
+ * <h4>Example: mocking the system environment for testing purposes</h4>
+ * <pre class="code">
+ * MutablePropertySources propertySources = environment.getPropertySources();
+ * MockPropertySource mockEnvVars = new MockPropertySource().withProperty("xyz", "myValue");
+ * propertySources.replace(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, mockEnvVars);
+ * </pre>
+ *
+ * When an {@link Environment} is being used by an {@code ApplicationContext}, it is
+ * important that any such {@code PropertySource} manipulations be performed
+ * <em>before</em> the context's {@link
+ * org.springframework.context.support.AbstractApplicationContext#refresh() refresh()}
+ * method is called. This ensures that all property sources are available during the
+ * container bootstrap process, including use by {@linkplain
+ * org.springframework.context.support.PropertySourcesPlaceholderConfigurer property
+ * placeholder configurers}.
+ *
+ *
+ * @author Chris Beams
+ * @since 3.1
+ * @see StandardEnvironment
+ * @see org.springframework.context.ConfigurableApplicationContext#getEnvironment
+ */
+public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
+
+ /**
+ * Specify the set of profiles active for this {@code Environment}. Profiles are
+ * evaluated during container bootstrap to determine whether bean definitions
+ * should be registered with the container.
+ * <p>Any existing active profiles will be replaced with the given arguments; call
+ * with zero arguments to clear the current set of active profiles. Use
+ * {@link #addActiveProfile} to add a profile while preserving the existing set.
+ * @see #addActiveProfile
+ * @see #setDefaultProfiles
+ * @see org.springframework.context.annotation.Profile
+ * @see AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME
+ * @throws IllegalArgumentException if any profile is null, empty or whitespace-only
+ */
+ void setActiveProfiles(String... profiles);
+
+ /**
+ * Add a profile to the current set of active profiles.
+ * @see #setActiveProfiles
+ * @throws IllegalArgumentException if the profile is null, empty or whitespace-only
+ */
+ void addActiveProfile(String profile);
+
+ /**
+ * Specify the set of profiles to be made active by default if no other profiles
+ * are explicitly made active through {@link #setActiveProfiles}.
+ * @see AbstractEnvironment#DEFAULT_PROFILES_PROPERTY_NAME
+ * @throws IllegalArgumentException if any profile is null, empty or whitespace-only
+ */
+ void setDefaultProfiles(String... profiles);
+
+ /**
+ * Return the {@link PropertySources} for this {@code Environment} in mutable form,
+ * allowing for manipulation of the set of {@link PropertySource} objects that should
+ * be searched when resolving properties against this {@code Environment} object.
+ * The various {@link MutablePropertySources} methods such as
+ * {@link MutablePropertySources#addFirst addFirst},
+ * {@link MutablePropertySources#addFirst addLast},
+ * {@link MutablePropertySources#addFirst addBefore} and
+ * {@link MutablePropertySources#addFirst addAfter} allow for fine-grained control
+ * over property source ordering. This is useful, for example, in ensuring that
+ * certain user-defined property sources have search precedence over default property
+ * sources such as the set of system properties or the set of system environment
+ * variables.
+ * @see AbstractEnvironment#customizePropertySources
+ */
+ MutablePropertySources getPropertySources();
+
+ /**
+ * Return the value of {@link System#getenv()} if allowed by the current
+ * {@link SecurityManager}, otherwise return a map implementation that will attempt
+ * to access individual keys using calls to {@link System#getenv(String)}.
+ * <p>Note that most {@link Environment} implementations will include this system
+ * environment map as a default {@link PropertySource} to be searched. Therefore, it
+ * is recommended that this method not be used directly unless bypassing other
+ * property sources is expressly intended.
+ * <p>Calls to {@link Map#get(Object)} on the Map returned will never throw
+ * {@link IllegalAccessException}; in cases where the SecurityManager forbids access
+ * to a property, {@code null} will be returned and an INFO-level log message will be
+ * issued noting the exception.
+ */
+ Map<String, Object> getSystemEnvironment();
+
+ /**
+ * Return the value of {@link System#getProperties()} if allowed by the current
+ * {@link SecurityManager}, otherwise return a map implementation that will attempt
+ * to access individual keys using calls to {@link System#getProperty(String)}.
+ * <p>Note that most {@code Environment} implementations will include this system
+ * properties map as a default {@link PropertySource} to be searched. Therefore, it is
+ * recommended that this method not be used directly unless bypassing other property
+ * sources is expressly intended.
+ * <p>Calls to {@link Map#get(Object)} on the Map returned will never throw
+ * {@link IllegalAccessException}; in cases where the SecurityManager forbids access
+ * to a property, {@code null} will be returned and an INFO-level log message will be
+ * issued noting the exception.
+ */
+ Map<String, Object> getSystemProperties();
+
+ /**
+ * Append the given parent environment's active profiles, default profiles and
+ * property sources to this (child) environment's respective collections of each.
+ * <p>For any identically-named {@code PropertySource} instance existing in both
+ * parent and child, the child instance is to be preserved and the parent instance
+ * discarded. This has the effect of allowing overriding of property sources by the
+ * child as well as avoiding redundant searches through common property source types,
+ * e.g. system environment and system properties.
+ * <p>Active and default profile names are also filtered for duplicates, to avoid
+ * confusion and redundant storage.
+ * <p>The parent environment remains unmodified in any case. Note that any changes to
+ * the parent environment occurring after the call to {@code merge} will not be
+ * reflected in the child. Therefore, care should be taken to configure parent
+ * property sources and profile information prior to calling {@code merge}.
+ * @param parent the environment to merge with
+ * @since 3.1.2
+ * @see org.springframework.context.support.AbstractApplicationContext#setParent
+ */
+ void merge(ConfigurableEnvironment parent);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java b/spring-core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java
new file mode 100644
index 00000000..e1ec8a27
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2002-2014 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.env;
+
+import org.springframework.core.convert.support.ConfigurableConversionService;
+
+/**
+ * Configuration interface to be implemented by most if not all {@link PropertyResolver
+ * PropertyResolver} types. Provides facilities for accessing and customizing the
+ * {@link org.springframework.core.convert.ConversionService ConversionService} used when
+ * converting property values from one type to another.
+ *
+ * @author Chris Beams
+ * @since 3.1
+ */
+public interface ConfigurablePropertyResolver extends PropertyResolver {
+
+ /**
+ * @return the {@link ConfigurableConversionService} used when performing type
+ * conversions on properties.
+ * <p>The configurable nature of the returned conversion service allows for
+ * the convenient addition and removal of individual {@code Converter} instances:
+ * <pre class="code">
+ * ConfigurableConversionService cs = env.getConversionService();
+ * cs.addConverter(new FooConverter());
+ * </pre>
+ * @see PropertyResolver#getProperty(String, Class)
+ * @see org.springframework.core.convert.converter.ConverterRegistry#addConverter
+ */
+ ConfigurableConversionService getConversionService();
+
+ /**
+ * Set the {@link ConfigurableConversionService} to be used when performing type
+ * conversions on properties.
+ * <p><strong>Note:</strong> as an alternative to fully replacing the {@code
+ * ConversionService}, consider adding or removing individual {@code Converter}
+ * instances by drilling into {@link #getConversionService()} and calling methods
+ * such as {@code #addConverter}.
+ * @see PropertyResolver#getProperty(String, Class)
+ * @see #getConversionService()
+ * @see org.springframework.core.convert.converter.ConverterRegistry#addConverter
+ */
+ void setConversionService(ConfigurableConversionService conversionService);
+
+ /**
+ * Set the prefix that placeholders replaced by this resolver must begin with.
+ */
+ void setPlaceholderPrefix(String placeholderPrefix);
+
+ /**
+ * Set the suffix that placeholders replaced by this resolver must end with.
+ */
+ void setPlaceholderSuffix(String placeholderSuffix);
+
+ /**
+ * Specify the separating character between the placeholders replaced by this
+ * resolver and their associated default value, or {@code null} if no such
+ * special character should be processed as a value separator.
+ */
+ void setValueSeparator(String valueSeparator);
+
+ /**
+ * Set whether to throw an exception when encountering an unresolvable placeholder
+ * nested within the value of a given property. A {@code false} value indicates strict
+ * resolution, i.e. that an exception will be thrown. A {@code true} value indicates
+ * that unresolvable nested placeholders should be passed through in their unresolved
+ * ${...} form.
+ * <p>Implementations of {@link #getProperty(String)} and its variants must inspect
+ * the value set here to determine correct behavior when property values contain
+ * unresolvable placeholders.
+ * @since 3.2
+ */
+ void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);
+
+ /**
+ * Specify which properties must be present, to be verified by
+ * {@link #validateRequiredProperties()}.
+ */
+ void setRequiredProperties(String... requiredProperties);
+
+ /**
+ * Validate that each of the properties specified by
+ * {@link #setRequiredProperties} is present and resolves to a
+ * non-{@code null} value.
+ * @throws MissingRequiredPropertiesException if any of the required
+ * properties are not resolvable.
+ */
+ void validateRequiredProperties() throws MissingRequiredPropertiesException;
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/EnumerablePropertySource.java b/spring-core/src/main/java/org/springframework/core/env/EnumerablePropertySource.java
new file mode 100644
index 00000000..c6742025
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/EnumerablePropertySource.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2002-2014 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.env;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.util.Assert;
+
+/**
+ * A {@link PropertySource} implementation capable of interrogating its
+ * underlying source object to enumerate all possible property name/value
+ * pairs. Exposes the {@link #getPropertyNames()} method to allow callers
+ * to introspect available properties without having to access the underlying
+ * source object. This also facilitates a more efficient implementation of
+ * {@link #containsProperty(String)}, in that it can call {@link #getPropertyNames()}
+ * and iterate through the returned array rather than attempting a call to
+ * {@link #getProperty(String)} which may be more expensive. Implementations may
+ * consider caching the result of {@link #getPropertyNames()} to fully exploit this
+ * performance opportunity.
+ *
+ * Most framework-provided {@code PropertySource} implementations are enumerable;
+ * a counter-example would be {@code JndiPropertySource} where, due to the
+ * nature of JNDI it is not possible to determine all possible property names at
+ * any given time; rather it is only possible to try to access a property
+ * (via {@link #getProperty(String)}) in order to evaluate whether it is present
+ * or not.
+ *
+ * @author Chris Beams
+ * @author Juergen Hoeller
+ * @since 3.1
+ */
+public abstract class EnumerablePropertySource<T> extends PropertySource<T> {
+
+ @Deprecated
+ protected static final String[] EMPTY_NAMES_ARRAY = new String[0];
+
+ protected final Log logger = LogFactory.getLog(getClass());
+
+
+ public EnumerablePropertySource(String name, T source) {
+ super(name, source);
+ }
+
+
+ /**
+ * Return whether this {@code PropertySource} contains a property with the given name.
+ * <p>This implementation checks for the presence of the given name within the
+ * {@link #getPropertyNames()} array.
+ * @param name the name of the property to find
+ */
+ public boolean containsProperty(String name) {
+ Assert.notNull(name, "Property name must not be null");
+ for (String candidate : getPropertyNames()) {
+ if (candidate.equals(name)) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("PropertySource [%s] contains '%s'", getName(), name));
+ }
+ return true;
+ }
+ }
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("PropertySource [%s] does not contain '%s'", getName(), name));
+ }
+ return false;
+ }
+
+ /**
+ * Return the names of all properties contained by the
+ * {@linkplain #getSource() source} object (never {@code null}).
+ */
+ public abstract String[] getPropertyNames();
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/Environment.java b/spring-core/src/main/java/org/springframework/core/env/Environment.java
new file mode 100644
index 00000000..17767732
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/Environment.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2002-2013 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.env;
+
+/**
+ * Interface representing the environment in which the current application is running.
+ * Models two key aspects of the application environment: <em>profiles</em> and
+ * <em>properties</em>. Methods related to property access are exposed via the
+ * {@link PropertyResolver} superinterface.
+ *
+ * <p>A <em>profile</em> is a named, logical group of bean definitions to be registered
+ * with the container only if the given profile is <em>active</em>. Beans may be assigned
+ * to a profile whether defined in XML or via annotations; see the spring-beans 3.1 schema
+ * or the {@link org.springframework.context.annotation.Profile @Profile} annotation for
+ * syntax details. The role of the {@code Environment} object with relation to profiles is
+ * in determining which profiles (if any) are currently {@linkplain #getActiveProfiles
+ * active}, and which profiles (if any) should be {@linkplain #getDefaultProfiles active
+ * by default}.
+ *
+ * <p><em>Properties</em> play an important role in almost all applications, and may
+ * originate from a variety of sources: properties files, JVM system properties, system
+ * environment variables, JNDI, servlet context parameters, ad-hoc Properties objects,
+ * Maps, and so on. The role of the environment object with relation to properties is to
+ * provide the user with a convenient service interface for configuring property sources
+ * and resolving properties from them.
+ *
+ * <p>Beans managed within an {@code ApplicationContext} may register to be {@link
+ * org.springframework.context.EnvironmentAware EnvironmentAware} or {@code @Inject} the
+ * {@code Environment} in order to query profile state or resolve properties directly.
+ *
+ * <p>In most cases, however, application-level beans should not need to interact with the
+ * {@code Environment} directly but instead may have to have {@code ${...}} property
+ * values replaced by a property placeholder configurer such as
+ * {@link org.springframework.context.support.PropertySourcesPlaceholderConfigurer
+ * PropertySourcesPlaceholderConfigurer}, which itself is {@code EnvironmentAware} and
+ * as of Spring 3.1 is registered by default when using
+ * {@code <context:property-placeholder/>}.
+ *
+ * <p>Configuration of the environment object must be done through the
+ * {@code ConfigurableEnvironment} interface, returned from all
+ * {@code AbstractApplicationContext} subclass {@code getEnvironment()} methods. See
+ * {@link ConfigurableEnvironment} Javadoc for usage examples demonstrating manipulation
+ * of property sources prior to application context {@code refresh()}.
+ *
+ * @author Chris Beams
+ * @since 3.1
+ * @see PropertyResolver
+ * @see EnvironmentCapable
+ * @see ConfigurableEnvironment
+ * @see AbstractEnvironment
+ * @see StandardEnvironment
+ * @see org.springframework.context.EnvironmentAware
+ * @see org.springframework.context.ConfigurableApplicationContext#getEnvironment
+ * @see org.springframework.context.ConfigurableApplicationContext#setEnvironment
+ * @see org.springframework.context.support.AbstractApplicationContext#createEnvironment
+ */
+public interface Environment extends PropertyResolver {
+
+ /**
+ * Return the set of profiles explicitly made active for this environment. Profiles
+ * are used for creating logical groupings of bean definitions to be registered
+ * conditionally, for example based on deployment environment. Profiles can be
+ * activated by setting {@linkplain AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME
+ * "spring.profiles.active"} as a system property or by calling
+ * {@link ConfigurableEnvironment#setActiveProfiles(String...)}.
+ * <p>If no profiles have explicitly been specified as active, then any {@linkplain
+ * #getDefaultProfiles() default profiles} will automatically be activated.
+ * @see #getDefaultProfiles
+ * @see ConfigurableEnvironment#setActiveProfiles
+ * @see AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME
+ */
+ String[] getActiveProfiles();
+
+ /**
+ * Return the set of profiles to be active by default when no active profiles have
+ * been set explicitly.
+ * @see #getActiveProfiles
+ * @see ConfigurableEnvironment#setDefaultProfiles
+ * @see AbstractEnvironment#DEFAULT_PROFILES_PROPERTY_NAME
+ */
+ String[] getDefaultProfiles();
+
+ /**
+ * Return whether one or more of the given profiles is active or, in the case of no
+ * explicit active profiles, whether one or more of the given profiles is included in
+ * the set of default profiles. If a profile begins with '!' the logic is inverted,
+ * i.e. the method will return true if the given profile is <em>not</em> active.
+ * For example, <pre class="code">env.acceptsProfiles("p1", "!p2")</pre> will
+ * return {@code true} if profile 'p1' is active or 'p2' is not active.
+ * @throws IllegalArgumentException if called with zero arguments
+ * or if any profile is {@code null}, empty or whitespace-only
+ * @see #getActiveProfiles
+ * @see #getDefaultProfiles
+ */
+ boolean acceptsProfiles(String... profiles);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/EnvironmentCapable.java b/spring-core/src/main/java/org/springframework/core/env/EnvironmentCapable.java
new file mode 100644
index 00000000..7d6ca387
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/EnvironmentCapable.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2013 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.env;
+
+/**
+ * Interface indicating a component that contains and exposes an {@link Environment} reference.
+ *
+ * <p>All Spring application contexts are EnvironmentCapable, and the interface is used primarily
+ * for performing {@code instanceof} checks in framework methods that accept BeanFactory
+ * instances that may or may not actually be ApplicationContext instances in order to interact
+ * with the environment if indeed it is available.
+ *
+ * <p>As mentioned, {@link org.springframework.context.ApplicationContext ApplicationContext}
+ * extends EnvironmentCapable, and thus exposes a {@link #getEnvironment()} method; however,
+ * {@link org.springframework.context.ConfigurableApplicationContext ConfigurableApplicationContext}
+ * redefines {@link org.springframework.context.ConfigurableApplicationContext#getEnvironment
+ * getEnvironment()} and narrows the signature to return a {@link ConfigurableEnvironment}.
+ * The effect is that an Environment object is 'read-only' until it is being accessed from
+ * a ConfigurableApplicationContext, at which point it too may be configured.
+ *
+ * @author Chris Beams
+ * @since 3.1
+ * @see Environment
+ * @see ConfigurableEnvironment
+ * @see org.springframework.context.ConfigurableApplicationContext#getEnvironment()
+ */
+public interface EnvironmentCapable {
+
+ /**
+ * Return the {@link Environment} associated with this component.
+ */
+ Environment getEnvironment();
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/JOptCommandLinePropertySource.java b/spring-core/src/main/java/org/springframework/core/env/JOptCommandLinePropertySource.java
new file mode 100644
index 00000000..61ed06a6
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/JOptCommandLinePropertySource.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2002-2014 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.env;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import joptsimple.OptionSet;
+
+import org.springframework.util.Assert;
+
+/**
+ * {@link CommandLinePropertySource} implementation backed by a JOpt {@link OptionSet}.
+ *
+ * <h2>Typical usage</h2>
+ * Configure and execute an {@code OptionParser} against the {@code String[]} of arguments
+ * supplied to the {@code main} method, and create a {@link JOptCommandLinePropertySource}
+ * using the resulting {@code OptionSet} object:
+ * <pre class="code">
+ * public static void main(String[] args) {
+ * OptionParser parser = new OptionParser();
+ * parser.accepts("option1");
+ * parser.accepts("option2").withRequiredArg();
+ * OptionSet options = parser.parse(args);
+ * PropertySource<?> ps = new JOptCommandLinePropertySource(options);
+ * // ...
+ * }</pre>
+ *
+ * See {@link CommandLinePropertySource} for complete general usage examples.
+ *
+ * <p>Requires JOpt version 3.0 or higher. Tested against JOpt up until 4.6.
+ *
+ * @author Chris Beams
+ * @author Juergen Hoeller
+ * @since 3.1
+ * @see CommandLinePropertySource
+ * @see joptsimple.OptionParser
+ * @see joptsimple.OptionSet
+ */
+public class JOptCommandLinePropertySource extends CommandLinePropertySource<OptionSet> {
+
+ /**
+ * Create a new {@code JOptCommandLinePropertySource} having the default name
+ * and backed by the given {@code OptionSet}.
+ * @see CommandLinePropertySource#COMMAND_LINE_PROPERTY_SOURCE_NAME
+ * @see CommandLinePropertySource#CommandLinePropertySource(Object)
+ */
+ public JOptCommandLinePropertySource(OptionSet options) {
+ super(options);
+ }
+
+ /**
+ * Create a new {@code JOptCommandLinePropertySource} having the given name
+ * and backed by the given {@code OptionSet}.
+ */
+ public JOptCommandLinePropertySource(String name, OptionSet options) {
+ super(name, options);
+ }
+
+
+ @Override
+ protected boolean containsOption(String name) {
+ return this.source.has(name);
+ }
+
+ @Override
+ public List<String> getOptionValues(String name) {
+ List<?> argValues = this.source.valuesOf(name);
+ List<String> stringArgValues = new ArrayList<String>();
+ for (Object argValue : argValues) {
+ Assert.isInstanceOf(String.class, argValue, "Argument values must be of type String");
+ stringArgValues.add((String) argValue);
+ }
+ if (stringArgValues.isEmpty()) {
+ return (this.source.has(name) ? Collections.<String>emptyList() : null);
+ }
+ return Collections.unmodifiableList(stringArgValues);
+ }
+
+ @Override
+ protected List<String> getNonOptionArgs() {
+ List<?> argValues = this.source.nonOptionArguments();
+ List<String> stringArgValues = new ArrayList<String>();
+ for (Object argValue : argValues) {
+ Assert.isInstanceOf(String.class, argValue, "Argument values must be of type String");
+ stringArgValues.add((String) argValue);
+ }
+ return (stringArgValues.isEmpty() ? Collections.<String>emptyList() :
+ Collections.unmodifiableList(stringArgValues));
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/MapPropertySource.java b/spring-core/src/main/java/org/springframework/core/env/MapPropertySource.java
new file mode 100644
index 00000000..3bcd2fed
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/MapPropertySource.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2014 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.env;
+
+import java.util.Map;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link PropertySource} that reads keys and values from a {@code Map} object.
+ *
+ * @author Chris Beams
+ * @since 3.1
+ * @see PropertiesPropertySource
+ */
+public class MapPropertySource extends EnumerablePropertySource<Map<String, Object>> {
+
+ public MapPropertySource(String name, Map<String, Object> source) {
+ super(name, source);
+ }
+
+
+ @Override
+ public Object getProperty(String name) {
+ return this.source.get(name);
+ }
+
+ @Override
+ public String[] getPropertyNames() {
+ return StringUtils.toStringArray(this.source.keySet());
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/MissingRequiredPropertiesException.java b/spring-core/src/main/java/org/springframework/core/env/MissingRequiredPropertiesException.java
new file mode 100644
index 00000000..8c4d338e
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/MissingRequiredPropertiesException.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.env;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Exception thrown when required properties are not found.
+ *
+ * @author Chris Beams
+ * @since 3.1
+ * @see ConfigurablePropertyResolver#setRequiredProperties(String...)
+ * @see ConfigurablePropertyResolver#validateRequiredProperties()
+ * @see org.springframework.context.support.AbstractApplicationContext#prepareRefresh()
+ */
+@SuppressWarnings("serial")
+public class MissingRequiredPropertiesException extends IllegalStateException {
+
+ private final Set<String> missingRequiredProperties = new LinkedHashSet<String>();
+
+ /**
+ * Return the set of properties marked as required but not present
+ * upon validation.
+ * @see ConfigurablePropertyResolver#setRequiredProperties(String...)
+ * @see ConfigurablePropertyResolver#validateRequiredProperties()
+ */
+ public Set<String> getMissingRequiredProperties() {
+ return missingRequiredProperties;
+ }
+
+ void addMissingRequiredProperty(String key) {
+ missingRequiredProperties.add(key);
+ }
+
+ @Override
+ public String getMessage() {
+ return String.format(
+ "The following properties were declared as required but could " +
+ "not be resolved: %s", this.getMissingRequiredProperties());
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/MutablePropertySources.java b/spring-core/src/main/java/org/springframework/core/env/MutablePropertySources.java
new file mode 100644
index 00000000..507704bd
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/MutablePropertySources.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2002-2014 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.env;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Default implementation of the {@link PropertySources} interface.
+ * Allows manipulation of contained property sources and provides a constructor
+ * for copying an existing {@code PropertySources} instance.
+ *
+ * <p>Where <em>precedence</em> is mentioned in methods such as {@link #addFirst}
+ * and {@link #addLast}, this is with regard to the order in which property sources
+ * will be searched when resolving a given property with a {@link PropertyResolver}.
+ *
+ * @author Chris Beams
+ * @since 3.1
+ * @see PropertySourcesPropertyResolver
+ */
+public class MutablePropertySources implements PropertySources {
+
+ static final String NON_EXISTENT_PROPERTY_SOURCE_MESSAGE = "PropertySource named [%s] does not exist";
+ static final String ILLEGAL_RELATIVE_ADDITION_MESSAGE = "PropertySource named [%s] cannot be added relative to itself";
+
+ private final Log logger;
+
+ private final LinkedList<PropertySource<?>> propertySourceList = new LinkedList<PropertySource<?>>();
+
+
+ /**
+ * Create a new {@link MutablePropertySources} object.
+ */
+ public MutablePropertySources() {
+ this.logger = LogFactory.getLog(this.getClass());
+ }
+
+ /**
+ * Create a new {@code MutablePropertySources} from the given propertySources
+ * object, preserving the original order of contained {@code PropertySource} objects.
+ */
+ public MutablePropertySources(PropertySources propertySources) {
+ this();
+ for (PropertySource<?> propertySource : propertySources) {
+ this.addLast(propertySource);
+ }
+ }
+
+ /**
+ * Create a new {@link MutablePropertySources} object and inherit the given logger,
+ * usually from an enclosing {@link Environment}.
+ */
+ MutablePropertySources(Log logger) {
+ this.logger = logger;
+ }
+
+
+ public boolean contains(String name) {
+ return this.propertySourceList.contains(PropertySource.named(name));
+ }
+
+ public PropertySource<?> get(String name) {
+ int index = this.propertySourceList.indexOf(PropertySource.named(name));
+ return index == -1 ? null : this.propertySourceList.get(index);
+ }
+
+ public Iterator<PropertySource<?>> iterator() {
+ return this.propertySourceList.iterator();
+ }
+
+ /**
+ * Add the given property source object with highest precedence.
+ */
+ public void addFirst(PropertySource<?> propertySource) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Adding [%s] PropertySource with highest search precedence",
+ propertySource.getName()));
+ }
+ removeIfPresent(propertySource);
+ this.propertySourceList.addFirst(propertySource);
+ }
+
+ /**
+ * Add the given property source object with lowest precedence.
+ */
+ public void addLast(PropertySource<?> propertySource) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Adding [%s] PropertySource with lowest search precedence",
+ propertySource.getName()));
+ }
+ removeIfPresent(propertySource);
+ this.propertySourceList.addLast(propertySource);
+ }
+
+ /**
+ * Add the given property source object with precedence immediately higher
+ * than the named relative property source.
+ */
+ public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Adding [%s] PropertySource with search precedence immediately higher than [%s]",
+ propertySource.getName(), relativePropertySourceName));
+ }
+ assertLegalRelativeAddition(relativePropertySourceName, propertySource);
+ removeIfPresent(propertySource);
+ int index = assertPresentAndGetIndex(relativePropertySourceName);
+ addAtIndex(index, propertySource);
+ }
+
+ /**
+ * Add the given property source object with precedence immediately lower
+ * than the named relative property source.
+ */
+ public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Adding [%s] PropertySource with search precedence immediately lower than [%s]",
+ propertySource.getName(), relativePropertySourceName));
+ }
+ assertLegalRelativeAddition(relativePropertySourceName, propertySource);
+ removeIfPresent(propertySource);
+ int index = assertPresentAndGetIndex(relativePropertySourceName);
+ addAtIndex(index + 1, propertySource);
+ }
+
+ /**
+ * Return the precedence of the given property source, {@code -1} if not found.
+ */
+ public int precedenceOf(PropertySource<?> propertySource) {
+ return this.propertySourceList.indexOf(propertySource);
+ }
+
+ /**
+ * Remove and return the property source with the given name, {@code null} if not found.
+ * @param name the name of the property source to find and remove
+ */
+ public PropertySource<?> remove(String name) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Removing [%s] PropertySource", name));
+ }
+ int index = this.propertySourceList.indexOf(PropertySource.named(name));
+ return index == -1 ? null : this.propertySourceList.remove(index);
+ }
+
+ /**
+ * Replace the property source with the given name with the given property source object.
+ * @param name the name of the property source to find and replace
+ * @param propertySource the replacement property source
+ * @throws IllegalArgumentException if no property source with the given name is present
+ * @see #contains
+ */
+ public void replace(String name, PropertySource<?> propertySource) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Replacing [%s] PropertySource with [%s]",
+ name, propertySource.getName()));
+ }
+ int index = assertPresentAndGetIndex(name);
+ this.propertySourceList.set(index, propertySource);
+ }
+
+ /**
+ * Return the number of {@link PropertySource} objects contained.
+ */
+ public int size() {
+ return this.propertySourceList.size();
+ }
+
+ @Override
+ public String toString() {
+ String[] names = new String[this.size()];
+ for (int i=0; i < size(); i++) {
+ names[i] = this.propertySourceList.get(i).getName();
+ }
+ return String.format("[%s]", StringUtils.arrayToCommaDelimitedString(names));
+ }
+
+ /**
+ * Ensure that the given property source is not being added relative to itself.
+ */
+ protected void assertLegalRelativeAddition(String relativePropertySourceName, PropertySource<?> propertySource) {
+ String newPropertySourceName = propertySource.getName();
+ Assert.isTrue(!relativePropertySourceName.equals(newPropertySourceName),
+ String.format(ILLEGAL_RELATIVE_ADDITION_MESSAGE, newPropertySourceName));
+ }
+
+ /**
+ * Remove the given property source if it is present.
+ */
+ protected void removeIfPresent(PropertySource<?> propertySource) {
+ if (this.propertySourceList.contains(propertySource)) {
+ this.propertySourceList.remove(propertySource);
+ }
+ }
+
+ /**
+ * Add the given property source at a particular index in the list.
+ */
+ private void addAtIndex(int index, PropertySource<?> propertySource) {
+ removeIfPresent(propertySource);
+ this.propertySourceList.add(index, propertySource);
+ }
+
+ /**
+ * Assert that the named property source is present and return its index.
+ * @param name the {@linkplain PropertySource#getName() name of the property source}
+ * to find
+ * @throws IllegalArgumentException if the named property source is not present
+ */
+ private int assertPresentAndGetIndex(String name) {
+ int index = this.propertySourceList.indexOf(PropertySource.named(name));
+ Assert.isTrue(index >= 0, String.format(NON_EXISTENT_PROPERTY_SOURCE_MESSAGE, name));
+ return index;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/PropertiesPropertySource.java b/spring-core/src/main/java/org/springframework/core/env/PropertiesPropertySource.java
new file mode 100644
index 00000000..492d142d
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/PropertiesPropertySource.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2013 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.env;
+
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * {@link PropertySource} implementation that extracts properties from a
+ * {@link java.util.Properties} object.
+ *
+ * <p>Note that because a {@code Properties} object is technically an
+ * {@code <Object, Object>} {@link java.util.Hashtable Hashtable}, one may contain
+ * non-{@code String} keys or values. This implementation, however is restricted to
+ * accessing only {@code String}-based keys and values, in the same fashion as
+ * {@link Properties#getProperty} and {@link Properties#setProperty}.
+ *
+ * @author Chris Beams
+ * @since 3.1
+ */
+public class PropertiesPropertySource extends MapPropertySource {
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public PropertiesPropertySource(String name, Properties source) {
+ super(name, (Map) source);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java b/spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java
new file mode 100644
index 00000000..b3ce15b6
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2002-2013 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.env;
+
+/**
+ * Interface for resolving properties against any underlying source.
+ *
+ * @author Chris Beams
+ * @since 3.1
+ * @see Environment
+ * @see PropertySourcesPropertyResolver
+ */
+public interface PropertyResolver {
+
+ /**
+ * Return whether the given property key is available for resolution, i.e.,
+ * the value for the given key is not {@code null}.
+ */
+ boolean containsProperty(String key);
+
+ /**
+ * Return the property value associated with the given key, or {@code null}
+ * if the key cannot be resolved.
+ * @param key the property name to resolve
+ * @see #getProperty(String, String)
+ * @see #getProperty(String, Class)
+ * @see #getRequiredProperty(String)
+ */
+ String getProperty(String key);
+
+ /**
+ * Return the property value associated with the given key, or
+ * {@code defaultValue} if the key cannot be resolved.
+ * @param key the property name to resolve
+ * @param defaultValue the default value to return if no value is found
+ * @see #getRequiredProperty(String)
+ * @see #getProperty(String, Class)
+ */
+ String getProperty(String key, String defaultValue);
+
+ /**
+ * Return the property value associated with the given key, or {@code null}
+ * if the key cannot be resolved.
+ * @param key the property name to resolve
+ * @param targetType the expected type of the property value
+ * @see #getRequiredProperty(String, Class)
+ */
+ <T> T getProperty(String key, Class<T> targetType);
+
+ /**
+ * Return the property value associated with the given key, or
+ * {@code defaultValue} if the key cannot be resolved.
+ * @param key the property name to resolve
+ * @param targetType the expected type of the property value
+ * @param defaultValue the default value to return if no value is found
+ * @see #getRequiredProperty(String, Class)
+ */
+ <T> T getProperty(String key, Class<T> targetType, T defaultValue);
+
+ /**
+ * Convert the property value associated with the given key to a {@code Class}
+ * of type {@code T} or {@code null} if the key cannot be resolved.
+ * @throws org.springframework.core.convert.ConversionException if class specified
+ * by property value cannot be found or loaded or if targetType is not assignable
+ * from class specified by property value
+ * @see #getProperty(String, Class)
+ */
+ <T> Class<T> getPropertyAsClass(String key, Class<T> targetType);
+
+ /**
+ * Return the property value associated with the given key, converted to the given
+ * targetType (never {@code null}).
+ * @throws IllegalStateException if the key cannot be resolved
+ * @see #getRequiredProperty(String, Class)
+ */
+ String getRequiredProperty(String key) throws IllegalStateException;
+
+ /**
+ * Return the property value associated with the given key, converted to the given
+ * targetType (never {@code null}).
+ * @throws IllegalStateException if the given key cannot be resolved
+ */
+ <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
+
+ /**
+ * Resolve ${...} placeholders in the given text, replacing them with corresponding
+ * property values as resolved by {@link #getProperty}. Unresolvable placeholders with
+ * no default value are ignored and passed through unchanged.
+ * @param text the String to resolve
+ * @return the resolved String (never {@code null})
+ * @throws IllegalArgumentException if given text is {@code null}
+ * @see #resolveRequiredPlaceholders
+ * @see org.springframework.util.SystemPropertyUtils#resolvePlaceholders(String)
+ */
+ String resolvePlaceholders(String text);
+
+ /**
+ * Resolve ${...} placeholders in the given text, replacing them with corresponding
+ * property values as resolved by {@link #getProperty}. Unresolvable placeholders with
+ * no default value will cause an IllegalArgumentException to be thrown.
+ * @return the resolved String (never {@code null})
+ * @throws IllegalArgumentException if given text is {@code null}
+ * or if any placeholders are unresolvable
+ * @see org.springframework.util.SystemPropertyUtils#resolvePlaceholders(String, boolean)
+ */
+ String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/PropertySource.java b/spring-core/src/main/java/org/springframework/core/env/PropertySource.java
new file mode 100644
index 00000000..3fbdd045
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/PropertySource.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2002-2014 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.env;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Abstract base class representing a source of name/value property pairs. The underlying
+ * {@linkplain #getSource() source object} may be of any type {@code T} that encapsulates
+ * properties. Examples include {@link java.util.Properties} objects, {@link java.util.Map}
+ * objects, {@code ServletContext} and {@code ServletConfig} objects (for access to init
+ * parameters). Explore the {@code PropertySource} type hierarchy to see provided
+ * implementations.
+ *
+ * <p>{@code PropertySource} objects are not typically used in isolation, but rather
+ * through a {@link PropertySources} object, which aggregates property sources and in
+ * conjunction with a {@link PropertyResolver} implementation that can perform
+ * precedence-based searches across the set of {@code PropertySources}.
+ *
+ * <p>{@code PropertySource} identity is determined not based on the content of
+ * encapsulated properties, but rather based on the {@link #getName() name} of the
+ * {@code PropertySource} alone. This is useful for manipulating {@code PropertySource}
+ * objects when in collection contexts. See operations in {@link MutablePropertySources}
+ * as well as the {@link #named(String)} and {@link #toString()} methods for details.
+ *
+ * <p>Note that when working with @{@link
+ * org.springframework.context.annotation.Configuration Configuration} classes that
+ * the @{@link org.springframework.context.annotation.PropertySource PropertySource}
+ * annotation provides a convenient and declarative way of adding property sources to the
+ * enclosing {@code Environment}.
+ *
+ * @author Chris Beams
+ * @since 3.1
+ * @see PropertySources
+ * @see PropertyResolver
+ * @see PropertySourcesPropertyResolver
+ * @see MutablePropertySources
+ * @see org.springframework.context.annotation.PropertySource
+ */
+public abstract class PropertySource<T> {
+
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ protected final String name;
+
+ protected final T source;
+
+
+ /**
+ * Create a new {@code PropertySource} with the given name and source object.
+ */
+ public PropertySource(String name, T source) {
+ Assert.hasText(name, "Property source name must contain at least one character");
+ Assert.notNull(source, "Property source must not be null");
+ this.name = name;
+ this.source = source;
+ }
+
+ /**
+ * Create a new {@code PropertySource} with the given name and with a new {@code Object}
+ * instance as the underlying source.
+ * <p>Often useful in testing scenarios when creating anonymous implementations that
+ * never query an actual source but rather return hard-coded values.
+ */
+ @SuppressWarnings("unchecked")
+ public PropertySource(String name) {
+ this(name, (T) new Object());
+ }
+
+
+ /**
+ * Return the name of this {@code PropertySource}
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Return the underlying source object for this {@code PropertySource}.
+ */
+ public T getSource() {
+ return this.source;
+ }
+
+ /**
+ * Return whether this {@code PropertySource} contains the given name.
+ * <p>This implementation simply checks for a {@code null} return value
+ * from {@link #getProperty(String)}. Subclasses may wish to implement
+ * a more efficient algorithm if possible.
+ * @param name the property name to find
+ */
+ public boolean containsProperty(String name) {
+ return (getProperty(name) != null);
+ }
+
+ /**
+ * Return the value associated with the given name,
+ * or {@code null} if not found.
+ * @param name the property to find
+ * @see PropertyResolver#getRequiredProperty(String)
+ */
+ public abstract Object getProperty(String name);
+
+
+ /**
+ * This {@code PropertySource} object is equal to the given object if:
+ * <ul>
+ * <li>they are the same instance
+ * <li>the {@code name} properties for both objects are equal
+ * </ul>
+ * <p>No properties other than {@code name} are evaluated.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return (this == obj || (obj instanceof PropertySource &&
+ ObjectUtils.nullSafeEquals(this.name, ((PropertySource<?>) obj).name)));
+ }
+
+ /**
+ * Return a hash code derived from the {@code name} property
+ * of this {@code PropertySource} object.
+ */
+ @Override
+ public int hashCode() {
+ return ObjectUtils.nullSafeHashCode(this.name);
+ }
+
+ /**
+ * Produce concise output (type and name) if the current log level does not include
+ * debug. If debug is enabled, produce verbose output including the hash code of the
+ * PropertySource instance and every name/value property pair.
+ * <p>This variable verbosity is useful as a property source such as system properties
+ * or environment variables may contain an arbitrary number of property pairs,
+ * potentially leading to difficult to read exception and log messages.
+ * @see Log#isDebugEnabled()
+ */
+ @Override
+ public String toString() {
+ if (logger.isDebugEnabled()) {
+ return String.format("%s@%s [name='%s', properties=%s]",
+ getClass().getSimpleName(), System.identityHashCode(this), this.name, this.source);
+ }
+ else {
+ return String.format("%s [name='%s']", getClass().getSimpleName(), this.name);
+ }
+ }
+
+
+ /**
+ * Return a {@code PropertySource} implementation intended for collection comparison purposes only.
+ * <p>Primarily for internal use, but given a collection of {@code PropertySource} objects, may be
+ * used as follows:
+ * <pre class="code">
+ * {@code List<PropertySource<?>> sources = new ArrayList<PropertySource<?>>();
+ * sources.add(new MapPropertySource("sourceA", mapA));
+ * sources.add(new MapPropertySource("sourceB", mapB));
+ * assert sources.contains(PropertySource.named("sourceA"));
+ * assert sources.contains(PropertySource.named("sourceB"));
+ * assert !sources.contains(PropertySource.named("sourceC"));
+ * }</pre>
+ * The returned {@code PropertySource} will throw {@code UnsupportedOperationException}
+ * if any methods other than {@code equals(Object)}, {@code hashCode()}, and {@code toString()}
+ * are called.
+ * @param name the name of the comparison {@code PropertySource} to be created and returned.
+ */
+ public static PropertySource<?> named(String name) {
+ return new ComparisonPropertySource(name);
+ }
+
+
+ /**
+ * {@code PropertySource} to be used as a placeholder in cases where an actual
+ * property source cannot be eagerly initialized at application context
+ * creation time. For example, a {@code ServletContext}-based property source
+ * must wait until the {@code ServletContext} object is available to its enclosing
+ * {@code ApplicationContext}. In such cases, a stub should be used to hold the
+ * intended default position/order of the property source, then be replaced
+ * during context refresh.
+ * @see org.springframework.context.support.AbstractApplicationContext#initPropertySources()
+ * @see org.springframework.web.context.support.StandardServletEnvironment
+ * @see org.springframework.web.context.support.ServletContextPropertySource
+ */
+ public static class StubPropertySource extends PropertySource<Object> {
+
+ public StubPropertySource(String name) {
+ super(name, new Object());
+ }
+
+ /**
+ * Always returns {@code null}.
+ */
+ @Override
+ public String getProperty(String name) {
+ return null;
+ }
+ }
+
+
+ /**
+ * @see PropertySource#named(String)
+ */
+ static class ComparisonPropertySource extends StubPropertySource {
+
+ private static final String USAGE_ERROR =
+ "ComparisonPropertySource instances are for use with collection comparison only";
+
+ public ComparisonPropertySource(String name) {
+ super(name);
+ }
+
+ @Override
+ public Object getSource() {
+ throw new UnsupportedOperationException(USAGE_ERROR);
+ }
+
+ @Override
+ public boolean containsProperty(String name) {
+ throw new UnsupportedOperationException(USAGE_ERROR);
+ }
+
+ @Override
+ public String getProperty(String name) {
+ throw new UnsupportedOperationException(USAGE_ERROR);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s [name='%s']", getClass().getSimpleName(), this.name);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/PropertySources.java b/spring-core/src/main/java/org/springframework/core/env/PropertySources.java
new file mode 100644
index 00000000..90f99594
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/PropertySources.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.env;
+
+/**
+ * Holder containing one or more {@link PropertySource} objects.
+ *
+ * @author Chris Beams
+ * @since 3.1
+ */
+public interface PropertySources extends Iterable<PropertySource<?>> {
+
+ /**
+ * Return whether a property source with the given name is contained.
+ * @param name the {@linkplain PropertySource#getName() name of the property source} to find
+ */
+ boolean contains(String name);
+
+ /**
+ * Return the property source with the given name, {@code null} if not found.
+ * @param name the {@linkplain PropertySource#getName() name of the property source} to find
+ */
+ PropertySource<?> get(String name);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java b/spring-core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java
new file mode 100644
index 00000000..9e336284
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2002-2013 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.env;
+
+import org.springframework.core.convert.ConversionException;
+import org.springframework.util.ClassUtils;
+
+/**
+ * {@link PropertyResolver} implementation that resolves property values against
+ * an underlying set of {@link PropertySources}.
+ *
+ * @author Chris Beams
+ * @since 3.1
+ * @see PropertySource
+ * @see PropertySources
+ * @see AbstractEnvironment
+ */
+public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
+
+ private final PropertySources propertySources;
+
+
+ /**
+ * Create a new resolver against the given property sources.
+ * @param propertySources the set of {@link PropertySource} objects to use
+ */
+ public PropertySourcesPropertyResolver(PropertySources propertySources) {
+ this.propertySources = propertySources;
+ }
+
+
+ @Override
+ public boolean containsProperty(String key) {
+ if (this.propertySources != null) {
+ for (PropertySource<?> propertySource : this.propertySources) {
+ if (propertySource.containsProperty(key)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String getProperty(String key) {
+ return getProperty(key, String.class, true);
+ }
+
+ @Override
+ public <T> T getProperty(String key, Class<T> targetValueType) {
+ return getProperty(key, targetValueType, true);
+ }
+
+ @Override
+ protected String getPropertyAsRawString(String key) {
+ return getProperty(key, String.class, false);
+ }
+
+ protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
+ boolean debugEnabled = logger.isDebugEnabled();
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("getProperty(\"%s\", %s)", key, targetValueType.getSimpleName()));
+ }
+ if (this.propertySources != null) {
+ for (PropertySource<?> propertySource : this.propertySources) {
+ if (debugEnabled) {
+ logger.debug(String.format("Searching for key '%s' in [%s]", key, propertySource.getName()));
+ }
+ Object value;
+ if ((value = propertySource.getProperty(key)) != null) {
+ Class<?> valueType = value.getClass();
+ if (resolveNestedPlaceholders && value instanceof String) {
+ value = resolveNestedPlaceholders((String) value);
+ }
+ if (debugEnabled) {
+ logger.debug(String.format("Found key '%s' in [%s] with type [%s] and value '%s'",
+ key, propertySource.getName(), valueType.getSimpleName(), value));
+ }
+ if (!this.conversionService.canConvert(valueType, targetValueType)) {
+ throw new IllegalArgumentException(String.format(
+ "Cannot convert value [%s] from source type [%s] to target type [%s]",
+ value, valueType.getSimpleName(), targetValueType.getSimpleName()));
+ }
+ return this.conversionService.convert(value, targetValueType);
+ }
+ }
+ }
+ if (debugEnabled) {
+ logger.debug(String.format("Could not find key '%s' in any property source. Returning [null]", key));
+ }
+ return null;
+ }
+
+ @Override
+ public <T> Class<T> getPropertyAsClass(String key, Class<T> targetValueType) {
+ boolean debugEnabled = logger.isDebugEnabled();
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("getPropertyAsClass(\"%s\", %s)", key, targetValueType.getSimpleName()));
+ }
+ if (this.propertySources != null) {
+ for (PropertySource<?> propertySource : this.propertySources) {
+ if (debugEnabled) {
+ logger.debug(String.format("Searching for key '%s' in [%s]", key, propertySource.getName()));
+ }
+ Object value = propertySource.getProperty(key);
+ if (value != null) {
+ if (debugEnabled) {
+ logger.debug(String.format("Found key '%s' in [%s] with value '%s'", key, propertySource.getName(), value));
+ }
+ Class<?> clazz;
+ if (value instanceof String) {
+ try {
+ clazz = ClassUtils.forName((String) value, null);
+ }
+ catch (Exception ex) {
+ throw new ClassConversionException((String) value, targetValueType, ex);
+ }
+ }
+ else if (value instanceof Class) {
+ clazz = (Class<?>)value;
+ }
+ else {
+ clazz = value.getClass();
+ }
+ if (!targetValueType.isAssignableFrom(clazz)) {
+ throw new ClassConversionException(clazz, targetValueType);
+ }
+ @SuppressWarnings("unchecked")
+ Class<T> targetClass = (Class<T>) clazz;
+ return targetClass;
+ }
+ }
+ }
+ if (debugEnabled) {
+ logger.debug(String.format("Could not find key '%s' in any property source. Returning [null]", key));
+ }
+ return null;
+ }
+
+
+ @SuppressWarnings("serial")
+ private static class ClassConversionException extends ConversionException {
+
+ public ClassConversionException(Class<?> actual, Class<?> expected) {
+ super(String.format("Actual type %s is not assignable to expected type %s", actual.getName(), expected.getName()));
+ }
+
+ public ClassConversionException(String actual, Class<?> expected, Exception ex) {
+ super(String.format("Could not find/load class %s during attempt to convert to %s", actual, expected.getName()), ex);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java b/spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java
new file mode 100644
index 00000000..2f770277
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2002-2013 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.env;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.util.Assert;
+
+/**
+ * Read-only {@code Map<String, String>} implementation that is backed by system
+ * properties or environment variables.
+ *
+ * <p>Used by {@link AbstractApplicationContext} when a {@link SecurityManager} prohibits
+ * access to {@link System#getProperties()} or {@link System#getenv()}. It is for this
+ * reason that the implementations of {@link #keySet()}, {@link #entrySet()}, and
+ * {@link #values()} always return empty even though {@link #get(Object)} may in fact
+ * return non-null if the current security manager allows access to individual keys.
+ *
+ * @author Arjen Poutsma
+ * @author Chris Beams
+ * @since 3.0
+ */
+abstract class ReadOnlySystemAttributesMap implements Map<String, String> {
+
+ public boolean containsKey(Object key) {
+ return (get(key) != null);
+ }
+
+ /**
+ * @param key the name of the system attribute to retrieve
+ * @throws IllegalArgumentException if given key is non-String
+ */
+ public String get(Object key) {
+ Assert.isInstanceOf(String.class, key,
+ String.format("Expected key [%s] to be of type String, got %s", key, key.getClass().getName()));
+ return this.getSystemAttribute((String) key);
+ }
+
+ public boolean isEmpty() {
+ return false;
+ }
+
+ /**
+ * Template method that returns the underlying system attribute.
+ * <p>Implementations typically call {@link System#getProperty(String)} or {@link System#getenv(String)} here.
+ */
+ protected abstract String getSystemAttribute(String attributeName);
+
+
+ // Unsupported
+
+ public int size() {
+ throw new UnsupportedOperationException();
+ }
+
+ public String put(String key, String value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean containsValue(Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public String remove(Object key) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Set<String> keySet() {
+ return Collections.emptySet();
+ }
+
+ public void putAll(Map<? extends String, ? extends String> map) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Collection<String> values() {
+ return Collections.emptySet();
+ }
+
+ public Set<Entry<String, String>> entrySet() {
+ return Collections.emptySet();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLineArgsParser.java b/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLineArgsParser.java
new file mode 100644
index 00000000..b249faf0
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLineArgsParser.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2013 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.env;
+
+/**
+ * Parses a {@code String[]} of command line arguments in order to populate a
+ * {@link CommandLineArgs} object.
+ *
+ * <h3>Working with option arguments</h3>
+ * Option arguments must adhere to the exact syntax:
+ * <pre class="code">--optName[=optValue]</pre>
+ * That is, options must be prefixed with "{@code --}", and may or may not specify a value.
+ * If a value is specified, the name and value must be separated <em>without spaces</em>
+ * by an equals sign ("=").
+ *
+ * <h4>Valid examples of option arguments</h4>
+ * <pre class="code">
+ * --foo
+ * --foo=bar
+ * --foo="bar then baz"
+ * --foo=bar,baz,biz</pre>
+ *
+ * <h4>Invalid examples of option arguments</h4>
+ * <pre class="code">
+ * -foo
+ * --foo bar
+ * --foo = bar
+ * --foo=bar --foo=baz --foo=biz</pre>
+ *
+ * <h3>Working with non-option arguments</h3>
+ * Any and all arguments specified at the command line without the "{@code --}" option
+ * prefix will be considered as "non-option arguments" and made available through the
+ * {@link CommandLineArgs#getNonOptionArgs()} method.
+ *
+ * @author Chris Beams
+ * @since 3.1
+ */
+class SimpleCommandLineArgsParser {
+
+ /**
+ * Parse the given {@code String} array based on the rules described
+ * {@linkplain SimpleCommandLineArgsParser above}, returning a
+ * fully-populated {@link CommandLineArgs} object.
+ * @param args command line arguments, typically from a {@code main()} method
+ */
+ public CommandLineArgs parse(String... args) {
+ CommandLineArgs commandLineArgs = new CommandLineArgs();
+ for (String arg : args) {
+ if (arg.startsWith("--")) {
+ String optionText = arg.substring(2, arg.length());
+ String optionName;
+ String optionValue = null;
+ if (optionText.contains("=")) {
+ optionName = optionText.substring(0, optionText.indexOf('='));
+ optionValue = optionText.substring(optionText.indexOf('=') + 1, optionText.length());
+ }
+ else {
+ optionName = optionText;
+ }
+ if (optionName.length() == 0 || (optionValue != null && optionValue.length() == 0)) {
+ throw new IllegalArgumentException("Invalid argument syntax: " + arg);
+ }
+ commandLineArgs.addOptionArg(optionName, optionValue);
+ }
+ else {
+ commandLineArgs.addNonOptionArg(arg);
+ }
+ }
+ return commandLineArgs;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLinePropertySource.java b/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLinePropertySource.java
new file mode 100644
index 00000000..d2022317
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLinePropertySource.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.env;
+
+import java.util.List;
+
+/**
+ * {@link CommandLinePropertySource} implementation backed by a simple String array.
+ *
+ * <h3>Purpose</h3>
+ * This {@code CommandLinePropertySource} implementation aims to provide the simplest
+ * possible approach to parsing command line arguments. As with all {@code
+ * CommandLinePropertySource} implementations, command line arguments are broken into two
+ * distinct groups: <em>option arguments</em> and <em>non-option arguments</em>, as
+ * described below <em>(some sections copied from Javadoc for {@link SimpleCommandLineArgsParser})</em>:
+ *
+ * <h3>Working with option arguments</h3>
+ * Option arguments must adhere to the exact syntax:
+ * <pre class="code">--optName[=optValue]</pre>
+ * That is, options must be prefixed with "{@code --}", and may or may not specify a value.
+ * If a value is specified, the name and value must be separated <em>without spaces</em>
+ * by an equals sign ("=").
+ *
+ * <h4>Valid examples of option arguments</h4>
+ * <pre class="code">
+ * --foo
+ * --foo=bar
+ * --foo="bar then baz"
+ * --foo=bar,baz,biz</pre>
+ *
+ * <h4>Invalid examples of option arguments</h4>
+ * <pre class="code">
+ * -foo
+ * --foo bar
+ * --foo = bar
+ * --foo=bar --foo=baz --foo=biz</pre>
+ *
+ * <h3>Working with non-option arguments</h3>
+ * Any and all arguments specified at the command line without the "{@code --}" option
+ * prefix will be considered as "non-option arguments" and made available through the
+ * {@link #getNonOptionArgs()} method.
+ *
+ * <h2>Typical usage</h2>
+ * <pre class="code">
+ * public static void main(String[] args) {
+ * PropertySource<?> ps = new SimpleCommandLinePropertySource(args);
+ * // ...
+ * }</pre>
+ *
+ * See {@link CommandLinePropertySource} for complete general usage examples.
+ *
+ * <h3>Beyond the basics</h3>
+ *
+ * <p>When more fully-featured command line parsing is necessary, consider using
+ * the provided {@link JOptCommandLinePropertySource}, or implement your own
+ * {@code CommandLinePropertySource} against the command line parsing library of your
+ * choice!
+ *
+ * @author Chris Beams
+ * @since 3.1
+ * @see CommandLinePropertySource
+ * @see JOptCommandLinePropertySource
+ */
+public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {
+
+ /**
+ * Create a new {@code SimpleCommandLinePropertySource} having the default name
+ * and backed by the given {@code String[]} of command line arguments.
+ * @see CommandLinePropertySource#COMMAND_LINE_PROPERTY_SOURCE_NAME
+ * @see CommandLinePropertySource#CommandLinePropertySource(Object)
+ */
+ public SimpleCommandLinePropertySource(String... args) {
+ super(new SimpleCommandLineArgsParser().parse(args));
+ }
+
+ /**
+ * Create a new {@code SimpleCommandLinePropertySource} having the given name
+ * and backed by the given {@code String[]} of command line arguments.
+ */
+ public SimpleCommandLinePropertySource(String name, String[] args) {
+ super(name, new SimpleCommandLineArgsParser().parse(args));
+ }
+
+ @Override
+ protected boolean containsOption(String name) {
+ return this.source.containsOption(name);
+ }
+
+ @Override
+ protected List<String> getOptionValues(String name) {
+ return this.source.getOptionValues(name);
+ }
+
+ @Override
+ protected List<String> getNonOptionArgs() {
+ return this.source.getNonOptionArgs();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/StandardEnvironment.java b/spring-core/src/main/java/org/springframework/core/env/StandardEnvironment.java
new file mode 100644
index 00000000..9437134f
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/StandardEnvironment.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2002-2013 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.env;
+
+/**
+ * {@link Environment} implementation suitable for use in 'standard' (i.e. non-web)
+ * applications.
+ *
+ * <p>In addition to the usual functions of a {@link ConfigurableEnvironment} such as
+ * property resolution and profile-related operations, this implementation configures two
+ * default property sources, to be searched in the following order:
+ * <ul>
+ * <li>{@linkplain AbstractEnvironment#getSystemProperties() system properties}
+ * <li>{@linkplain AbstractEnvironment#getSystemEnvironment() system environment variables}
+ * </ul>
+ *
+ * That is, if the key "xyz" is present both in the JVM system properties as well as in
+ * the set of environment variables for the current process, the value of key "xyz" from
+ * system properties will return from a call to {@code environment.getProperty("xyz")}.
+ * This ordering is chosen by default because system properties are per-JVM, while
+ * environment variables may be the same across many JVMs on a given system. Giving
+ * system properties precedence allows for overriding of environment variables on a
+ * per-JVM basis.
+ *
+ * <p>These default property sources may be removed, reordered, or replaced; and
+ * additional property sources may be added using the {@link MutablePropertySources}
+ * instance available from {@link #getPropertySources()}. See
+ * {@link ConfigurableEnvironment} Javadoc for usage examples.
+ *
+ * <p>See {@link SystemEnvironmentPropertySource} javadoc for details on special handling
+ * of property names in shell environments (e.g. Bash) that disallow period characters in
+ * variable names.
+ *
+ * @author Chris Beams
+ * @since 3.1
+ * @see ConfigurableEnvironment
+ * @see SystemEnvironmentPropertySource
+ * @see org.springframework.web.context.support.StandardServletEnvironment
+ */
+public class StandardEnvironment extends AbstractEnvironment {
+
+ /** System environment property source name: {@value} */
+ public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
+
+ /** JVM system properties property source name: {@value} */
+ public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
+
+
+ /**
+ * Customize the set of property sources with those appropriate for any standard
+ * Java environment:
+ * <ul>
+ * <li>{@value #SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME}
+ * <li>{@value #SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME}
+ * </ul>
+ * <p>Properties present in {@value #SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME} will
+ * take precedence over those in {@value #SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME}.
+ * @see AbstractEnvironment#customizePropertySources(MutablePropertySources)
+ * @see #getSystemProperties()
+ * @see #getSystemEnvironment()
+ */
+ @Override
+ protected void customizePropertySources(MutablePropertySources propertySources) {
+ propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
+ propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java b/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java
new file mode 100644
index 00000000..b0ca4dc3
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2002-2014 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.env;
+
+import java.util.Map;
+
+import org.springframework.util.Assert;
+
+/**
+ * Specialization of {@link MapPropertySource} designed for use with
+ * {@linkplain AbstractEnvironment#getSystemEnvironment() system environment variables}.
+ * Compensates for constraints in Bash and other shells that do not allow for variables
+ * containing the period character; also allows for uppercase variations on property
+ * names for more idiomatic shell use.
+ *
+ * <p>For example, a call to {@code getProperty("foo.bar")} will attempt to find a value
+ * for the original property or any 'equivalent' property, returning the first found:
+ * <ul>
+ * <li>{@code foo.bar} - the original name</li>
+ * <li>{@code foo_bar} - with underscores for periods (if any)</li>
+ * <li>{@code FOO.BAR} - original, with upper case</li>
+ * <li>{@code FOO_BAR} - with underscores and upper case</li>
+ * </ul>
+ *
+ * The same applies for calls to {@link #containsProperty(String)}, which returns
+ * {@code true} if any of the above properties are present, otherwise {@code false}.
+ *
+ * <p>This feature is particularly useful when specifying active or default profiles as
+ * environment variables. The following is not allowable under Bash:
+ *
+ * <pre class="code">spring.profiles.active=p1 java -classpath ... MyApp</pre>
+ *
+ * However, the following syntax is permitted and is also more conventional:
+ *
+ * <pre class="code">SPRING_PROFILES_ACTIVE=p1 java -classpath ... MyApp</pre>
+ *
+ * <p>Enable debug- or trace-level logging for this class (or package) for messages
+ * explaining when these 'property name resolutions' occur.
+ *
+ * <p>This property source is included by default in {@link StandardEnvironment}
+ * and all its subclasses.
+ *
+ * @author Chris Beams
+ * @since 3.1
+ * @see StandardEnvironment
+ * @see AbstractEnvironment#getSystemEnvironment()
+ * @see AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME
+ */
+public class SystemEnvironmentPropertySource extends MapPropertySource {
+
+ /**
+ * Create a new {@code SystemEnvironmentPropertySource} with the given name and
+ * delegating to the given {@code MapPropertySource}.
+ */
+ public SystemEnvironmentPropertySource(String name, Map<String, Object> source) {
+ super(name, source);
+ }
+
+
+ /**
+ * Return {@code true} if a property with the given name or any underscore/uppercase variant
+ * thereof exists in this property source.
+ */
+ @Override
+ public boolean containsProperty(String name) {
+ return (getProperty(name) != null);
+ }
+
+ /**
+ * This implementation returns {@code true} if a property with the given name or
+ * any underscore/uppercase variant thereof exists in this property source.
+ */
+ @Override
+ public Object getProperty(String name) {
+ String actualName = resolvePropertyName(name);
+ if (logger.isDebugEnabled() && !name.equals(actualName)) {
+ logger.debug(String.format("PropertySource [%s] does not contain '%s', but found equivalent '%s'",
+ getName(), name, actualName));
+ }
+ return super.getProperty(actualName);
+ }
+
+ /**
+ * Check to see if this property source contains a property with the given name, or
+ * any underscore / uppercase variation thereof. Return the resolved name if one is
+ * found or otherwise the original name. Never returns {@code null}.
+ */
+ private String resolvePropertyName(String name) {
+ Assert.notNull(name, "Property name must not be null");
+ if (super.containsProperty(name)) {
+ return name;
+ }
+
+ String usName = name.replace('.', '_');
+ if (!name.equals(usName) && super.containsProperty(usName)) {
+ return usName;
+ }
+
+ String ucName = name.toUpperCase();
+ if (!name.equals(ucName)) {
+ if (super.containsProperty(ucName)) {
+ return ucName;
+ }
+ else {
+ String usUcName = ucName.replace('.', '_');
+ if (!ucName.equals(usUcName) && super.containsProperty(usUcName)) {
+ return usUcName;
+ }
+ }
+ }
+
+ return name;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/env/package-info.java b/spring-core/src/main/java/org/springframework/core/env/package-info.java
new file mode 100644
index 00000000..bfaccbfd
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/env/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * Spring's environment abstraction consisting of bean definition
+ * profile and hierarchical property source support.
+ * @author Chris Beams
+ * @since 3.1
+ */
+package org.springframework.core.env;
diff --git a/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java b/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java
new file mode 100644
index 00000000..c58ea9ab
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2002-2013 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;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.springframework.util.ResourceUtils;
+
+/**
+ * Abstract base class for resources which resolve URLs into File references,
+ * such as {@link UrlResource} or {@link ClassPathResource}.
+ *
+ * <p>Detects the "file" protocol as well as the JBoss "vfs" protocol in URLs,
+ * resolving file system references accordingly.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public abstract class AbstractFileResolvingResource extends AbstractResource {
+
+ /**
+ * This implementation returns a File reference for the underlying class path
+ * resource, provided that it refers to a file in the file system.
+ * @see org.springframework.util.ResourceUtils#getFile(java.net.URL, String)
+ */
+ @Override
+ public File getFile() throws IOException {
+ URL url = getURL();
+ if (url.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
+ return VfsResourceDelegate.getResource(url).getFile();
+ }
+ return ResourceUtils.getFile(url, getDescription());
+ }
+
+ /**
+ * This implementation determines the underlying File
+ * (or jar file, in case of a resource in a jar/zip).
+ */
+ @Override
+ protected File getFileForLastModifiedCheck() throws IOException {
+ URL url = getURL();
+ if (ResourceUtils.isJarURL(url)) {
+ URL actualUrl = ResourceUtils.extractJarFileURL(url);
+ if (actualUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
+ return VfsResourceDelegate.getResource(actualUrl).getFile();
+ }
+ return ResourceUtils.getFile(actualUrl, "Jar URL");
+ }
+ else {
+ return getFile();
+ }
+ }
+
+ /**
+ * This implementation returns a File reference for the underlying class path
+ * resource, provided that it refers to a file in the file system.
+ * @see org.springframework.util.ResourceUtils#getFile(java.net.URI, String)
+ */
+ protected File getFile(URI uri) throws IOException {
+ if (uri.getScheme().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
+ return VfsResourceDelegate.getResource(uri).getFile();
+ }
+ return ResourceUtils.getFile(uri, getDescription());
+ }
+
+
+ @Override
+ public boolean exists() {
+ try {
+ URL url = getURL();
+ if (ResourceUtils.isFileURL(url)) {
+ // Proceed with file system resolution...
+ return getFile().exists();
+ }
+ else {
+ // Try a URL connection content-length header...
+ URLConnection con = url.openConnection();
+ customizeConnection(con);
+ HttpURLConnection httpCon =
+ (con instanceof HttpURLConnection ? (HttpURLConnection) con : null);
+ if (httpCon != null) {
+ int code = httpCon.getResponseCode();
+ if (code == HttpURLConnection.HTTP_OK) {
+ return true;
+ }
+ else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
+ return false;
+ }
+ }
+ if (con.getContentLength() >= 0) {
+ return true;
+ }
+ if (httpCon != null) {
+ // no HTTP OK status, and no content-length header: give up
+ httpCon.disconnect();
+ return false;
+ }
+ else {
+ // Fall back to stream existence: can we open the stream?
+ InputStream is = getInputStream();
+ is.close();
+ return true;
+ }
+ }
+ }
+ catch (IOException ex) {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isReadable() {
+ try {
+ URL url = getURL();
+ if (ResourceUtils.isFileURL(url)) {
+ // Proceed with file system resolution...
+ File file = getFile();
+ return (file.canRead() && !file.isDirectory());
+ }
+ else {
+ return true;
+ }
+ }
+ catch (IOException ex) {
+ return false;
+ }
+ }
+
+ @Override
+ public long contentLength() throws IOException {
+ URL url = getURL();
+ if (ResourceUtils.isFileURL(url)) {
+ // Proceed with file system resolution...
+ return getFile().length();
+ }
+ else {
+ // Try a URL connection content-length header...
+ URLConnection con = url.openConnection();
+ customizeConnection(con);
+ return con.getContentLength();
+ }
+ }
+
+ @Override
+ public long lastModified() throws IOException {
+ URL url = getURL();
+ if (ResourceUtils.isFileURL(url) || ResourceUtils.isJarURL(url)) {
+ // Proceed with file system resolution...
+ return super.lastModified();
+ }
+ else {
+ // Try a URL connection last-modified header...
+ URLConnection con = url.openConnection();
+ customizeConnection(con);
+ return con.getLastModified();
+ }
+ }
+
+
+ /**
+ * Customize the given {@link URLConnection}, obtained in the course of an
+ * {@link #exists()}, {@link #contentLength()} or {@link #lastModified()} call.
+ * <p>Calls {@link ResourceUtils#useCachesIfNecessary(URLConnection)} and
+ * delegates to {@link #customizeConnection(HttpURLConnection)} if possible.
+ * Can be overridden in subclasses.
+ * @param con the URLConnection to customize
+ * @throws IOException if thrown from URLConnection methods
+ */
+ protected void customizeConnection(URLConnection con) throws IOException {
+ ResourceUtils.useCachesIfNecessary(con);
+ if (con instanceof HttpURLConnection) {
+ customizeConnection((HttpURLConnection) con);
+ }
+ }
+
+ /**
+ * Customize the given {@link HttpURLConnection}, obtained in the course of an
+ * {@link #exists()}, {@link #contentLength()} or {@link #lastModified()} call.
+ * <p>Sets request method "HEAD" by default. Can be overridden in subclasses.
+ * @param con the HttpURLConnection to customize
+ * @throws IOException if thrown from HttpURLConnection methods
+ */
+ protected void customizeConnection(HttpURLConnection con) throws IOException {
+ con.setRequestMethod("HEAD");
+ }
+
+
+ /**
+ * Inner delegate class, avoiding a hard JBoss VFS API dependency at runtime.
+ */
+ private static class VfsResourceDelegate {
+
+ public static Resource getResource(URL url) throws IOException {
+ return new VfsResource(VfsUtils.getRoot(url));
+ }
+
+ public static Resource getResource(URI uri) throws IOException {
+ return new VfsResource(VfsUtils.getRoot(uri));
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java b/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java
new file mode 100644
index 00000000..91ea1288
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2002-2014 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;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import org.springframework.core.NestedIOException;
+import org.springframework.util.Assert;
+import org.springframework.util.ResourceUtils;
+
+/**
+ * Convenience base class for {@link Resource} implementations,
+ * pre-implementing typical behavior.
+ *
+ * <p>The "exists" method will check whether a File or InputStream can
+ * be opened; "isOpen" will always return false; "getURL" and "getFile"
+ * throw an exception; and "toString" will return the description.
+ *
+ * @author Juergen Hoeller
+ * @since 28.12.2003
+ */
+public abstract class AbstractResource implements Resource {
+
+ /**
+ * This implementation checks whether a File can be opened,
+ * falling back to whether an InputStream can be opened.
+ * This will cover both directories and content resources.
+ */
+ public boolean exists() {
+ // Try file existence: can we find the file in the file system?
+ try {
+ return getFile().exists();
+ }
+ catch (IOException ex) {
+ // Fall back to stream existence: can we open the stream?
+ try {
+ InputStream is = getInputStream();
+ is.close();
+ return true;
+ }
+ catch (Throwable isEx) {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * This implementation always returns {@code true}.
+ */
+ public boolean isReadable() {
+ return true;
+ }
+
+ /**
+ * This implementation always returns {@code false}.
+ */
+ public boolean isOpen() {
+ return false;
+ }
+
+ /**
+ * This implementation throws a FileNotFoundException, assuming
+ * that the resource cannot be resolved to a URL.
+ */
+ public URL getURL() throws IOException {
+ throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
+ }
+
+ /**
+ * This implementation builds a URI based on the URL returned
+ * by {@link #getURL()}.
+ */
+ public URI getURI() throws IOException {
+ URL url = getURL();
+ try {
+ return ResourceUtils.toURI(url);
+ }
+ catch (URISyntaxException ex) {
+ throw new NestedIOException("Invalid URI [" + url + "]", ex);
+ }
+ }
+
+ /**
+ * This implementation throws a FileNotFoundException, assuming
+ * that the resource cannot be resolved to an absolute file path.
+ */
+ public File getFile() throws IOException {
+ throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
+ }
+
+ /**
+ * This implementation reads the entire InputStream to calculate the
+ * content length. Subclasses will almost always be able to provide
+ * a more optimal version of this, e.g. checking a File length.
+ * @see #getInputStream()
+ * @throws IllegalStateException if {@link #getInputStream()} returns null.
+ */
+ public long contentLength() throws IOException {
+ InputStream is = this.getInputStream();
+ Assert.state(is != null, "resource input stream must not be null");
+ try {
+ long size = 0;
+ byte[] buf = new byte[255];
+ int read;
+ while ((read = is.read(buf)) != -1) {
+ size += read;
+ }
+ return size;
+ }
+ finally {
+ try {
+ is.close();
+ }
+ catch (IOException ex) {
+ }
+ }
+ }
+
+ /**
+ * This implementation checks the timestamp of the underlying File,
+ * if available.
+ * @see #getFileForLastModifiedCheck()
+ */
+ public long lastModified() throws IOException {
+ long lastModified = getFileForLastModifiedCheck().lastModified();
+ if (lastModified == 0L) {
+ throw new FileNotFoundException(getDescription() +
+ " cannot be resolved in the file system for resolving its last-modified timestamp");
+ }
+ return lastModified;
+ }
+
+ /**
+ * Determine the File to use for timestamp checking.
+ * <p>The default implementation delegates to {@link #getFile()}.
+ * @return the File to use for timestamp checking (never {@code null})
+ * @throws IOException if the resource cannot be resolved as absolute
+ * file path, i.e. if the resource is not available in a file system
+ */
+ protected File getFileForLastModifiedCheck() throws IOException {
+ return getFile();
+ }
+
+ /**
+ * This implementation throws a FileNotFoundException, assuming
+ * that relative resources cannot be created for this resource.
+ */
+ public Resource createRelative(String relativePath) throws IOException {
+ throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
+ }
+
+ /**
+ * This implementation always returns {@code null},
+ * assuming that this resource type does not have a filename.
+ */
+ public String getFilename() {
+ return null;
+ }
+
+
+ /**
+ * This implementation returns the description of this resource.
+ * @see #getDescription()
+ */
+ @Override
+ public String toString() {
+ return getDescription();
+ }
+
+ /**
+ * This implementation compares description strings.
+ * @see #getDescription()
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return (obj == this ||
+ (obj instanceof Resource && ((Resource) obj).getDescription().equals(getDescription())));
+ }
+
+ /**
+ * This implementation returns the description's hash code.
+ * @see #getDescription()
+ */
+ @Override
+ public int hashCode() {
+ return getDescription().hashCode();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/ByteArrayResource.java b/spring-core/src/main/java/org/springframework/core/io/ByteArrayResource.java
new file mode 100644
index 00000000..d195def9
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/ByteArrayResource.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+/**
+ * {@link Resource} implementation for a given byte array.
+ * Creates a ByteArrayInputStreams for the given byte array.
+ *
+ * <p>Useful for loading content from any given byte array,
+ * without having to resort to a single-use {@link InputStreamResource}.
+ * Particularly useful for creating mail attachments from local content,
+ * where JavaMail needs to be able to read the stream multiple times.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2.3
+ * @see java.io.ByteArrayInputStream
+ * @see InputStreamResource
+ * @see org.springframework.mail.javamail.MimeMessageHelper#addAttachment(String, InputStreamSource)
+ */
+public class ByteArrayResource extends AbstractResource {
+
+ private final byte[] byteArray;
+
+ private final String description;
+
+
+ /**
+ * Create a new ByteArrayResource.
+ * @param byteArray the byte array to wrap
+ */
+ public ByteArrayResource(byte[] byteArray) {
+ this(byteArray, "resource loaded from byte array");
+ }
+
+ /**
+ * Create a new ByteArrayResource.
+ * @param byteArray the byte array to wrap
+ * @param description where the byte array comes from
+ */
+ public ByteArrayResource(byte[] byteArray, String description) {
+ if (byteArray == null) {
+ throw new IllegalArgumentException("Byte array must not be null");
+ }
+ this.byteArray = byteArray;
+ this.description = (description != null ? description : "");
+ }
+
+ /**
+ * Return the underlying byte array.
+ */
+ public final byte[] getByteArray() {
+ return this.byteArray;
+ }
+
+
+ /**
+ * This implementation always returns {@code true}.
+ */
+ @Override
+ public boolean exists() {
+ return true;
+ }
+
+ /**
+ * This implementation returns the length of the underlying byte array.
+ */
+ @Override
+ public long contentLength() {
+ return this.byteArray.length;
+ }
+
+ /**
+ * This implementation returns a ByteArrayInputStream for the
+ * underlying byte array.
+ * @see java.io.ByteArrayInputStream
+ */
+ public InputStream getInputStream() throws IOException {
+ return new ByteArrayInputStream(this.byteArray);
+ }
+
+ /**
+ * This implementation returns the passed-in description, if any.
+ */
+ public String getDescription() {
+ return this.description;
+ }
+
+
+ /**
+ * This implementation compares the underlying byte array.
+ * @see java.util.Arrays#equals(byte[], byte[])
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return (obj == this ||
+ (obj instanceof ByteArrayResource && Arrays.equals(((ByteArrayResource) obj).byteArray, this.byteArray)));
+ }
+
+ /**
+ * This implementation returns the hash code based on the
+ * underlying byte array.
+ */
+ @Override
+ public int hashCode() {
+ return (byte[].class.hashCode() * 29 * this.byteArray.length);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java b/spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java
new file mode 100644
index 00000000..b1d2998e
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2002-2014 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;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link Resource} implementation for class path resources.
+ * Uses either a given ClassLoader or a given Class for loading resources.
+ *
+ * <p>Supports resolution as {@code java.io.File} if the class path
+ * resource resides in the file system, but not for resources in a JAR.
+ * Always supports resolution as URL.
+ *
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @since 28.12.2003
+ * @see ClassLoader#getResourceAsStream(String)
+ * @see Class#getResourceAsStream(String)
+ */
+public class ClassPathResource extends AbstractFileResolvingResource {
+
+ private final String path;
+
+ private ClassLoader classLoader;
+
+ private Class<?> clazz;
+
+
+ /**
+ * Create a new {@code ClassPathResource} for {@code ClassLoader} usage.
+ * A leading slash will be removed, as the ClassLoader resource access
+ * methods will not accept it.
+ * <p>The thread context class loader will be used for
+ * loading the resource.
+ * @param path the absolute path within the class path
+ * @see java.lang.ClassLoader#getResourceAsStream(String)
+ * @see org.springframework.util.ClassUtils#getDefaultClassLoader()
+ */
+ public ClassPathResource(String path) {
+ this(path, (ClassLoader) null);
+ }
+
+ /**
+ * Create a new {@code ClassPathResource} for {@code ClassLoader} usage.
+ * A leading slash will be removed, as the ClassLoader resource access
+ * methods will not accept it.
+ * @param path the absolute path within the classpath
+ * @param classLoader the class loader to load the resource with,
+ * or {@code null} for the thread context class loader
+ * @see ClassLoader#getResourceAsStream(String)
+ */
+ public ClassPathResource(String path, ClassLoader classLoader) {
+ Assert.notNull(path, "Path must not be null");
+ String pathToUse = StringUtils.cleanPath(path);
+ if (pathToUse.startsWith("/")) {
+ pathToUse = pathToUse.substring(1);
+ }
+ this.path = pathToUse;
+ this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
+ }
+
+ /**
+ * Create a new {@code ClassPathResource} for {@code Class} usage.
+ * The path can be relative to the given class, or absolute within
+ * the classpath via a leading slash.
+ * @param path relative or absolute path within the class path
+ * @param clazz the class to load resources with
+ * @see java.lang.Class#getResourceAsStream
+ */
+ public ClassPathResource(String path, Class<?> clazz) {
+ Assert.notNull(path, "Path must not be null");
+ this.path = StringUtils.cleanPath(path);
+ this.clazz = clazz;
+ }
+
+ /**
+ * Create a new {@code ClassPathResource} with optional {@code ClassLoader}
+ * and {@code Class}. Only for internal usage.
+ * @param path relative or absolute path within the classpath
+ * @param classLoader the class loader to load the resource with, if any
+ * @param clazz the class to load resources with, if any
+ */
+ protected ClassPathResource(String path, ClassLoader classLoader, Class<?> clazz) {
+ this.path = StringUtils.cleanPath(path);
+ this.classLoader = classLoader;
+ this.clazz = clazz;
+ }
+
+
+ /**
+ * Return the path for this resource (as resource path within the class path).
+ */
+ public final String getPath() {
+ return this.path;
+ }
+
+ /**
+ * Return the ClassLoader that this resource will be obtained from.
+ */
+ public final ClassLoader getClassLoader() {
+ return (this.clazz != null ? this.clazz.getClassLoader() : this.classLoader);
+ }
+
+
+ /**
+ * This implementation checks for the resolution of a resource URL.
+ * @see java.lang.ClassLoader#getResource(String)
+ * @see java.lang.Class#getResource(String)
+ */
+ @Override
+ public boolean exists() {
+ return (resolveURL() != null);
+ }
+
+ /**
+ * Resolves a URL for the underlying class path resource.
+ * @return the resolved URL, or {@code null} if not resolvable
+ */
+ protected URL resolveURL() {
+ if (this.clazz != null) {
+ return this.clazz.getResource(this.path);
+ }
+ else if (this.classLoader != null) {
+ return this.classLoader.getResource(this.path);
+ }
+ else {
+ return ClassLoader.getSystemResource(this.path);
+ }
+ }
+
+ /**
+ * This implementation opens an InputStream for the given class path resource.
+ * @see java.lang.ClassLoader#getResourceAsStream(String)
+ * @see java.lang.Class#getResourceAsStream(String)
+ */
+ public InputStream getInputStream() throws IOException {
+ InputStream is;
+ if (this.clazz != null) {
+ is = this.clazz.getResourceAsStream(this.path);
+ }
+ else if (this.classLoader != null) {
+ is = this.classLoader.getResourceAsStream(this.path);
+ }
+ else {
+ is = ClassLoader.getSystemResourceAsStream(this.path);
+ }
+ if (is == null) {
+ throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
+ }
+ return is;
+ }
+
+ /**
+ * This implementation returns a URL for the underlying class path resource,
+ * if available.
+ * @see java.lang.ClassLoader#getResource(String)
+ * @see java.lang.Class#getResource(String)
+ */
+ @Override
+ public URL getURL() throws IOException {
+ URL url = resolveURL();
+ if (url == null) {
+ throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
+ }
+ return url;
+ }
+
+ /**
+ * This implementation creates a ClassPathResource, applying the given path
+ * relative to the path of the underlying resource of this descriptor.
+ * @see org.springframework.util.StringUtils#applyRelativePath(String, String)
+ */
+ @Override
+ public Resource createRelative(String relativePath) {
+ String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
+ return new ClassPathResource(pathToUse, this.classLoader, this.clazz);
+ }
+
+ /**
+ * This implementation returns the name of the file that this class path
+ * resource refers to.
+ * @see org.springframework.util.StringUtils#getFilename(String)
+ */
+ @Override
+ public String getFilename() {
+ return StringUtils.getFilename(this.path);
+ }
+
+ /**
+ * This implementation returns a description that includes the class path location.
+ */
+ public String getDescription() {
+ StringBuilder builder = new StringBuilder("class path resource [");
+ String pathToUse = path;
+ if (this.clazz != null && !pathToUse.startsWith("/")) {
+ builder.append(ClassUtils.classPackageAsResourcePath(this.clazz));
+ builder.append('/');
+ }
+ if (pathToUse.startsWith("/")) {
+ pathToUse = pathToUse.substring(1);
+ }
+ builder.append(pathToUse);
+ builder.append(']');
+ return builder.toString();
+ }
+
+ /**
+ * This implementation compares the underlying class path locations.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof ClassPathResource) {
+ ClassPathResource otherRes = (ClassPathResource) obj;
+ return (this.path.equals(otherRes.path) &&
+ ObjectUtils.nullSafeEquals(this.classLoader, otherRes.classLoader) &&
+ ObjectUtils.nullSafeEquals(this.clazz, otherRes.clazz));
+ }
+ return false;
+ }
+
+ /**
+ * This implementation returns the hash code of the underlying
+ * class path location.
+ */
+ @Override
+ public int hashCode() {
+ return this.path.hashCode();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/ClassRelativeResourceLoader.java b/spring-core/src/main/java/org/springframework/core/io/ClassRelativeResourceLoader.java
new file mode 100644
index 00000000..00f267e4
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/ClassRelativeResourceLoader.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2002-2012 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;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link ResourceLoader} implementation that interprets plain resource paths
+ * as relative to a given {@code java.lang.Class}.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ * @see Class#getResource(String)
+ * @see ClassPathResource#ClassPathResource(String, Class)
+ */
+public class ClassRelativeResourceLoader extends DefaultResourceLoader {
+
+ private final Class clazz;
+
+
+ /**
+ * Create a new ClassRelativeResourceLoader for the given class.
+ * @param clazz the class to load resources through
+ */
+ public ClassRelativeResourceLoader(Class clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ this.clazz = clazz;
+ setClassLoader(clazz.getClassLoader());
+ }
+
+ protected Resource getResourceByPath(String path) {
+ return new ClassRelativeContextResource(path, this.clazz);
+ }
+
+
+ /**
+ * ClassPathResource that explicitly expresses a context-relative path
+ * through implementing the ContextResource interface.
+ */
+ private static class ClassRelativeContextResource extends ClassPathResource implements ContextResource {
+
+ private final Class clazz;
+
+ public ClassRelativeContextResource(String path, Class clazz) {
+ super(path, clazz);
+ this.clazz = clazz;
+ }
+
+ public String getPathWithinContext() {
+ return getPath();
+ }
+
+ @Override
+ public Resource createRelative(String relativePath) {
+ String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
+ return new ClassRelativeContextResource(pathToUse, this.clazz);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/ContextResource.java b/spring-core/src/main/java/org/springframework/core/io/ContextResource.java
new file mode 100644
index 00000000..0493cfa7
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/ContextResource.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2007 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;
+
+/**
+ * Extended interface for a resource that is loaded from an enclosing
+ * 'context', e.g. from a {@link javax.servlet.ServletContext} or a
+ * {@link javax.portlet.PortletContext} but also from plain classpath paths
+ * or relative file system paths (specified without an explicit prefix,
+ * hence applying relative to the local {@link ResourceLoader}'s context).
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see org.springframework.web.context.support.ServletContextResource
+ * @see org.springframework.web.portlet.context.PortletContextResource
+ */
+public interface ContextResource extends Resource {
+
+ /**
+ * Return the path within the enclosing 'context'.
+ * <p>This is typically path relative to a context-specific root directory,
+ * e.g. a ServletContext root or a PortletContext root.
+ */
+ String getPathWithinContext();
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java b/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java
new file mode 100644
index 00000000..1a390f04
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Default implementation of the {@link ResourceLoader} interface.
+ * Used by {@link ResourceEditor}, and serves as base class for
+ * {@link org.springframework.context.support.AbstractApplicationContext}.
+ * Can also be used standalone.
+ *
+ * <p>Will return a {@link UrlResource} if the location value is a URL,
+ * and a {@link ClassPathResource} if it is a non-URL path or a
+ * "classpath:" pseudo-URL.
+ *
+ * @author Juergen Hoeller
+ * @since 10.03.2004
+ * @see FileSystemResourceLoader
+ * @see org.springframework.context.support.ClassPathXmlApplicationContext
+ */
+public class DefaultResourceLoader implements ResourceLoader {
+
+ private ClassLoader classLoader;
+
+
+ /**
+ * Create a new DefaultResourceLoader.
+ * <p>ClassLoader access will happen using the thread context class loader
+ * at the time of this ResourceLoader's initialization.
+ * @see java.lang.Thread#getContextClassLoader()
+ */
+ public DefaultResourceLoader() {
+ this.classLoader = ClassUtils.getDefaultClassLoader();
+ }
+
+ /**
+ * Create a new DefaultResourceLoader.
+ * @param classLoader the ClassLoader to load class path resources with, or {@code null}
+ * for using the thread context class loader at the time of actual resource access
+ */
+ public DefaultResourceLoader(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+
+ /**
+ * Specify the ClassLoader to load class path resources with, or {@code null}
+ * for using the thread context class loader at the time of actual resource access.
+ * <p>The default is that ClassLoader access will happen using the thread context
+ * class loader at the time of this ResourceLoader's initialization.
+ */
+ public void setClassLoader(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+ /**
+ * Return the ClassLoader to load class path resources with.
+ * <p>Will get passed to ClassPathResource's constructor for all
+ * ClassPathResource objects created by this resource loader.
+ * @see ClassPathResource
+ */
+ public ClassLoader getClassLoader() {
+ return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
+ }
+
+
+ public Resource getResource(String location) {
+ Assert.notNull(location, "Location must not be null");
+ if (location.startsWith(CLASSPATH_URL_PREFIX)) {
+ return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
+ }
+ else {
+ try {
+ // Try to parse the location as a URL...
+ URL url = new URL(location);
+ return new UrlResource(url);
+ }
+ catch (MalformedURLException ex) {
+ // No URL -> resolve as resource path.
+ return getResourceByPath(location);
+ }
+ }
+ }
+
+ /**
+ * Return a Resource handle for the resource at the given path.
+ * <p>The default implementation supports class path locations. This should
+ * be appropriate for standalone implementations but can be overridden,
+ * e.g. for implementations targeted at a Servlet container.
+ * @param path the path to the resource
+ * @return the corresponding Resource handle
+ * @see ClassPathResource
+ * @see org.springframework.context.support.FileSystemXmlApplicationContext#getResourceByPath
+ * @see org.springframework.web.context.support.XmlWebApplicationContext#getResourceByPath
+ */
+ protected Resource getResourceByPath(String path) {
+ return new ClassPathContextResource(path, getClassLoader());
+ }
+
+
+ /**
+ * ClassPathResource that explicitly expresses a context-relative path
+ * through implementing the ContextResource interface.
+ */
+ private static class ClassPathContextResource extends ClassPathResource implements ContextResource {
+
+ public ClassPathContextResource(String path, ClassLoader classLoader) {
+ super(path, classLoader);
+ }
+
+ public String getPathWithinContext() {
+ return getPath();
+ }
+
+ @Override
+ public Resource createRelative(String relativePath) {
+ String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
+ return new ClassPathContextResource(pathToUse, getClassLoader());
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/DescriptiveResource.java b/spring-core/src/main/java/org/springframework/core/io/DescriptiveResource.java
new file mode 100644
index 00000000..8385891e
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/DescriptiveResource.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Simple {@link Resource} implementation that holds a resource description
+ * but does not point to an actually readable resource.
+ *
+ * <p>To be used as placeholder if a {@code Resource} argument is
+ * expected by an API but not necessarily used for actual reading.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2.6
+ */
+public class DescriptiveResource extends AbstractResource {
+
+ private final String description;
+
+
+ /**
+ * Create a new DescriptiveResource.
+ * @param description the resource description
+ */
+ public DescriptiveResource(String description) {
+ this.description = (description != null ? description : "");
+ }
+
+
+ @Override
+ public boolean exists() {
+ return false;
+ }
+
+ @Override
+ public boolean isReadable() {
+ return false;
+ }
+
+ public InputStream getInputStream() throws IOException {
+ throw new FileNotFoundException(
+ getDescription() + " cannot be opened because it does not point to a readable resource");
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+
+ /**
+ * This implementation compares the underlying description String.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return (obj == this ||
+ (obj instanceof DescriptiveResource && ((DescriptiveResource) obj).description.equals(this.description)));
+ }
+
+ /**
+ * This implementation returns the hash code of the underlying description String.
+ */
+ @Override
+ public int hashCode() {
+ return this.description.hashCode();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java b/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java
new file mode 100644
index 00000000..05202a3b
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2002-2014 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;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URL;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link Resource} implementation for {@code java.io.File} handles.
+ * Obviously supports resolution as File, and also as URL.
+ * Implements the extended {@link WritableResource} interface.
+ *
+ * @author Juergen Hoeller
+ * @since 28.12.2003
+ * @see java.io.File
+ */
+public class FileSystemResource extends AbstractResource implements WritableResource {
+
+ private final File file;
+
+ private final String path;
+
+
+ /**
+ * Create a new {@code FileSystemResource} from a {@link File} handle.
+ * <p>Note: When building relative resources via {@link #createRelative},
+ * the relative path will apply <i>at the same directory level</i>:
+ * e.g. new File("C:/dir1"), relative path "dir2" -> "C:/dir2"!
+ * If you prefer to have relative paths built underneath the given root
+ * directory, use the {@link #FileSystemResource(String) constructor with a file path}
+ * to append a trailing slash to the root path: "C:/dir1/", which
+ * indicates this directory as root for all relative paths.
+ * @param file a File handle
+ */
+ public FileSystemResource(File file) {
+ Assert.notNull(file, "File must not be null");
+ this.file = file;
+ this.path = StringUtils.cleanPath(file.getPath());
+ }
+
+ /**
+ * Create a new {@code FileSystemResource} from a file path.
+ * <p>Note: When building relative resources via {@link #createRelative},
+ * it makes a difference whether the specified resource base path here
+ * ends with a slash or not. In the case of "C:/dir1/", relative paths
+ * will be built underneath that root: e.g. relative path "dir2" ->
+ * "C:/dir1/dir2". In the case of "C:/dir1", relative paths will apply
+ * at the same directory level: relative path "dir2" -> "C:/dir2".
+ * @param path a file path
+ */
+ public FileSystemResource(String path) {
+ Assert.notNull(path, "Path must not be null");
+ this.file = new File(path);
+ this.path = StringUtils.cleanPath(path);
+ }
+
+
+ /**
+ * Return the file path for this resource.
+ */
+ public final String getPath() {
+ return this.path;
+ }
+
+
+ /**
+ * This implementation returns whether the underlying file exists.
+ * @see java.io.File#exists()
+ */
+ @Override
+ public boolean exists() {
+ return this.file.exists();
+ }
+
+ /**
+ * This implementation checks whether the underlying file is marked as readable
+ * (and corresponds to an actual file with content, not to a directory).
+ * @see java.io.File#canRead()
+ * @see java.io.File#isDirectory()
+ */
+ @Override
+ public boolean isReadable() {
+ return (this.file.canRead() && !this.file.isDirectory());
+ }
+
+ /**
+ * This implementation opens a FileInputStream for the underlying file.
+ * @see java.io.FileInputStream
+ */
+ public InputStream getInputStream() throws IOException {
+ return new FileInputStream(this.file);
+ }
+
+ /**
+ * This implementation returns a URL for the underlying file.
+ * @see java.io.File#toURI()
+ */
+ @Override
+ public URL getURL() throws IOException {
+ return this.file.toURI().toURL();
+ }
+
+ /**
+ * This implementation returns a URI for the underlying file.
+ * @see java.io.File#toURI()
+ */
+ @Override
+ public URI getURI() throws IOException {
+ return this.file.toURI();
+ }
+
+ /**
+ * This implementation returns the underlying File reference.
+ */
+ @Override
+ public File getFile() {
+ return this.file;
+ }
+
+ /**
+ * This implementation returns the underlying File's length.
+ */
+ @Override
+ public long contentLength() throws IOException {
+ return this.file.length();
+ }
+
+ /**
+ * This implementation creates a FileSystemResource, applying the given path
+ * relative to the path of the underlying file of this resource descriptor.
+ * @see org.springframework.util.StringUtils#applyRelativePath(String, String)
+ */
+ @Override
+ public Resource createRelative(String relativePath) {
+ String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
+ return new FileSystemResource(pathToUse);
+ }
+
+ /**
+ * This implementation returns the name of the file.
+ * @see java.io.File#getName()
+ */
+ @Override
+ public String getFilename() {
+ return this.file.getName();
+ }
+
+ /**
+ * This implementation returns a description that includes the absolute
+ * path of the file.
+ * @see java.io.File#getAbsolutePath()
+ */
+ public String getDescription() {
+ return "file [" + this.file.getAbsolutePath() + "]";
+ }
+
+
+ // implementation of WritableResource
+
+ /**
+ * This implementation checks whether the underlying file is marked as writable
+ * (and corresponds to an actual file with content, not to a directory).
+ * @see java.io.File#canWrite()
+ * @see java.io.File#isDirectory()
+ */
+ public boolean isWritable() {
+ return (this.file.canWrite() && !this.file.isDirectory());
+ }
+
+ /**
+ * This implementation opens a FileOutputStream for the underlying file.
+ * @see java.io.FileOutputStream
+ */
+ public OutputStream getOutputStream() throws IOException {
+ return new FileOutputStream(this.file);
+ }
+
+
+ /**
+ * This implementation compares the underlying File references.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return (obj == this ||
+ (obj instanceof FileSystemResource && this.path.equals(((FileSystemResource) obj).path)));
+ }
+
+ /**
+ * This implementation returns the hash code of the underlying File reference.
+ */
+ @Override
+ public int hashCode() {
+ return this.path.hashCode();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/FileSystemResourceLoader.java b/spring-core/src/main/java/org/springframework/core/io/FileSystemResourceLoader.java
new file mode 100644
index 00000000..d811d91f
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/FileSystemResourceLoader.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2002-2007 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;
+
+/**
+ * {@link ResourceLoader} implementation that resolves plain paths as
+ * file system resources rather than as class path resources
+ * (the latter is {@link DefaultResourceLoader}'s default strategy).
+ *
+ * <p><b>NOTE:</b> Plain paths will always be interpreted as relative
+ * to the current VM working directory, even if they start with a slash.
+ * (This is consistent with the semantics in a Servlet container.)
+ * <b>Use an explicit "file:" prefix to enforce an absolute file path.</b>
+ *
+ * <p>{@link org.springframework.context.support.FileSystemXmlApplicationContext}
+ * is a full-fledged ApplicationContext implementation that provides
+ * the same resource path resolution strategy.
+ *
+ * @author Juergen Hoeller
+ * @since 1.1.3
+ * @see DefaultResourceLoader
+ * @see org.springframework.context.support.FileSystemXmlApplicationContext
+ */
+public class FileSystemResourceLoader extends DefaultResourceLoader {
+
+ /**
+ * Resolve resource paths as file system paths.
+ * <p>Note: Even if a given path starts with a slash, it will get
+ * interpreted as relative to the current VM working directory.
+ * @param path the path to the resource
+ * @return the corresponding Resource handle
+ * @see FileSystemResource
+ * @see org.springframework.web.context.support.ServletContextResourceLoader#getResourceByPath
+ */
+ @Override
+ protected Resource getResourceByPath(String path) {
+ if (path != null && path.startsWith("/")) {
+ path = path.substring(1);
+ }
+ return new FileSystemContextResource(path);
+ }
+
+
+ /**
+ * FileSystemResource that explicitly expresses a context-relative path
+ * through implementing the ContextResource interface.
+ */
+ private static class FileSystemContextResource extends FileSystemResource implements ContextResource {
+
+ public FileSystemContextResource(String path) {
+ super(path);
+ }
+
+ public String getPathWithinContext() {
+ return getPath();
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/InputStreamResource.java b/spring-core/src/main/java/org/springframework/core/io/InputStreamResource.java
new file mode 100644
index 00000000..d9b2405c
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/InputStreamResource.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * {@link Resource} implementation for a given InputStream. Should only
+ * be used if no specific Resource implementation is applicable.
+ * In particular, prefer {@link ByteArrayResource} or any of the
+ * file-based Resource implementations where possible.
+ *
+ * <p>In contrast to other Resource implementations, this is a descriptor
+ * for an <i>already opened</i> resource - therefore returning "true" from
+ * {@code isOpen()}. Do not use it if you need to keep the resource
+ * descriptor somewhere, or if you need to read a stream multiple times.
+ *
+ * @author Juergen Hoeller
+ * @since 28.12.2003
+ * @see ByteArrayResource
+ * @see ClassPathResource
+ * @see FileSystemResource
+ * @see UrlResource
+ */
+public class InputStreamResource extends AbstractResource {
+
+ private final InputStream inputStream;
+
+ private final String description;
+
+ private boolean read = false;
+
+
+ /**
+ * Create a new InputStreamResource.
+ * @param inputStream the InputStream to use
+ */
+ public InputStreamResource(InputStream inputStream) {
+ this(inputStream, "resource loaded through InputStream");
+ }
+
+ /**
+ * Create a new InputStreamResource.
+ * @param inputStream the InputStream to use
+ * @param description where the InputStream comes from
+ */
+ public InputStreamResource(InputStream inputStream, String description) {
+ if (inputStream == null) {
+ throw new IllegalArgumentException("InputStream must not be null");
+ }
+ this.inputStream = inputStream;
+ this.description = (description != null ? description : "");
+ }
+
+
+ /**
+ * This implementation always returns {@code true}.
+ */
+ @Override
+ public boolean exists() {
+ return true;
+ }
+
+ /**
+ * This implementation always returns {@code true}.
+ */
+ @Override
+ public boolean isOpen() {
+ return true;
+ }
+
+ /**
+ * This implementation throws IllegalStateException if attempting to
+ * read the underlying stream multiple times.
+ */
+ public InputStream getInputStream() throws IOException, IllegalStateException {
+ if (this.read) {
+ throw new IllegalStateException("InputStream has already been read - " +
+ "do not use InputStreamResource if a stream needs to be read multiple times");
+ }
+ this.read = true;
+ return this.inputStream;
+ }
+
+ /**
+ * This implementation returns the passed-in description, if any.
+ */
+ public String getDescription() {
+ return this.description;
+ }
+
+
+ /**
+ * This implementation compares the underlying InputStream.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return (obj == this ||
+ (obj instanceof InputStreamResource && ((InputStreamResource) obj).inputStream.equals(this.inputStream)));
+ }
+
+ /**
+ * This implementation returns the hash code of the underlying InputStream.
+ */
+ @Override
+ public int hashCode() {
+ return this.inputStream.hashCode();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/InputStreamSource.java b/spring-core/src/main/java/org/springframework/core/io/InputStreamSource.java
new file mode 100644
index 00000000..f31e6ef9
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/InputStreamSource.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Simple interface for objects that are sources for an {@link InputStream}.
+ *
+ * <p>This is the base interface for Spring's more extensive {@link Resource} interface.
+ *
+ * <p>For single-use streams, {@link InputStreamResource} can be used for any
+ * given {@code InputStream}. Spring's {@link ByteArrayResource} or any
+ * file-based {@code Resource} implementation can be used as a concrete
+ * instance, allowing one to read the underlying content stream multiple times.
+ * This makes this interface useful as an abstract content source for mail
+ * attachments, for example.
+ *
+ * @author Juergen Hoeller
+ * @since 20.01.2004
+ * @see java.io.InputStream
+ * @see Resource
+ * @see InputStreamResource
+ * @see ByteArrayResource
+ */
+public interface InputStreamSource {
+
+ /**
+ * Return an {@link InputStream}.
+ * <p>It is expected that each call creates a <i>fresh</i> stream.
+ * <p>This requirement is particularly important when you consider an API such
+ * as JavaMail, which needs to be able to read the stream multiple times when
+ * creating mail attachments. For such a use case, it is <i>required</i>
+ * that each {@code getInputStream()} call returns a fresh stream.
+ * @return the input stream for the underlying resource (must not be {@code null})
+ * @throws IOException if the stream could not be opened
+ * @see org.springframework.mail.javamail.MimeMessageHelper#addAttachment(String, InputStreamSource)
+ */
+ InputStream getInputStream() throws IOException;
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/Resource.java b/spring-core/src/main/java/org/springframework/core/io/Resource.java
new file mode 100644
index 00000000..cc6eef30
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/Resource.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+
+/**
+ * Interface for a resource descriptor that abstracts from the actual
+ * type of underlying resource, such as a file or class path resource.
+ *
+ * <p>An InputStream can be opened for every resource if it exists in
+ * physical form, but a URL or File handle can just be returned for
+ * certain resources. The actual behavior is implementation-specific.
+ *
+ * @author Juergen Hoeller
+ * @since 28.12.2003
+ * @see #getInputStream()
+ * @see #getURL()
+ * @see #getURI()
+ * @see #getFile()
+ * @see WritableResource
+ * @see ContextResource
+ * @see FileSystemResource
+ * @see ClassPathResource
+ * @see UrlResource
+ * @see ByteArrayResource
+ * @see InputStreamResource
+ */
+public interface Resource extends InputStreamSource {
+
+ /**
+ * Return whether this resource actually exists in physical form.
+ * <p>This method performs a definitive existence check, whereas the
+ * existence of a {@code Resource} handle only guarantees a
+ * valid descriptor handle.
+ */
+ boolean exists();
+
+ /**
+ * Return whether the contents of this resource can be read,
+ * e.g. via {@link #getInputStream()} or {@link #getFile()}.
+ * <p>Will be {@code true} for typical resource descriptors;
+ * note that actual content reading may still fail when attempted.
+ * However, a value of {@code false} is a definitive indication
+ * that the resource content cannot be read.
+ * @see #getInputStream()
+ */
+ boolean isReadable();
+
+ /**
+ * Return whether this resource represents a handle with an open
+ * stream. If true, the InputStream cannot be read multiple times,
+ * and must be read and closed to avoid resource leaks.
+ * <p>Will be {@code false} for typical resource descriptors.
+ */
+ boolean isOpen();
+
+ /**
+ * Return a URL handle for this resource.
+ * @throws IOException if the resource cannot be resolved as URL,
+ * i.e. if the resource is not available as descriptor
+ */
+ URL getURL() throws IOException;
+
+ /**
+ * Return a URI handle for this resource.
+ * @throws IOException if the resource cannot be resolved as URI,
+ * i.e. if the resource is not available as descriptor
+ */
+ URI getURI() throws IOException;
+
+ /**
+ * Return a File handle for this resource.
+ * @throws IOException if the resource cannot be resolved as absolute
+ * file path, i.e. if the resource is not available in a file system
+ */
+ File getFile() throws IOException;
+
+ /**
+ * Determine the content length for this resource.
+ * @throws IOException if the resource cannot be resolved
+ * (in the file system or as some other known physical resource type)
+ */
+ long contentLength() throws IOException;
+
+ /**
+ * Determine the last-modified timestamp for this resource.
+ * @throws IOException if the resource cannot be resolved
+ * (in the file system or as some other known physical resource type)
+ */
+ long lastModified() throws IOException;
+
+ /**
+ * Create a resource relative to this resource.
+ * @param relativePath the relative path (relative to this resource)
+ * @return the resource handle for the relative resource
+ * @throws IOException if the relative resource cannot be determined
+ */
+ Resource createRelative(String relativePath) throws IOException;
+
+ /**
+ * Determine a filename for this resource, i.e. typically the last
+ * part of the path: for example, "myfile.txt".
+ * <p>Returns {@code null} if this type of resource does not
+ * have a filename.
+ */
+ String getFilename();
+
+ /**
+ * Return a description for this resource,
+ * to be used for error output when working with the resource.
+ * <p>Implementations are also encouraged to return this value
+ * from their {@code toString} method.
+ * @see Object#toString()
+ */
+ String getDescription();
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/ResourceEditor.java b/spring-core/src/main/java/org/springframework/core/io/ResourceEditor.java
new file mode 100644
index 00000000..a01d5f7f
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/ResourceEditor.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2002-2013 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;
+
+import java.beans.PropertyEditorSupport;
+import java.io.IOException;
+
+import org.springframework.core.env.PropertyResolver;
+import org.springframework.core.env.StandardEnvironment;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link java.beans.PropertyEditor Editor} for {@link Resource}
+ * descriptors, to automatically convert {@code String} locations
+ * e.g. {@code file:C:/myfile.txt} or {@code classpath:myfile.txt} to
+ * {@code Resource} properties instead of using a {@code String} location property.
+ *
+ * <p>The path may contain {@code ${...}} placeholders, to be
+ * resolved as {@link org.springframework.core.env.Environment} properties:
+ * e.g. {@code ${user.dir}}. Unresolvable placeholders are ignored by default.
+ *
+ * <p>Delegates to a {@link ResourceLoader} to do the heavy lifting,
+ * by default using a {@link DefaultResourceLoader}.
+ *
+ * @author Juergen Hoeller
+ * @author Dave Syer
+ * @author Chris Beams
+ * @since 28.12.2003
+ * @see Resource
+ * @see ResourceLoader
+ * @see DefaultResourceLoader
+ * @see PropertyResolver#resolvePlaceholders
+ */
+public class ResourceEditor extends PropertyEditorSupport {
+
+ private final ResourceLoader resourceLoader;
+
+ private PropertyResolver propertyResolver;
+
+ private final boolean ignoreUnresolvablePlaceholders;
+
+
+ /**
+ * Create a new instance of the {@link ResourceEditor} class
+ * using a {@link DefaultResourceLoader} and {@link StandardEnvironment}.
+ */
+ public ResourceEditor() {
+ this(new DefaultResourceLoader(), null);
+ }
+
+ /**
+ * Create a new instance of the {@link ResourceEditor} class
+ * using the given {@link ResourceLoader} and a {@link StandardEnvironment}.
+ * @param resourceLoader the {@code ResourceLoader} to use
+ * @deprecated as of Spring 3.1 in favor of
+ * {@link #ResourceEditor(ResourceLoader, PropertyResolver)}
+ */
+ @Deprecated
+ public ResourceEditor(ResourceLoader resourceLoader) {
+ this(resourceLoader, null, true);
+ }
+
+ /**
+ * Create a new instance of the {@link ResourceEditor} class
+ * using the given {@link ResourceLoader}.
+ * @param resourceLoader the {@code ResourceLoader} to use
+ * @param ignoreUnresolvablePlaceholders whether to ignore unresolvable placeholders
+ * if no corresponding property could be found
+ * @deprecated as of Spring 3.1 in favor of
+ * {@link #ResourceEditor(ResourceLoader, PropertyResolver, boolean)}
+ */
+ @Deprecated
+ public ResourceEditor(ResourceLoader resourceLoader, boolean ignoreUnresolvablePlaceholders) {
+ this(resourceLoader, null, ignoreUnresolvablePlaceholders);
+ }
+
+ /**
+ * Create a new instance of the {@link ResourceEditor} class
+ * using the given {@link ResourceLoader} and {@link PropertyResolver}.
+ * @param resourceLoader the {@code ResourceLoader} to use
+ * @param propertyResolver the {@code PropertyResolver} to use
+ */
+ public ResourceEditor(ResourceLoader resourceLoader, PropertyResolver propertyResolver) {
+ this(resourceLoader, propertyResolver, true);
+ }
+
+ /**
+ * Create a new instance of the {@link ResourceEditor} class
+ * using the given {@link ResourceLoader}.
+ * @param resourceLoader the {@code ResourceLoader} to use
+ * @param propertyResolver the {@code PropertyResolver} to use
+ * @param ignoreUnresolvablePlaceholders whether to ignore unresolvable placeholders
+ * if no corresponding property could be found in the given {@code propertyResolver}
+ */
+ public ResourceEditor(ResourceLoader resourceLoader, PropertyResolver propertyResolver, boolean ignoreUnresolvablePlaceholders) {
+ Assert.notNull(resourceLoader, "ResourceLoader must not be null");
+ this.resourceLoader = resourceLoader;
+ this.propertyResolver = propertyResolver;
+ this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
+ }
+
+
+ @Override
+ public void setAsText(String text) {
+ if (StringUtils.hasText(text)) {
+ String locationToUse = resolvePath(text).trim();
+ setValue(this.resourceLoader.getResource(locationToUse));
+ }
+ else {
+ setValue(null);
+ }
+ }
+
+ /**
+ * Resolve the given path, replacing placeholders with corresponding
+ * property values from the {@code environment} if necessary.
+ * @param path the original file path
+ * @return the resolved file path
+ * @see PropertyResolver#resolvePlaceholders
+ * @see PropertyResolver#resolveRequiredPlaceholders
+ */
+ protected String resolvePath(String path) {
+ if (this.propertyResolver == null) {
+ this.propertyResolver = new StandardEnvironment();
+ }
+ return (this.ignoreUnresolvablePlaceholders ? this.propertyResolver.resolvePlaceholders(path) :
+ this.propertyResolver.resolveRequiredPlaceholders(path));
+ }
+
+
+ @Override
+ public String getAsText() {
+ Resource value = (Resource) getValue();
+ try {
+ // Try to determine URL for resource.
+ return (value != null ? value.getURL().toExternalForm() : "");
+ }
+ catch (IOException ex) {
+ // Couldn't determine resource URL - return null to indicate
+ // that there is no appropriate text representation.
+ return null;
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/ResourceLoader.java b/spring-core/src/main/java/org/springframework/core/io/ResourceLoader.java
new file mode 100644
index 00000000..b753535a
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/ResourceLoader.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2002-2014 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;
+
+import org.springframework.util.ResourceUtils;
+
+/**
+ * Strategy interface for loading resources (e.. class path or file system
+ * resources). An {@link org.springframework.context.ApplicationContext}
+ * is required to provide this functionality, plus extended
+ * {@link org.springframework.core.io.support.ResourcePatternResolver} support.
+ *
+ * <p>{@link DefaultResourceLoader} is a standalone implementation that is
+ * usable outside an ApplicationContext, also used by {@link ResourceEditor}.
+ *
+ * <p>Bean properties of type Resource and Resource array can be populated
+ * from Strings when running in an ApplicationContext, using the particular
+ * context's resource loading strategy.
+ *
+ * @author Juergen Hoeller
+ * @since 10.03.2004
+ * @see Resource
+ * @see org.springframework.core.io.support.ResourcePatternResolver
+ * @see org.springframework.context.ApplicationContext
+ * @see org.springframework.context.ResourceLoaderAware
+ */
+public interface ResourceLoader {
+
+ /** Pseudo URL prefix for loading from the class path: "classpath:" */
+ String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
+
+
+ /**
+ * Return a Resource handle for the specified resource.
+ * The handle should always be a reusable resource descriptor,
+ * allowing for multiple {@link Resource#getInputStream()} calls.
+ * <p><ul>
+ * <li>Must support fully qualified URLs, e.g. "file:C:/test.dat".
+ * <li>Must support classpath pseudo-URLs, e.g. "classpath:test.dat".
+ * <li>Should support relative file paths, e.g. "WEB-INF/test.dat".
+ * (This will be implementation-specific, typically provided by an
+ * ApplicationContext implementation.)
+ * </ul>
+ * <p>Note that a Resource handle does not imply an existing resource;
+ * you need to invoke {@link Resource#exists} to check for existence.
+ * @param location the resource location
+ * @return a corresponding Resource handle
+ * @see #CLASSPATH_URL_PREFIX
+ * @see org.springframework.core.io.Resource#exists
+ * @see org.springframework.core.io.Resource#getInputStream
+ */
+ Resource getResource(String location);
+
+ /**
+ * Expose the ClassLoader used by this ResourceLoader.
+ * <p>Clients which need to access the ClassLoader directly can do so
+ * in a uniform manner with the ResourceLoader, rather than relying
+ * on the thread context ClassLoader.
+ * @return the ClassLoader (only {@code null} if even the system
+ * ClassLoader isn't accessible)
+ * @see org.springframework.util.ClassUtils#getDefaultClassLoader()
+ */
+ ClassLoader getClassLoader();
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/UrlResource.java b/spring-core/src/main/java/org/springframework/core/io/UrlResource.java
new file mode 100644
index 00000000..626f8f70
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/UrlResource.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2002-2013 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;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ResourceUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link Resource} implementation for {@code java.net.URL} locators.
+ * Obviously supports resolution as URL, and also as File in case of
+ * the "file:" protocol.
+ *
+ * @author Juergen Hoeller
+ * @since 28.12.2003
+ * @see java.net.URL
+ */
+public class UrlResource extends AbstractFileResolvingResource {
+
+ /**
+ * Original URI, if available; used for URI and File access.
+ */
+ private final URI uri;
+
+ /**
+ * Original URL, used for actual access.
+ */
+ private final URL url;
+
+ /**
+ * Cleaned URL (with normalized path), used for comparisons.
+ */
+ private final URL cleanedUrl;
+
+
+ /**
+ * Create a new UrlResource based on the given URI object.
+ * @param uri a URI
+ * @throws MalformedURLException if the given URL path is not valid
+ */
+ public UrlResource(URI uri) throws MalformedURLException {
+ Assert.notNull(uri, "URI must not be null");
+ this.uri = uri;
+ this.url = uri.toURL();
+ this.cleanedUrl = getCleanedUrl(this.url, uri.toString());
+ }
+
+ /**
+ * Create a new UrlResource based on the given URL object.
+ * @param url a URL
+ */
+ public UrlResource(URL url) {
+ Assert.notNull(url, "URL must not be null");
+ this.url = url;
+ this.cleanedUrl = getCleanedUrl(this.url, url.toString());
+ this.uri = null;
+ }
+
+ /**
+ * Create a new UrlResource based on a URL path.
+ * <p>Note: The given path needs to be pre-encoded if necessary.
+ * @param path a URL path
+ * @throws MalformedURLException if the given URL path is not valid
+ * @see java.net.URL#URL(String)
+ */
+ public UrlResource(String path) throws MalformedURLException {
+ Assert.notNull(path, "Path must not be null");
+ this.uri = null;
+ this.url = new URL(path);
+ this.cleanedUrl = getCleanedUrl(this.url, path);
+ }
+
+ /**
+ * Create a new UrlResource based on a URI specification.
+ * <p>The given parts will automatically get encoded if necessary.
+ * @param protocol the URL protocol to use (e.g. "jar" or "file" - without colon);
+ * also known as "scheme"
+ * @param location the location (e.g. the file path within that protocol);
+ * also known as "scheme-specific part"
+ * @throws MalformedURLException if the given URL specification is not valid
+ * @see java.net.URI#URI(String, String, String)
+ */
+ public UrlResource(String protocol, String location) throws MalformedURLException {
+ this(protocol, location, null);
+ }
+
+ /**
+ * Create a new UrlResource based on a URI specification.
+ * <p>The given parts will automatically get encoded if necessary.
+ * @param protocol the URL protocol to use (e.g. "jar" or "file" - without colon);
+ * also known as "scheme"
+ * @param location the location (e.g. the file path within that protocol);
+ * also known as "scheme-specific part"
+ * @param fragment the fragment within that location (e.g. anchor on an HTML page,
+ * as following after a "#" separator)
+ * @throws MalformedURLException if the given URL specification is not valid
+ * @see java.net.URI#URI(String, String, String)
+ */
+ public UrlResource(String protocol, String location, String fragment) throws MalformedURLException {
+ try {
+ this.uri = new URI(protocol, location, fragment);
+ this.url = this.uri.toURL();
+ this.cleanedUrl = getCleanedUrl(this.url, this.uri.toString());
+ }
+ catch (URISyntaxException ex) {
+ MalformedURLException exToThrow = new MalformedURLException(ex.getMessage());
+ exToThrow.initCause(ex);
+ throw exToThrow;
+ }
+ }
+
+ /**
+ * Determine a cleaned URL for the given original URL.
+ * @param originalUrl the original URL
+ * @param originalPath the original URL path
+ * @return the cleaned URL
+ * @see org.springframework.util.StringUtils#cleanPath
+ */
+ private URL getCleanedUrl(URL originalUrl, String originalPath) {
+ try {
+ return new URL(StringUtils.cleanPath(originalPath));
+ }
+ catch (MalformedURLException ex) {
+ // Cleaned URL path cannot be converted to URL
+ // -> take original URL.
+ return originalUrl;
+ }
+ }
+
+
+ /**
+ * This implementation opens an InputStream for the given URL.
+ * It sets the "UseCaches" flag to {@code false},
+ * mainly to avoid jar file locking on Windows.
+ * @see java.net.URL#openConnection()
+ * @see java.net.URLConnection#setUseCaches(boolean)
+ * @see java.net.URLConnection#getInputStream()
+ */
+ public InputStream getInputStream() throws IOException {
+ URLConnection con = this.url.openConnection();
+ ResourceUtils.useCachesIfNecessary(con);
+ try {
+ return con.getInputStream();
+ }
+ catch (IOException ex) {
+ // Close the HTTP connection (if applicable).
+ if (con instanceof HttpURLConnection) {
+ ((HttpURLConnection) con).disconnect();
+ }
+ throw ex;
+ }
+ }
+
+ /**
+ * This implementation returns the underlying URL reference.
+ */
+ @Override
+ public URL getURL() throws IOException {
+ return this.url;
+ }
+
+ /**
+ * This implementation returns the underlying URI directly,
+ * if possible.
+ */
+ @Override
+ public URI getURI() throws IOException {
+ if (this.uri != null) {
+ return this.uri;
+ }
+ else {
+ return super.getURI();
+ }
+ }
+
+ /**
+ * This implementation returns a File reference for the underlying URL/URI,
+ * provided that it refers to a file in the file system.
+ * @see org.springframework.util.ResourceUtils#getFile(java.net.URL, String)
+ */
+ @Override
+ public File getFile() throws IOException {
+ if (this.uri != null) {
+ return super.getFile(this.uri);
+ }
+ else {
+ return super.getFile();
+ }
+ }
+
+ /**
+ * This implementation creates a UrlResource, applying the given path
+ * relative to the path of the underlying URL of this resource descriptor.
+ * @see java.net.URL#URL(java.net.URL, String)
+ */
+ @Override
+ public Resource createRelative(String relativePath) throws MalformedURLException {
+ if (relativePath.startsWith("/")) {
+ relativePath = relativePath.substring(1);
+ }
+ return new UrlResource(new URL(this.url, relativePath));
+ }
+
+ /**
+ * This implementation returns the name of the file that this URL refers to.
+ * @see java.net.URL#getFile()
+ * @see java.io.File#getName()
+ */
+ @Override
+ public String getFilename() {
+ return new File(this.url.getFile()).getName();
+ }
+
+ /**
+ * This implementation returns a description that includes the URL.
+ */
+ public String getDescription() {
+ return "URL [" + this.url + "]";
+ }
+
+
+ /**
+ * This implementation compares the underlying URL references.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return (obj == this ||
+ (obj instanceof UrlResource && this.cleanedUrl.equals(((UrlResource) obj).cleanedUrl)));
+ }
+
+ /**
+ * This implementation returns the hash code of the underlying URL reference.
+ */
+ @Override
+ public int hashCode() {
+ return this.cleanedUrl.hashCode();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/VfsResource.java b/spring-core/src/main/java/org/springframework/core/io/VfsResource.java
new file mode 100644
index 00000000..4877d0ad
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/VfsResource.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+
+import org.springframework.core.NestedIOException;
+import org.springframework.util.Assert;
+
+/**
+ * VFS based {@link Resource} implementation.
+ * Supports the corresponding VFS API versions on JBoss AS 5.x as well as 6.x and 7.x.
+ *
+ * @author Ales Justin
+ * @author Juergen Hoeller
+ * @author Costin Leau
+ * @since 3.0
+ * @see org.jboss.vfs.VirtualFile
+ */
+public class VfsResource extends AbstractResource {
+
+ private final Object resource;
+
+
+ public VfsResource(Object resources) {
+ Assert.notNull(resources, "VirtualFile must not be null");
+ this.resource = resources;
+ }
+
+
+ public InputStream getInputStream() throws IOException {
+ return VfsUtils.getInputStream(this.resource);
+ }
+
+ @Override
+ public boolean exists() {
+ return VfsUtils.exists(this.resource);
+ }
+
+ @Override
+ public boolean isReadable() {
+ return VfsUtils.isReadable(this.resource);
+ }
+
+ @Override
+ public URL getURL() throws IOException {
+ try {
+ return VfsUtils.getURL(this.resource);
+ }
+ catch (Exception ex) {
+ throw new NestedIOException("Failed to obtain URL for file " + this.resource, ex);
+ }
+ }
+
+ @Override
+ public URI getURI() throws IOException {
+ try {
+ return VfsUtils.getURI(this.resource);
+ }
+ catch (Exception ex) {
+ throw new NestedIOException("Failed to obtain URI for " + this.resource, ex);
+ }
+ }
+
+ @Override
+ public File getFile() throws IOException {
+ return VfsUtils.getFile(this.resource);
+ }
+
+ @Override
+ public long contentLength() throws IOException {
+ return VfsUtils.getSize(this.resource);
+ }
+
+ @Override
+ public long lastModified() throws IOException {
+ return VfsUtils.getLastModified(this.resource);
+ }
+
+ @Override
+ public Resource createRelative(String relativePath) throws IOException {
+ if (!relativePath.startsWith(".") && relativePath.contains("/")) {
+ try {
+ return new VfsResource(VfsUtils.getChild(this.resource, relativePath));
+ }
+ catch (IOException ex) {
+ // fall back to getRelative
+ }
+ }
+
+ return new VfsResource(VfsUtils.getRelative(new URL(getURL(), relativePath)));
+ }
+
+ @Override
+ public String getFilename() {
+ return VfsUtils.getName(this.resource);
+ }
+
+ public String getDescription() {
+ return this.resource.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return (obj == this || (obj instanceof VfsResource && this.resource.equals(((VfsResource) obj).resource)));
+ }
+
+ @Override
+ public int hashCode() {
+ return this.resource.hashCode();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/VfsUtils.java b/spring-core/src/main/java/org/springframework/core/io/VfsUtils.java
new file mode 100644
index 00000000..8c9a7aa1
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/VfsUtils.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.net.URL;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.core.NestedIOException;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * Utility for detecting the JBoss VFS version available in the classpath.
+ * JBoss AS 5+ uses VFS 2.x (package {@code org.jboss.virtual}) while
+ * JBoss AS 6+ uses VFS 3.x (package {@code org.jboss.vfs}).
+ *
+ * <p>Thanks go to Marius Bogoevici for the initial patch.
+ *
+ * <b>Note:</b> This is an internal class and should not be used outside the framework.
+ *
+ * @author Costin Leau
+ * @since 3.0.3
+ */
+public abstract class VfsUtils {
+
+ private static final Log logger = LogFactory.getLog(VfsUtils.class);
+
+ private static final String VFS2_PKG = "org.jboss.virtual.";
+ private static final String VFS3_PKG = "org.jboss.vfs.";
+ private static final String VFS_NAME = "VFS";
+
+ private static enum VFS_VER { V2, V3 }
+
+ private static VFS_VER version;
+
+ private static Method VFS_METHOD_GET_ROOT_URL = null;
+ private static Method VFS_METHOD_GET_ROOT_URI = null;
+
+ private static Method VIRTUAL_FILE_METHOD_EXISTS = null;
+ private static Method VIRTUAL_FILE_METHOD_GET_INPUT_STREAM;
+ private static Method VIRTUAL_FILE_METHOD_GET_SIZE;
+ private static Method VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED;
+ private static Method VIRTUAL_FILE_METHOD_TO_URL;
+ private static Method VIRTUAL_FILE_METHOD_TO_URI;
+ private static Method VIRTUAL_FILE_METHOD_GET_NAME;
+ private static Method VIRTUAL_FILE_METHOD_GET_PATH_NAME;
+ private static Method VIRTUAL_FILE_METHOD_GET_CHILD;
+
+ protected static Class<?> VIRTUAL_FILE_VISITOR_INTERFACE;
+ protected static Method VIRTUAL_FILE_METHOD_VISIT;
+
+ private static Method VFS_UTILS_METHOD_IS_NESTED_FILE = null;
+ private static Method VFS_UTILS_METHOD_GET_COMPATIBLE_URI = null;
+ private static Field VISITOR_ATTRIBUTES_FIELD_RECURSE = null;
+ private static Method GET_PHYSICAL_FILE = null;
+
+ static {
+ ClassLoader loader = VfsUtils.class.getClassLoader();
+ String pkg;
+ Class<?> vfsClass;
+
+ // check for JBoss 6
+ try {
+ vfsClass = loader.loadClass(VFS3_PKG + VFS_NAME);
+ version = VFS_VER.V3;
+ pkg = VFS3_PKG;
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("JBoss VFS packages for JBoss AS 6 found");
+ }
+ }
+ catch (ClassNotFoundException ex) {
+ // fallback to JBoss 5
+ if (logger.isDebugEnabled())
+ logger.debug("JBoss VFS packages for JBoss AS 6 not found; falling back to JBoss AS 5 packages");
+ try {
+ vfsClass = loader.loadClass(VFS2_PKG + VFS_NAME);
+
+ version = VFS_VER.V2;
+ pkg = VFS2_PKG;
+
+ if (logger.isDebugEnabled())
+ logger.debug("JBoss VFS packages for JBoss AS 5 found");
+ }
+ catch (ClassNotFoundException ex2) {
+ logger.error("JBoss VFS packages (for both JBoss AS 5 and 6) were not found - JBoss VFS support disabled");
+ throw new IllegalStateException("Cannot detect JBoss VFS packages", ex2);
+ }
+ }
+
+ // cache reflective information
+ try {
+ String methodName = (VFS_VER.V3.equals(version) ? "getChild" : "getRoot");
+
+ VFS_METHOD_GET_ROOT_URL = ReflectionUtils.findMethod(vfsClass, methodName, URL.class);
+ VFS_METHOD_GET_ROOT_URI = ReflectionUtils.findMethod(vfsClass, methodName, URI.class);
+
+ Class<?> virtualFile = loader.loadClass(pkg + "VirtualFile");
+
+ VIRTUAL_FILE_METHOD_EXISTS = ReflectionUtils.findMethod(virtualFile, "exists");
+ VIRTUAL_FILE_METHOD_GET_INPUT_STREAM = ReflectionUtils.findMethod(virtualFile, "openStream");
+ VIRTUAL_FILE_METHOD_GET_SIZE = ReflectionUtils.findMethod(virtualFile, "getSize");
+ VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED = ReflectionUtils.findMethod(virtualFile, "getLastModified");
+ VIRTUAL_FILE_METHOD_TO_URI = ReflectionUtils.findMethod(virtualFile, "toURI");
+ VIRTUAL_FILE_METHOD_TO_URL = ReflectionUtils.findMethod(virtualFile, "toURL");
+ VIRTUAL_FILE_METHOD_GET_NAME = ReflectionUtils.findMethod(virtualFile, "getName");
+ VIRTUAL_FILE_METHOD_GET_PATH_NAME = ReflectionUtils.findMethod(virtualFile, "getPathName");
+ GET_PHYSICAL_FILE = ReflectionUtils.findMethod(virtualFile, "getPhysicalFile");
+
+ methodName = (VFS_VER.V3.equals(version) ? "getChild" : "findChild");
+
+ VIRTUAL_FILE_METHOD_GET_CHILD = ReflectionUtils.findMethod(virtualFile, methodName, String.class);
+
+ Class<?> utilsClass = loader.loadClass(pkg + "VFSUtils");
+
+ VFS_UTILS_METHOD_GET_COMPATIBLE_URI = ReflectionUtils.findMethod(utilsClass, "getCompatibleURI",
+ virtualFile);
+ VFS_UTILS_METHOD_IS_NESTED_FILE = ReflectionUtils.findMethod(utilsClass, "isNestedFile", virtualFile);
+
+ VIRTUAL_FILE_VISITOR_INTERFACE = loader.loadClass(pkg + "VirtualFileVisitor");
+ VIRTUAL_FILE_METHOD_VISIT = ReflectionUtils.findMethod(virtualFile, "visit", VIRTUAL_FILE_VISITOR_INTERFACE);
+
+ Class<?> visitorAttributesClass = loader.loadClass(pkg + "VisitorAttributes");
+ VISITOR_ATTRIBUTES_FIELD_RECURSE = ReflectionUtils.findField(visitorAttributesClass, "RECURSE");
+ }
+ catch (ClassNotFoundException ex) {
+ throw new IllegalStateException("Could not detect the JBoss VFS infrastructure", ex);
+ }
+ }
+
+ protected static Object invokeVfsMethod(Method method, Object target, Object... args) throws IOException {
+ try {
+ return method.invoke(target, args);
+ }
+ catch (InvocationTargetException ex) {
+ Throwable targetEx = ex.getTargetException();
+ if (targetEx instanceof IOException) {
+ throw (IOException) targetEx;
+ }
+ ReflectionUtils.handleInvocationTargetException(ex);
+ }
+ catch (Exception ex) {
+ ReflectionUtils.handleReflectionException(ex);
+ }
+
+ throw new IllegalStateException("Invalid code path reached");
+ }
+
+ static boolean exists(Object vfsResource) {
+ try {
+ return (Boolean) invokeVfsMethod(VIRTUAL_FILE_METHOD_EXISTS, vfsResource);
+ }
+ catch (IOException ex) {
+ return false;
+ }
+ }
+
+ static boolean isReadable(Object vfsResource) {
+ try {
+ return ((Long) invokeVfsMethod(VIRTUAL_FILE_METHOD_GET_SIZE, vfsResource) > 0);
+ }
+ catch (IOException ex) {
+ return false;
+ }
+ }
+
+ static long getSize(Object vfsResource) throws IOException {
+ return (Long) invokeVfsMethod(VIRTUAL_FILE_METHOD_GET_SIZE, vfsResource);
+ }
+
+ static long getLastModified(Object vfsResource) throws IOException {
+ return (Long) invokeVfsMethod(VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED, vfsResource);
+ }
+
+ static InputStream getInputStream(Object vfsResource) throws IOException {
+ return (InputStream) invokeVfsMethod(VIRTUAL_FILE_METHOD_GET_INPUT_STREAM, vfsResource);
+ }
+
+ static URL getURL(Object vfsResource) throws IOException {
+ return (URL) invokeVfsMethod(VIRTUAL_FILE_METHOD_TO_URL, vfsResource);
+ }
+
+ static URI getURI(Object vfsResource) throws IOException {
+ return (URI) invokeVfsMethod(VIRTUAL_FILE_METHOD_TO_URI, vfsResource);
+ }
+
+ static String getName(Object vfsResource) {
+ try {
+ return (String) invokeVfsMethod(VIRTUAL_FILE_METHOD_GET_NAME, vfsResource);
+ }
+ catch (IOException ex) {
+ throw new IllegalStateException("Cannot get resource name", ex);
+ }
+ }
+
+ static Object getRelative(URL url) throws IOException {
+ return invokeVfsMethod(VFS_METHOD_GET_ROOT_URL, null, url);
+ }
+
+ static Object getChild(Object vfsResource, String path) throws IOException {
+ return invokeVfsMethod(VIRTUAL_FILE_METHOD_GET_CHILD, vfsResource, path);
+ }
+
+ static File getFile(Object vfsResource) throws IOException {
+ if (VFS_VER.V2.equals(version)) {
+ if ((Boolean) invokeVfsMethod(VFS_UTILS_METHOD_IS_NESTED_FILE, null, vfsResource)) {
+ throw new IOException("File resolution not supported for nested resource: " + vfsResource);
+ }
+ try {
+ return new File((URI) invokeVfsMethod(VFS_UTILS_METHOD_GET_COMPATIBLE_URI, null, vfsResource));
+ }
+ catch (Exception ex) {
+ throw new NestedIOException("Failed to obtain File reference for " + vfsResource, ex);
+ }
+ }
+ else {
+ return (File) invokeVfsMethod(GET_PHYSICAL_FILE, vfsResource);
+ }
+ }
+
+ static Object getRoot(URI url) throws IOException {
+ return invokeVfsMethod(VFS_METHOD_GET_ROOT_URI, null, url);
+ }
+
+ // protected methods used by the support sub-package
+
+ protected static Object getRoot(URL url) throws IOException {
+ return invokeVfsMethod(VFS_METHOD_GET_ROOT_URL, null, url);
+ }
+
+ protected static Object doGetVisitorAttribute() {
+ return ReflectionUtils.getField(VISITOR_ATTRIBUTES_FIELD_RECURSE, null);
+ }
+
+ protected static String doGetPath(Object resource) {
+ return (String) ReflectionUtils.invokeMethod(VIRTUAL_FILE_METHOD_GET_PATH_NAME, resource);
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/WritableResource.java b/spring-core/src/main/java/org/springframework/core/io/WritableResource.java
new file mode 100644
index 00000000..72d4fe34
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/WritableResource.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2012 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;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Extended interface for a resource that supports writing to it.
+ * Provides an {@link #getOutputStream() OutputStream accessor}.
+ *
+ * @author Juergen Hoeller
+ * @since 3.1
+ * @see java.io.OutputStream
+ */
+public interface WritableResource extends Resource {
+
+ /**
+ * Return whether the contents of this resource can be modified,
+ * e.g. via {@link #getOutputStream()} or {@link #getFile()}.
+ * <p>Will be {@code true} for typical resource descriptors;
+ * note that actual content writing may still fail when attempted.
+ * However, a value of {@code false} is a definitive indication
+ * that the resource content cannot be modified.
+ * @see #getOutputStream()
+ * @see #isReadable()
+ */
+ boolean isWritable();
+
+ /**
+ * Return an {@link OutputStream} for the underlying resource,
+ * allowing to (over-)write its content.
+ * @throws IOException if the stream could not be opened
+ * @see #getInputStream()
+ */
+ OutputStream getOutputStream() throws IOException;
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/package-info.java b/spring-core/src/main/java/org/springframework/core/io/package-info.java
new file mode 100644
index 00000000..4631d05a
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/package-info.java
@@ -0,0 +1,8 @@
+
+/**
+ *
+ * Generic abstraction for (file-based) resources, used throughout the framework.
+ *
+ */
+package org.springframework.core.io;
+
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java b/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java
new file mode 100644
index 00000000..04a17383
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2002-2013 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.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+
+import org.springframework.core.io.Resource;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Holder that combines a {@link org.springframework.core.io.Resource}
+ * with a specific encoding to be used for reading from the resource.
+ *
+ * <p>Used as argument for operations that support to read content with
+ * a specific encoding (usually through a {@code java.io.Reader}.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2.6
+ * @see java.io.Reader
+ */
+public class EncodedResource {
+
+ private final Resource resource;
+
+ private String encoding;
+
+ private Charset charset;
+
+
+ /**
+ * Create a new EncodedResource for the given Resource,
+ * not specifying a specific encoding.
+ * @param resource the Resource to hold
+ */
+ public EncodedResource(Resource resource) {
+ Assert.notNull(resource, "Resource must not be null");
+ this.resource = resource;
+ }
+
+ /**
+ * Create a new EncodedResource for the given Resource,
+ * using the specified encoding.
+ * @param resource the Resource to hold
+ * @param encoding the encoding to use for reading from the resource
+ */
+ public EncodedResource(Resource resource, String encoding) {
+ Assert.notNull(resource, "Resource must not be null");
+ this.resource = resource;
+ this.encoding = encoding;
+ }
+
+ /**
+ * Create a new EncodedResource for the given Resource,
+ * using the specified encoding.
+ * @param resource the Resource to hold
+ * @param charset the charset to use for reading from the resource
+ */
+ public EncodedResource(Resource resource, Charset charset) {
+ Assert.notNull(resource, "Resource must not be null");
+ this.resource = resource;
+ this.charset = charset;
+ }
+
+
+ /**
+ * Return the Resource held.
+ */
+ public final Resource getResource() {
+ return this.resource;
+ }
+
+ /**
+ * Return the encoding to use for reading from the resource,
+ * or {@code null} if none specified.
+ */
+ public final String getEncoding() {
+ return this.encoding;
+ }
+
+ /**
+ * Return the charset to use for reading from the resource,
+ * or {@code null} if none specified.
+ */
+ public final Charset getCharset() {
+ return this.charset;
+ }
+
+
+ /**
+ * Determine whether a {@link Reader} is required as opposed to an {@link InputStream},
+ * i.e. whether an encoding or a charset has been specified.
+ * @see #getReader()
+ * @see #getInputStream()
+ */
+ public boolean requiresReader() {
+ return (this.encoding != null || this.charset != null);
+ }
+
+ /**
+ * Open a {@code java.io.Reader} for the specified resource,
+ * using the specified encoding (if any).
+ * @throws IOException if opening the Reader failed
+ * @see #requiresReader()
+ */
+ public Reader getReader() throws IOException {
+ if (this.charset != null) {
+ return new InputStreamReader(this.resource.getInputStream(), this.charset);
+ }
+ else if (this.encoding != null) {
+ return new InputStreamReader(this.resource.getInputStream(), this.encoding);
+ }
+ else {
+ return new InputStreamReader(this.resource.getInputStream());
+ }
+ }
+
+ /**
+ * Open an {@code java.io.InputStream} for the specified resource,
+ * typically assuming that there is no specific encoding to use.
+ * @throws IOException if opening the InputStream failed
+ * @see #requiresReader()
+ */
+ public InputStream getInputStream() throws IOException {
+ return this.resource.getInputStream();
+ }
+
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof EncodedResource) {
+ EncodedResource otherRes = (EncodedResource) obj;
+ return (this.resource.equals(otherRes.resource) &&
+ ObjectUtils.nullSafeEquals(this.encoding, otherRes.encoding));
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.resource.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return this.resource.toString();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/LocalizedResourceHelper.java b/spring-core/src/main/java/org/springframework/core/io/support/LocalizedResourceHelper.java
new file mode 100644
index 00000000..57f2645a
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/support/LocalizedResourceHelper.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2002-2012 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.util.Locale;
+
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.util.Assert;
+
+/**
+ * Helper class for loading a localized resource,
+ * specified through name, extension and current locale.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2.5
+ */
+public class LocalizedResourceHelper {
+
+ /** The default separator to use inbetween file name parts: an underscore */
+ public static final String DEFAULT_SEPARATOR = "_";
+
+
+ private final ResourceLoader resourceLoader;
+
+ private String separator = DEFAULT_SEPARATOR;
+
+
+ /**
+ * Create a new LocalizedResourceHelper with a DefaultResourceLoader.
+ * @see org.springframework.core.io.DefaultResourceLoader
+ */
+ public LocalizedResourceHelper() {
+ this.resourceLoader = new DefaultResourceLoader();
+ }
+
+ /**
+ * Create a new LocalizedResourceHelper with the given ResourceLoader.
+ * @param resourceLoader the ResourceLoader to use
+ */
+ public LocalizedResourceHelper(ResourceLoader resourceLoader) {
+ Assert.notNull(resourceLoader, "ResourceLoader must not be null");
+ this.resourceLoader = resourceLoader;
+ }
+
+ /**
+ * Set the separator to use inbetween file name parts.
+ * Default is an underscore ("_").
+ */
+ public void setSeparator(String separator) {
+ this.separator = (separator != null ? separator : DEFAULT_SEPARATOR);
+ }
+
+
+ /**
+ * Find the most specific localized resource for the given name,
+ * extension and locale:
+ * <p>The file will be searched with locations in the following order,
+ * similar to {@code java.util.ResourceBundle}'s search order:
+ * <ul>
+ * <li>[name]_[language]_[country]_[variant][extension]
+ * <li>[name]_[language]_[country][extension]
+ * <li>[name]_[language][extension]
+ * <li>[name][extension]
+ * </ul>
+ * <p>If none of the specific files can be found, a resource
+ * descriptor for the default location will be returned.
+ * @param name the name of the file, without localization part nor extension
+ * @param extension the file extension (e.g. ".xls")
+ * @param locale the current locale (may be {@code null})
+ * @return the most specific localized resource found
+ * @see java.util.ResourceBundle
+ */
+ public Resource findLocalizedResource(String name, String extension, Locale locale) {
+ Assert.notNull(name, "Name must not be null");
+ Assert.notNull(extension, "Extension must not be null");
+
+ Resource resource = null;
+
+ if (locale != null) {
+ String lang = locale.getLanguage();
+ String country = locale.getCountry();
+ String variant = locale.getVariant();
+
+ // Check for file with language, country and variant localization.
+ if (variant.length() > 0) {
+ String location =
+ name + this.separator + lang + this.separator + country + this.separator + variant + extension;
+ resource = this.resourceLoader.getResource(location);
+ }
+
+ // Check for file with language and country localization.
+ if ((resource == null || !resource.exists()) && country.length() > 0) {
+ String location = name + this.separator + lang + this.separator + country + extension;
+ resource = this.resourceLoader.getResource(location);
+ }
+
+ // Check for document with language localization.
+ if ((resource == null || !resource.exists()) && lang.length() > 0) {
+ String location = name + this.separator + lang + extension;
+ resource = this.resourceLoader.getResource(location);
+ }
+ }
+
+ // Check for document without localization.
+ if (resource == null || !resource.exists()) {
+ String location = name + extension;
+ resource = this.resourceLoader.getResource(location);
+ }
+
+ return resource;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java
new file mode 100644
index 00000000..72b0c481
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java
@@ -0,0 +1,733 @@
+/*
+ * Copyright 2002-2014 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.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.net.JarURLConnection;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.io.UrlResource;
+import org.springframework.core.io.VfsResource;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.PathMatcher;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.ResourceUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * A {@link ResourcePatternResolver} implementation that is able to resolve a
+ * specified resource location path into one or more matching Resources.
+ * The source path may be a simple path which has a one-to-one mapping to a
+ * target {@link org.springframework.core.io.Resource}, or alternatively
+ * may contain the special "{@code classpath*:}" prefix and/or
+ * internal Ant-style regular expressions (matched using Spring's
+ * {@link org.springframework.util.AntPathMatcher} utility).
+ * Both of the latter are effectively wildcards.
+ *
+ * <p><b>No Wildcards:</b>
+ *
+ * <p>In the simple case, if the specified location path does not start with the
+ * {@code "classpath*:}" prefix, and does not contain a PathMatcher pattern,
+ * this resolver will simply return a single resource via a
+ * {@code getResource()} call on the underlying {@code ResourceLoader}.
+ * Examples are real URLs such as "{@code file:C:/context.xml}", pseudo-URLs
+ * such as "{@code classpath:/context.xml}", and simple unprefixed paths
+ * such as "{@code /WEB-INF/context.xml}". The latter will resolve in a
+ * fashion specific to the underlying {@code ResourceLoader} (e.g.
+ * {@code ServletContextResource} for a {@code WebApplicationContext}).
+ *
+ * <p><b>Ant-style Patterns:</b>
+ *
+ * <p>When the path location contains an Ant-style pattern, e.g.:
+ * <pre class="code">
+ * /WEB-INF/*-context.xml
+ * com/mycompany/**&#47;applicationContext.xml
+ * file:C:/some/path/*-context.xml
+ * classpath:com/mycompany/**&#47;applicationContext.xml</pre>
+ * the resolver follows a more complex but defined procedure to try to resolve
+ * the wildcard. It produces a {@code Resource} for the path up to the last
+ * non-wildcard segment and obtains a {@code URL} from it. If this URL is
+ * not a "{@code jar:}" URL or container-specific variant (e.g.
+ * "{@code zip:}" in WebLogic, "{@code wsjar}" in WebSphere", etc.),
+ * then a {@code java.io.File} is obtained from it, and used to resolve the
+ * wildcard by walking the filesystem. In the case of a jar URL, the resolver
+ * either gets a {@code java.net.JarURLConnection} from it, or manually parses
+ * the jar URL, and then traverses the contents of the jar file, to resolve the
+ * wildcards.
+ *
+ * <p><b>Implications on portability:</b>
+ *
+ * <p>If the specified path is already a file URL (either explicitly, or
+ * implicitly because the base {@code ResourceLoader} is a filesystem one,
+ * then wildcarding is guaranteed to work in a completely portable fashion.
+ *
+ * <p>If the specified path is a classpath location, then the resolver must
+ * obtain the last non-wildcard path segment URL via a
+ * {@code Classloader.getResource()} call. Since this is just a
+ * node of the path (not the file at the end) it is actually undefined
+ * (in the ClassLoader Javadocs) exactly what sort of a URL is returned in
+ * this case. In practice, it is usually a {@code java.io.File} representing
+ * the directory, where the classpath resource resolves to a filesystem
+ * location, or a jar URL of some sort, where the classpath resource resolves
+ * to a jar location. Still, there is a portability concern on this operation.
+ *
+ * <p>If a jar URL is obtained for the last non-wildcard segment, the resolver
+ * must be able to get a {@code java.net.JarURLConnection} from it, or
+ * manually parse the jar URL, to be able to walk the contents of the jar,
+ * and resolve the wildcard. This will work in most environments, but will
+ * fail in others, and it is strongly recommended that the wildcard
+ * resolution of resources coming from jars be thoroughly tested in your
+ * specific environment before you rely on it.
+ *
+ * <p><b>{@code classpath*:} Prefix:</b>
+ *
+ * <p>There is special support for retrieving multiple class path resources with
+ * the same name, via the "{@code classpath*:}" prefix. For example,
+ * "{@code classpath*:META-INF/beans.xml}" will find all "beans.xml"
+ * files in the class path, be it in "classes" directories or in JAR files.
+ * This is particularly useful for autodetecting config files of the same name
+ * at the same location within each jar file. Internally, this happens via a
+ * {@code ClassLoader.getResources()} call, and is completely portable.
+ *
+ * <p>The "classpath*:" prefix can also be combined with a PathMatcher pattern in
+ * the rest of the location path, for example "classpath*:META-INF/*-beans.xml".
+ * In this case, the resolution strategy is fairly simple: a
+ * {@code ClassLoader.getResources()} call is used on the last non-wildcard
+ * path segment to get all the matching resources in the class loader hierarchy,
+ * and then off each resource the same PathMatcher resolution strategy described
+ * above is used for the wildcard subpath.
+ *
+ * <p><b>Other notes:</b>
+ *
+ * <p><b>WARNING:</b> Note that "{@code classpath*:}" when combined with
+ * Ant-style patterns will only work reliably with at least one root directory
+ * before the pattern starts, unless the actual target files reside in the file
+ * system. This means that a pattern like "{@code classpath*:*.xml}" will
+ * <i>not</i> retrieve files from the root of jar files but rather only from the
+ * root of expanded directories. This originates from a limitation in the JDK's
+ * {@code ClassLoader.getResources()} method which only returns file system
+ * locations for a passed-in empty String (indicating potential roots to search).
+ *
+ * <p><b>WARNING:</b> Ant-style patterns with "classpath:" resources are not
+ * guaranteed to find matching resources if the root package to search is available
+ * in multiple class path locations. This is because a resource such as
+ * <pre class="code">
+ * com/mycompany/package1/service-context.xml
+ * </pre>
+ * may be in only one location, but when a path such as
+ * <pre class="code">
+ * classpath:com/mycompany/**&#47;service-context.xml
+ * </pre>
+ * is used to try to resolve it, the resolver will work off the (first) URL
+ * returned by {@code getResource("com/mycompany");}. If this base package
+ * node exists in multiple classloader locations, the actual end resource may
+ * not be underneath. Therefore, preferably, use "{@code classpath*:}" with the same
+ * Ant-style pattern in such a case, which will search <i>all</i> class path
+ * locations that contain the root package.
+ *
+ * @author Juergen Hoeller
+ * @author Colin Sampaleanu
+ * @author Marius Bogoevici
+ * @author Costin Leau
+ * @since 1.0.2
+ * @see #CLASSPATH_ALL_URL_PREFIX
+ * @see org.springframework.util.AntPathMatcher
+ * @see org.springframework.core.io.ResourceLoader#getResource(String)
+ * @see ClassLoader#getResources(String)
+ */
+public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
+
+ private static final Log logger = LogFactory.getLog(PathMatchingResourcePatternResolver.class);
+
+ private static Method equinoxResolveMethod;
+
+ static {
+ try {
+ // Detect Equinox OSGi (e.g. on WebSphere 6.1)
+ Class<?> fileLocatorClass = ClassUtils.forName("org.eclipse.core.runtime.FileLocator",
+ PathMatchingResourcePatternResolver.class.getClassLoader());
+ equinoxResolveMethod = fileLocatorClass.getMethod("resolve", URL.class);
+ logger.debug("Found Equinox FileLocator for OSGi bundle URL resolution");
+ }
+ catch (Throwable ex) {
+ equinoxResolveMethod = null;
+ }
+ }
+
+
+ private final ResourceLoader resourceLoader;
+
+ private PathMatcher pathMatcher = new AntPathMatcher();
+
+
+ /**
+ * Create a new PathMatchingResourcePatternResolver with a DefaultResourceLoader.
+ * <p>ClassLoader access will happen via the thread context class loader.
+ * @see org.springframework.core.io.DefaultResourceLoader
+ */
+ public PathMatchingResourcePatternResolver() {
+ this.resourceLoader = new DefaultResourceLoader();
+ }
+
+ /**
+ * Create a new PathMatchingResourcePatternResolver.
+ * <p>ClassLoader access will happen via the thread context class loader.
+ * @param resourceLoader the ResourceLoader to load root directories and
+ * actual resources with
+ */
+ public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
+ Assert.notNull(resourceLoader, "ResourceLoader must not be null");
+ this.resourceLoader = resourceLoader;
+ }
+
+ /**
+ * Create a new PathMatchingResourcePatternResolver with a DefaultResourceLoader.
+ * @param classLoader the ClassLoader to load classpath resources with,
+ * or {@code null} for using the thread context class loader
+ * at the time of actual resource access
+ * @see org.springframework.core.io.DefaultResourceLoader
+ */
+ public PathMatchingResourcePatternResolver(ClassLoader classLoader) {
+ this.resourceLoader = new DefaultResourceLoader(classLoader);
+ }
+
+
+ /**
+ * Return the ResourceLoader that this pattern resolver works with.
+ */
+ public ResourceLoader getResourceLoader() {
+ return this.resourceLoader;
+ }
+
+ public ClassLoader getClassLoader() {
+ return getResourceLoader().getClassLoader();
+ }
+
+ /**
+ * Set the PathMatcher implementation to use for this
+ * resource pattern resolver. Default is AntPathMatcher.
+ * @see org.springframework.util.AntPathMatcher
+ */
+ public void setPathMatcher(PathMatcher pathMatcher) {
+ Assert.notNull(pathMatcher, "PathMatcher must not be null");
+ this.pathMatcher = pathMatcher;
+ }
+
+ /**
+ * Return the PathMatcher that this resource pattern resolver uses.
+ */
+ public PathMatcher getPathMatcher() {
+ return this.pathMatcher;
+ }
+
+
+ public Resource getResource(String location) {
+ return getResourceLoader().getResource(location);
+ }
+
+ public Resource[] getResources(String locationPattern) throws IOException {
+ Assert.notNull(locationPattern, "Location pattern must not be null");
+ if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
+ // a class path resource (multiple resources for same name possible)
+ if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
+ // a class path resource pattern
+ return findPathMatchingResources(locationPattern);
+ }
+ else {
+ // all class path resources with the given name
+ return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
+ }
+ }
+ else {
+ // Only look for a pattern after a prefix here
+ // (to not get fooled by a pattern symbol in a strange prefix).
+ int prefixEnd = locationPattern.indexOf(":") + 1;
+ if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
+ // a file pattern
+ return findPathMatchingResources(locationPattern);
+ }
+ else {
+ // a single resource with the given name
+ return new Resource[] {getResourceLoader().getResource(locationPattern)};
+ }
+ }
+ }
+
+ /**
+ * Find all class location resources with the given location via the ClassLoader.
+ * @param location the absolute path within the classpath
+ * @return the result as Resource array
+ * @throws IOException in case of I/O errors
+ * @see java.lang.ClassLoader#getResources
+ * @see #convertClassLoaderURL
+ */
+ protected Resource[] findAllClassPathResources(String location) throws IOException {
+ String path = location;
+ if (path.startsWith("/")) {
+ path = path.substring(1);
+ }
+ ClassLoader cl = getClassLoader();
+ Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
+ Set<Resource> result = new LinkedHashSet<Resource>(16);
+ while (resourceUrls.hasMoreElements()) {
+ URL url = resourceUrls.nextElement();
+ result.add(convertClassLoaderURL(url));
+ }
+ return result.toArray(new Resource[result.size()]);
+ }
+
+ /**
+ * Convert the given URL as returned from the ClassLoader into a Resource object.
+ * <p>The default implementation simply creates a UrlResource instance.
+ * @param url a URL as returned from the ClassLoader
+ * @return the corresponding Resource object
+ * @see java.lang.ClassLoader#getResources
+ * @see org.springframework.core.io.Resource
+ */
+ protected Resource convertClassLoaderURL(URL url) {
+ return new UrlResource(url);
+ }
+
+ /**
+ * Find all resources that match the given location pattern via the
+ * Ant-style PathMatcher. Supports resources in jar files and zip files
+ * and in the file system.
+ * @param locationPattern the location pattern to match
+ * @return the result as Resource array
+ * @throws IOException in case of I/O errors
+ * @see #doFindPathMatchingJarResources
+ * @see #doFindPathMatchingFileResources
+ * @see org.springframework.util.PathMatcher
+ */
+ protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
+ String rootDirPath = determineRootDir(locationPattern);
+ String subPattern = locationPattern.substring(rootDirPath.length());
+ Resource[] rootDirResources = getResources(rootDirPath);
+ Set<Resource> result = new LinkedHashSet<Resource>(16);
+ for (Resource rootDirResource : rootDirResources) {
+ rootDirResource = resolveRootDirResource(rootDirResource);
+ if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
+ result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
+ }
+ else if (isJarResource(rootDirResource)) {
+ result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
+ }
+ else {
+ result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
+ }
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
+ }
+ return result.toArray(new Resource[result.size()]);
+ }
+
+ /**
+ * Determine the root directory for the given location.
+ * <p>Used for determining the starting point for file matching,
+ * resolving the root directory location to a {@code java.io.File}
+ * and passing it into {@code retrieveMatchingFiles}, with the
+ * remainder of the location as pattern.
+ * <p>Will return "/WEB-INF/" for the pattern "/WEB-INF/*.xml",
+ * for example.
+ * @param location the location to check
+ * @return the part of the location that denotes the root directory
+ * @see #retrieveMatchingFiles
+ */
+ protected String determineRootDir(String location) {
+ int prefixEnd = location.indexOf(":") + 1;
+ int rootDirEnd = location.length();
+ while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) {
+ rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1;
+ }
+ if (rootDirEnd == 0) {
+ rootDirEnd = prefixEnd;
+ }
+ return location.substring(0, rootDirEnd);
+ }
+
+ /**
+ * Resolve the specified resource for path matching.
+ * <p>The default implementation detects an Equinox OSGi "bundleresource:"
+ * / "bundleentry:" URL and resolves it into a standard jar file URL that
+ * can be traversed using Spring's standard jar file traversal algorithm.
+ * @param original the resource to resolve
+ * @return the resolved resource (may be identical to the passed-in resource)
+ * @throws IOException in case of resolution failure
+ */
+ protected Resource resolveRootDirResource(Resource original) throws IOException {
+ if (equinoxResolveMethod != null) {
+ URL url = original.getURL();
+ if (url.getProtocol().startsWith("bundle")) {
+ return new UrlResource((URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, url));
+ }
+ }
+ return original;
+ }
+
+ /**
+ * Return whether the given resource handle indicates a jar resource
+ * that the {@code doFindPathMatchingJarResources} method can handle.
+ * <p>The default implementation checks against the URL protocols
+ * "jar", "zip" and "wsjar" (the latter are used by BEA WebLogic Server
+ * and IBM WebSphere, respectively, but can be treated like jar files).
+ * @param resource the resource handle to check
+ * (usually the root directory to start path matching from)
+ * @see #doFindPathMatchingJarResources
+ * @see org.springframework.util.ResourceUtils#isJarURL
+ */
+ protected boolean isJarResource(Resource resource) throws IOException {
+ return ResourceUtils.isJarURL(resource.getURL());
+ }
+
+ /**
+ * Find all resources in jar files that match the given location pattern
+ * via the Ant-style PathMatcher.
+ * @param rootDirResource the root directory as Resource
+ * @param subPattern the sub pattern to match (below the root directory)
+ * @return the Set of matching Resource instances
+ * @throws IOException in case of I/O errors
+ * @see java.net.JarURLConnection
+ * @see org.springframework.util.PathMatcher
+ */
+ protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, String subPattern)
+ throws IOException {
+
+ URLConnection con = rootDirResource.getURL().openConnection();
+ JarFile jarFile;
+ String jarFileUrl;
+ String rootEntryPath;
+ boolean newJarFile = false;
+
+ if (con instanceof JarURLConnection) {
+ // Should usually be the case for traditional JAR files.
+ JarURLConnection jarCon = (JarURLConnection) con;
+ ResourceUtils.useCachesIfNecessary(jarCon);
+ jarFile = jarCon.getJarFile();
+ jarFileUrl = jarCon.getJarFileURL().toExternalForm();
+ JarEntry jarEntry = jarCon.getJarEntry();
+ rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
+ }
+ else {
+ // No JarURLConnection -> need to resort to URL file parsing.
+ // We'll assume URLs of the format "jar:path!/entry", with the protocol
+ // being arbitrary as long as following the entry format.
+ // We'll also handle paths with and without leading "file:" prefix.
+ String urlFile = rootDirResource.getURL().getFile();
+ int separatorIndex = urlFile.indexOf(ResourceUtils.JAR_URL_SEPARATOR);
+ if (separatorIndex != -1) {
+ jarFileUrl = urlFile.substring(0, separatorIndex);
+ rootEntryPath = urlFile.substring(separatorIndex + ResourceUtils.JAR_URL_SEPARATOR.length());
+ jarFile = getJarFile(jarFileUrl);
+ }
+ else {
+ jarFile = new JarFile(urlFile);
+ jarFileUrl = urlFile;
+ rootEntryPath = "";
+ }
+ newJarFile = true;
+ }
+
+ try {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Looking for matching resources in jar file [" + jarFileUrl + "]");
+ }
+ if (!"".equals(rootEntryPath) && !rootEntryPath.endsWith("/")) {
+ // Root entry path must end with slash to allow for proper matching.
+ // The Sun JRE does not return a slash here, but BEA JRockit does.
+ rootEntryPath = rootEntryPath + "/";
+ }
+ Set<Resource> result = new LinkedHashSet<Resource>(8);
+ for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
+ JarEntry entry = entries.nextElement();
+ String entryPath = entry.getName();
+ if (entryPath.startsWith(rootEntryPath)) {
+ String relativePath = entryPath.substring(rootEntryPath.length());
+ if (getPathMatcher().match(subPattern, relativePath)) {
+ result.add(rootDirResource.createRelative(relativePath));
+ }
+ }
+ }
+ return result;
+ }
+ finally {
+ // Close jar file, but only if freshly obtained -
+ // not from JarURLConnection, which might cache the file reference.
+ if (newJarFile) {
+ jarFile.close();
+ }
+ }
+ }
+
+ /**
+ * Resolve the given jar file URL into a JarFile object.
+ */
+ protected JarFile getJarFile(String jarFileUrl) throws IOException {
+ if (jarFileUrl.startsWith(ResourceUtils.FILE_URL_PREFIX)) {
+ try {
+ return new JarFile(ResourceUtils.toURI(jarFileUrl).getSchemeSpecificPart());
+ }
+ catch (URISyntaxException ex) {
+ // Fallback for URLs that are not valid URIs (should hardly ever happen).
+ return new JarFile(jarFileUrl.substring(ResourceUtils.FILE_URL_PREFIX.length()));
+ }
+ }
+ else {
+ return new JarFile(jarFileUrl);
+ }
+ }
+
+ /**
+ * Find all resources in the file system that match the given location pattern
+ * via the Ant-style PathMatcher.
+ * @param rootDirResource the root directory as Resource
+ * @param subPattern the sub pattern to match (below the root directory)
+ * @return the Set of matching Resource instances
+ * @throws IOException in case of I/O errors
+ * @see #retrieveMatchingFiles
+ * @see org.springframework.util.PathMatcher
+ */
+ protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
+ throws IOException {
+
+ File rootDir;
+ try {
+ rootDir = rootDirResource.getFile().getAbsoluteFile();
+ }
+ catch (IOException ex) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Cannot search for matching files underneath " + rootDirResource +
+ " because it does not correspond to a directory in the file system", ex);
+ }
+ return Collections.emptySet();
+ }
+ return doFindMatchingFileSystemResources(rootDir, subPattern);
+ }
+
+ /**
+ * Find all resources in the file system that match the given location pattern
+ * via the Ant-style PathMatcher.
+ * @param rootDir the root directory in the file system
+ * @param subPattern the sub pattern to match (below the root directory)
+ * @return the Set of matching Resource instances
+ * @throws IOException in case of I/O errors
+ * @see #retrieveMatchingFiles
+ * @see org.springframework.util.PathMatcher
+ */
+ protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
+ }
+ Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
+ Set<Resource> result = new LinkedHashSet<Resource>(matchingFiles.size());
+ for (File file : matchingFiles) {
+ result.add(new FileSystemResource(file));
+ }
+ return result;
+ }
+
+ /**
+ * Retrieve files that match the given path pattern,
+ * checking the given directory and its subdirectories.
+ * @param rootDir the directory to start from
+ * @param pattern the pattern to match against,
+ * relative to the root directory
+ * @return the Set of matching File instances
+ * @throws IOException if directory contents could not be retrieved
+ */
+ protected Set<File> retrieveMatchingFiles(File rootDir, String pattern) throws IOException {
+ if (!rootDir.exists()) {
+ // Silently skip non-existing directories.
+ if (logger.isDebugEnabled()) {
+ logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not exist");
+ }
+ return Collections.emptySet();
+ }
+ if (!rootDir.isDirectory()) {
+ // Complain louder if it exists but is no directory.
+ if (logger.isWarnEnabled()) {
+ logger.warn("Skipping [" + rootDir.getAbsolutePath() + "] because it does not denote a directory");
+ }
+ return Collections.emptySet();
+ }
+ if (!rootDir.canRead()) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Cannot search for matching files underneath directory [" + rootDir.getAbsolutePath() +
+ "] because the application is not allowed to read the directory");
+ }
+ return Collections.emptySet();
+ }
+ String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
+ if (!pattern.startsWith("/")) {
+ fullPattern += "/";
+ }
+ fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
+ Set<File> result = new LinkedHashSet<File>(8);
+ doRetrieveMatchingFiles(fullPattern, rootDir, result);
+ return result;
+ }
+
+ /**
+ * Recursively retrieve files that match the given pattern,
+ * adding them to the given result list.
+ * @param fullPattern the pattern to match against,
+ * with prepended root directory path
+ * @param dir the current directory
+ * @param result the Set of matching File instances to add to
+ * @throws IOException if directory contents could not be retrieved
+ */
+ protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Searching directory [" + dir.getAbsolutePath() +
+ "] for files matching pattern [" + fullPattern + "]");
+ }
+ File[] dirContents = dir.listFiles();
+ if (dirContents == null) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
+ }
+ return;
+ }
+ for (File content : dirContents) {
+ String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
+ if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
+ if (!content.canRead()) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
+ "] because the application is not allowed to read the directory");
+ }
+ }
+ else {
+ doRetrieveMatchingFiles(fullPattern, content, result);
+ }
+ }
+ if (getPathMatcher().match(fullPattern, currPath)) {
+ result.add(content);
+ }
+ }
+ }
+
+
+ /**
+ * Inner delegate class, avoiding a hard JBoss VFS API dependency at runtime.
+ */
+ private static class VfsResourceMatchingDelegate {
+
+ public static Set<Resource> findMatchingResources(
+ Resource rootResource, String locationPattern, PathMatcher pathMatcher) throws IOException {
+ Object root = VfsPatternUtils.findRoot(rootResource.getURL());
+ PatternVirtualFileVisitor visitor =
+ new PatternVirtualFileVisitor(VfsPatternUtils.getPath(root), locationPattern, pathMatcher);
+ VfsPatternUtils.visit(root, visitor);
+ return visitor.getResources();
+ }
+ }
+
+
+ /**
+ * VFS visitor for path matching purposes.
+ */
+ @SuppressWarnings("unused")
+ private static class PatternVirtualFileVisitor implements InvocationHandler {
+
+ private final String subPattern;
+
+ private final PathMatcher pathMatcher;
+
+ private final String rootPath;
+
+ private final Set<Resource> resources = new LinkedHashSet<Resource>();
+
+ public PatternVirtualFileVisitor(String rootPath, String subPattern, PathMatcher pathMatcher) {
+ this.subPattern = subPattern;
+ this.pathMatcher = pathMatcher;
+ this.rootPath = (rootPath.length() == 0 || rootPath.endsWith("/") ? rootPath : rootPath + "/");
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ String methodName = method.getName();
+ if (Object.class.equals(method.getDeclaringClass())) {
+ if (methodName.equals("equals")) {
+ // Only consider equal when proxies are identical.
+ return (proxy == args[0]);
+ }
+ else if (methodName.equals("hashCode")) {
+ return System.identityHashCode(proxy);
+ }
+ }
+ else if ("getAttributes".equals(methodName)) {
+ return getAttributes();
+ }
+ else if ("visit".equals(methodName)) {
+ visit(args[0]);
+ return null;
+ }
+ else if ("toString".equals(methodName)) {
+ return toString();
+ }
+
+ throw new IllegalStateException("Unexpected method invocation: " + method);
+ }
+
+ public void visit(Object vfsResource) {
+ if (this.pathMatcher.match(this.subPattern,
+ VfsPatternUtils.getPath(vfsResource).substring(this.rootPath.length()))) {
+ this.resources.add(new VfsResource(vfsResource));
+ }
+ }
+
+ public Object getAttributes() {
+ return VfsPatternUtils.getVisitorAttribute();
+ }
+
+ public Set<Resource> getResources() {
+ return this.resources;
+ }
+
+ public int size() {
+ return this.resources.size();
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("sub-pattern: ").append(this.subPattern);
+ sb.append(", resources: ").append(this.resources);
+ return sb.toString();
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java b/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java
new file mode 100644
index 00000000..fb950839
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2002-2014 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.io.IOException;
+import java.util.Properties;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.core.io.Resource;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.DefaultPropertiesPersister;
+import org.springframework.util.PropertiesPersister;
+
+/**
+ * Base class for JavaBean-style components that need to load properties
+ * from one or more resources. Supports local properties as well, with
+ * configurable overriding.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ */
+public abstract class PropertiesLoaderSupport {
+
+ /** Logger available to subclasses */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ protected Properties[] localProperties;
+
+ protected boolean localOverride = false;
+
+ private Resource[] locations;
+
+ private boolean ignoreResourceNotFound = false;
+
+ private String fileEncoding;
+
+ private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister();
+
+
+ /**
+ * Set local properties, e.g. via the "props" tag in XML bean definitions.
+ * These can be considered defaults, to be overridden by properties
+ * loaded from files.
+ */
+ public void setProperties(Properties properties) {
+ this.localProperties = new Properties[] {properties};
+ }
+
+ /**
+ * Set local properties, e.g. via the "props" tag in XML bean definitions,
+ * allowing for merging multiple properties sets into one.
+ */
+ public void setPropertiesArray(Properties... propertiesArray) {
+ this.localProperties = propertiesArray;
+ }
+
+ /**
+ * Set a location of a properties file to be loaded.
+ * <p>Can point to a classic properties file or to an XML file
+ * that follows JDK 1.5's properties XML format.
+ */
+ public void setLocation(Resource location) {
+ this.locations = new Resource[] {location};
+ }
+
+ /**
+ * Set locations of properties files to be loaded.
+ * <p>Can point to classic properties files or to XML files
+ * that follow JDK 1.5's properties XML format.
+ * <p>Note: Properties defined in later files will override
+ * properties defined earlier files, in case of overlapping keys.
+ * Hence, make sure that the most specific files are the last
+ * ones in the given list of locations.
+ */
+ public void setLocations(Resource... locations) {
+ this.locations = locations;
+ }
+
+ /**
+ * Set whether local properties override properties from files.
+ * <p>Default is "false": Properties from files override local defaults.
+ * Can be switched to "true" to let local properties override defaults
+ * from files.
+ */
+ public void setLocalOverride(boolean localOverride) {
+ this.localOverride = localOverride;
+ }
+
+ /**
+ * Set if failure to find the property resource should be ignored.
+ * <p>"true" is appropriate if the properties file is completely optional.
+ * Default is "false".
+ */
+ public void setIgnoreResourceNotFound(boolean ignoreResourceNotFound) {
+ this.ignoreResourceNotFound = ignoreResourceNotFound;
+ }
+
+ /**
+ * Set the encoding to use for parsing properties files.
+ * <p>Default is none, using the {@code java.util.Properties}
+ * default encoding.
+ * <p>Only applies to classic properties files, not to XML files.
+ * @see org.springframework.util.PropertiesPersister#load
+ */
+ public void setFileEncoding(String encoding) {
+ this.fileEncoding = encoding;
+ }
+
+ /**
+ * Set the PropertiesPersister to use for parsing properties files.
+ * The default is DefaultPropertiesPersister.
+ * @see org.springframework.util.DefaultPropertiesPersister
+ */
+ public void setPropertiesPersister(PropertiesPersister propertiesPersister) {
+ this.propertiesPersister =
+ (propertiesPersister != null ? propertiesPersister : new DefaultPropertiesPersister());
+ }
+
+
+ /**
+ * Return a merged Properties instance containing both the
+ * loaded properties and properties set on this FactoryBean.
+ */
+ protected Properties mergeProperties() throws IOException {
+ Properties result = new Properties();
+
+ if (this.localOverride) {
+ // Load properties from file upfront, to let local properties override.
+ loadProperties(result);
+ }
+
+ if (this.localProperties != null) {
+ for (Properties localProp : this.localProperties) {
+ CollectionUtils.mergePropertiesIntoMap(localProp, result);
+ }
+ }
+
+ if (!this.localOverride) {
+ // Load properties from file afterwards, to let those properties override.
+ loadProperties(result);
+ }
+
+ return result;
+ }
+
+ /**
+ * Load properties into the given instance.
+ * @param props the Properties instance to load into
+ * @throws IOException in case of I/O errors
+ * @see #setLocations
+ */
+ protected void loadProperties(Properties props) throws IOException {
+ if (this.locations != null) {
+ for (Resource location : this.locations) {
+ if (logger.isInfoEnabled()) {
+ logger.info("Loading properties file from " + location);
+ }
+ try {
+ PropertiesLoaderUtils.fillProperties(
+ props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister);
+ }
+ catch (IOException ex) {
+ if (this.ignoreResourceNotFound) {
+ if (logger.isWarnEnabled()) {
+ logger.warn("Could not load properties from " + location + ": " + ex.getMessage());
+ }
+ }
+ else {
+ throw ex;
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderUtils.java b/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderUtils.java
new file mode 100644
index 00000000..c501a52f
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderUtils.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2002-2014 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.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Enumeration;
+import java.util.Properties;
+
+import org.springframework.core.io.Resource;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.DefaultPropertiesPersister;
+import org.springframework.util.PropertiesPersister;
+import org.springframework.util.ResourceUtils;
+
+/**
+ * Convenient utility methods for loading of {@code java.util.Properties},
+ * performing standard handling of input streams.
+ *
+ * <p>For more configurable properties loading, including the option of a
+ * customized encoding, consider using the PropertiesLoaderSupport class.
+ *
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @since 2.0
+ * @see PropertiesLoaderSupport
+ */
+public abstract class PropertiesLoaderUtils {
+
+ private static final String XML_FILE_EXTENSION = ".xml";
+
+
+ /**
+ * Load properties from the given EncodedResource,
+ * potentially defining a specific encoding for the properties file.
+ * @see #fillProperties(java.util.Properties, EncodedResource)
+ */
+ public static Properties loadProperties(EncodedResource resource) throws IOException {
+ Properties props = new Properties();
+ fillProperties(props, resource);
+ return props;
+ }
+
+ /**
+ * Fill the given properties from the given EncodedResource,
+ * potentially defining a specific encoding for the properties file.
+ * @param props the Properties instance to load into
+ * @param resource the resource to load from
+ * @throws IOException in case of I/O errors
+ */
+ public static void fillProperties(Properties props, EncodedResource resource)
+ throws IOException {
+
+ fillProperties(props, resource, new DefaultPropertiesPersister());
+ }
+
+ /**
+ * Actually load properties from the given EncodedResource into the given Properties instance.
+ * @param props the Properties instance to load into
+ * @param resource the resource to load from
+ * @param persister the PropertiesPersister to use
+ * @throws IOException in case of I/O errors
+ */
+ static void fillProperties(Properties props, EncodedResource resource, PropertiesPersister persister)
+ throws IOException {
+
+ InputStream stream = null;
+ Reader reader = null;
+ try {
+ String filename = resource.getResource().getFilename();
+ if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
+ stream = resource.getInputStream();
+ persister.loadFromXml(props, stream);
+ }
+ else if (resource.requiresReader()) {
+ reader = resource.getReader();
+ persister.load(props, reader);
+ }
+ else {
+ stream = resource.getInputStream();
+ persister.load(props, stream);
+ }
+ }
+ finally {
+ if (stream != null) {
+ stream.close();
+ }
+ if (reader != null) {
+ reader.close();
+ }
+ }
+ }
+
+ /**
+ * Load properties from the given resource (in ISO-8859-1 encoding).
+ * @param resource the resource to load from
+ * @return the populated Properties instance
+ * @throws IOException if loading failed
+ * @see #fillProperties(java.util.Properties, Resource)
+ */
+ public static Properties loadProperties(Resource resource) throws IOException {
+ Properties props = new Properties();
+ fillProperties(props, resource);
+ return props;
+ }
+
+ /**
+ * Fill the given properties from the given resource (in ISO-8859-1 encoding).
+ * @param props the Properties instance to fill
+ * @param resource the resource to load from
+ * @throws IOException if loading failed
+ */
+ public static void fillProperties(Properties props, Resource resource) throws IOException {
+ InputStream is = resource.getInputStream();
+ try {
+ String filename = resource.getFilename();
+ if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
+ props.loadFromXML(is);
+ }
+ else {
+ props.load(is);
+ }
+ }
+ finally {
+ is.close();
+ }
+ }
+
+ /**
+ * Load all properties from the specified class path resource
+ * (in ISO-8859-1 encoding), using the default class loader.
+ * <p>Merges properties if more than one resource of the same name
+ * found in the class path.
+ * @param resourceName the name of the class path resource
+ * @return the populated Properties instance
+ * @throws IOException if loading failed
+ */
+ public static Properties loadAllProperties(String resourceName) throws IOException {
+ return loadAllProperties(resourceName, null);
+ }
+
+ /**
+ * Load all properties from the specified class path resource
+ * (in ISO-8859-1 encoding), using the given class loader.
+ * <p>Merges properties if more than one resource of the same name
+ * found in the class path.
+ * @param resourceName the name of the class path resource
+ * @param classLoader the ClassLoader to use for loading
+ * (or {@code null} to use the default class loader)
+ * @return the populated Properties instance
+ * @throws IOException if loading failed
+ */
+ public static Properties loadAllProperties(String resourceName, ClassLoader classLoader) throws IOException {
+ Assert.notNull(resourceName, "Resource name must not be null");
+ ClassLoader classLoaderToUse = classLoader;
+ if (classLoaderToUse == null) {
+ classLoaderToUse = ClassUtils.getDefaultClassLoader();
+ }
+ Enumeration<URL> urls = (classLoaderToUse != null ? classLoaderToUse.getResources(resourceName) :
+ ClassLoader.getSystemResources(resourceName));
+ Properties props = new Properties();
+ while (urls.hasMoreElements()) {
+ URL url = urls.nextElement();
+ URLConnection con = url.openConnection();
+ ResourceUtils.useCachesIfNecessary(con);
+ InputStream is = con.getInputStream();
+ try {
+ if (resourceName != null && resourceName.endsWith(XML_FILE_EXTENSION)) {
+ props.loadFromXML(is);
+ }
+ else {
+ props.load(is);
+ }
+ }
+ finally {
+ is.close();
+ }
+ }
+ return props;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/ResourceArrayPropertyEditor.java b/spring-core/src/main/java/org/springframework/core/io/support/ResourceArrayPropertyEditor.java
new file mode 100644
index 00000000..e37a21f0
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/support/ResourceArrayPropertyEditor.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2002-2013 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.beans.PropertyEditorSupport;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.core.env.Environment;
+import org.springframework.core.env.PropertyResolver;
+import org.springframework.core.env.StandardEnvironment;
+import org.springframework.core.io.Resource;
+import org.springframework.util.Assert;
+
+/**
+ * Editor for {@link org.springframework.core.io.Resource} arrays, to
+ * automatically convert {@code String} location patterns
+ * (e.g. {@code "file:C:/my*.txt"} or {@code "classpath*:myfile.txt"})
+ * to {@code Resource} array properties. Can also translate a collection
+ * or array of location patterns into a merged Resource array.
+ *
+ * <p>A path may contain {@code ${...}} placeholders, to be
+ * resolved as {@link org.springframework.core.env.Environment} properties:
+ * e.g. {@code ${user.dir}}. Unresolvable placeholders are ignored by default.
+ *
+ * <p>Delegates to a {@link ResourcePatternResolver},
+ * by default using a {@link PathMatchingResourcePatternResolver}.
+ *
+ * @author Juergen Hoeller
+ * @author Chris Beams
+ * @since 1.1.2
+ * @see org.springframework.core.io.Resource
+ * @see ResourcePatternResolver
+ * @see PathMatchingResourcePatternResolver
+ */
+public class ResourceArrayPropertyEditor extends PropertyEditorSupport {
+
+ private static final Log logger = LogFactory.getLog(ResourceArrayPropertyEditor.class);
+
+ private final ResourcePatternResolver resourcePatternResolver;
+
+ private PropertyResolver propertyResolver;
+
+ private final boolean ignoreUnresolvablePlaceholders;
+
+
+ /**
+ * Create a new ResourceArrayPropertyEditor with a default
+ * {@link PathMatchingResourcePatternResolver} and {@link StandardEnvironment}.
+ * @see PathMatchingResourcePatternResolver
+ * @see Environment
+ */
+ public ResourceArrayPropertyEditor() {
+ this(new PathMatchingResourcePatternResolver(), null, true);
+ }
+
+ /**
+ * Create a new ResourceArrayPropertyEditor with the given {@link ResourcePatternResolver}
+ * and a {@link StandardEnvironment}.
+ * @param resourcePatternResolver the ResourcePatternResolver to use
+ * @deprecated as of 3.1 in favor of {@link #ResourceArrayPropertyEditor(ResourcePatternResolver, PropertyResolver)}
+ */
+ @Deprecated
+ public ResourceArrayPropertyEditor(ResourcePatternResolver resourcePatternResolver) {
+ this(resourcePatternResolver, null, true);
+ }
+
+ /**
+ * Create a new ResourceArrayPropertyEditor with the given {@link ResourcePatternResolver}
+ * and a {@link StandardEnvironment}.
+ * @param resourcePatternResolver the ResourcePatternResolver to use
+ * @param ignoreUnresolvablePlaceholders whether to ignore unresolvable placeholders
+ * if no corresponding system property could be found
+ * @deprecated as of 3.1 in favor of {@link #ResourceArrayPropertyEditor(ResourcePatternResolver, PropertyResolver, boolean)}
+ */
+ @Deprecated
+ public ResourceArrayPropertyEditor(ResourcePatternResolver resourcePatternResolver, boolean ignoreUnresolvablePlaceholders) {
+ this(resourcePatternResolver, null, ignoreUnresolvablePlaceholders);
+ }
+
+ /**
+ * Create a new ResourceArrayPropertyEditor with the given {@link ResourcePatternResolver}
+ * and {@link PropertyResolver} (typically an {@link Environment}).
+ * @param resourcePatternResolver the ResourcePatternResolver to use
+ * @param propertyResolver the PropertyResolver to use
+ */
+ public ResourceArrayPropertyEditor(ResourcePatternResolver resourcePatternResolver, PropertyResolver propertyResolver) {
+ this(resourcePatternResolver, propertyResolver, true);
+ }
+
+ /**
+ * Create a new ResourceArrayPropertyEditor with the given {@link ResourcePatternResolver}
+ * and {@link PropertyResolver} (typically an {@link Environment}).
+ * @param resourcePatternResolver the ResourcePatternResolver to use
+ * @param propertyResolver the PropertyResolver to use
+ * @param ignoreUnresolvablePlaceholders whether to ignore unresolvable placeholders
+ * if no corresponding system property could be found
+ */
+ public ResourceArrayPropertyEditor(ResourcePatternResolver resourcePatternResolver,
+ PropertyResolver propertyResolver, boolean ignoreUnresolvablePlaceholders) {
+
+ Assert.notNull(resourcePatternResolver, "ResourcePatternResolver must not be null");
+ this.resourcePatternResolver = resourcePatternResolver;
+ this.propertyResolver = propertyResolver;
+ this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
+ }
+
+
+ /**
+ * Treat the given text as a location pattern and convert it to a Resource array.
+ */
+ @Override
+ public void setAsText(String text) {
+ String pattern = resolvePath(text).trim();
+ try {
+ setValue(this.resourcePatternResolver.getResources(pattern));
+ }
+ catch (IOException ex) {
+ throw new IllegalArgumentException(
+ "Could not resolve resource location pattern [" + pattern + "]: " + ex.getMessage());
+ }
+ }
+
+ /**
+ * Treat the given value as a collection or array and convert it to a Resource array.
+ * Considers String elements as location patterns and takes Resource elements as-is.
+ */
+ @Override
+ public void setValue(Object value) throws IllegalArgumentException {
+ if (value instanceof Collection || (value instanceof Object[] && !(value instanceof Resource[]))) {
+ Collection<?> input = (value instanceof Collection ? (Collection<?>) value : Arrays.asList((Object[]) value));
+ List<Resource> merged = new ArrayList<Resource>();
+ for (Object element : input) {
+ if (element instanceof String) {
+ // A location pattern: resolve it into a Resource array.
+ // Might point to a single resource or to multiple resources.
+ String pattern = resolvePath((String) element).trim();
+ try {
+ Resource[] resources = this.resourcePatternResolver.getResources(pattern);
+ for (Resource resource : resources) {
+ if (!merged.contains(resource)) {
+ merged.add(resource);
+ }
+ }
+ }
+ catch (IOException ex) {
+ // ignore - might be an unresolved placeholder or non-existing base directory
+ if (logger.isDebugEnabled()) {
+ logger.debug("Could not retrieve resources for pattern '" + pattern + "'", ex);
+ }
+ }
+ }
+ else if (element instanceof Resource) {
+ // A Resource object: add it to the result.
+ Resource resource = (Resource) element;
+ if (!merged.contains(resource)) {
+ merged.add(resource);
+ }
+ }
+ else {
+ throw new IllegalArgumentException("Cannot convert element [" + element + "] to [" +
+ Resource.class.getName() + "]: only location String and Resource object supported");
+ }
+ }
+ super.setValue(merged.toArray(new Resource[merged.size()]));
+ }
+
+ else {
+ // An arbitrary value: probably a String or a Resource array.
+ // setAsText will be called for a String; a Resource array will be used as-is.
+ super.setValue(value);
+ }
+ }
+
+ /**
+ * Resolve the given path, replacing placeholders with
+ * corresponding system property values if necessary.
+ * @param path the original file path
+ * @return the resolved file path
+ * @see PropertyResolver#resolvePlaceholders
+ * @see PropertyResolver#resolveRequiredPlaceholders(String)
+ */
+ protected String resolvePath(String path) {
+ if (this.propertyResolver == null) {
+ this.propertyResolver = new StandardEnvironment();
+ }
+ return (this.ignoreUnresolvablePlaceholders ? this.propertyResolver.resolvePlaceholders(path) :
+ this.propertyResolver.resolveRequiredPlaceholders(path));
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/ResourcePatternResolver.java b/spring-core/src/main/java/org/springframework/core/io/support/ResourcePatternResolver.java
new file mode 100644
index 00000000..2bbe19cc
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/support/ResourcePatternResolver.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2002-2007 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.io.IOException;
+
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+
+/**
+ * Strategy interface for resolving a location pattern (for example,
+ * an Ant-style path pattern) into Resource objects.
+ *
+ * <p>This is an extension to the {@link org.springframework.core.io.ResourceLoader}
+ * interface. A passed-in ResourceLoader (for example, an
+ * {@link org.springframework.context.ApplicationContext} passed in via
+ * {@link org.springframework.context.ResourceLoaderAware} when running in a context)
+ * can be checked whether it implements this extended interface too.
+ *
+ * <p>{@link PathMatchingResourcePatternResolver} is a standalone implementation
+ * that is usable outside an ApplicationContext, also used by
+ * {@link ResourceArrayPropertyEditor} for populating Resource array bean properties.
+ *
+ * <p>Can be used with any sort of location pattern (e.g. "/WEB-INF/*-context.xml"):
+ * Input patterns have to match the strategy implementation. This interface just
+ * specifies the conversion method rather than a specific pattern format.
+ *
+ * <p>This interface also suggests a new resource prefix "classpath*:" for all
+ * matching resources from the class path. Note that the resource location is
+ * expected to be a path without placeholders in this case (e.g. "/beans.xml");
+ * JAR files or classes directories can contain multiple files of the same name.
+ *
+ * @author Juergen Hoeller
+ * @since 1.0.2
+ * @see org.springframework.core.io.Resource
+ * @see org.springframework.core.io.ResourceLoader
+ * @see org.springframework.context.ApplicationContext
+ * @see org.springframework.context.ResourceLoaderAware
+ */
+public interface ResourcePatternResolver extends ResourceLoader {
+
+ /**
+ * Pseudo URL prefix for all matching resources from the class path: "classpath*:"
+ * This differs from ResourceLoader's classpath URL prefix in that it
+ * retrieves all matching resources for a given name (e.g. "/beans.xml"),
+ * for example in the root of all deployed JAR files.
+ * @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX
+ */
+ String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
+
+ /**
+ * Resolve the given location pattern into Resource objects.
+ * <p>Overlapping resource entries that point to the same physical
+ * resource should be avoided, as far as possible. The result should
+ * have set semantics.
+ * @param locationPattern the location pattern to resolve
+ * @return the corresponding Resource objects
+ * @throws IOException in case of I/O errors
+ */
+ Resource[] getResources(String locationPattern) throws IOException;
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/ResourcePatternUtils.java b/spring-core/src/main/java/org/springframework/core/io/support/ResourcePatternUtils.java
new file mode 100644
index 00000000..9c0c12ee
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/support/ResourcePatternUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2002-2012 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.springframework.core.io.ResourceLoader;
+import org.springframework.util.Assert;
+import org.springframework.util.ResourceUtils;
+
+/**
+ * Utility class for determining whether a given URL is a resource
+ * location that can be loaded via a ResourcePatternResolver.
+ *
+ * <p>Callers will usually assume that a location is a relative path
+ * if the {@link #isUrl(String)} method returns {@code false}.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2.3
+ */
+public abstract class ResourcePatternUtils {
+
+ /**
+ * Return whether the given resource location is a URL: either a
+ * special "classpath" or "classpath*" pseudo URL or a standard URL.
+ * @param resourceLocation the location String to check
+ * @return whether the location qualifies as a URL
+ * @see ResourcePatternResolver#CLASSPATH_ALL_URL_PREFIX
+ * @see org.springframework.util.ResourceUtils#CLASSPATH_URL_PREFIX
+ * @see org.springframework.util.ResourceUtils#isUrl(String)
+ * @see java.net.URL
+ */
+ public static boolean isUrl(String resourceLocation) {
+ return (resourceLocation != null &&
+ (resourceLocation.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX) ||
+ ResourceUtils.isUrl(resourceLocation)));
+ }
+
+ /**
+ * Return a default ResourcePatternResolver for the given ResourceLoader.
+ * <p>This might be the ResourceLoader itself, if it implements the
+ * ResourcePatternResolver extension, or a PathMatchingResourcePatternResolver
+ * built on the given ResourceLoader.
+ * @param resourceLoader the ResourceLoader to build a pattern resolver for
+ * (may be {@code null} to indicate a default ResourceLoader)
+ * @return the ResourcePatternResolver
+ * @see PathMatchingResourcePatternResolver
+ */
+ public static ResourcePatternResolver getResourcePatternResolver(ResourceLoader resourceLoader) {
+ Assert.notNull(resourceLoader, "ResourceLoader must not be null");
+ if (resourceLoader instanceof ResourcePatternResolver) {
+ return (ResourcePatternResolver) resourceLoader;
+ }
+ else if (resourceLoader != null) {
+ return new PathMatchingResourcePatternResolver(resourceLoader);
+ }
+ else {
+ return new PathMatchingResourcePatternResolver();
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/ResourcePropertySource.java b/spring-core/src/main/java/org/springframework/core/io/support/ResourcePropertySource.java
new file mode 100644
index 00000000..d0795545
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/support/ResourcePropertySource.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2002-2013 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.io.IOException;
+import java.util.Properties;
+
+import org.springframework.core.env.PropertiesPropertySource;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.Resource;
+import org.springframework.util.StringUtils;
+
+/**
+ * Subclass of {@link PropertiesPropertySource} that loads a {@link Properties}
+ * object from a given {@link org.springframework.core.io.Resource} or resource location such as
+ * {@code "classpath:/com/myco/foo.properties"} or {@code "file:/path/to/file.xml"}.
+ * Both traditional and XML-based properties file formats are supported, however in order
+ * for XML processing to take effect, the underlying {@code Resource}'s
+ * {@link org.springframework.core.io.Resource#getFilename() getFilename()} method must
+ * return non-{@code null} and end in ".xml".
+ *
+ * @author Chris Beams
+ * @author Juergen Hoeller
+ * @since 3.1
+ */
+public class ResourcePropertySource extends PropertiesPropertySource {
+
+ /**
+ * Create a PropertySource having the given name based on Properties
+ * loaded from the given encoded resource.
+ */
+ public ResourcePropertySource(String name, EncodedResource resource) throws IOException {
+ super(name, PropertiesLoaderUtils.loadProperties(resource));
+ }
+
+ /**
+ * Create a PropertySource based on Properties loaded from the given resource.
+ * The name of the PropertySource will be generated based on the
+ * {@link Resource#getDescription() description} of the given resource.
+ */
+ public ResourcePropertySource(EncodedResource resource) throws IOException {
+ this(getNameForResource(resource.getResource()), resource);
+ }
+
+ /**
+ * Create a PropertySource having the given name based on Properties
+ * loaded from the given encoded resource.
+ */
+ public ResourcePropertySource(String name, Resource resource) throws IOException {
+ super(name, PropertiesLoaderUtils.loadProperties(new EncodedResource(resource)));
+ }
+
+ /**
+ * Create a PropertySource based on Properties loaded from the given resource.
+ * The name of the PropertySource will be generated based on the
+ * {@link Resource#getDescription() description} of the given resource.
+ */
+ public ResourcePropertySource(Resource resource) throws IOException {
+ this(getNameForResource(resource), resource);
+ }
+
+ /**
+ * Create a PropertySource having the given name based on Properties loaded from
+ * the given resource location and using the given class loader to load the
+ * resource (assuming it is prefixed with {@code classpath:}).
+ */
+ public ResourcePropertySource(String name, String location, ClassLoader classLoader) throws IOException {
+ this(name, new DefaultResourceLoader(classLoader).getResource(location));
+ }
+
+ /**
+ * Create a PropertySource based on Properties loaded from the given resource
+ * location and use the given class loader to load the resource, assuming it is
+ * prefixed with {@code classpath:}. The name of the PropertySource will be
+ * generated based on the {@link Resource#getDescription() description} of the
+ * resource.
+ */
+ public ResourcePropertySource(String location, ClassLoader classLoader) throws IOException {
+ this(new DefaultResourceLoader(classLoader).getResource(location));
+ }
+
+ /**
+ * Create a PropertySource having the given name based on Properties loaded from
+ * the given resource location. The default thread context class loader will be
+ * used to load the resource (assuming the location string is prefixed with
+ * {@code classpath:}.
+ */
+ public ResourcePropertySource(String name, String location) throws IOException {
+ this(name, new DefaultResourceLoader().getResource(location));
+ }
+
+ /**
+ * Create a PropertySource based on Properties loaded from the given resource
+ * location. The name of the PropertySource will be generated based on the
+ * {@link Resource#getDescription() description} of the resource.
+ */
+ public ResourcePropertySource(String location) throws IOException {
+ this(new DefaultResourceLoader().getResource(location));
+ }
+
+
+ /**
+ * Return the description string for the resource, and if empty returns
+ * the class name of the resource plus its identity hash code.
+ */
+ private static String getNameForResource(Resource resource) {
+ String name = resource.getDescription();
+ if (!StringUtils.hasText(name)) {
+ name = resource.getClass().getSimpleName() + "@" + System.identityHashCode(resource);
+ }
+ return name;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java b/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java
new file mode 100644
index 00000000..91257165
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2002-2014 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.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.core.OrderComparator;
+import org.springframework.core.io.UrlResource;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * General purpose factory loading mechanism for internal use within the framework.
+ *
+ * <p>The {@code SpringFactoriesLoader} loads and instantiates factories of a given type
+ * from "META-INF/spring.factories" files. The file should be in {@link Properties} format,
+ * where the key is the fully qualified interface or abstract class name, and the value
+ * is a comma-separated list of implementation class names. For instance:
+ *
+ * <pre class="code">example.MyService=example.MyServiceImpl1,example.MyServiceImpl2</pre>
+ *
+ * where {@code MyService} is the name of the interface, and {@code MyServiceImpl1} and
+ * {@code MyServiceImpl2} are the two implementations.
+ *
+ * @author Arjen Poutsma
+ * @author Juergen Hoeller
+ * @since 3.2
+ */
+public abstract class SpringFactoriesLoader {
+
+ /** The location to look for the factories. Can be present in multiple JAR files. */
+ private static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
+
+ private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
+
+
+ /**
+ * Load the factory implementations of the given type from the default location,
+ * using the given class loader.
+ * <p>The returned factories are ordered in accordance with the {@link OrderComparator}.
+ * @param factoryClass the interface or abstract class representing the factory
+ * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
+ */
+ public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
+ Assert.notNull(factoryClass, "'factoryClass' must not be null");
+ ClassLoader classLoaderToUse = classLoader;
+ if (classLoaderToUse == null) {
+ classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
+ }
+ List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
+ if (logger.isTraceEnabled()) {
+ logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
+ }
+ List<T> result = new ArrayList<T>(factoryNames.size());
+ for (String factoryName : factoryNames) {
+ result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
+ }
+ OrderComparator.sort(result);
+ return result;
+ }
+
+ public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
+ String factoryClassName = factoryClass.getName();
+ try {
+ Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
+ ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
+ List<String> result = new ArrayList<String>();
+ while (urls.hasMoreElements()) {
+ URL url = urls.nextElement();
+ Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
+ String factoryClassNames = properties.getProperty(factoryClassName);
+ result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
+ }
+ return result;
+ }
+ catch (IOException ex) {
+ throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
+ "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
+ try {
+ Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
+ if (!factoryClass.isAssignableFrom(instanceClass)) {
+ throw new IllegalArgumentException(
+ "Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
+ }
+ return (T) instanceClass.newInstance();
+ }
+ catch (Throwable ex) {
+ throw new IllegalArgumentException("Cannot instantiate factory class: " + factoryClass.getName(), ex);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/VfsPatternUtils.java b/spring-core/src/main/java/org/springframework/core/io/support/VfsPatternUtils.java
new file mode 100644
index 00000000..f6e523ea
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/support/VfsPatternUtils.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2002-2010 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.io.IOException;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Proxy;
+import java.net.URL;
+
+import org.springframework.core.io.VfsUtils;
+
+/**
+ * Artificial class used for accessing the {@link VfsUtils} methods
+ * without exposing them to the entire world.
+ *
+ * @author Costin Leau
+ * @since 3.0.3
+ */
+abstract class VfsPatternUtils extends VfsUtils {
+
+ static Object getVisitorAttribute() {
+ return doGetVisitorAttribute();
+ }
+
+ static String getPath(Object resource) {
+ return doGetPath(resource);
+ }
+
+ static Object findRoot(URL url) throws IOException {
+ return getRoot(url);
+ }
+
+ static void visit(Object resource, InvocationHandler visitor) throws IOException {
+ Object visitorProxy = Proxy.newProxyInstance(VIRTUAL_FILE_VISITOR_INTERFACE.getClassLoader(),
+ new Class<?>[] { VIRTUAL_FILE_VISITOR_INTERFACE }, visitor);
+ invokeVfsMethod(VIRTUAL_FILE_METHOD_VISIT, resource, visitorProxy);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/package-info.java b/spring-core/src/main/java/org/springframework/core/io/support/package-info.java
new file mode 100644
index 00000000..dc3e6b98
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/support/package-info.java
@@ -0,0 +1,9 @@
+
+/**
+ *
+ * Support classes for Spring's resource abstraction.
+ * Includes a ResourcePatternResolver mechanism.
+ *
+ */
+package org.springframework.core.io.support;
+
diff --git a/spring-core/src/main/java/org/springframework/core/package-info.java b/spring-core/src/main/java/org/springframework/core/package-info.java
new file mode 100644
index 00000000..9b2e8458
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/package-info.java
@@ -0,0 +1,9 @@
+
+/**
+ *
+ * Provides basic classes for exception handling and version detection,
+ * and other core helpers that are not specific to any part of the framework.
+ *
+ */
+package org.springframework.core;
+
diff --git a/spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java b/spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java
new file mode 100644
index 00000000..f61f697d
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2012 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.serializer;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+
+import org.springframework.core.NestedIOException;
+
+/**
+ * Deserializer that reads an input stream using Java Serialization.
+ *
+ * @author Gary Russell
+ * @author Mark Fisher
+ * @since 3.0.5
+ */
+public class DefaultDeserializer implements Deserializer<Object> {
+
+ /**
+ * Reads the input stream and deserializes into an object.
+ */
+ public Object deserialize(InputStream inputStream) throws IOException {
+ ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
+ try {
+ return objectInputStream.readObject();
+ }
+ catch (ClassNotFoundException ex) {
+ throw new NestedIOException("Failed to deserialize object type", ex);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/serializer/DefaultSerializer.java b/spring-core/src/main/java/org/springframework/core/serializer/DefaultSerializer.java
new file mode 100644
index 00000000..04fe8bed
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/serializer/DefaultSerializer.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2012 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.serializer;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+
+/**
+ * Serializer that writes an object to an output stream using Java Serialization.
+ *
+ * @author Gary Russell
+ * @author Mark Fisher
+ * @since 3.0.5
+ */
+public class DefaultSerializer implements Serializer<Object> {
+
+ /**
+ * Writes the source object to an output stream using Java Serialization.
+ * The source object must implement {@link Serializable}.
+ */
+ public void serialize(Object object, OutputStream outputStream) throws IOException {
+ if (!(object instanceof Serializable)) {
+ throw new IllegalArgumentException(getClass().getSimpleName() + " requires a Serializable payload " +
+ "but received an object of type [" + object.getClass().getName() + "]");
+ }
+ ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
+ objectOutputStream.writeObject(object);
+ objectOutputStream.flush();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/serializer/Deserializer.java b/spring-core/src/main/java/org/springframework/core/serializer/Deserializer.java
new file mode 100644
index 00000000..f64fbdf7
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/serializer/Deserializer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2012 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.serializer;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A strategy interface for converting from data in an InputStream to an Object.
+ *
+ * @author Gary Russell
+ * @author Mark Fisher
+ * @since 3.0.5
+ */
+public interface Deserializer<T> {
+
+ /**
+ * Read (assemble) an object of type T from the given InputStream.
+ * <p>Note: Implementations should not close the given InputStream
+ * (or any decorators of that InputStream) but rather leave this up
+ * to the caller.
+ * @param inputStream the input stream
+ * @return the deserialized object
+ * @throws IOException in case of errors reading from the stream
+ */
+ T deserialize(InputStream inputStream) throws IOException;
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/serializer/Serializer.java b/spring-core/src/main/java/org/springframework/core/serializer/Serializer.java
new file mode 100644
index 00000000..8af65195
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/serializer/Serializer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2012 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.serializer;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * A strategy interface for streaming an object to an OutputStream.
+ *
+ * @author Gary Russell
+ * @author Mark Fisher
+ * @since 3.0.5
+ */
+public interface Serializer<T> {
+
+ /**
+ * Write an object of type T to the given OutputStream.
+ * <p>Note: Implementations should not close the given OutputStream
+ * (or any decorators of that OutputStream) but rather leave this up
+ * to the caller.
+ * @param object the object to serialize
+ * @param outputStream the output stream
+ * @throws IOException in case of errors writing to the stream
+ */
+ void serialize(T object, OutputStream outputStream) throws IOException;
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/serializer/package-info.java b/spring-core/src/main/java/org/springframework/core/serializer/package-info.java
new file mode 100644
index 00000000..36a79f41
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/serializer/package-info.java
@@ -0,0 +1,10 @@
+
+/**
+ *
+ * Root package for Spring's serializer interfaces and implementations.
+ * Provides an abstraction over various serialization techniques.
+ * Includes exceptions for serialization and deserialization failures.
+ *
+ */
+package org.springframework.core.serializer;
+
diff --git a/spring-core/src/main/java/org/springframework/core/serializer/support/DeserializingConverter.java b/spring-core/src/main/java/org/springframework/core/serializer/support/DeserializingConverter.java
new file mode 100644
index 00000000..20020611
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/serializer/support/DeserializingConverter.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2010 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.serializer.support;
+
+import java.io.ByteArrayInputStream;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.core.serializer.DefaultDeserializer;
+import org.springframework.core.serializer.Deserializer;
+import org.springframework.util.Assert;
+
+/**
+ * A {@link Converter} that delegates to a {@link org.springframework.core.serializer.Deserializer}
+ * to convert data in a byte array to an object.
+ *
+ * @author Gary Russell
+ * @author Mark Fisher
+ * @since 3.0.5
+ */
+public class DeserializingConverter implements Converter<byte[], Object> {
+
+ private final Deserializer<Object> deserializer;
+
+
+ /**
+ * Create a default DeserializingConverter that uses standard Java deserialization.
+ */
+ public DeserializingConverter() {
+ this.deserializer = new DefaultDeserializer();
+ }
+
+ /**
+ * Create a DeserializingConverter that delegates to the provided {@link Deserializer}.
+ */
+ public DeserializingConverter(Deserializer<Object> deserializer) {
+ Assert.notNull(deserializer, "Deserializer must not be null");
+ this.deserializer = deserializer;
+ }
+
+
+ public Object convert(byte[] source) {
+ ByteArrayInputStream byteStream = new ByteArrayInputStream(source);
+ try {
+ return this.deserializer.deserialize(byteStream);
+ }
+ catch (Throwable ex) {
+ throw new SerializationFailedException("Failed to deserialize payload. " +
+ "Is the byte array a result of corresponding serialization for " +
+ this.deserializer.getClass().getSimpleName() + "?", ex);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/serializer/support/SerializationFailedException.java b/spring-core/src/main/java/org/springframework/core/serializer/support/SerializationFailedException.java
new file mode 100644
index 00000000..274a6cb0
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/serializer/support/SerializationFailedException.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2012 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.serializer.support;
+
+import org.springframework.core.NestedRuntimeException;
+
+/**
+ * Wrapper for the native IOException (or similar) when a
+ * {@link org.springframework.core.serializer.Serializer} or
+ * {@link org.springframework.core.serializer.Deserializer} failed.
+ * Thrown by {@link SerializingConverter} and {@link DeserializingConverter}.
+ *
+ * @author Gary Russell
+ * @author Juergen Hoeller
+ * @since 3.0.5
+ */
+@SuppressWarnings("serial")
+public class SerializationFailedException extends NestedRuntimeException {
+
+ /**
+ * Construct a {@code SerializationException} with the specified detail message.
+ * @param message the detail message
+ */
+ public SerializationFailedException(String message) {
+ super(message);
+ }
+
+ /**
+ * Construct a {@code SerializationException} with the specified detail message
+ * and nested exception.
+ * @param message the detail message
+ * @param cause the nested exception
+ */
+ public SerializationFailedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/serializer/support/SerializingConverter.java b/spring-core/src/main/java/org/springframework/core/serializer/support/SerializingConverter.java
new file mode 100644
index 00000000..b9909f61
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/serializer/support/SerializingConverter.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2002-2014 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.serializer.support;
+
+import java.io.ByteArrayOutputStream;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.core.serializer.DefaultSerializer;
+import org.springframework.core.serializer.Serializer;
+import org.springframework.util.Assert;
+
+/**
+ * A {@link Converter} that delegates to a {@link org.springframework.core.serializer.Serializer}
+ * to convert an object to a byte array.
+ *
+ * @author Gary Russell
+ * @author Mark Fisher
+ * @since 3.0.5
+ */
+public class SerializingConverter implements Converter<Object, byte[]> {
+
+ private final Serializer<Object> serializer;
+
+
+ /**
+ * Create a default SerializingConverter that uses standard Java serialization.
+ */
+ public SerializingConverter() {
+ this.serializer = new DefaultSerializer();
+ }
+
+ /**
+ * Create a SerializingConverter that delegates to the provided {@link Serializer}
+ */
+ public SerializingConverter(Serializer<Object> serializer) {
+ Assert.notNull(serializer, "Serializer must not be null");
+ this.serializer = serializer;
+ }
+
+
+ /**
+ * Serializes the source object and returns the byte array result.
+ */
+ public byte[] convert(Object source) {
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream(256);
+ try {
+ this.serializer.serialize(source, byteStream);
+ return byteStream.toByteArray();
+ }
+ catch (Throwable ex) {
+ throw new SerializationFailedException("Failed to serialize object using " +
+ this.serializer.getClass().getSimpleName(), ex);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/serializer/support/package-info.java b/spring-core/src/main/java/org/springframework/core/serializer/support/package-info.java
new file mode 100644
index 00000000..33f7365f
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/serializer/support/package-info.java
@@ -0,0 +1,9 @@
+
+/**
+ *
+ * Support classes for Spring's serializer abstraction.
+ * Includes adapters to the Converter SPI.
+ *
+ */
+package org.springframework.core.serializer.support;
+
diff --git a/spring-core/src/main/java/org/springframework/core/style/DefaultToStringStyler.java b/spring-core/src/main/java/org/springframework/core/style/DefaultToStringStyler.java
new file mode 100644
index 00000000..f81cb4a8
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/style/DefaultToStringStyler.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2002-2012 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.style;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Spring's default {@code toString()} styler.
+ *
+ * <p>This class is used by {@link ToStringCreator} to style {@code toString()}
+ * output in a consistent manner according to Spring conventions.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ */
+public class DefaultToStringStyler implements ToStringStyler {
+
+ private final ValueStyler valueStyler;
+
+
+ /**
+ * Create a new DefaultToStringStyler.
+ * @param valueStyler the ValueStyler to use
+ */
+ public DefaultToStringStyler(ValueStyler valueStyler) {
+ Assert.notNull(valueStyler, "ValueStyler must not be null");
+ this.valueStyler = valueStyler;
+ }
+
+ /**
+ * Return the ValueStyler used by this ToStringStyler.
+ */
+ protected final ValueStyler getValueStyler() {
+ return this.valueStyler;
+ }
+
+
+ public void styleStart(StringBuilder buffer, Object obj) {
+ if (!obj.getClass().isArray()) {
+ buffer.append('[').append(ClassUtils.getShortName(obj.getClass()));
+ styleIdentityHashCode(buffer, obj);
+ }
+ else {
+ buffer.append('[');
+ styleIdentityHashCode(buffer, obj);
+ buffer.append(' ');
+ styleValue(buffer, obj);
+ }
+ }
+
+ private void styleIdentityHashCode(StringBuilder buffer, Object obj) {
+ buffer.append('@');
+ buffer.append(ObjectUtils.getIdentityHexString(obj));
+ }
+
+ public void styleEnd(StringBuilder buffer, Object o) {
+ buffer.append(']');
+ }
+
+ public void styleField(StringBuilder buffer, String fieldName, Object value) {
+ styleFieldStart(buffer, fieldName);
+ styleValue(buffer, value);
+ styleFieldEnd(buffer, fieldName);
+ }
+
+ protected void styleFieldStart(StringBuilder buffer, String fieldName) {
+ buffer.append(' ').append(fieldName).append(" = ");
+ }
+
+ protected void styleFieldEnd(StringBuilder buffer, String fieldName) {
+ }
+
+ public void styleValue(StringBuilder buffer, Object value) {
+ buffer.append(this.valueStyler.style(value));
+ }
+
+ public void styleFieldSeparator(StringBuilder buffer) {
+ buffer.append(',');
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/style/DefaultValueStyler.java b/spring-core/src/main/java/org/springframework/core/style/DefaultValueStyler.java
new file mode 100644
index 00000000..2e6251c0
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/style/DefaultValueStyler.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2002-2012 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.style;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * Converts objects to String form, generally for debugging purposes,
+ * using Spring's {@code toString} styling conventions.
+ *
+ * <p>Uses the reflective visitor pattern underneath the hood to nicely
+ * encapsulate styling algorithms for each type of styled object.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ */
+public class DefaultValueStyler implements ValueStyler {
+
+ private static final String EMPTY = "[empty]";
+ private static final String NULL = "[null]";
+ private static final String COLLECTION = "collection";
+ private static final String SET = "set";
+ private static final String LIST = "list";
+ private static final String MAP = "map";
+ private static final String ARRAY = "array";
+
+
+ public String style(Object value) {
+ if (value == null) {
+ return NULL;
+ }
+ else if (value instanceof String) {
+ return "\'" + value + "\'";
+ }
+ else if (value instanceof Class) {
+ return ClassUtils.getShortName((Class) value);
+ }
+ else if (value instanceof Method) {
+ Method method = (Method) value;
+ return method.getName() + "@" + ClassUtils.getShortName(method.getDeclaringClass());
+ }
+ else if (value instanceof Map) {
+ return style((Map) value);
+ }
+ else if (value instanceof Map.Entry) {
+ return style((Map.Entry) value);
+ }
+ else if (value instanceof Collection) {
+ return style((Collection) value);
+ }
+ else if (value.getClass().isArray()) {
+ return styleArray(ObjectUtils.toObjectArray(value));
+ }
+ else {
+ return String.valueOf(value);
+ }
+ }
+
+ private String style(Map value) {
+ StringBuilder result = new StringBuilder(value.size() * 8 + 16);
+ result.append(MAP + "[");
+ for (Iterator it = value.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ result.append(style(entry));
+ if (it.hasNext()) {
+ result.append(',').append(' ');
+ }
+ }
+ if (value.isEmpty()) {
+ result.append(EMPTY);
+ }
+ result.append("]");
+ return result.toString();
+ }
+
+ private String style(Map.Entry value) {
+ return style(value.getKey()) + " -> " + style(value.getValue());
+ }
+
+ private String style(Collection value) {
+ StringBuilder result = new StringBuilder(value.size() * 8 + 16);
+ result.append(getCollectionTypeString(value)).append('[');
+ for (Iterator i = value.iterator(); i.hasNext();) {
+ result.append(style(i.next()));
+ if (i.hasNext()) {
+ result.append(',').append(' ');
+ }
+ }
+ if (value.isEmpty()) {
+ result.append(EMPTY);
+ }
+ result.append("]");
+ return result.toString();
+ }
+
+ private String getCollectionTypeString(Collection value) {
+ if (value instanceof List) {
+ return LIST;
+ }
+ else if (value instanceof Set) {
+ return SET;
+ }
+ else {
+ return COLLECTION;
+ }
+ }
+
+ private String styleArray(Object[] array) {
+ StringBuilder result = new StringBuilder(array.length * 8 + 16);
+ result.append(ARRAY + "<").append(ClassUtils.getShortName(array.getClass().getComponentType())).append(">[");
+ for (int i = 0; i < array.length - 1; i++) {
+ result.append(style(array[i]));
+ result.append(',').append(' ');
+ }
+ if (array.length > 0) {
+ result.append(style(array[array.length - 1]));
+ }
+ else {
+ result.append(EMPTY);
+ }
+ result.append("]");
+ return result.toString();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/style/StylerUtils.java b/spring-core/src/main/java/org/springframework/core/style/StylerUtils.java
new file mode 100644
index 00000000..91e09dee
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/style/StylerUtils.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2002-2012 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.style;
+
+/**
+ * Simple utility class to allow for convenient access to value
+ * styling logic, mainly to support descriptive logging messages.
+ *
+ * <p>For more sophisticated needs, use the {@link ValueStyler} abstraction
+ * directly. This class simply uses a shared {@link DefaultValueStyler}
+ * instance underneath.
+ *
+ * @author Keith Donald
+ * @since 1.2.2
+ * @see ValueStyler
+ * @see DefaultValueStyler
+ */
+public abstract class StylerUtils {
+
+ /**
+ * Default ValueStyler instance used by the {@code style} method.
+ * Also available for the {@link ToStringCreator} class in this package.
+ */
+ static final ValueStyler DEFAULT_VALUE_STYLER = new DefaultValueStyler();
+
+ /**
+ * Style the specified value according to default conventions.
+ * @param value the Object value to style
+ * @return the styled String
+ * @see DefaultValueStyler
+ */
+ public static String style(Object value) {
+ return DEFAULT_VALUE_STYLER.style(value);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/style/ToStringCreator.java b/spring-core/src/main/java/org/springframework/core/style/ToStringCreator.java
new file mode 100644
index 00000000..6f682939
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/style/ToStringCreator.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2002-2014 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.style;
+
+import org.springframework.util.Assert;
+
+/**
+ * Utility class that builds pretty-printing {@code toString()} methods
+ * with pluggable styling conventions. By default, ToStringCreator adheres
+ * to Spring's {@code toString()} styling conventions.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ */
+public class ToStringCreator {
+
+ /**
+ * Default ToStringStyler instance used by this ToStringCreator.
+ */
+ private static final ToStringStyler DEFAULT_TO_STRING_STYLER =
+ new DefaultToStringStyler(StylerUtils.DEFAULT_VALUE_STYLER);
+
+
+ private final StringBuilder buffer = new StringBuilder(256);
+
+ private final ToStringStyler styler;
+
+ private final Object object;
+
+ private boolean styledFirstField;
+
+
+ /**
+ * Create a ToStringCreator for the given object.
+ * @param obj the object to be stringified
+ */
+ public ToStringCreator(Object obj) {
+ this(obj, (ToStringStyler) null);
+ }
+
+ /**
+ * Create a ToStringCreator for the given object, using the provided style.
+ * @param obj the object to be stringified
+ * @param styler the ValueStyler encapsulating pretty-print instructions
+ */
+ public ToStringCreator(Object obj, ValueStyler styler) {
+ this(obj, new DefaultToStringStyler(styler != null ? styler : StylerUtils.DEFAULT_VALUE_STYLER));
+ }
+
+ /**
+ * Create a ToStringCreator for the given object, using the provided style.
+ * @param obj the object to be stringified
+ * @param styler the ToStringStyler encapsulating pretty-print instructions
+ */
+ public ToStringCreator(Object obj, ToStringStyler styler) {
+ Assert.notNull(obj, "The object to be styled must not be null");
+ this.object = obj;
+ this.styler = (styler != null ? styler : DEFAULT_TO_STRING_STYLER);
+ this.styler.styleStart(this.buffer, this.object);
+ }
+
+
+ /**
+ * Append a byte field value.
+ * @param fieldName the name of the field, usually the member variable name
+ * @param value the field value
+ * @return this, to support call-chaining
+ */
+ public ToStringCreator append(String fieldName, byte value) {
+ return append(fieldName, new Byte(value));
+ }
+
+ /**
+ * Append a short field value.
+ * @param fieldName the name of the field, usually the member variable name
+ * @param value the field value
+ * @return this, to support call-chaining
+ */
+ public ToStringCreator append(String fieldName, short value) {
+ return append(fieldName, new Short(value));
+ }
+
+ /**
+ * Append a integer field value.
+ * @param fieldName the name of the field, usually the member variable name
+ * @param value the field value
+ * @return this, to support call-chaining
+ */
+ public ToStringCreator append(String fieldName, int value) {
+ return append(fieldName, new Integer(value));
+ }
+
+ /**
+ * Append a long field value.
+ * @param fieldName the name of the field, usually the member variable name
+ * @param value the field value
+ * @return this, to support call-chaining
+ */
+ public ToStringCreator append(String fieldName, long value) {
+ return append(fieldName, new Long(value));
+ }
+
+ /**
+ * Append a float field value.
+ * @param fieldName the name of the field, usually the member variable name
+ * @param value the field value
+ * @return this, to support call-chaining
+ */
+ public ToStringCreator append(String fieldName, float value) {
+ return append(fieldName, new Float(value));
+ }
+
+ /**
+ * Append a double field value.
+ * @param fieldName the name of the field, usually the member variable name
+ * @param value the field value
+ * @return this, to support call-chaining
+ */
+ public ToStringCreator append(String fieldName, double value) {
+ return append(fieldName, new Double(value));
+ }
+
+ /**
+ * Append a boolean field value.
+ * @param fieldName the name of the field, usually the member variable name
+ * @param value the field value
+ * @return this, to support call-chaining
+ */
+ public ToStringCreator append(String fieldName, boolean value) {
+ return append(fieldName, Boolean.valueOf(value));
+ }
+
+ /**
+ * Append a field value.
+ * @param fieldName the name of the field, usually the member variable name
+ * @param value the field value
+ * @return this, to support call-chaining
+ */
+ public ToStringCreator append(String fieldName, Object value) {
+ printFieldSeparatorIfNecessary();
+ this.styler.styleField(this.buffer, fieldName, value);
+ return this;
+ }
+
+ private void printFieldSeparatorIfNecessary() {
+ if (this.styledFirstField) {
+ this.styler.styleFieldSeparator(this.buffer);
+ }
+ else {
+ this.styledFirstField = true;
+ }
+ }
+
+ /**
+ * Append the provided value.
+ * @param value The value to append
+ * @return this, to support call-chaining.
+ */
+ public ToStringCreator append(Object value) {
+ this.styler.styleValue(this.buffer, value);
+ return this;
+ }
+
+
+ /**
+ * Return the String representation that this ToStringCreator built.
+ */
+ @Override
+ public String toString() {
+ this.styler.styleEnd(this.buffer, this.object);
+ return this.buffer.toString();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/style/ToStringStyler.java b/spring-core/src/main/java/org/springframework/core/style/ToStringStyler.java
new file mode 100644
index 00000000..9ae12df5
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/style/ToStringStyler.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2012 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.style;
+
+/**
+ * A strategy interface for pretty-printing {@code toString()} methods.
+ * Encapsulates the print algorithms; some other object such as a builder
+ * should provide the workflow.
+ *
+ * @author Keith Donald
+ * @since 1.2.2
+ */
+public interface ToStringStyler {
+
+ /**
+ * Style a {@code toString()}'ed object before its fields are styled.
+ * @param buffer the buffer to print to
+ * @param obj the object to style
+ */
+ void styleStart(StringBuilder buffer, Object obj);
+
+ /**
+ * Style a {@code toString()}'ed object after it's fields are styled.
+ * @param buffer the buffer to print to
+ * @param obj the object to style
+ */
+ void styleEnd(StringBuilder buffer, Object obj);
+
+ /**
+ * Style a field value as a string.
+ * @param buffer the buffer to print to
+ * @param fieldName the he name of the field
+ * @param value the field value
+ */
+ void styleField(StringBuilder buffer, String fieldName, Object value);
+
+ /**
+ * Style the given value.
+ * @param buffer the buffer to print to
+ * @param value the field value
+ */
+ void styleValue(StringBuilder buffer, Object value);
+
+ /**
+ * Style the field separator.
+ * @param buffer buffer to print to
+ */
+ void styleFieldSeparator(StringBuilder buffer);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/style/ValueStyler.java b/spring-core/src/main/java/org/springframework/core/style/ValueStyler.java
new file mode 100644
index 00000000..731fb3ae
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/style/ValueStyler.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2002-2007 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.style;
+
+/**
+ * Strategy that encapsulates value String styling algorithms
+ * according to Spring conventions.
+ *
+ * @author Keith Donald
+ * @since 1.2.2
+ */
+public interface ValueStyler {
+
+ /**
+ * Style the given value, returning a String representation.
+ * @param value the Object value to style
+ * @return the styled String
+ */
+ String style(Object value);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/style/package-info.java b/spring-core/src/main/java/org/springframework/core/style/package-info.java
new file mode 100644
index 00000000..a88ee998
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/style/package-info.java
@@ -0,0 +1,8 @@
+
+/**
+ *
+ * Support for styling values as Strings, with ToStringCreator as central class.
+ *
+ */
+package org.springframework.core.style;
+
diff --git a/spring-core/src/main/java/org/springframework/core/task/AsyncTaskExecutor.java b/spring-core/src/main/java/org/springframework/core/task/AsyncTaskExecutor.java
new file mode 100644
index 00000000..f650e1d5
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/task/AsyncTaskExecutor.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2002-2013 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.task;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+
+/**
+ * Extended interface for asynchronous {@link TaskExecutor} implementations,
+ * offering an overloaded {@link #execute(Runnable, long)} variant with a start
+ * timeout parameter as well support for {@link java.util.concurrent.Callable}.
+ *
+ * <p>Note: The {@link java.util.concurrent.Executors} class includes a set of
+ * methods that can convert some other common closure-like objects, for example,
+ * {@link java.security.PrivilegedAction} to {@link Callable} before executing them.
+ *
+ * <p>Implementing this interface also indicates that the {@link #execute(Runnable)}
+ * method will not execute its Runnable in the caller's thread but rather
+ * asynchronously in some other thread.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0.3
+ * @see SimpleAsyncTaskExecutor
+ * @see org.springframework.scheduling.SchedulingTaskExecutor
+ * @see java.util.concurrent.Callable
+ * @see java.util.concurrent.Executors
+ */
+public interface AsyncTaskExecutor extends TaskExecutor {
+
+ /** Constant that indicates immediate execution */
+ long TIMEOUT_IMMEDIATE = 0;
+
+ /** Constant that indicates no time limit */
+ long TIMEOUT_INDEFINITE = Long.MAX_VALUE;
+
+
+ /**
+ * Execute the given {@code task}.
+ * @param task the {@code Runnable} to execute (never {@code null})
+ * @param startTimeout the time duration (milliseconds) within which the task is
+ * supposed to start. This is intended as a hint to the executor, allowing for
+ * preferred handling of immediate tasks. Typical values are {@link #TIMEOUT_IMMEDIATE}
+ * or {@link #TIMEOUT_INDEFINITE} (the default as used by {@link #execute(Runnable)}).
+ * @throws TaskTimeoutException in case of the task being rejected because
+ * of the timeout (i.e. it cannot be started in time)
+ * @throws TaskRejectedException if the given task was not accepted
+ */
+ void execute(Runnable task, long startTimeout);
+
+ /**
+ * Submit a Runnable task for execution, receiving a Future representing that task.
+ * The Future will return a {@code null} result upon completion.
+ * @param task the {@code Runnable} to execute (never {@code null})
+ * @return a Future representing pending completion of the task
+ * @throws TaskRejectedException if the given task was not accepted
+ * @since 3.0
+ */
+ Future<?> submit(Runnable task);
+
+ /**
+ * Submit a Callable task for execution, receiving a Future representing that task.
+ * The Future will return the Callable's result upon completion.
+ * @param task the {@code Callable} to execute (never {@code null})
+ * @return a Future representing pending completion of the task
+ * @throws TaskRejectedException if the given task was not accepted
+ * @since 3.0
+ */
+ <T> Future<T> submit(Callable<T> task);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java b/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java
new file mode 100644
index 00000000..f6b71400
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2002-2012 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.task;
+
+import java.io.Serializable;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.ThreadFactory;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ConcurrencyThrottleSupport;
+import org.springframework.util.CustomizableThreadCreator;
+
+/**
+ * {@link TaskExecutor} implementation that fires up a new Thread for each task,
+ * executing it asynchronously.
+ *
+ * <p>Supports limiting concurrent threads through the "concurrencyLimit"
+ * bean property. By default, the number of concurrent threads is unlimited.
+ *
+ * <p><b>NOTE: This implementation does not reuse threads!</b> Consider a
+ * thread-pooling TaskExecutor implementation instead, in particular for
+ * executing a large number of short-lived tasks.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see #setConcurrencyLimit
+ * @see SyncTaskExecutor
+ * @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
+ * @see org.springframework.scheduling.commonj.WorkManagerTaskExecutor
+ */
+@SuppressWarnings("serial")
+public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implements AsyncTaskExecutor, Serializable {
+
+ /**
+ * Permit any number of concurrent invocations: that is, don't throttle concurrency.
+ */
+ public static final int UNBOUNDED_CONCURRENCY = ConcurrencyThrottleSupport.UNBOUNDED_CONCURRENCY;
+
+ /**
+ * Switch concurrency 'off': that is, don't allow any concurrent invocations.
+ */
+ public static final int NO_CONCURRENCY = ConcurrencyThrottleSupport.NO_CONCURRENCY;
+
+
+ /** Internal concurrency throttle used by this executor */
+ private final ConcurrencyThrottleAdapter concurrencyThrottle = new ConcurrencyThrottleAdapter();
+
+ private ThreadFactory threadFactory;
+
+
+ /**
+ * Create a new SimpleAsyncTaskExecutor with default thread name prefix.
+ */
+ public SimpleAsyncTaskExecutor() {
+ super();
+ }
+
+ /**
+ * Create a new SimpleAsyncTaskExecutor with the given thread name prefix.
+ * @param threadNamePrefix the prefix to use for the names of newly created threads
+ */
+ public SimpleAsyncTaskExecutor(String threadNamePrefix) {
+ super(threadNamePrefix);
+ }
+
+ /**
+ * Create a new SimpleAsyncTaskExecutor with the given external thread factory.
+ * @param threadFactory the factory to use for creating new Threads
+ */
+ public SimpleAsyncTaskExecutor(ThreadFactory threadFactory) {
+ this.threadFactory = threadFactory;
+ }
+
+
+ /**
+ * Specify an external factory to use for creating new Threads,
+ * instead of relying on the local properties of this executor.
+ * <p>You may specify an inner ThreadFactory bean or also a ThreadFactory reference
+ * obtained from JNDI (on a Java EE 6 server) or some other lookup mechanism.
+ * @see #setThreadNamePrefix
+ * @see #setThreadPriority
+ */
+ public void setThreadFactory(ThreadFactory threadFactory) {
+ this.threadFactory = threadFactory;
+ }
+
+ /**
+ * Return the external factory to use for creating new Threads, if any.
+ */
+ public final ThreadFactory getThreadFactory() {
+ return this.threadFactory;
+ }
+
+ /**
+ * Set the maximum number of parallel accesses allowed.
+ * -1 indicates no concurrency limit at all.
+ * <p>In principle, this limit can be changed at runtime,
+ * although it is generally designed as a config time setting.
+ * NOTE: Do not switch between -1 and any concrete limit at runtime,
+ * as this will lead to inconsistent concurrency counts: A limit
+ * of -1 effectively turns off concurrency counting completely.
+ * @see #UNBOUNDED_CONCURRENCY
+ */
+ public void setConcurrencyLimit(int concurrencyLimit) {
+ this.concurrencyThrottle.setConcurrencyLimit(concurrencyLimit);
+ }
+
+ /**
+ * Return the maximum number of parallel accesses allowed.
+ */
+ public final int getConcurrencyLimit() {
+ return this.concurrencyThrottle.getConcurrencyLimit();
+ }
+
+ /**
+ * Return whether this throttle is currently active.
+ * @return {@code true} if the concurrency limit for this instance is active
+ * @see #getConcurrencyLimit()
+ * @see #setConcurrencyLimit
+ */
+ public final boolean isThrottleActive() {
+ return this.concurrencyThrottle.isThrottleActive();
+ }
+
+
+ /**
+ * Executes the given task, within a concurrency throttle
+ * if configured (through the superclass's settings).
+ * @see #doExecute(Runnable)
+ */
+ public void execute(Runnable task) {
+ execute(task, TIMEOUT_INDEFINITE);
+ }
+
+ /**
+ * Executes the given task, within a concurrency throttle
+ * if configured (through the superclass's settings).
+ * <p>Executes urgent tasks (with 'immediate' timeout) directly,
+ * bypassing the concurrency throttle (if active). All other
+ * tasks are subject to throttling.
+ * @see #TIMEOUT_IMMEDIATE
+ * @see #doExecute(Runnable)
+ */
+ public void execute(Runnable task, long startTimeout) {
+ Assert.notNull(task, "Runnable must not be null");
+ if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
+ this.concurrencyThrottle.beforeAccess();
+ doExecute(new ConcurrencyThrottlingRunnable(task));
+ }
+ else {
+ doExecute(task);
+ }
+ }
+
+ public Future<?> submit(Runnable task) {
+ FutureTask<Object> future = new FutureTask<Object>(task, null);
+ execute(future, TIMEOUT_INDEFINITE);
+ return future;
+ }
+
+ public <T> Future<T> submit(Callable<T> task) {
+ FutureTask<T> future = new FutureTask<T>(task);
+ execute(future, TIMEOUT_INDEFINITE);
+ return future;
+ }
+
+ /**
+ * Template method for the actual execution of a task.
+ * <p>The default implementation creates a new Thread and starts it.
+ * @param task the Runnable to execute
+ * @see #setThreadFactory
+ * @see #createThread
+ * @see java.lang.Thread#start()
+ */
+ protected void doExecute(Runnable task) {
+ Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
+ thread.start();
+ }
+
+
+ /**
+ * Subclass of the general ConcurrencyThrottleSupport class,
+ * making {@code beforeAccess()} and {@code afterAccess()}
+ * visible to the surrounding class.
+ */
+ private static class ConcurrencyThrottleAdapter extends ConcurrencyThrottleSupport {
+
+ @Override
+ protected void beforeAccess() {
+ super.beforeAccess();
+ }
+
+ @Override
+ protected void afterAccess() {
+ super.afterAccess();
+ }
+ }
+
+
+ /**
+ * This Runnable calls {@code afterAccess()} after the
+ * target Runnable has finished its execution.
+ */
+ private class ConcurrencyThrottlingRunnable implements Runnable {
+
+ private final Runnable target;
+
+ public ConcurrencyThrottlingRunnable(Runnable target) {
+ this.target = target;
+ }
+
+ public void run() {
+ try {
+ this.target.run();
+ }
+ finally {
+ concurrencyThrottle.afterAccess();
+ }
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/task/SyncTaskExecutor.java b/spring-core/src/main/java/org/springframework/core/task/SyncTaskExecutor.java
new file mode 100644
index 00000000..47bebf46
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/task/SyncTaskExecutor.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2012 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.task;
+
+import java.io.Serializable;
+
+import org.springframework.util.Assert;
+
+/**
+ * {@link TaskExecutor} implementation that executes each task <i>synchronously</i>
+ * in the calling thread.
+ *
+ * <p>Mainly intended for testing scenarios.
+ *
+ * <p>Execution in the calling thread does have the advantage of participating
+ * in it's thread context, for example the thread context class loader or the
+ * thread's current transaction association. That said, in many cases,
+ * asynchronous execution will be preferable: choose an asynchronous
+ * {@code TaskExecutor} instead for such scenarios.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see SimpleAsyncTaskExecutor
+ */
+@SuppressWarnings("serial")
+public class SyncTaskExecutor implements TaskExecutor, Serializable {
+
+ /**
+ * Executes the given {@code task} synchronously, through direct
+ * invocation of it's {@link Runnable#run() run()} method.
+ * @throws IllegalArgumentException if the given {@code task} is {@code null}
+ */
+ public void execute(Runnable task) {
+ Assert.notNull(task, "Runnable must not be null");
+ task.run();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/task/TaskExecutor.java b/spring-core/src/main/java/org/springframework/core/task/TaskExecutor.java
new file mode 100644
index 00000000..fea960e3
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/task/TaskExecutor.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2002-2012 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.task;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Simple task executor interface that abstracts the execution
+ * of a {@link Runnable}.
+ *
+ * <p>Implementations can use all sorts of different execution strategies,
+ * such as: synchronous, asynchronous, using a thread pool, and more.
+ *
+ * <p>Equivalent to JDK 1.5's {@link java.util.concurrent.Executor}
+ * interface; extending it now in Spring 3.0, so that clients may declare
+ * a dependency on an Executor and receive any TaskExecutor implementation.
+ * This interface remains separate from the standard Executor interface
+ * mainly for backwards compatibility with JDK 1.4 in Spring 2.x.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ * @see java.util.concurrent.Executor
+ */
+public interface TaskExecutor extends Executor {
+
+ /**
+ * Execute the given {@code task}.
+ * <p>The call might return immediately if the implementation uses
+ * an asynchronous execution strategy, or might block in the case
+ * of synchronous execution.
+ * @param task the {@code Runnable} to execute (never {@code null})
+ * @throws TaskRejectedException if the given task was not accepted
+ */
+ void execute(Runnable task);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/task/TaskRejectedException.java b/spring-core/src/main/java/org/springframework/core/task/TaskRejectedException.java
new file mode 100644
index 00000000..feacf971
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/task/TaskRejectedException.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2002-2012 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.task;
+
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * Exception thrown when a {@link TaskExecutor} rejects to accept
+ * a given task for execution.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0.1
+ * @see TaskExecutor#execute(Runnable)
+ * @see TaskTimeoutException
+ */
+@SuppressWarnings("serial")
+public class TaskRejectedException extends RejectedExecutionException {
+
+ /**
+ * Create a new {@code TaskRejectedException}
+ * with the specified detail message and no root cause.
+ * @param msg the detail message
+ */
+ public TaskRejectedException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Create a new {@code TaskRejectedException}
+ * with the specified detail message and the given root cause.
+ * @param msg the detail message
+ * @param cause the root cause (usually from using an underlying
+ * API such as the {@code java.util.concurrent} package)
+ * @see java.util.concurrent.RejectedExecutionException
+ */
+ public TaskRejectedException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/task/TaskTimeoutException.java b/spring-core/src/main/java/org/springframework/core/task/TaskTimeoutException.java
new file mode 100644
index 00000000..476658bb
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/task/TaskTimeoutException.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2012 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.task;
+
+/**
+ * Exception thrown when a {@link AsyncTaskExecutor} rejects to accept
+ * a given task for execution because of the specified timeout.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0.3
+ * @see AsyncTaskExecutor#execute(Runnable, long)
+ * @see TaskRejectedException
+ */
+@SuppressWarnings("serial")
+public class TaskTimeoutException extends TaskRejectedException {
+
+ /**
+ * Create a new {@code TaskTimeoutException}
+ * with the specified detail message and no root cause.
+ * @param msg the detail message
+ */
+ public TaskTimeoutException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Create a new {@code TaskTimeoutException}
+ * with the specified detail message and the given root cause.
+ * @param msg the detail message
+ * @param cause the root cause (usually from using an underlying
+ * API such as the {@code java.util.concurrent} package)
+ * @see java.util.concurrent.RejectedExecutionException
+ */
+ public TaskTimeoutException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/task/package-info.java b/spring-core/src/main/java/org/springframework/core/task/package-info.java
new file mode 100644
index 00000000..fdbf0010
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/task/package-info.java
@@ -0,0 +1,9 @@
+
+/**
+ *
+ * This package defines Spring's core TaskExecutor abstraction,
+ * and provides SyncTaskExecutor and SimpleAsyncTaskExecutor implementations.
+ *
+ */
+package org.springframework.core.task;
+
diff --git a/spring-core/src/main/java/org/springframework/core/task/support/ConcurrentExecutorAdapter.java b/spring-core/src/main/java/org/springframework/core/task/support/ConcurrentExecutorAdapter.java
new file mode 100644
index 00000000..2acf1a28
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/task/support/ConcurrentExecutorAdapter.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.task.support;
+
+import java.util.concurrent.Executor;
+
+import org.springframework.core.task.TaskExecutor;
+import org.springframework.util.Assert;
+
+/**
+ * Adapter that exposes the {@link java.util.concurrent.Executor} interface
+ * for any Spring {@link org.springframework.core.task.TaskExecutor}.
+ *
+ * <p>This is less useful as of Spring 3.0, since TaskExecutor itself
+ * extends the Executor interface. The adapter is only relevant for
+ * <em>hiding</em> the TaskExecutor nature of a given object now,
+ * solely exposing the standard Executor interface to a client.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see java.util.concurrent.Executor
+ * @see org.springframework.core.task.TaskExecutor
+ */
+public class ConcurrentExecutorAdapter implements Executor {
+
+ private final TaskExecutor taskExecutor;
+
+
+ /**
+ * Create a new ConcurrentExecutorAdapter for the given Spring TaskExecutor.
+ * @param taskExecutor the Spring TaskExecutor to wrap
+ */
+ public ConcurrentExecutorAdapter(TaskExecutor taskExecutor) {
+ Assert.notNull(taskExecutor, "TaskExecutor must not be null");
+ this.taskExecutor = taskExecutor;
+ }
+
+
+ public void execute(Runnable command) {
+ this.taskExecutor.execute(command);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/task/support/ExecutorServiceAdapter.java b/spring-core/src/main/java/org/springframework/core/task/support/ExecutorServiceAdapter.java
new file mode 100644
index 00000000..0c594b93
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/task/support/ExecutorServiceAdapter.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2002-2012 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.task.support;
+
+import java.util.List;
+import java.util.concurrent.AbstractExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.springframework.core.task.TaskExecutor;
+import org.springframework.util.Assert;
+
+/**
+ * Adapter that takes a Spring {@link org.springframework.core.task.TaskExecutor})
+ * and exposes a full {@code java.util.concurrent.ExecutorService} for it.
+ *
+ * <p>This is primarily for adapting to client components that communicate via the
+ * {@code java.util.concurrent.ExecutorService} API. It can also be used as
+ * common ground between a local Spring {@code TaskExecutor} backend and a
+ * JNDI-located {@code ManagedExecutorService} in a Java EE 6 environment.
+ *
+ * <p><b>NOTE:</b> This ExecutorService adapter does <em>not</em> support the
+ * lifecycle methods in the {@code java.util.concurrent.ExecutorService} API
+ * ("shutdown()" etc), similar to a server-wide {@code ManagedExecutorService}
+ * in a Java EE 6 environment. The lifecycle is always up to the backend pool,
+ * with this adapter acting as an access-only proxy for that target pool.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ * @see java.util.concurrent.ExecutorService
+ */
+public class ExecutorServiceAdapter extends AbstractExecutorService {
+
+ private final TaskExecutor taskExecutor;
+
+
+ /**
+ * Create a new ExecutorServiceAdapter, using the given target executor.
+ * @param taskExecutor the target executor to delegate to
+ */
+ public ExecutorServiceAdapter(TaskExecutor taskExecutor) {
+ Assert.notNull(taskExecutor, "TaskExecutor must not be null");
+ this.taskExecutor = taskExecutor;
+ }
+
+
+ public void execute(Runnable task) {
+ this.taskExecutor.execute(task);
+ }
+
+ public void shutdown() {
+ throw new IllegalStateException(
+ "Manual shutdown not supported - ExecutorServiceAdapter is dependent on an external lifecycle");
+ }
+
+ public List<Runnable> shutdownNow() {
+ throw new IllegalStateException(
+ "Manual shutdown not supported - ExecutorServiceAdapter is dependent on an external lifecycle");
+ }
+
+ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+ throw new IllegalStateException(
+ "Manual shutdown not supported - ExecutorServiceAdapter is dependent on an external lifecycle");
+ }
+
+ public boolean isShutdown() {
+ return false;
+ }
+
+ public boolean isTerminated() {
+ return false;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java b/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java
new file mode 100644
index 00000000..f980c4d4
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2002-2013 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.task.support;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.RejectedExecutionException;
+
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.core.task.TaskRejectedException;
+import org.springframework.util.Assert;
+
+/**
+ * Adapter that takes a JDK {@code java.util.concurrent.Executor} and
+ * exposes a Spring {@link org.springframework.core.task.TaskExecutor} for it.
+ * Also detects an extended {@code java.util.concurrent.ExecutorService}, adapting
+ * the {@link org.springframework.core.task.AsyncTaskExecutor} interface accordingly.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ * @see java.util.concurrent.Executor
+ * @see java.util.concurrent.ExecutorService
+ * @see java.util.concurrent.Executors
+ */
+public class TaskExecutorAdapter implements AsyncTaskExecutor {
+
+ private final Executor concurrentExecutor;
+
+
+ /**
+ * Create a new TaskExecutorAdapter,
+ * using the given JDK concurrent executor.
+ * @param concurrentExecutor the JDK concurrent executor to delegate to
+ */
+ public TaskExecutorAdapter(Executor concurrentExecutor) {
+ Assert.notNull(concurrentExecutor, "Executor must not be null");
+ this.concurrentExecutor = concurrentExecutor;
+ }
+
+
+ /**
+ * Delegates to the specified JDK concurrent executor.
+ * @see java.util.concurrent.Executor#execute(Runnable)
+ */
+ public void execute(Runnable task) {
+ try {
+ this.concurrentExecutor.execute(task);
+ }
+ catch (RejectedExecutionException ex) {
+ throw new TaskRejectedException(
+ "Executor [" + this.concurrentExecutor + "] did not accept task: " + task, ex);
+ }
+ }
+
+ public void execute(Runnable task, long startTimeout) {
+ execute(task);
+ }
+
+ public Future<?> submit(Runnable task) {
+ try {
+ if (this.concurrentExecutor instanceof ExecutorService) {
+ return ((ExecutorService) this.concurrentExecutor).submit(task);
+ }
+ else {
+ FutureTask<Object> future = new FutureTask<Object>(task, null);
+ this.concurrentExecutor.execute(future);
+ return future;
+ }
+ }
+ catch (RejectedExecutionException ex) {
+ throw new TaskRejectedException(
+ "Executor [" + this.concurrentExecutor + "] did not accept task: " + task, ex);
+ }
+ }
+
+ public <T> Future<T> submit(Callable<T> task) {
+ try {
+ if (this.concurrentExecutor instanceof ExecutorService) {
+ return ((ExecutorService) this.concurrentExecutor).submit(task);
+ }
+ else {
+ FutureTask<T> future = new FutureTask<T>(task);
+ this.concurrentExecutor.execute(future);
+ return future;
+ }
+ }
+ catch (RejectedExecutionException ex) {
+ throw new TaskRejectedException(
+ "Executor [" + this.concurrentExecutor + "] did not accept task: " + task, ex);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/task/support/package-info.java b/spring-core/src/main/java/org/springframework/core/task/support/package-info.java
new file mode 100644
index 00000000..b1bd9430
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/task/support/package-info.java
@@ -0,0 +1,9 @@
+
+/**
+ *
+ * Support classes for Spring's TaskExecutor abstraction.
+ * Includes an adapter for the standard ExecutorService interface.
+ *
+ */
+package org.springframework.core.task.support;
+
diff --git a/spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java b/spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java
new file mode 100644
index 00000000..beeb7d93
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2002-2012 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.type;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Interface that defines abstract access to the annotations of a specific
+ * class, in a form that does not require that class to be loaded yet.
+ *
+ * @author Juergen Hoeller
+ * @author Mark Fisher
+ * @since 2.5
+ * @see StandardAnnotationMetadata
+ * @see org.springframework.core.type.classreading.MetadataReader#getAnnotationMetadata()
+ */
+public interface AnnotationMetadata extends ClassMetadata {
+
+ /**
+ * Return the names of all annotation types defined on the underlying class.
+ * @return the annotation type names
+ */
+ Set<String> getAnnotationTypes();
+
+ /**
+ * Return the names of all meta-annotation types defined on the
+ * given annotation type of the underlying class.
+ * @param annotationType the meta-annotation type to look for
+ * @return the meta-annotation type names
+ */
+ Set<String> getMetaAnnotationTypes(String annotationType);
+
+ /**
+ * Determine whether the underlying class has an annotation of the given
+ * type defined.
+ * @param annotationType the annotation type to look for
+ * @return whether a matching annotation is defined
+ */
+ boolean hasAnnotation(String annotationType);
+
+ /**
+ * Determine whether the underlying class has an annotation that
+ * is itself annotated with the meta-annotation of the given type.
+ * @param metaAnnotationType the meta-annotation type to look for
+ * @return whether a matching meta-annotation is defined
+ */
+ boolean hasMetaAnnotation(String metaAnnotationType);
+
+ /**
+ * Determine whether the underlying class has an annotation or
+ * meta-annotation of the given type defined.
+ * <p>This is equivalent to a "hasAnnotation || hasMetaAnnotation"
+ * check. If this method returns {@code true}, then
+ * {@link #getAnnotationAttributes} will return a non-null Map.
+ * @param annotationType the annotation type to look for
+ * @return whether a matching annotation is defined
+ */
+ boolean isAnnotated(String annotationType);
+
+ /**
+ * Retrieve the attributes of the annotation of the given type,
+ * if any (i.e. if defined on the underlying class, as direct
+ * annotation or as meta-annotation).
+ * @param annotationType the annotation type to look for
+ * @return a Map of attributes, with the attribute name as key (e.g. "value")
+ * and the defined attribute value as Map value. This return value will be
+ * {@code null} if no matching annotation is defined.
+ */
+ Map<String, Object> getAnnotationAttributes(String annotationType);
+
+ /**
+ * Retrieve the attributes of the annotation of the given type,
+ * if any (i.e. if defined on the underlying class, as direct
+ * annotation or as meta-annotation).
+ * @param annotationType the annotation type to look for
+ * @param classValuesAsString whether to convert class references to String
+ * class names for exposure as values in the returned Map, instead of Class
+ * references which might potentially have to be loaded first
+ * @return a Map of attributes, with the attribute name as key (e.g. "value")
+ * and the defined attribute value as Map value. This return value will be
+ * {@code null} if no matching annotation is defined.
+ */
+ Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString);
+
+ /**
+ * Determine whether the underlying class has any methods that are
+ * annotated (or meta-annotated) with the given annotation type.
+ */
+ boolean hasAnnotatedMethods(String annotationType);
+
+ /**
+ * Retrieve the method metadata for all methods that are annotated
+ * (or meta-annotated) with the given annotation type.
+ * <p>For any returned method, {@link MethodMetadata#isAnnotated} will
+ * return {@code true} for the given annotation type.
+ * @param annotationType the annotation type to look for
+ * @return a Set of {@link MethodMetadata} for methods that have a matching
+ * annotation. The return value will be an empty set if no methods match
+ * the annotation type.
+ */
+ Set<MethodMetadata> getAnnotatedMethods(String annotationType);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/ClassMetadata.java b/spring-core/src/main/java/org/springframework/core/type/ClassMetadata.java
new file mode 100644
index 00000000..3f649713
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/ClassMetadata.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2002-2012 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.type;
+
+/**
+ * Interface that defines abstract metadata of a specific class,
+ * in a form that does not require that class to be loaded yet.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see StandardClassMetadata
+ * @see org.springframework.core.type.classreading.MetadataReader#getClassMetadata()
+ * @see AnnotationMetadata
+ */
+public interface ClassMetadata {
+
+ /**
+ * Return the name of the underlying class.
+ */
+ String getClassName();
+
+ /**
+ * Return whether the underlying class represents an interface.
+ */
+ boolean isInterface();
+
+ /**
+ * Return whether the underlying class is marked as abstract.
+ */
+ boolean isAbstract();
+
+ /**
+ * Return whether the underlying class represents a concrete class,
+ * i.e. neither an interface nor an abstract class.
+ */
+ boolean isConcrete();
+
+ /**
+ * Return whether the underlying class is marked as 'final'.
+ */
+ boolean isFinal();
+
+ /**
+ * Determine whether the underlying class is independent,
+ * i.e. whether it is a top-level class or a nested class
+ * (static inner class) that can be constructed independent
+ * from an enclosing class.
+ */
+ boolean isIndependent();
+
+ /**
+ * Return whether the underlying class has an enclosing class
+ * (i.e. the underlying class is an inner/nested class or
+ * a local class within a method).
+ * <p>If this method returns {@code false}, then the
+ * underlying class is a top-level class.
+ */
+ boolean hasEnclosingClass();
+
+ /**
+ * Return the name of the enclosing class of the underlying class,
+ * or {@code null} if the underlying class is a top-level class.
+ */
+ String getEnclosingClassName();
+
+ /**
+ * Return whether the underlying class has a super class.
+ */
+ boolean hasSuperClass();
+
+ /**
+ * Return the name of the super class of the underlying class,
+ * or {@code null} if there is no super class defined.
+ */
+ String getSuperClassName();
+
+ /**
+ * Return the names of all interfaces that the underlying class
+ * implements, or an empty array if there are none.
+ */
+ String[] getInterfaceNames();
+
+ /**
+ * Return the names of all classes declared as members of the class represented by
+ * this ClassMetadata object. This includes public, protected, default (package)
+ * access, and private classes and interfaces declared by the class, but excludes
+ * inherited classes and interfaces. An empty array is returned if no member classes
+ * or interfaces exist.
+ */
+ String[] getMemberClassNames();
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java b/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java
new file mode 100644
index 00000000..17d7bab2
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2002-2012 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.type;
+
+import java.util.Map;
+
+/**
+ * Interface that defines abstract access to the annotations of a specific
+ * class, in a form that does not require that class to be loaded yet.
+ *
+ * @author Juergen Hoeller
+ * @author Mark Pollack
+ * @author Chris Beams
+ * @since 3.0
+ * @see StandardMethodMetadata
+ * @see AnnotationMetadata#getAnnotatedMethods
+ */
+public interface MethodMetadata {
+
+ /**
+ * Return the name of the method.
+ */
+ String getMethodName();
+
+ /**
+ * Return the fully-qualified name of the class that declares this method.
+ */
+ public String getDeclaringClassName();
+
+ /**
+ * Return whether the underlying method is declared as 'static'.
+ */
+ boolean isStatic();
+
+ /**
+ * Return whether the underlying method is marked as 'final'.
+ */
+ boolean isFinal();
+
+ /**
+ * Return whether the underlying method is overridable,
+ * i.e. not marked as static, final or private.
+ */
+ boolean isOverridable();
+
+ /**
+ * Determine whether the underlying method has an annotation or
+ * meta-annotation of the given type defined.
+ * @param annotationType the annotation type to look for
+ * @return whether a matching annotation is defined
+ */
+ boolean isAnnotated(String annotationType);
+
+ /**
+ * Retrieve the attributes of the annotation of the given type,
+ * if any (i.e. if defined on the underlying method, as direct
+ * annotation or as meta-annotation).
+ * @param annotationType the annotation type to look for
+ * @return a Map of attributes, with the attribute name as key (e.g. "value")
+ * and the defined attribute value as Map value. This return value will be
+ * {@code null} if no matching annotation is defined.
+ */
+ Map<String, Object> getAnnotationAttributes(String annotationType);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java
new file mode 100644
index 00000000..1a4e5365
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2002-2014 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.type;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.annotation.AnnotationUtils;
+
+/**
+ * {@link AnnotationMetadata} implementation that uses standard reflection
+ * to introspect a given {@link Class}.
+ *
+ * @author Juergen Hoeller
+ * @author Mark Fisher
+ * @author Chris Beams
+ * @since 2.5
+ */
+public class StandardAnnotationMetadata extends StandardClassMetadata implements AnnotationMetadata {
+
+ private final boolean nestedAnnotationsAsMap;
+
+
+ /**
+ * Create a new {@code StandardAnnotationMetadata} wrapper for the given Class.
+ * @param introspectedClass the Class to introspect
+ * @see #StandardAnnotationMetadata(Class, boolean)
+ */
+ public StandardAnnotationMetadata(Class<?> introspectedClass) {
+ this(introspectedClass, false);
+ }
+
+ /**
+ * Create a new {@link StandardAnnotationMetadata} wrapper for the given Class,
+ * providing the option to return any nested annotations or annotation arrays in the
+ * form of {@link AnnotationAttributes} instead of actual {@link Annotation} instances.
+ * @param introspectedClass the Class to instrospect
+ * @param nestedAnnotationsAsMap return nested annotations and annotation arrays as
+ * {@link AnnotationAttributes} for compatibility with ASM-based
+ * {@link AnnotationMetadata} implementations
+ * @since 3.1.1
+ */
+ public StandardAnnotationMetadata(Class<?> introspectedClass, boolean nestedAnnotationsAsMap) {
+ super(introspectedClass);
+ this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
+ }
+
+
+ public Set<String> getAnnotationTypes() {
+ Set<String> types = new LinkedHashSet<String>();
+ Annotation[] anns = getIntrospectedClass().getAnnotations();
+ for (Annotation ann : anns) {
+ types.add(ann.annotationType().getName());
+ }
+ return types;
+ }
+
+ public Set<String> getMetaAnnotationTypes(String annotationType) {
+ Annotation[] anns = getIntrospectedClass().getAnnotations();
+ for (Annotation ann : anns) {
+ if (ann.annotationType().getName().equals(annotationType)) {
+ Set<String> types = new LinkedHashSet<String>();
+ Annotation[] metaAnns = ann.annotationType().getAnnotations();
+ for (Annotation metaAnn : metaAnns) {
+ types.add(metaAnn.annotationType().getName());
+ for (Annotation metaMetaAnn : metaAnn.annotationType().getAnnotations()) {
+ types.add(metaMetaAnn.annotationType().getName());
+ }
+ }
+ return types;
+ }
+ }
+ return null;
+ }
+
+ public boolean hasAnnotation(String annotationType) {
+ Annotation[] anns = getIntrospectedClass().getAnnotations();
+ for (Annotation ann : anns) {
+ if (ann.annotationType().getName().equals(annotationType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean hasMetaAnnotation(String annotationType) {
+ Annotation[] anns = getIntrospectedClass().getAnnotations();
+ for (Annotation ann : anns) {
+ Annotation[] metaAnns = ann.annotationType().getAnnotations();
+ for (Annotation metaAnn : metaAnns) {
+ if (metaAnn.annotationType().getName().equals(annotationType)) {
+ return true;
+ }
+ for (Annotation metaMetaAnn : metaAnn.annotationType().getAnnotations()) {
+ if (metaMetaAnn.annotationType().getName().equals(annotationType)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ public boolean isAnnotated(String annotationType) {
+ Annotation[] anns = getIntrospectedClass().getAnnotations();
+ for (Annotation ann : anns) {
+ if (ann.annotationType().getName().equals(annotationType)) {
+ return true;
+ }
+ for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
+ if (metaAnn.annotationType().getName().equals(annotationType)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public Map<String, Object> getAnnotationAttributes(String annotationType) {
+ return this.getAnnotationAttributes(annotationType, false);
+ }
+
+ public Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString) {
+ Annotation[] anns = getIntrospectedClass().getAnnotations();
+ for (Annotation ann : anns) {
+ if (ann.annotationType().getName().equals(annotationType)) {
+ return AnnotationUtils.getAnnotationAttributes(
+ ann, classValuesAsString, this.nestedAnnotationsAsMap);
+ }
+ }
+ for (Annotation ann : anns) {
+ for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
+ if (metaAnn.annotationType().getName().equals(annotationType)) {
+ return AnnotationUtils.getAnnotationAttributes(
+ metaAnn, classValuesAsString, this.nestedAnnotationsAsMap);
+ }
+ }
+ }
+ return null;
+ }
+
+ public boolean hasAnnotatedMethods(String annotationType) {
+ Method[] methods = getIntrospectedClass().getDeclaredMethods();
+ for (Method method : methods) {
+ if (!method.isBridge()) {
+ for (Annotation ann : method.getAnnotations()) {
+ if (ann.annotationType().getName().equals(annotationType)) {
+ return true;
+ }
+ else {
+ for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
+ if (metaAnn.annotationType().getName().equals(annotationType)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ public Set<MethodMetadata> getAnnotatedMethods(String annotationType) {
+ Set<MethodMetadata> annotatedMethods = new LinkedHashSet<MethodMetadata>();
+ Method[] methods = getIntrospectedClass().getDeclaredMethods();
+ for (Method method : methods) {
+ if (!method.isBridge()) {
+ for (Annotation ann : method.getAnnotations()) {
+ if (ann.annotationType().getName().equals(annotationType)) {
+ annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap));
+ break;
+ }
+ else {
+ for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
+ if (metaAnn.annotationType().getName().equals(annotationType)) {
+ annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap));
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ return annotatedMethods;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardClassMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardClassMetadata.java
new file mode 100644
index 00000000..4ac99d4d
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/StandardClassMetadata.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2002-2012 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.type;
+
+import java.lang.reflect.Modifier;
+import java.util.LinkedHashSet;
+
+import org.springframework.util.Assert;
+
+/**
+ * {@link ClassMetadata} implementation that uses standard reflection
+ * to introspect a given {@code Class}.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+public class StandardClassMetadata implements ClassMetadata {
+
+ private final Class introspectedClass;
+
+
+ /**
+ * Create a new StandardClassMetadata wrapper for the given Class.
+ * @param introspectedClass the Class to introspect
+ */
+ public StandardClassMetadata(Class introspectedClass) {
+ Assert.notNull(introspectedClass, "Class must not be null");
+ this.introspectedClass = introspectedClass;
+ }
+
+ /**
+ * Return the underlying Class.
+ */
+ public final Class getIntrospectedClass() {
+ return this.introspectedClass;
+ }
+
+
+ public String getClassName() {
+ return this.introspectedClass.getName();
+ }
+
+ public boolean isInterface() {
+ return this.introspectedClass.isInterface();
+ }
+
+ public boolean isAbstract() {
+ return Modifier.isAbstract(this.introspectedClass.getModifiers());
+ }
+
+ public boolean isConcrete() {
+ return !(isInterface() || isAbstract());
+ }
+
+ public boolean isFinal() {
+ return Modifier.isFinal(this.introspectedClass.getModifiers());
+ }
+
+ public boolean isIndependent() {
+ return (!hasEnclosingClass() ||
+ (this.introspectedClass.getDeclaringClass() != null &&
+ Modifier.isStatic(this.introspectedClass.getModifiers())));
+ }
+
+ public boolean hasEnclosingClass() {
+ return (this.introspectedClass.getEnclosingClass() != null);
+ }
+
+ public String getEnclosingClassName() {
+ Class enclosingClass = this.introspectedClass.getEnclosingClass();
+ return (enclosingClass != null ? enclosingClass.getName() : null);
+ }
+
+ public boolean hasSuperClass() {
+ return (this.introspectedClass.getSuperclass() != null);
+ }
+
+ public String getSuperClassName() {
+ Class superClass = this.introspectedClass.getSuperclass();
+ return (superClass != null ? superClass.getName() : null);
+ }
+
+ public String[] getInterfaceNames() {
+ Class[] ifcs = this.introspectedClass.getInterfaces();
+ String[] ifcNames = new String[ifcs.length];
+ for (int i = 0; i < ifcs.length; i++) {
+ ifcNames[i] = ifcs[i].getName();
+ }
+ return ifcNames;
+ }
+
+ public String[] getMemberClassNames() {
+ LinkedHashSet<String> memberClassNames = new LinkedHashSet<String>();
+ for (Class<?> nestedClass : this.introspectedClass.getDeclaredClasses()) {
+ memberClassNames.add(nestedClass.getName());
+ }
+ return memberClassNames.toArray(new String[memberClassNames.size()]);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java
new file mode 100644
index 00000000..8288debe
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2002-2013 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.type;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Map;
+
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.Assert;
+
+/**
+ * {@link MethodMetadata} implementation that uses standard reflection
+ * to introspect a given {@code Method}.
+ *
+ * @author Juergen Hoeller
+ * @author Mark Pollack
+ * @author Chris Beams
+ * @since 3.0
+ */
+public class StandardMethodMetadata implements MethodMetadata {
+
+ private final Method introspectedMethod;
+
+ private final boolean nestedAnnotationsAsMap;
+
+
+ /**
+ * Create a new StandardMethodMetadata wrapper for the given Method.
+ * @param introspectedMethod the Method to introspect
+ */
+ public StandardMethodMetadata(Method introspectedMethod) {
+ this(introspectedMethod, false);
+ }
+
+ /**
+ * Create a new StandardMethodMetadata wrapper for the given Method,
+ * providing the option to return any nested annotations or annotation arrays in the
+ * form of {@link org.springframework.core.annotation.AnnotationAttributes} instead
+ * of actual {@link java.lang.annotation.Annotation} instances.
+ * @param introspectedMethod the Method to introspect
+ * @param nestedAnnotationsAsMap return nested annotations and annotation arrays as
+ * {@link org.springframework.core.annotation.AnnotationAttributes} for compatibility
+ * with ASM-based {@link AnnotationMetadata} implementations
+ * @since 3.1.1
+ */
+ public StandardMethodMetadata(Method introspectedMethod, boolean nestedAnnotationsAsMap) {
+ Assert.notNull(introspectedMethod, "Method must not be null");
+ this.introspectedMethod = introspectedMethod;
+ this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
+ }
+
+ /**
+ * Return the underlying Method.
+ */
+ public final Method getIntrospectedMethod() {
+ return this.introspectedMethod;
+ }
+
+
+ public String getMethodName() {
+ return this.introspectedMethod.getName();
+ }
+
+ public String getDeclaringClassName() {
+ return this.introspectedMethod.getDeclaringClass().getName();
+ }
+
+ public boolean isStatic() {
+ return Modifier.isStatic(this.introspectedMethod.getModifiers());
+ }
+
+ public boolean isFinal() {
+ return Modifier.isFinal(this.introspectedMethod.getModifiers());
+ }
+
+ public boolean isOverridable() {
+ return (!isStatic() && !isFinal() && !Modifier.isPrivate(this.introspectedMethod.getModifiers()));
+ }
+
+ public boolean isAnnotated(String annotationType) {
+ Annotation[] anns = this.introspectedMethod.getAnnotations();
+ for (Annotation ann : anns) {
+ if (ann.annotationType().getName().equals(annotationType)) {
+ return true;
+ }
+ for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
+ if (metaAnn.annotationType().getName().equals(annotationType)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public Map<String, Object> getAnnotationAttributes(String annotationType) {
+ Annotation[] anns = this.introspectedMethod.getAnnotations();
+ for (Annotation ann : anns) {
+ if (ann.annotationType().getName().equals(annotationType)) {
+ return AnnotationUtils.getAnnotationAttributes(
+ ann, true, nestedAnnotationsAsMap);
+ }
+ for (Annotation metaAnn : ann.annotationType().getAnnotations()) {
+ if (metaAnn.annotationType().getName().equals(annotationType)) {
+ return AnnotationUtils.getAnnotationAttributes(
+ metaAnn, true, this.nestedAnnotationsAsMap);
+ }
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java
new file mode 100644
index 00000000..4e9bb8fa
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2002-2014 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.type.classreading;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.asm.AnnotationVisitor;
+import org.springframework.asm.SpringAsmInfo;
+import org.springframework.asm.Type;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * @author Chris Beams
+ * @author Juergen Hoeller
+ * @since 3.1.1
+ */
+abstract class AbstractRecursiveAnnotationVisitor extends AnnotationVisitor {
+
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ protected final AnnotationAttributes attributes;
+
+ protected final ClassLoader classLoader;
+
+ public AbstractRecursiveAnnotationVisitor(ClassLoader classLoader, AnnotationAttributes attributes) {
+ super(SpringAsmInfo.ASM_VERSION);
+ this.classLoader = classLoader;
+ this.attributes = attributes;
+ }
+
+ public void visit(String attributeName, Object attributeValue) {
+ this.attributes.put(attributeName, attributeValue);
+ }
+
+ public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) {
+ String annotationType = Type.getType(asmTypeDescriptor).getClassName();
+ AnnotationAttributes nestedAttributes = new AnnotationAttributes();
+ this.attributes.put(attributeName, nestedAttributes);
+ return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader);
+ }
+
+ public AnnotationVisitor visitArray(String attributeName) {
+ return new RecursiveAnnotationArrayVisitor(attributeName, this.attributes, this.classLoader);
+ }
+
+ public void visitEnum(String attributeName, String asmTypeDescriptor, String attributeValue) {
+ Object newValue = getEnumValue(asmTypeDescriptor, attributeValue);
+ visit(attributeName, newValue);
+ }
+
+ protected Object getEnumValue(String asmTypeDescriptor, String attributeValue) {
+ Object valueToUse = attributeValue;
+ try {
+ Class<?> enumType = this.classLoader.loadClass(Type.getType(asmTypeDescriptor).getClassName());
+ Field enumConstant = ReflectionUtils.findField(enumType, attributeValue);
+ if (enumConstant != null) {
+ valueToUse = enumConstant.get(null);
+ }
+ }
+ catch (ClassNotFoundException ex) {
+ logger.debug("Failed to classload enum type while reading annotation metadata", ex);
+ }
+ catch (IllegalAccessException ex) {
+ logger.warn("Could not access enum value while reading annotation metadata", ex);
+ }
+ return valueToUse;
+ }
+}
+
+
+/**
+ * @author Chris Beams
+ * @author Juergen Hoeller
+ * @since 3.1.1
+ */
+final class RecursiveAnnotationArrayVisitor extends AbstractRecursiveAnnotationVisitor {
+
+ private final String attributeName;
+
+ private final List<AnnotationAttributes> allNestedAttributes = new ArrayList<AnnotationAttributes>();
+
+ public RecursiveAnnotationArrayVisitor(
+ String attributeName, AnnotationAttributes attributes, ClassLoader classLoader) {
+ super(classLoader, attributes);
+ this.attributeName = attributeName;
+ }
+
+ @Override
+ public void visit(String attributeName, Object attributeValue) {
+ Object newValue = attributeValue;
+ Object existingValue = this.attributes.get(this.attributeName);
+ if (existingValue != null) {
+ newValue = ObjectUtils.addObjectToArray((Object[]) existingValue, newValue);
+ }
+ else {
+ Class<?> arrayClass = newValue.getClass();
+ if (Enum.class.isAssignableFrom(arrayClass)) {
+ while (arrayClass.getSuperclass() != null && !arrayClass.isEnum()) {
+ arrayClass = arrayClass.getSuperclass();
+ }
+ }
+ Object[] newArray = (Object[]) Array.newInstance(arrayClass, 1);
+ newArray[0] = newValue;
+ newValue = newArray;
+ }
+ this.attributes.put(this.attributeName, newValue);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) {
+ String annotationType = Type.getType(asmTypeDescriptor).getClassName();
+ AnnotationAttributes nestedAttributes = new AnnotationAttributes();
+ this.allNestedAttributes.add(nestedAttributes);
+ return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader);
+ }
+
+ public void visitEnd() {
+ if (!this.allNestedAttributes.isEmpty()) {
+ this.attributes.put(this.attributeName,
+ this.allNestedAttributes.toArray(new AnnotationAttributes[this.allNestedAttributes.size()]));
+ }
+ }
+}
+
+
+/**
+ * @author Chris Beams
+ * @author Juergen Hoeller
+ * @since 3.1.1
+ */
+class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVisitor {
+
+ private final String annotationType;
+
+ public RecursiveAnnotationAttributesVisitor(String annotationType, AnnotationAttributes attributes,
+ ClassLoader classLoader) {
+ super(classLoader, attributes);
+ this.annotationType = annotationType;
+ }
+
+ public final void visitEnd() {
+ try {
+ Class<?> annotationClass = this.classLoader.loadClass(this.annotationType);
+ doVisitEnd(annotationClass);
+ }
+ catch (ClassNotFoundException ex) {
+ logger.debug("Failed to class-load type while reading annotation metadata. " +
+ "This is a non-fatal error, but certain annotation metadata may be unavailable.", ex);
+ }
+ }
+
+ protected void doVisitEnd(Class<?> annotationClass) {
+ registerDefaultValues(annotationClass);
+ }
+
+ private void registerDefaultValues(Class<?> annotationClass) {
+ // Only do further scanning for public annotations; we'd run into
+ // IllegalAccessExceptions otherwise, and we don't want to mess with
+ // accessibility in a SecurityManager environment.
+ if (Modifier.isPublic(annotationClass.getModifiers())) {
+ // Check declared default values of attributes in the annotation type.
+ Method[] annotationAttributes = annotationClass.getMethods();
+ for (Method annotationAttribute : annotationAttributes) {
+ String attributeName = annotationAttribute.getName();
+ Object defaultValue = annotationAttribute.getDefaultValue();
+ if (defaultValue != null && !this.attributes.containsKey(attributeName)) {
+ if (defaultValue instanceof Annotation) {
+ defaultValue = AnnotationAttributes.fromMap(AnnotationUtils.getAnnotationAttributes(
+ (Annotation) defaultValue, false, true));
+ }
+ else if (defaultValue instanceof Annotation[]) {
+ Annotation[] realAnnotations = (Annotation[]) defaultValue;
+ AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length];
+ for (int i = 0; i < realAnnotations.length; i++) {
+ mappedAnnotations[i] = AnnotationAttributes.fromMap(AnnotationUtils.getAnnotationAttributes(
+ realAnnotations[i], false, true));
+ }
+ defaultValue = mappedAnnotations;
+ }
+ this.attributes.put(attributeName, defaultValue);
+ }
+ }
+ }
+ }
+}
+
+
+/**
+ * ASM visitor which looks for the annotations defined on a class or method, including
+ * tracking meta-annotations.
+ *
+ * <p>As of Spring 3.1.1, this visitor is fully recursive, taking into account any nested
+ * annotations or nested annotation arrays. These annotations are in turn read into
+ * {@link AnnotationAttributes} map structures.
+ *
+ * @author Juergen Hoeller
+ * @author Chris Beams
+ * @since 3.0
+ */
+final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor {
+
+ private final String annotationType;
+
+ private final Map<String, AnnotationAttributes> attributesMap;
+
+ private final Map<String, Set<String>> metaAnnotationMap;
+
+ public AnnotationAttributesReadingVisitor(
+ String annotationType, Map<String, AnnotationAttributes> attributesMap,
+ Map<String, Set<String>> metaAnnotationMap, ClassLoader classLoader) {
+
+ super(annotationType, new AnnotationAttributes(), classLoader);
+ this.annotationType = annotationType;
+ this.attributesMap = attributesMap;
+ this.metaAnnotationMap = metaAnnotationMap;
+ }
+
+ @Override
+ public void doVisitEnd(Class<?> annotationClass) {
+ super.doVisitEnd(annotationClass);
+ this.attributesMap.put(this.annotationType, this.attributes);
+ registerMetaAnnotations(annotationClass);
+ }
+
+ private void registerMetaAnnotations(Class<?> annotationClass) {
+ // Register annotations that the annotation type is annotated with.
+ Set<String> metaAnnotationTypeNames = new LinkedHashSet<String>();
+ for (Annotation metaAnnotation : annotationClass.getAnnotations()) {
+ metaAnnotationTypeNames.add(metaAnnotation.annotationType().getName());
+ // Only do further scanning for public annotations; we'd run into IllegalAccessExceptions
+ // otherwise, and don't want to mess with accessibility in a SecurityManager environment.
+ if (Modifier.isPublic(metaAnnotation.annotationType().getModifiers())) {
+ if (!this.attributesMap.containsKey(metaAnnotation.annotationType().getName())) {
+ this.attributesMap.put(metaAnnotation.annotationType().getName(),
+ AnnotationUtils.getAnnotationAttributes(metaAnnotation, true, true));
+ }
+ for (Annotation metaMetaAnnotation : metaAnnotation.annotationType().getAnnotations()) {
+ metaAnnotationTypeNames.add(metaMetaAnnotation.annotationType().getName());
+ }
+ }
+ }
+ if (this.metaAnnotationMap != null) {
+ this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java
new file mode 100644
index 00000000..e1739846
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2002-2014 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.type.classreading;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.asm.AnnotationVisitor;
+import org.springframework.asm.MethodVisitor;
+import org.springframework.asm.Opcodes;
+import org.springframework.asm.Type;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.core.type.MethodMetadata;
+
+/**
+ * ASM class visitor which looks for the class name and implemented types as
+ * well as for the annotations defined on the class, exposing them through
+ * the {@link org.springframework.core.type.AnnotationMetadata} interface.
+ *
+ * @author Juergen Hoeller
+ * @author Mark Fisher
+ * @author Costin Leau
+ * @since 2.5
+ */
+final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata {
+
+ private final ClassLoader classLoader;
+
+ private final Set<String> annotationSet = new LinkedHashSet<String>(4);
+
+ private final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<String, Set<String>>(4);
+
+ private final Map<String, AnnotationAttributes> attributeMap = new LinkedHashMap<String, AnnotationAttributes>(4);
+
+ private final Set<MethodMetadata> methodMetadataSet = new LinkedHashSet<MethodMetadata>(4);
+
+
+ public AnnotationMetadataReadingVisitor(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ // Skip bridge methods - we're only interested in original annotation-defining user methods.
+ // On JDK 8, we'd otherwise run into double detection of the same annotated method...
+ if ((access & Opcodes.ACC_BRIDGE) != 0) {
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+ return new MethodMetadataReadingVisitor(name, access, getClassName(), this.classLoader, this.methodMetadataSet);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(final String desc, boolean visible) {
+ String className = Type.getType(desc).getClassName();
+ this.annotationSet.add(className);
+ return new AnnotationAttributesReadingVisitor(className, this.attributeMap, this.metaAnnotationMap, this.classLoader);
+ }
+
+
+ public Set<String> getAnnotationTypes() {
+ return this.annotationSet;
+ }
+
+ public Set<String> getMetaAnnotationTypes(String annotationType) {
+ return this.metaAnnotationMap.get(annotationType);
+ }
+
+ public boolean hasAnnotation(String annotationType) {
+ return this.annotationSet.contains(annotationType);
+ }
+
+ public boolean hasMetaAnnotation(String metaAnnotationType) {
+ Collection<Set<String>> allMetaTypes = this.metaAnnotationMap.values();
+ for (Set<String> metaTypes : allMetaTypes) {
+ if (metaTypes.contains(metaAnnotationType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean isAnnotated(String annotationType) {
+ return this.attributeMap.containsKey(annotationType);
+ }
+
+ public AnnotationAttributes getAnnotationAttributes(String annotationType) {
+ return getAnnotationAttributes(annotationType, false);
+ }
+
+ public AnnotationAttributes getAnnotationAttributes(String annotationType, boolean classValuesAsString) {
+ AnnotationAttributes raw = this.attributeMap.get(annotationType);
+ return convertClassValues(raw, classValuesAsString);
+ }
+
+ private AnnotationAttributes convertClassValues(AnnotationAttributes original, boolean classValuesAsString) {
+ if (original == null) {
+ return null;
+ }
+ AnnotationAttributes result = new AnnotationAttributes(original.size());
+ for (Map.Entry<String, Object> entry : original.entrySet()) {
+ try {
+ Object value = entry.getValue();
+ if (value instanceof AnnotationAttributes) {
+ value = convertClassValues((AnnotationAttributes) value, classValuesAsString);
+ }
+ else if (value instanceof AnnotationAttributes[]) {
+ AnnotationAttributes[] values = (AnnotationAttributes[])value;
+ for (int i = 0; i < values.length; i++) {
+ values[i] = convertClassValues(values[i], classValuesAsString);
+ }
+ }
+ else if (value instanceof Type) {
+ value = (classValuesAsString ? ((Type) value).getClassName() :
+ this.classLoader.loadClass(((Type) value).getClassName()));
+ }
+ else if (value instanceof Type[]) {
+ Type[] array = (Type[]) value;
+ Object[] convArray = (classValuesAsString ? new String[array.length] : new Class[array.length]);
+ for (int i = 0; i < array.length; i++) {
+ convArray[i] = (classValuesAsString ? array[i].getClassName() :
+ this.classLoader.loadClass(array[i].getClassName()));
+ }
+ value = convArray;
+ }
+ else if (classValuesAsString) {
+ if (value instanceof Class) {
+ value = ((Class<?>) value).getName();
+ }
+ else if (value instanceof Class[]) {
+ Class<?>[] clazzArray = (Class[]) value;
+ String[] newValue = new String[clazzArray.length];
+ for (int i = 0; i < clazzArray.length; i++) {
+ newValue[i] = clazzArray[i].getName();
+ }
+ value = newValue;
+ }
+ }
+ result.put(entry.getKey(), value);
+ }
+ catch (Exception ex) {
+ // Class not found - can't resolve class reference in annotation attribute.
+ }
+ }
+ return result;
+ }
+
+ public boolean hasAnnotatedMethods(String annotationType) {
+ for (MethodMetadata methodMetadata : this.methodMetadataSet) {
+ if (methodMetadata.isAnnotated(annotationType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public Set<MethodMetadata> getAnnotatedMethods(String annotationType) {
+ Set<MethodMetadata> annotatedMethods = new LinkedHashSet<MethodMetadata>(4);
+ for (MethodMetadata methodMetadata : this.methodMetadataSet) {
+ if (methodMetadata.isAnnotated(annotationType)) {
+ annotatedMethods.add(methodMetadata);
+ }
+ }
+ return annotatedMethods;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java b/spring-core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java
new file mode 100644
index 00000000..ef8201d6
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/CachingMetadataReaderFactory.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2002-2012 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.type.classreading;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+
+/**
+ * Caching implementation of the {@link MetadataReaderFactory} interface,
+ * caching {@link MetadataReader} per Spring {@link Resource} handle
+ * (i.e. per ".class" file).
+ *
+ * @author Juergen Hoeller
+ * @author Costin Leau
+ * @since 2.5
+ */
+public class CachingMetadataReaderFactory extends SimpleMetadataReaderFactory {
+
+ /** Default maximum number of entries for the MetadataReader cache: 256 */
+ public static final int DEFAULT_CACHE_LIMIT = 256;
+
+
+ private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;
+
+ @SuppressWarnings("serial")
+ private final Map<Resource, MetadataReader> metadataReaderCache =
+ new LinkedHashMap<Resource, MetadataReader>(DEFAULT_CACHE_LIMIT, 0.75f, true) {
+ @Override
+ protected boolean removeEldestEntry(Map.Entry<Resource, MetadataReader> eldest) {
+ return size() > getCacheLimit();
+ }
+ };
+
+
+ /**
+ * Create a new CachingMetadataReaderFactory for the default class loader.
+ */
+ public CachingMetadataReaderFactory() {
+ super();
+ }
+
+ /**
+ * Create a new CachingMetadataReaderFactory for the given resource loader.
+ * @param resourceLoader the Spring ResourceLoader to use
+ * (also determines the ClassLoader to use)
+ */
+ public CachingMetadataReaderFactory(ResourceLoader resourceLoader) {
+ super(resourceLoader);
+ }
+
+ /**
+ * Create a new CachingMetadataReaderFactory for the given class loader.
+ * @param classLoader the ClassLoader to use
+ */
+ public CachingMetadataReaderFactory(ClassLoader classLoader) {
+ super(classLoader);
+ }
+
+
+ /**
+ * Specify the maximum number of entries for the MetadataReader cache.
+ * Default is 256.
+ */
+ public void setCacheLimit(int cacheLimit) {
+ this.cacheLimit = cacheLimit;
+ }
+
+ /**
+ * Return the maximum number of entries for the MetadataReader cache.
+ */
+ public int getCacheLimit() {
+ return this.cacheLimit;
+ }
+
+
+ @Override
+ public MetadataReader getMetadataReader(Resource resource) throws IOException {
+ if (getCacheLimit() <= 0) {
+ return super.getMetadataReader(resource);
+ }
+ synchronized (this.metadataReaderCache) {
+ MetadataReader metadataReader = this.metadataReaderCache.get(resource);
+ if (metadataReader == null) {
+ metadataReader = super.getMetadataReader(resource);
+ this.metadataReaderCache.put(resource, metadataReader);
+ }
+ return metadataReader;
+ }
+ }
+
+ /**
+ * Clear the entire MetadataReader cache, removing all cached class metadata.
+ */
+ public void clearCache() {
+ synchronized (this.metadataReaderCache) {
+ this.metadataReaderCache.clear();
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java
new file mode 100644
index 00000000..45785bc8
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2002-2012 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.type.classreading;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.springframework.asm.AnnotationVisitor;
+import org.springframework.asm.Attribute;
+import org.springframework.asm.ClassVisitor;
+import org.springframework.asm.FieldVisitor;
+import org.springframework.asm.MethodVisitor;
+import org.springframework.asm.Opcodes;
+import org.springframework.asm.SpringAsmInfo;
+import org.springframework.core.type.ClassMetadata;
+import org.springframework.util.ClassUtils;
+
+/**
+ * ASM class visitor which looks only for the class name and implemented types,
+ * exposing them through the {@link org.springframework.core.type.ClassMetadata}
+ * interface.
+ *
+ * @author Rod Johnson
+ * @author Costin Leau
+ * @author Mark Fisher
+ * @author Ramnivas Laddad
+ * @author Chris Beams
+ * @since 2.5
+ */
+class ClassMetadataReadingVisitor extends ClassVisitor implements ClassMetadata {
+
+ private String className;
+
+ private boolean isInterface;
+
+ private boolean isAbstract;
+
+ private boolean isFinal;
+
+ private String enclosingClassName;
+
+ private boolean independentInnerClass;
+
+ private String superClassName;
+
+ private String[] interfaces;
+
+ private Set<String> memberClassNames = new LinkedHashSet<String>();
+
+
+ public ClassMetadataReadingVisitor() {
+ super(SpringAsmInfo.ASM_VERSION);
+ }
+
+
+ public void visit(int version, int access, String name, String signature, String supername, String[] interfaces) {
+ this.className = ClassUtils.convertResourcePathToClassName(name);
+ this.isInterface = ((access & Opcodes.ACC_INTERFACE) != 0);
+ this.isAbstract = ((access & Opcodes.ACC_ABSTRACT) != 0);
+ this.isFinal = ((access & Opcodes.ACC_FINAL) != 0);
+ if (supername != null) {
+ this.superClassName = ClassUtils.convertResourcePathToClassName(supername);
+ }
+ this.interfaces = new String[interfaces.length];
+ for (int i = 0; i < interfaces.length; i++) {
+ this.interfaces[i] = ClassUtils.convertResourcePathToClassName(interfaces[i]);
+ }
+ }
+
+ public void visitOuterClass(String owner, String name, String desc) {
+ this.enclosingClassName = ClassUtils.convertResourcePathToClassName(owner);
+ }
+
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ if (outerName != null) {
+ String fqName = ClassUtils.convertResourcePathToClassName(name);
+ String fqOuterName = ClassUtils.convertResourcePathToClassName(outerName);
+ if (this.className.equals(fqName)) {
+ this.enclosingClassName = fqOuterName;
+ this.independentInnerClass = ((access & Opcodes.ACC_STATIC) != 0);
+ }
+ else if (this.className.equals(fqOuterName)) {
+ this.memberClassNames.add(fqName);
+ }
+ }
+ }
+
+ public void visitSource(String source, String debug) {
+ // no-op
+ }
+
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ // no-op
+ return new EmptyAnnotationVisitor();
+ }
+
+ public void visitAttribute(Attribute attr) {
+ // no-op
+ }
+
+ public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
+ // no-op
+ return new EmptyFieldVisitor();
+ }
+
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ // no-op
+ return new EmptyMethodVisitor();
+ }
+
+ public void visitEnd() {
+ // no-op
+ }
+
+
+ public String getClassName() {
+ return this.className;
+ }
+
+ public boolean isInterface() {
+ return this.isInterface;
+ }
+
+ public boolean isAbstract() {
+ return this.isAbstract;
+ }
+
+ public boolean isConcrete() {
+ return !(this.isInterface || this.isAbstract);
+ }
+
+ public boolean isFinal() {
+ return this.isFinal;
+ }
+
+ public boolean isIndependent() {
+ return (this.enclosingClassName == null || this.independentInnerClass);
+ }
+
+ public boolean hasEnclosingClass() {
+ return (this.enclosingClassName != null);
+ }
+
+ public String getEnclosingClassName() {
+ return this.enclosingClassName;
+ }
+
+ public boolean hasSuperClass() {
+ return (this.superClassName != null);
+ }
+
+ public String getSuperClassName() {
+ return this.superClassName;
+ }
+
+ public String[] getInterfaceNames() {
+ return this.interfaces;
+ }
+
+ public String[] getMemberClassNames() {
+ return this.memberClassNames.toArray(new String[this.memberClassNames.size()]);
+ }
+
+}
+
+
+class EmptyAnnotationVisitor extends AnnotationVisitor {
+
+ public EmptyAnnotationVisitor() {
+ super(SpringAsmInfo.ASM_VERSION);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String desc) {
+ return this;
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ return this;
+ }
+}
+
+
+class EmptyMethodVisitor extends MethodVisitor {
+
+ public EmptyMethodVisitor() {
+ super(SpringAsmInfo.ASM_VERSION);
+ }
+}
+
+
+class EmptyFieldVisitor extends FieldVisitor {
+
+ public EmptyFieldVisitor() {
+ super(SpringAsmInfo.ASM_VERSION);
+ }
+
+} \ No newline at end of file
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReader.java b/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReader.java
new file mode 100644
index 00000000..99b67d4d
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReader.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.type.classreading;
+
+import org.springframework.core.io.Resource;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.core.type.ClassMetadata;
+
+/**
+ * Simple facade for accessing class metadata,
+ * as read by an ASM {@link org.springframework.asm.ClassReader}.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+public interface MetadataReader {
+
+ /**
+ * Return the resource reference for the class file.
+ */
+ Resource getResource();
+
+ /**
+ * Read basic class metadata for the underlying class.
+ */
+ ClassMetadata getClassMetadata();
+
+ /**
+ * Read full annotation metadata for the underlying class,
+ * including metadata for annotated methods.
+ */
+ AnnotationMetadata getAnnotationMetadata();
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java b/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java
new file mode 100644
index 00000000..e2e010c9
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/MetadataReaderFactory.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2002-2012 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.type.classreading;
+
+import java.io.IOException;
+
+import org.springframework.core.io.Resource;
+
+/**
+ * Factory interface for {@link MetadataReader} instances.
+ * Allows for caching a MetadataReader per original resource.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see SimpleMetadataReaderFactory
+ * @see CachingMetadataReaderFactory
+ */
+public interface MetadataReaderFactory {
+
+ /**
+ * Obtain a MetadataReader for the given class name.
+ * @param className the class name (to be resolved to a ".class" file)
+ * @return a holder for the ClassReader instance (never {@code null})
+ * @throws IOException in case of I/O failure
+ */
+ MetadataReader getMetadataReader(String className) throws IOException;
+
+ /**
+ * Obtain a MetadataReader for the given resource.
+ * @param resource the resource (pointing to a ".class" file)
+ * @return a holder for the ClassReader instance (never {@code null})
+ * @throws IOException in case of I/O failure
+ */
+ MetadataReader getMetadataReader(Resource resource) throws IOException;
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java
new file mode 100644
index 00000000..dde3790c
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2002-2014 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.type.classreading;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.asm.AnnotationVisitor;
+import org.springframework.asm.MethodVisitor;
+import org.springframework.asm.Opcodes;
+import org.springframework.asm.SpringAsmInfo;
+import org.springframework.asm.Type;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.type.MethodMetadata;
+
+/**
+ * ASM method visitor which looks for the annotations defined on the method,
+ * exposing them through the {@link org.springframework.core.type.MethodMetadata}
+ * interface.
+ *
+ * @author Juergen Hoeller
+ * @author Mark Pollack
+ * @author Costin Leau
+ * @author Chris Beams
+ * @since 3.0
+ */
+final class MethodMetadataReadingVisitor extends MethodVisitor implements MethodMetadata {
+
+ private final String name;
+
+ private final int access;
+
+ private final String declaringClassName;
+
+ private final ClassLoader classLoader;
+
+ private final Set<MethodMetadata> methodMetadataSet;
+
+ private final Map<String, AnnotationAttributes> attributeMap = new LinkedHashMap<String, AnnotationAttributes>(2);
+
+
+ public MethodMetadataReadingVisitor(String name, int access, String declaringClassName,
+ ClassLoader classLoader, Set<MethodMetadata> methodMetadataSet) {
+
+ super(SpringAsmInfo.ASM_VERSION);
+ this.name = name;
+ this.access = access;
+ this.declaringClassName = declaringClassName;
+ this.classLoader = classLoader;
+ this.methodMetadataSet = methodMetadataSet;
+ }
+
+
+ @Override
+ public AnnotationVisitor visitAnnotation(final String desc, boolean visible) {
+ String className = Type.getType(desc).getClassName();
+ this.methodMetadataSet.add(this);
+ return new AnnotationAttributesReadingVisitor(className, this.attributeMap, null, this.classLoader);
+ }
+
+ public String getMethodName() {
+ return this.name;
+ }
+
+ public boolean isStatic() {
+ return ((this.access & Opcodes.ACC_STATIC) != 0);
+ }
+
+ public boolean isFinal() {
+ return ((this.access & Opcodes.ACC_FINAL) != 0);
+ }
+
+ public boolean isOverridable() {
+ return (!isStatic() && !isFinal() && ((this.access & Opcodes.ACC_PRIVATE) == 0));
+ }
+
+ public boolean isAnnotated(String annotationType) {
+ return this.attributeMap.containsKey(annotationType);
+ }
+
+ public AnnotationAttributes getAnnotationAttributes(String annotationType) {
+ return this.attributeMap.get(annotationType);
+ }
+
+ public String getDeclaringClassName() {
+ return this.declaringClassName;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java
new file mode 100644
index 00000000..36c495e5
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2002-2013 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.type.classreading;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.springframework.asm.ClassReader;
+import org.springframework.core.NestedIOException;
+import org.springframework.core.io.Resource;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.core.type.ClassMetadata;
+
+/**
+ * {@link MetadataReader} implementation based on an ASM
+ * {@link org.springframework.asm.ClassReader}.
+ *
+ * <p>Package-visible in order to allow for repackaging the ASM library
+ * without effect on users of the {@code core.type} package.
+ *
+ * @author Juergen Hoeller
+ * @author Costin Leau
+ * @since 2.5
+ */
+final class SimpleMetadataReader implements MetadataReader {
+
+ private final Resource resource;
+
+ private final ClassMetadata classMetadata;
+
+ private final AnnotationMetadata annotationMetadata;
+
+
+ SimpleMetadataReader(Resource resource, ClassLoader classLoader) throws IOException {
+ InputStream is = new BufferedInputStream(resource.getInputStream());
+ ClassReader classReader;
+ try {
+ classReader = new ClassReader(is);
+ }
+ catch (IllegalArgumentException ex) {
+ throw new NestedIOException("ASM ClassReader failed to parse class file - " +
+ "probably due to a new Java class file version that isn't supported yet: " + resource, ex);
+ }
+ finally {
+ is.close();
+ }
+
+ AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(classLoader);
+ classReader.accept(visitor, ClassReader.SKIP_DEBUG);
+
+ this.annotationMetadata = visitor;
+ // (since AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor)
+ this.classMetadata = visitor;
+ this.resource = resource;
+ }
+
+
+ public Resource getResource() {
+ return this.resource;
+ }
+
+ public ClassMetadata getClassMetadata() {
+ return this.classMetadata;
+ }
+
+ public AnnotationMetadata getAnnotationMetadata() {
+ return this.annotationMetadata;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java
new file mode 100644
index 00000000..9e1c736f
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2002-2014 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.type.classreading;
+
+import java.io.IOException;
+
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Simple implementation of the {@link MetadataReaderFactory} interface,
+ * creating a new ASM {@link org.springframework.asm.ClassReader} for every request.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+public class SimpleMetadataReaderFactory implements MetadataReaderFactory {
+
+ private final ResourceLoader resourceLoader;
+
+
+ /**
+ * Create a new SimpleMetadataReaderFactory for the default class loader.
+ */
+ public SimpleMetadataReaderFactory() {
+ this.resourceLoader = new DefaultResourceLoader();
+ }
+
+ /**
+ * Create a new SimpleMetadataReaderFactory for the given resource loader.
+ * @param resourceLoader the Spring ResourceLoader to use
+ * (also determines the ClassLoader to use)
+ */
+ public SimpleMetadataReaderFactory(ResourceLoader resourceLoader) {
+ this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader());
+ }
+
+ /**
+ * Create a new SimpleMetadataReaderFactory for the given class loader.
+ * @param classLoader the ClassLoader to use
+ */
+ public SimpleMetadataReaderFactory(ClassLoader classLoader) {
+ this.resourceLoader =
+ (classLoader != null ? new DefaultResourceLoader(classLoader) : new DefaultResourceLoader());
+ }
+
+
+ /**
+ * Return the ResourceLoader that this MetadataReaderFactory has been
+ * constructed with.
+ */
+ public final ResourceLoader getResourceLoader() {
+ return this.resourceLoader;
+ }
+
+
+ public MetadataReader getMetadataReader(String className) throws IOException {
+ String resourcePath = ResourceLoader.CLASSPATH_URL_PREFIX +
+ ClassUtils.convertClassNameToResourcePath(className) + ClassUtils.CLASS_FILE_SUFFIX;
+ Resource resource = this.resourceLoader.getResource(resourcePath);
+ if (!resource.exists()) {
+ // Maybe an inner class name using the dot name syntax? Need to use the dollar syntax here...
+ // ClassUtils.forName has an equivalent check for resolution into Class references later on.
+ int lastDotIndex = className.lastIndexOf('.');
+ if (lastDotIndex != -1) {
+ String innerClassName =
+ className.substring(0, lastDotIndex) + '$' + className.substring(lastDotIndex + 1);
+ String innerClassResourcePath = ResourceLoader.CLASSPATH_URL_PREFIX +
+ ClassUtils.convertClassNameToResourcePath(innerClassName) + ClassUtils.CLASS_FILE_SUFFIX;
+ Resource innerClassResource = this.resourceLoader.getResource(innerClassResourcePath);
+ if (innerClassResource.exists()) {
+ resource = innerClassResource;
+ }
+ }
+ }
+ return getMetadataReader(resource);
+ }
+
+ public MetadataReader getMetadataReader(Resource resource) throws IOException {
+ return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader());
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/package-info.java b/spring-core/src/main/java/org/springframework/core/type/classreading/package-info.java
new file mode 100644
index 00000000..62b269f4
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/package-info.java
@@ -0,0 +1,8 @@
+
+/**
+ *
+ * Support classes for reading annotation and class-level metadata.
+ *
+ */
+package org.springframework.core.type.classreading;
+
diff --git a/spring-core/src/main/java/org/springframework/core/type/filter/AbstractClassTestingTypeFilter.java b/spring-core/src/main/java/org/springframework/core/type/filter/AbstractClassTestingTypeFilter.java
new file mode 100644
index 00000000..e63571f6
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/filter/AbstractClassTestingTypeFilter.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2002-2014 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.type.filter;
+
+import java.io.IOException;
+
+import org.springframework.core.type.ClassMetadata;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+
+/**
+ * Type filter that exposes a
+ * {@link org.springframework.core.type.ClassMetadata} object
+ * to subclasses, for class testing purposes.
+ *
+ * @author Rod Johnson
+ * @author Costin Leau
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see #match(org.springframework.core.type.ClassMetadata)
+ */
+public abstract class AbstractClassTestingTypeFilter implements TypeFilter {
+
+ public final boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
+ throws IOException {
+
+ return match(metadataReader.getClassMetadata());
+ }
+
+ /**
+ * Determine a match based on the given ClassMetadata object.
+ * @param metadata the ClassMetadata object
+ * @return whether this filter matches on the specified type
+ */
+ protected abstract boolean match(ClassMetadata metadata);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java b/spring-core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java
new file mode 100644
index 00000000..6ee0c875
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2002-2014 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.type.filter;
+
+import java.io.IOException;
+
+import org.springframework.core.type.ClassMetadata;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+
+/**
+ * Type filter that is aware of traversing over hierarchy.
+ *
+ * <p>This filter is useful when matching needs to be made based on potentially the
+ * whole class/interface hierarchy. The algorithm employed uses a succeed-fast
+ * strategy: if at any time a match is declared, no further processing is
+ * carried out.
+ *
+ * @author Ramnivas Laddad
+ * @author Mark Fisher
+ * @since 2.5
+ */
+public abstract class AbstractTypeHierarchyTraversingFilter implements TypeFilter {
+
+ private final boolean considerInherited;
+
+ private final boolean considerInterfaces;
+
+
+ protected AbstractTypeHierarchyTraversingFilter(boolean considerInherited, boolean considerInterfaces) {
+ this.considerInherited = considerInherited;
+ this.considerInterfaces = considerInterfaces;
+ }
+
+
+ public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
+ throws IOException {
+
+ // This method optimizes avoiding unnecessary creation of ClassReaders
+ // as well as visiting over those readers.
+ if (matchSelf(metadataReader)) {
+ return true;
+ }
+ ClassMetadata metadata = metadataReader.getClassMetadata();
+ if (matchClassName(metadata.getClassName())) {
+ return true;
+ }
+
+ if (!this.considerInherited) {
+ return false;
+ }
+ if (metadata.hasSuperClass()) {
+ // Optimization to avoid creating ClassReader for super class.
+ Boolean superClassMatch = matchSuperClass(metadata.getSuperClassName());
+ if (superClassMatch != null) {
+ if (superClassMatch.booleanValue()) {
+ return true;
+ }
+ }
+ else {
+ // Need to read super class to determine a match...
+ if (match(metadata.getSuperClassName(), metadataReaderFactory)) {
+ return true;
+ }
+ }
+ }
+
+ if (!this.considerInterfaces) {
+ return false;
+ }
+ for (String ifc : metadata.getInterfaceNames()) {
+ // Optimization to avoid creating ClassReader for super class
+ Boolean interfaceMatch = matchInterface(ifc);
+ if (interfaceMatch != null) {
+ if (interfaceMatch.booleanValue()) {
+ return true;
+ }
+ }
+ else {
+ // Need to read interface to determine a match...
+ if (match(ifc, metadataReaderFactory)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private boolean match(String className, MetadataReaderFactory metadataReaderFactory) throws IOException {
+ return match(metadataReaderFactory.getMetadataReader(className), metadataReaderFactory);
+ }
+
+ /**
+ * Override this to match self characteristics alone. Typically,
+ * the implementation will use a visitor to extract information
+ * to perform matching.
+ */
+ protected boolean matchSelf(MetadataReader metadataReader) {
+ return false;
+ }
+
+ /**
+ * Override this to match on type name.
+ */
+ protected boolean matchClassName(String className) {
+ return false;
+ }
+
+ /**
+ * Override this to match on super type name.
+ */
+ protected Boolean matchSuperClass(String superClassName) {
+ return null;
+ }
+
+ /**
+ * Override this to match on interface type name.
+ */
+ protected Boolean matchInterface(String interfaceName) {
+ return null;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java b/spring-core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java
new file mode 100644
index 00000000..7d2d674c
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2002-2014 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.type.filter;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Inherited;
+
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.util.ClassUtils;
+
+/**
+ * A simple filter which matches classes with a given annotation,
+ * checking inherited annotations as well.
+ *
+ * <p>The matching logic mirrors that of {@code Class.isAnnotationPresent()}.
+ *
+ * @author Mark Fisher
+ * @author Ramnivas Laddad
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+public class AnnotationTypeFilter extends AbstractTypeHierarchyTraversingFilter {
+
+ private final Class<? extends Annotation> annotationType;
+
+ private final boolean considerMetaAnnotations;
+
+
+ /**
+ * Create a new AnnotationTypeFilter for the given annotation type.
+ * This filter will also match meta-annotations. To disable the
+ * meta-annotation matching, use the constructor that accepts a
+ * '{@code considerMetaAnnotations}' argument. The filter will
+ * not match interfaces.
+ * @param annotationType the annotation type to match
+ */
+ public AnnotationTypeFilter(Class<? extends Annotation> annotationType) {
+ this(annotationType, true, false);
+ }
+
+ /**
+ * Create a new AnnotationTypeFilter for the given annotation type.
+ * The filter will not match interfaces.
+ * @param annotationType the annotation type to match
+ * @param considerMetaAnnotations whether to also match on meta-annotations
+ */
+ public AnnotationTypeFilter(Class<? extends Annotation> annotationType, boolean considerMetaAnnotations) {
+ this(annotationType, considerMetaAnnotations, false);
+ }
+
+ /**
+ * Create a new {@link AnnotationTypeFilter} for the given annotation type.
+ * @param annotationType the annotation type to match
+ * @param considerMetaAnnotations whether to also match on meta-annotations
+ * @param considerInterfaces whether to also match interfaces
+ */
+ public AnnotationTypeFilter(Class<? extends Annotation> annotationType, boolean considerMetaAnnotations, boolean considerInterfaces) {
+ super(annotationType.isAnnotationPresent(Inherited.class), considerInterfaces);
+ this.annotationType = annotationType;
+ this.considerMetaAnnotations = considerMetaAnnotations;
+ }
+
+
+ @Override
+ protected boolean matchSelf(MetadataReader metadataReader) {
+ AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
+ return metadata.hasAnnotation(this.annotationType.getName()) ||
+ (this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
+ }
+
+ @Override
+ protected Boolean matchSuperClass(String superClassName) {
+ return hasAnnotation(superClassName);
+ }
+
+ @Override
+ protected Boolean matchInterface(String interfaceName) {
+ return hasAnnotation(interfaceName);
+ }
+
+ protected Boolean hasAnnotation(String typeName) {
+ if (Object.class.getName().equals(typeName)) {
+ return false;
+ }
+ else if (typeName.startsWith("java")) {
+ try {
+ Class<?> clazz = ClassUtils.forName(typeName, getClass().getClassLoader());
+ return ((this.considerMetaAnnotations ? AnnotationUtils.getAnnotation(clazz, this.annotationType) :
+ clazz.getAnnotation(this.annotationType)) != null);
+ }
+ catch (Throwable ex) {
+ // Class not regularly loadable - can't determine a match that way.
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/filter/AspectJTypeFilter.java b/spring-core/src/main/java/org/springframework/core/type/filter/AspectJTypeFilter.java
new file mode 100644
index 00000000..0e2a3a5d
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/filter/AspectJTypeFilter.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2002-2014 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.type.filter;
+
+import java.io.IOException;
+
+import org.aspectj.bridge.IMessageHandler;
+import org.aspectj.weaver.ResolvedType;
+import org.aspectj.weaver.World;
+import org.aspectj.weaver.bcel.BcelWorld;
+import org.aspectj.weaver.patterns.Bindings;
+import org.aspectj.weaver.patterns.FormalBinding;
+import org.aspectj.weaver.patterns.IScope;
+import org.aspectj.weaver.patterns.PatternParser;
+import org.aspectj.weaver.patterns.SimpleScope;
+import org.aspectj.weaver.patterns.TypePattern;
+
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+
+/**
+ * Type filter that uses AspectJ type pattern for matching.
+ *
+ * <p>A critical implementation details of this type filter is that it does not
+ * load the class being examined to match with a type pattern.
+ *
+ * @author Ramnivas Laddad
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+public class AspectJTypeFilter implements TypeFilter {
+
+ private final World world;
+
+ private final TypePattern typePattern;
+
+
+ public AspectJTypeFilter(String typePatternExpression, ClassLoader classLoader) {
+ this.world = new BcelWorld(classLoader, IMessageHandler.THROW, null);
+ this.world.setBehaveInJava5Way(true);
+ PatternParser patternParser = new PatternParser(typePatternExpression);
+ TypePattern typePattern = patternParser.parseTypePattern();
+ typePattern.resolve(this.world);
+ IScope scope = new SimpleScope(this.world, new FormalBinding[0]);
+ this.typePattern = typePattern.resolveBindings(scope, Bindings.NONE, false, false);
+ }
+
+
+ public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
+ throws IOException {
+
+ String className = metadataReader.getClassMetadata().getClassName();
+ ResolvedType resolvedType = this.world.resolve(className);
+ return this.typePattern.matchesStatically(resolvedType);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/filter/AssignableTypeFilter.java b/spring-core/src/main/java/org/springframework/core/type/filter/AssignableTypeFilter.java
new file mode 100644
index 00000000..63653046
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/filter/AssignableTypeFilter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2014 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.type.filter;
+
+import org.springframework.util.ClassUtils;
+
+/**
+ * A simple filter which matches classes that are assignable to a given type.
+ *
+ * @author Rod Johnson
+ * @author Mark Fisher
+ * @author Ramnivas Laddad
+ * @since 2.5
+ */
+public class AssignableTypeFilter extends AbstractTypeHierarchyTraversingFilter {
+
+ private final Class<?> targetType;
+
+
+ /**
+ * Create a new AssignableTypeFilter for the given type.
+ * @param targetType the type to match
+ */
+ public AssignableTypeFilter(Class<?> targetType) {
+ super(true, true);
+ this.targetType = targetType;
+ }
+
+
+ @Override
+ protected boolean matchClassName(String className) {
+ return this.targetType.getName().equals(className);
+ }
+
+ @Override
+ protected Boolean matchSuperClass(String superClassName) {
+ return matchTargetType(superClassName);
+ }
+
+ @Override
+ protected Boolean matchInterface(String interfaceName) {
+ return matchTargetType(interfaceName);
+ }
+
+ protected Boolean matchTargetType(String typeName) {
+ if (this.targetType.getName().equals(typeName)) {
+ return true;
+ }
+ else if (Object.class.getName().equals(typeName)) {
+ return false;
+ }
+ else if (typeName.startsWith("java")) {
+ try {
+ Class<?> clazz = ClassUtils.forName(typeName, getClass().getClassLoader());
+ return this.targetType.isAssignableFrom(clazz);
+ }
+ catch (Throwable ex) {
+ // Class not regularly loadable - can't determine a match that way.
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/filter/RegexPatternTypeFilter.java b/spring-core/src/main/java/org/springframework/core/type/filter/RegexPatternTypeFilter.java
new file mode 100644
index 00000000..16605d64
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/filter/RegexPatternTypeFilter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2007 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.type.filter;
+
+import java.util.regex.Pattern;
+
+import org.springframework.core.type.ClassMetadata;
+import org.springframework.util.Assert;
+
+/**
+ * A simple filter for matching a fully-qualified class name with a regex {@link Pattern}.
+ *
+ * @author Mark Fisher
+ * @author Juergen Hoeller
+ * @since 2.5
+ */
+public class RegexPatternTypeFilter extends AbstractClassTestingTypeFilter {
+
+ private final Pattern pattern;
+
+
+ public RegexPatternTypeFilter(Pattern pattern) {
+ Assert.notNull(pattern, "Pattern must not be null");
+ this.pattern = pattern;
+ }
+
+
+ @Override
+ protected boolean match(ClassMetadata metadata) {
+ return this.pattern.matcher(metadata.getClassName()).matches();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/filter/TypeFilter.java b/spring-core/src/main/java/org/springframework/core/type/filter/TypeFilter.java
new file mode 100644
index 00000000..bdcf3352
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/filter/TypeFilter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2007 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.type.filter;
+
+import java.io.IOException;
+
+import org.springframework.core.type.classreading.MetadataReader;
+import org.springframework.core.type.classreading.MetadataReaderFactory;
+
+/**
+ * Base interface for type filters using a
+ * {@link org.springframework.core.type.classreading.MetadataReader}.
+ *
+ * @author Costin Leau
+ * @author Juergen Hoeller
+ * @author Mark Fisher
+ * @since 2.5
+ */
+public interface TypeFilter {
+
+ /**
+ * Determine whether this filter matches for the class described by
+ * the given metadata.
+ * @param metadataReader the metadata reader for the target class
+ * @param metadataReaderFactory a factory for obtaining metadata readers
+ * for other classes (such as superclasses and interfaces)
+ * @return whether this filter matches
+ * @throws IOException in case of I/O failure when reading metadata
+ */
+ boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
+ throws IOException;
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/filter/package-info.java b/spring-core/src/main/java/org/springframework/core/type/filter/package-info.java
new file mode 100644
index 00000000..05c63a26
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/filter/package-info.java
@@ -0,0 +1,8 @@
+
+/**
+ *
+ * Core support package for type filtering (e.g. for classpath scanning).
+ *
+ */
+package org.springframework.core.type.filter;
+
diff --git a/spring-core/src/main/java/org/springframework/core/type/package-info.java b/spring-core/src/main/java/org/springframework/core/type/package-info.java
new file mode 100644
index 00000000..f9508358
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/package-info.java
@@ -0,0 +1,8 @@
+
+/**
+ *
+ * Core support package for type introspection.
+ *
+ */
+package org.springframework.core.type;
+
diff --git a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java
new file mode 100644
index 00000000..a4f74a4b
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java
@@ -0,0 +1,576 @@
+/*
+ * Copyright 2002-2013 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.util;
+
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * PathMatcher implementation for Ant-style path patterns. Examples are provided below.
+ *
+ * <p>Part of this mapping code has been kindly borrowed from <a href="http://ant.apache.org">Apache Ant</a>.
+ *
+ * <p>The mapping matches URLs using the following rules:<br> <ul> <li>? matches one character</li> <li>* matches zero
+ * or more characters</li> <li>** matches zero or more 'directories' in a path</li> </ul>
+ *
+ * <p>Some examples:<br> <ul> <li>{@code com/t?st.jsp} - matches {@code com/test.jsp} but also
+ * {@code com/tast.jsp} or {@code com/txst.jsp}</li> <li>{@code com/*.jsp} - matches all
+ * {@code .jsp} files in the {@code com} directory</li> <li>{@code com/&#42;&#42;/test.jsp} - matches all
+ * {@code test.jsp} files underneath the {@code com} path</li> <li>{@code org/springframework/&#42;&#42;/*.jsp}
+ * - matches all {@code .jsp} files underneath the {@code org/springframework} path</li>
+ * <li>{@code org/&#42;&#42;/servlet/bla.jsp} - matches {@code org/springframework/servlet/bla.jsp} but also
+ * {@code org/springframework/testing/servlet/bla.jsp} and {@code org/servlet/bla.jsp}</li> </ul>
+ *
+ * @author Alef Arendsen
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @author Arjen Poutsma
+ * @author Rossen Stoyanchev
+ * @since 16.07.2003
+ */
+public class AntPathMatcher implements PathMatcher {
+
+ /** Default path separator: "/" */
+ public static final String DEFAULT_PATH_SEPARATOR = "/";
+
+ private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?\\}");
+
+
+ private String pathSeparator = DEFAULT_PATH_SEPARATOR;
+
+ private boolean trimTokens = true;
+
+ private final Map<String, AntPathStringMatcher> stringMatcherCache =
+ new ConcurrentHashMap<String, AntPathStringMatcher>(256);
+
+
+ /**
+ * Set the path separator to use for pattern parsing.
+ * Default is "/", as in Ant.
+ */
+ public void setPathSeparator(String pathSeparator) {
+ this.pathSeparator = (pathSeparator != null ? pathSeparator : DEFAULT_PATH_SEPARATOR);
+ }
+
+ /**
+ * Specify whether to trim tokenized paths and patterns.
+ * Default is {@code true}.
+ */
+ public void setTrimTokens(boolean trimTokens) {
+ this.trimTokens = trimTokens;
+ }
+
+
+ public boolean isPattern(String path) {
+ return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
+ }
+
+ public boolean match(String pattern, String path) {
+ return doMatch(pattern, path, true, null);
+ }
+
+ public boolean matchStart(String pattern, String path) {
+ return doMatch(pattern, path, false, null);
+ }
+
+
+ /**
+ * Actually match the given {@code path} against the given {@code pattern}.
+ * @param pattern the pattern to match against
+ * @param path the path String to test
+ * @param fullMatch whether a full pattern match is required (else a pattern match
+ * as far as the given base path goes is sufficient)
+ * @return {@code true} if the supplied {@code path} matched, {@code false} if it didn't
+ */
+ protected boolean doMatch(String pattern, String path, boolean fullMatch, Map<String, String> uriTemplateVariables) {
+ if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
+ return false;
+ }
+
+ String[] pattDirs = tokenizePath(pattern);
+ String[] pathDirs = tokenizePath(path);
+
+ int pattIdxStart = 0;
+ int pattIdxEnd = pattDirs.length - 1;
+ int pathIdxStart = 0;
+ int pathIdxEnd = pathDirs.length - 1;
+
+ // Match all elements up to the first **
+ while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
+ String pattDir = pattDirs[pattIdxStart];
+ if ("**".equals(pattDir)) {
+ break;
+ }
+ if (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
+ return false;
+ }
+ pattIdxStart++;
+ pathIdxStart++;
+ }
+
+ if (pathIdxStart > pathIdxEnd) {
+ // Path is exhausted, only match if rest of pattern is * or **'s
+ if (pattIdxStart > pattIdxEnd) {
+ return (pattern.endsWith(this.pathSeparator) ? path.endsWith(this.pathSeparator) :
+ !path.endsWith(this.pathSeparator));
+ }
+ if (!fullMatch) {
+ return true;
+ }
+ if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {
+ return true;
+ }
+ for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
+ if (!pattDirs[i].equals("**")) {
+ return false;
+ }
+ }
+ return true;
+ }
+ else if (pattIdxStart > pattIdxEnd) {
+ // String not exhausted, but pattern is. Failure.
+ return false;
+ }
+ else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
+ // Path start definitely matches due to "**" part in pattern.
+ return true;
+ }
+
+ // up to last '**'
+ while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
+ String pattDir = pattDirs[pattIdxEnd];
+ if (pattDir.equals("**")) {
+ break;
+ }
+ if (!matchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {
+ return false;
+ }
+ pattIdxEnd--;
+ pathIdxEnd--;
+ }
+ if (pathIdxStart > pathIdxEnd) {
+ // String is exhausted
+ for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
+ if (!pattDirs[i].equals("**")) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
+ int patIdxTmp = -1;
+ for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
+ if (pattDirs[i].equals("**")) {
+ patIdxTmp = i;
+ break;
+ }
+ }
+ if (patIdxTmp == pattIdxStart + 1) {
+ // '**/**' situation, so skip one
+ pattIdxStart++;
+ continue;
+ }
+ // Find the pattern between padIdxStart & padIdxTmp in str between
+ // strIdxStart & strIdxEnd
+ int patLength = (patIdxTmp - pattIdxStart - 1);
+ int strLength = (pathIdxEnd - pathIdxStart + 1);
+ int foundIdx = -1;
+
+ strLoop:
+ for (int i = 0; i <= strLength - patLength; i++) {
+ for (int j = 0; j < patLength; j++) {
+ String subPat = pattDirs[pattIdxStart + j + 1];
+ String subStr = pathDirs[pathIdxStart + i + j];
+ if (!matchStrings(subPat, subStr, uriTemplateVariables)) {
+ continue strLoop;
+ }
+ }
+ foundIdx = pathIdxStart + i;
+ break;
+ }
+
+ if (foundIdx == -1) {
+ return false;
+ }
+
+ pattIdxStart = patIdxTmp;
+ pathIdxStart = foundIdx + patLength;
+ }
+
+ for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
+ if (!pattDirs[i].equals("**")) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Tokenize the given path String into parts, based on this matcher's settings.
+ * @param path the path to tokenize
+ * @return the tokenized path parts
+ */
+ protected String[] tokenizePath(String path) {
+ return StringUtils.tokenizeToStringArray(path, this.pathSeparator, this.trimTokens, true);
+ }
+
+ /**
+ * Tests whether or not a string matches against a pattern. The pattern may contain two special characters:
+ * <br>'*' means zero or more characters
+ * <br>'?' means one and only one character
+ * @param pattern pattern to match against. Must not be {@code null}.
+ * @param str string which must be matched against the pattern. Must not be {@code null}.
+ * @return {@code true} if the string matches against the pattern, or {@code false} otherwise.
+ */
+ private boolean matchStrings(String pattern, String str, Map<String, String> uriTemplateVariables) {
+ AntPathStringMatcher matcher = this.stringMatcherCache.get(pattern);
+ if (matcher == null) {
+ matcher = new AntPathStringMatcher(pattern);
+ this.stringMatcherCache.put(pattern, matcher);
+ }
+ return matcher.matchStrings(str, uriTemplateVariables);
+ }
+
+ /**
+ * Given a pattern and a full path, determine the pattern-mapped part. <p>For example: <ul>
+ * <li>'{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} -> ''</li>
+ * <li>'{@code /docs/*}' and '{@code /docs/cvs/commit} -> '{@code cvs/commit}'</li>
+ * <li>'{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} -> '{@code commit.html}'</li>
+ * <li>'{@code /docs/**}' and '{@code /docs/cvs/commit} -> '{@code cvs/commit}'</li>
+ * <li>'{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} -> '{@code cvs/commit.html}'</li>
+ * <li>'{@code /*.html}' and '{@code /docs/cvs/commit.html} -> '{@code docs/cvs/commit.html}'</li>
+ * <li>'{@code *.html}' and '{@code /docs/cvs/commit.html} -> '{@code /docs/cvs/commit.html}'</li>
+ * <li>'{@code *}' and '{@code /docs/cvs/commit.html} -> '{@code /docs/cvs/commit.html}'</li> </ul>
+ * <p>Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but
+ * does <strong>not</strong> enforce this.
+ */
+ public String extractPathWithinPattern(String pattern, String path) {
+ String[] patternParts = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator, this.trimTokens, true);
+ String[] pathParts = StringUtils.tokenizeToStringArray(path, this.pathSeparator, this.trimTokens, true);
+
+ StringBuilder builder = new StringBuilder();
+
+ // Add any path parts that have a wildcarded pattern part.
+ int puts = 0;
+ for (int i = 0; i < patternParts.length; i++) {
+ String patternPart = patternParts[i];
+ if ((patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) && pathParts.length >= i + 1) {
+ if (puts > 0 || (i == 0 && !pattern.startsWith(this.pathSeparator))) {
+ builder.append(this.pathSeparator);
+ }
+ builder.append(pathParts[i]);
+ puts++;
+ }
+ }
+
+ // Append any trailing path parts.
+ for (int i = patternParts.length; i < pathParts.length; i++) {
+ if (puts > 0 || i > 0) {
+ builder.append(this.pathSeparator);
+ }
+ builder.append(pathParts[i]);
+ }
+
+ return builder.toString();
+ }
+
+ public Map<String, String> extractUriTemplateVariables(String pattern, String path) {
+ Map<String, String> variables = new LinkedHashMap<String, String>();
+ boolean result = doMatch(pattern, path, true, variables);
+ Assert.state(result, "Pattern \"" + pattern + "\" is not a match for \"" + path + "\"");
+ return variables;
+ }
+
+ /**
+ * Combines two patterns into a new pattern that is returned.
+ * <p>This implementation simply concatenates the two patterns, unless the first pattern
+ * contains a file extension match (such as {@code *.html}. In that case, the second pattern
+ * should be included in the first, or an {@code IllegalArgumentException} is thrown.
+ * <p>For example: <table>
+ * <tr><th>Pattern 1</th><th>Pattern 2</th><th>Result</th></tr> <tr><td>/hotels</td><td>{@code
+ * null}</td><td>/hotels</td></tr> <tr><td>{@code null}</td><td>/hotels</td><td>/hotels</td></tr>
+ * <tr><td>/hotels</td><td>/bookings</td><td>/hotels/bookings</td></tr> <tr><td>/hotels</td><td>bookings</td><td>/hotels/bookings</td></tr>
+ * <tr><td>/hotels/*</td><td>/bookings</td><td>/hotels/bookings</td></tr> <tr><td>/hotels/&#42;&#42;</td><td>/bookings</td><td>/hotels/&#42;&#42;/bookings</td></tr>
+ * <tr><td>/hotels</td><td>{hotel}</td><td>/hotels/{hotel}</td></tr> <tr><td>/hotels/*</td><td>{hotel}</td><td>/hotels/{hotel}</td></tr>
+ * <tr><td>/hotels/&#42;&#42;</td><td>{hotel}</td><td>/hotels/&#42;&#42;/{hotel}</td></tr>
+ * <tr><td>/*.html</td><td>/hotels.html</td><td>/hotels.html</td></tr> <tr><td>/*.html</td><td>/hotels</td><td>/hotels.html</td></tr>
+ * <tr><td>/*.html</td><td>/*.txt</td><td>IllegalArgumentException</td></tr> </table>
+ * @param pattern1 the first pattern
+ * @param pattern2 the second pattern
+ * @return the combination of the two patterns
+ * @throws IllegalArgumentException when the two patterns cannot be combined
+ */
+ public String combine(String pattern1, String pattern2) {
+ if (!StringUtils.hasText(pattern1) && !StringUtils.hasText(pattern2)) {
+ return "";
+ }
+ else if (!StringUtils.hasText(pattern1)) {
+ return pattern2;
+ }
+ else if (!StringUtils.hasText(pattern2)) {
+ return pattern1;
+ }
+
+ boolean pattern1ContainsUriVar = pattern1.indexOf('{') != -1;
+ if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) {
+ // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html
+ // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar
+ return pattern2;
+ }
+ else if (pattern1.endsWith("/*")) {
+ if (pattern2.startsWith("/")) {
+ // /hotels/* + /booking -> /hotels/booking
+ return pattern1.substring(0, pattern1.length() - 1) + pattern2.substring(1);
+ }
+ else {
+ // /hotels/* + booking -> /hotels/booking
+ return pattern1.substring(0, pattern1.length() - 1) + pattern2;
+ }
+ }
+ else if (pattern1.endsWith("/**")) {
+ if (pattern2.startsWith("/")) {
+ // /hotels/** + /booking -> /hotels/**/booking
+ return pattern1 + pattern2;
+ }
+ else {
+ // /hotels/** + booking -> /hotels/**/booking
+ return pattern1 + "/" + pattern2;
+ }
+ }
+ else {
+ int dotPos1 = pattern1.indexOf('.');
+ if (dotPos1 == -1 || pattern1ContainsUriVar) {
+ // simply concatenate the two patterns
+ if (pattern1.endsWith("/") || pattern2.startsWith("/")) {
+ return pattern1 + pattern2;
+ }
+ else {
+ return pattern1 + "/" + pattern2;
+ }
+ }
+ String fileName1 = pattern1.substring(0, dotPos1);
+ String extension1 = pattern1.substring(dotPos1);
+ String fileName2;
+ String extension2;
+ int dotPos2 = pattern2.indexOf('.');
+ if (dotPos2 != -1) {
+ fileName2 = pattern2.substring(0, dotPos2);
+ extension2 = pattern2.substring(dotPos2);
+ }
+ else {
+ fileName2 = pattern2;
+ extension2 = "";
+ }
+ String fileName = fileName1.endsWith("*") ? fileName2 : fileName1;
+ String extension = extension1.startsWith("*") ? extension2 : extension1;
+
+ return fileName + extension;
+ }
+ }
+
+ /**
+ * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of explicitness.
+ * <p>The returned {@code Comparator} will {@linkplain java.util.Collections#sort(java.util.List,
+ * java.util.Comparator) sort} a list so that more specific patterns (without uri templates or wild cards) come before
+ * generic patterns. So given a list with the following patterns: <ol> <li>{@code /hotels/new}</li>
+ * <li>{@code /hotels/{hotel}}</li> <li>{@code /hotels/*}</li> </ol> the returned comparator will sort this
+ * list so that the order will be as indicated.
+ * <p>The full path given as parameter is used to test for exact matches. So when the given path is {@code /hotels/2},
+ * the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}.
+ * @param path the full path to use for comparison
+ * @return a comparator capable of sorting patterns in order of explicitness
+ */
+ public Comparator<String> getPatternComparator(String path) {
+ return new AntPatternComparator(path);
+ }
+
+
+ private static class AntPatternComparator implements Comparator<String> {
+
+ private final String path;
+
+ private AntPatternComparator(String path) {
+ this.path = path;
+ }
+
+ public int compare(String pattern1, String pattern2) {
+ if (pattern1 == null && pattern2 == null) {
+ return 0;
+ }
+ else if (pattern1 == null) {
+ return 1;
+ }
+ else if (pattern2 == null) {
+ return -1;
+ }
+ boolean pattern1EqualsPath = pattern1.equals(path);
+ boolean pattern2EqualsPath = pattern2.equals(path);
+ if (pattern1EqualsPath && pattern2EqualsPath) {
+ return 0;
+ }
+ else if (pattern1EqualsPath) {
+ return -1;
+ }
+ else if (pattern2EqualsPath) {
+ return 1;
+ }
+ int wildCardCount1 = getWildCardCount(pattern1);
+ int wildCardCount2 = getWildCardCount(pattern2);
+
+ int bracketCount1 = StringUtils.countOccurrencesOf(pattern1, "{");
+ int bracketCount2 = StringUtils.countOccurrencesOf(pattern2, "{");
+
+ int totalCount1 = wildCardCount1 + bracketCount1;
+ int totalCount2 = wildCardCount2 + bracketCount2;
+
+ if (totalCount1 != totalCount2) {
+ return totalCount1 - totalCount2;
+ }
+
+ int pattern1Length = getPatternLength(pattern1);
+ int pattern2Length = getPatternLength(pattern2);
+
+ if (pattern1Length != pattern2Length) {
+ return pattern2Length - pattern1Length;
+ }
+
+ if (wildCardCount1 < wildCardCount2) {
+ return -1;
+ }
+ else if (wildCardCount2 < wildCardCount1) {
+ return 1;
+ }
+
+ if (bracketCount1 < bracketCount2) {
+ return -1;
+ }
+ else if (bracketCount2 < bracketCount1) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ private int getWildCardCount(String pattern) {
+ if (pattern.endsWith(".*")) {
+ pattern = pattern.substring(0, pattern.length() - 2);
+ }
+ return StringUtils.countOccurrencesOf(pattern, "*");
+ }
+
+ /**
+ * Returns the length of the given pattern, where template variables are considered to be 1 long.
+ */
+ private int getPatternLength(String pattern) {
+ Matcher m = VARIABLE_PATTERN.matcher(pattern);
+ return m.replaceAll("#").length();
+ }
+ }
+
+
+ /**
+ * Tests whether or not a string matches against a pattern via a {@link Pattern}.
+ * <p>The pattern may contain special characters: '*' means zero or more characters; '?' means one and
+ * only one character; '{' and '}' indicate a URI template pattern. For example <tt>/users/{user}</tt>.
+ */
+ private static class AntPathStringMatcher {
+
+ private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}");
+
+ private static final String DEFAULT_VARIABLE_PATTERN = "(.*)";
+
+ private final Pattern pattern;
+
+ private final List<String> variableNames = new LinkedList<String>();
+
+ public AntPathStringMatcher(String pattern) {
+ StringBuilder patternBuilder = new StringBuilder();
+ Matcher m = GLOB_PATTERN.matcher(pattern);
+ int end = 0;
+ while (m.find()) {
+ patternBuilder.append(quote(pattern, end, m.start()));
+ String match = m.group();
+ if ("?".equals(match)) {
+ patternBuilder.append('.');
+ }
+ else if ("*".equals(match)) {
+ patternBuilder.append(".*");
+ }
+ else if (match.startsWith("{") && match.endsWith("}")) {
+ int colonIdx = match.indexOf(':');
+ if (colonIdx == -1) {
+ patternBuilder.append(DEFAULT_VARIABLE_PATTERN);
+ this.variableNames.add(m.group(1));
+ }
+ else {
+ String variablePattern = match.substring(colonIdx + 1, match.length() - 1);
+ patternBuilder.append('(');
+ patternBuilder.append(variablePattern);
+ patternBuilder.append(')');
+ String variableName = match.substring(1, colonIdx);
+ this.variableNames.add(variableName);
+ }
+ }
+ end = m.end();
+ }
+ patternBuilder.append(quote(pattern, end, pattern.length()));
+ this.pattern = Pattern.compile(patternBuilder.toString());
+ }
+
+ private String quote(String s, int start, int end) {
+ if (start == end) {
+ return "";
+ }
+ return Pattern.quote(s.substring(start, end));
+ }
+
+ /**
+ * Main entry point.
+ * @return {@code true} if the string matches against the pattern, or {@code false} otherwise.
+ */
+ public boolean matchStrings(String str, Map<String, String> uriTemplateVariables) {
+ Matcher matcher = this.pattern.matcher(str);
+ if (matcher.matches()) {
+ if (uriTemplateVariables != null) {
+ // SPR-8455
+ Assert.isTrue(this.variableNames.size() == matcher.groupCount(),
+ "The number of capturing groups in the pattern segment " + this.pattern +
+ " does not match the number of URI template variables it defines, which can occur if " +
+ " capturing groups are used in a URI template regex. Use non-capturing groups instead.");
+ for (int i = 1; i <= matcher.groupCount(); i++) {
+ String name = this.variableNames.get(i - 1);
+ String value = matcher.group(i);
+ uriTemplateVariables.put(name, value);
+ }
+ }
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/Assert.java b/spring-core/src/main/java/org/springframework/util/Assert.java
new file mode 100644
index 00000000..55b2325d
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/Assert.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright 2002-2013 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.util;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Assertion utility class that assists in validating arguments.
+ * Useful for identifying programmer errors early and clearly at runtime.
+ *
+ * <p>For example, if the contract of a public method states it does not
+ * allow {@code null} arguments, Assert can be used to validate that
+ * contract. Doing this clearly indicates a contract violation when it
+ * occurs and protects the class's invariants.
+ *
+ * <p>Typically used to validate method arguments rather than configuration
+ * properties, to check for cases that are usually programmer errors rather than
+ * configuration errors. In contrast to config initialization code, there is
+ * usally no point in falling back to defaults in such methods.
+ *
+ * <p>This class is similar to JUnit's assertion library. If an argument value is
+ * deemed invalid, an {@link IllegalArgumentException} is thrown (typically).
+ * For example:
+ *
+ * <pre class="code">
+ * Assert.notNull(clazz, "The class must not be null");
+ * Assert.isTrue(i > 0, "The value must be greater than zero");</pre>
+ *
+ * Mainly for internal use within the framework; consider Jakarta's Commons Lang
+ * >= 2.0 for a more comprehensive suite of assertion utilities.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @author Colin Sampaleanu
+ * @author Rob Harrop
+ * @since 1.1.2
+ */
+public abstract class Assert {
+
+ /**
+ * Assert a boolean expression, throwing {@code IllegalArgumentException}
+ * if the test result is {@code false}.
+ * <pre class="code">Assert.isTrue(i &gt; 0, "The value must be greater than zero");</pre>
+ * @param expression a boolean expression
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if expression is {@code false}
+ */
+ public static void isTrue(boolean expression, String message) {
+ if (!expression) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert a boolean expression, throwing {@code IllegalArgumentException}
+ * if the test result is {@code false}.
+ * <pre class="code">Assert.isTrue(i &gt; 0);</pre>
+ * @param expression a boolean expression
+ * @throws IllegalArgumentException if expression is {@code false}
+ */
+ public static void isTrue(boolean expression) {
+ isTrue(expression, "[Assertion failed] - this expression must be true");
+ }
+
+ /**
+ * Assert that an object is {@code null} .
+ * <pre class="code">Assert.isNull(value, "The value must be null");</pre>
+ * @param object the object to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the object is not {@code null}
+ */
+ public static void isNull(Object object, String message) {
+ if (object != null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that an object is {@code null} .
+ * <pre class="code">Assert.isNull(value);</pre>
+ * @param object the object to check
+ * @throws IllegalArgumentException if the object is not {@code null}
+ */
+ public static void isNull(Object object) {
+ isNull(object, "[Assertion failed] - the object argument must be null");
+ }
+
+ /**
+ * Assert that an object is not {@code null} .
+ * <pre class="code">Assert.notNull(clazz, "The class must not be null");</pre>
+ * @param object the object to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the object is {@code null}
+ */
+ public static void notNull(Object object, String message) {
+ if (object == null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that an object is not {@code null} .
+ * <pre class="code">Assert.notNull(clazz);</pre>
+ * @param object the object to check
+ * @throws IllegalArgumentException if the object is {@code null}
+ */
+ public static void notNull(Object object) {
+ notNull(object, "[Assertion failed] - this argument is required; it must not be null");
+ }
+
+ /**
+ * Assert that the given String is not empty; that is,
+ * it must not be {@code null} and not the empty String.
+ * <pre class="code">Assert.hasLength(name, "Name must not be empty");</pre>
+ * @param text the String to check
+ * @param message the exception message to use if the assertion fails
+ * @see StringUtils#hasLength
+ */
+ public static void hasLength(String text, String message) {
+ if (!StringUtils.hasLength(text)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that the given String is not empty; that is,
+ * it must not be {@code null} and not the empty String.
+ * <pre class="code">Assert.hasLength(name);</pre>
+ * @param text the String to check
+ * @see StringUtils#hasLength
+ */
+ public static void hasLength(String text) {
+ hasLength(text,
+ "[Assertion failed] - this String argument must have length; it must not be null or empty");
+ }
+
+ /**
+ * Assert that the given String has valid text content; that is, it must not
+ * be {@code null} and must contain at least one non-whitespace character.
+ * <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
+ * @param text the String to check
+ * @param message the exception message to use if the assertion fails
+ * @see StringUtils#hasText
+ */
+ public static void hasText(String text, String message) {
+ if (!StringUtils.hasText(text)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that the given String has valid text content; that is, it must not
+ * be {@code null} and must contain at least one non-whitespace character.
+ * <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
+ * @param text the String to check
+ * @see StringUtils#hasText
+ */
+ public static void hasText(String text) {
+ hasText(text,
+ "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");
+ }
+
+ /**
+ * Assert that the given text does not contain the given substring.
+ * <pre class="code">Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");</pre>
+ * @param textToSearch the text to search
+ * @param substring the substring to find within the text
+ * @param message the exception message to use if the assertion fails
+ */
+ public static void doesNotContain(String textToSearch, String substring, String message) {
+ if (StringUtils.hasLength(textToSearch) && StringUtils.hasLength(substring) &&
+ textToSearch.contains(substring)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that the given text does not contain the given substring.
+ * <pre class="code">Assert.doesNotContain(name, "rod");</pre>
+ * @param textToSearch the text to search
+ * @param substring the substring to find within the text
+ */
+ public static void doesNotContain(String textToSearch, String substring) {
+ doesNotContain(textToSearch, substring,
+ "[Assertion failed] - this String argument must not contain the substring [" + substring + "]");
+ }
+
+
+ /**
+ * Assert that an array has elements; that is, it must not be
+ * {@code null} and must have at least one element.
+ * <pre class="code">Assert.notEmpty(array, "The array must have elements");</pre>
+ * @param array the array to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the object array is {@code null} or has no elements
+ */
+ public static void notEmpty(Object[] array, String message) {
+ if (ObjectUtils.isEmpty(array)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that an array has elements; that is, it must not be
+ * {@code null} and must have at least one element.
+ * <pre class="code">Assert.notEmpty(array);</pre>
+ * @param array the array to check
+ * @throws IllegalArgumentException if the object array is {@code null} or has no elements
+ */
+ public static void notEmpty(Object[] array) {
+ notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element");
+ }
+
+ /**
+ * Assert that an array has no null elements.
+ * Note: Does not complain if the array is empty!
+ * <pre class="code">Assert.noNullElements(array, "The array must have non-null elements");</pre>
+ * @param array the array to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the object array contains a {@code null} element
+ */
+ public static void noNullElements(Object[] array, String message) {
+ if (array != null) {
+ for (Object element : array) {
+ if (element == null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+ }
+ }
+
+ /**
+ * Assert that an array has no null elements.
+ * Note: Does not complain if the array is empty!
+ * <pre class="code">Assert.noNullElements(array);</pre>
+ * @param array the array to check
+ * @throws IllegalArgumentException if the object array contains a {@code null} element
+ */
+ public static void noNullElements(Object[] array) {
+ noNullElements(array, "[Assertion failed] - this array must not contain any null elements");
+ }
+
+ /**
+ * Assert that a collection has elements; that is, it must not be
+ * {@code null} and must have at least one element.
+ * <pre class="code">Assert.notEmpty(collection, "Collection must have elements");</pre>
+ * @param collection the collection to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the collection is {@code null} or has no elements
+ */
+ public static void notEmpty(Collection collection, String message) {
+ if (CollectionUtils.isEmpty(collection)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that a collection has elements; that is, it must not be
+ * {@code null} and must have at least one element.
+ * <pre class="code">Assert.notEmpty(collection, "Collection must have elements");</pre>
+ * @param collection the collection to check
+ * @throws IllegalArgumentException if the collection is {@code null} or has no elements
+ */
+ public static void notEmpty(Collection collection) {
+ notEmpty(collection,
+ "[Assertion failed] - this collection must not be empty: it must contain at least 1 element");
+ }
+
+ /**
+ * Assert that a Map has entries; that is, it must not be {@code null}
+ * and must have at least one entry.
+ * <pre class="code">Assert.notEmpty(map, "Map must have entries");</pre>
+ * @param map the map to check
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalArgumentException if the map is {@code null} or has no entries
+ */
+ public static void notEmpty(Map map, String message) {
+ if (CollectionUtils.isEmpty(map)) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ /**
+ * Assert that a Map has entries; that is, it must not be {@code null}
+ * and must have at least one entry.
+ * <pre class="code">Assert.notEmpty(map);</pre>
+ * @param map the map to check
+ * @throws IllegalArgumentException if the map is {@code null} or has no entries
+ */
+ public static void notEmpty(Map map) {
+ notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry");
+ }
+
+
+ /**
+ * Assert that the provided object is an instance of the provided class.
+ * <pre class="code">Assert.instanceOf(Foo.class, foo);</pre>
+ * @param clazz the required class
+ * @param obj the object to check
+ * @throws IllegalArgumentException if the object is not an instance of clazz
+ * @see Class#isInstance
+ */
+ public static void isInstanceOf(Class<?> clazz, Object obj) {
+ isInstanceOf(clazz, obj, "");
+ }
+
+ /**
+ * Assert that the provided object is an instance of the provided class.
+ * <pre class="code">Assert.instanceOf(Foo.class, foo);</pre>
+ * @param type the type to check against
+ * @param obj the object to check
+ * @param message a message which will be prepended to the message produced by
+ * the function itself, and which may be used to provide context. It should
+ * normally end in a ": " or ". " so that the function generate message looks
+ * ok when prepended to it.
+ * @throws IllegalArgumentException if the object is not an instance of clazz
+ * @see Class#isInstance
+ */
+ public static void isInstanceOf(Class<?> type, Object obj, String message) {
+ notNull(type, "Type to check against must not be null");
+ if (!type.isInstance(obj)) {
+ throw new IllegalArgumentException(
+ (StringUtils.hasLength(message) ? message + " " : "") +
+ "Object of class [" + (obj != null ? obj.getClass().getName() : "null") +
+ "] must be an instance of " + type);
+ }
+ }
+
+ /**
+ * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}.
+ * <pre class="code">Assert.isAssignable(Number.class, myClass);</pre>
+ * @param superType the super type to check
+ * @param subType the sub type to check
+ * @throws IllegalArgumentException if the classes are not assignable
+ */
+ public static void isAssignable(Class<?> superType, Class<?> subType) {
+ isAssignable(superType, subType, "");
+ }
+
+ /**
+ * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}.
+ * <pre class="code">Assert.isAssignable(Number.class, myClass);</pre>
+ * @param superType the super type to check against
+ * @param subType the sub type to check
+ * @param message a message which will be prepended to the message produced by
+ * the function itself, and which may be used to provide context. It should
+ * normally end in a ": " or ". " so that the function generate message looks
+ * ok when prepended to it.
+ * @throws IllegalArgumentException if the classes are not assignable
+ */
+ public static void isAssignable(Class<?> superType, Class<?> subType, String message) {
+ notNull(superType, "Type to check against must not be null");
+ if (subType == null || !superType.isAssignableFrom(subType)) {
+ throw new IllegalArgumentException(message + subType + " is not assignable to " + superType);
+ }
+ }
+
+
+ /**
+ * Assert a boolean expression, throwing {@code IllegalStateException}
+ * if the test result is {@code false}. Call isTrue if you wish to
+ * throw IllegalArgumentException on an assertion failure.
+ * <pre class="code">Assert.state(id == null, "The id property must not already be initialized");</pre>
+ * @param expression a boolean expression
+ * @param message the exception message to use if the assertion fails
+ * @throws IllegalStateException if expression is {@code false}
+ */
+ public static void state(boolean expression, String message) {
+ if (!expression) {
+ throw new IllegalStateException(message);
+ }
+ }
+
+ /**
+ * Assert a boolean expression, throwing {@link IllegalStateException}
+ * if the test result is {@code false}.
+ * <p>Call {@link #isTrue(boolean)} if you wish to
+ * throw {@link IllegalArgumentException} on an assertion failure.
+ * <pre class="code">Assert.state(id == null);</pre>
+ * @param expression a boolean expression
+ * @throws IllegalStateException if the supplied expression is {@code false}
+ */
+ public static void state(boolean expression) {
+ state(expression, "[Assertion failed] - this state invariant must be true");
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java b/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java
new file mode 100644
index 00000000..7a2135fb
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2002-2012 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.util;
+
+import java.io.Serializable;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * Simple {@link List} wrapper class that allows for elements to be
+ * automatically populated as they are requested. This is particularly
+ * useful for data binding to {@link List Lists}, allowing for elements
+ * to be created and added to the {@link List} in a "just in time" fashion.
+ *
+ * <p>Note: This class is not thread-safe. To create a thread-safe version,
+ * use the {@link java.util.Collections#synchronizedList} utility methods.
+ *
+ * <p>Inspired by {@code LazyList} from Commons Collections.
+ *
+ * @author Rob Harrop
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+@SuppressWarnings("serial")
+public class AutoPopulatingList<E> implements List<E>, Serializable {
+
+ /**
+ * The {@link List} that all operations are eventually delegated to.
+ */
+ private final List<E> backingList;
+
+ /**
+ * The {@link ElementFactory} to use to create new {@link List} elements
+ * on demand.
+ */
+ private final ElementFactory<E> elementFactory;
+
+
+ /**
+ * Creates a new {@code AutoPopulatingList} that is backed by a standard
+ * {@link ArrayList} and adds new instances of the supplied {@link Class element Class}
+ * to the backing {@link List} on demand.
+ */
+ public AutoPopulatingList(Class<? extends E> elementClass) {
+ this(new ArrayList<E>(), elementClass);
+ }
+
+ /**
+ * Creates a new {@code AutoPopulatingList} that is backed by the supplied {@link List}
+ * and adds new instances of the supplied {@link Class element Class} to the backing
+ * {@link List} on demand.
+ */
+ public AutoPopulatingList(List<E> backingList, Class<? extends E> elementClass) {
+ this(backingList, new ReflectiveElementFactory<E>(elementClass));
+ }
+
+ /**
+ * Creates a new {@code AutoPopulatingList} that is backed by a standard
+ * {@link ArrayList} and creates new elements on demand using the supplied {@link ElementFactory}.
+ */
+ public AutoPopulatingList(ElementFactory<E> elementFactory) {
+ this(new ArrayList<E>(), elementFactory);
+ }
+
+ /**
+ * Creates a new {@code AutoPopulatingList} that is backed by the supplied {@link List}
+ * and creates new elements on demand using the supplied {@link ElementFactory}.
+ */
+ public AutoPopulatingList(List<E> backingList, ElementFactory<E> elementFactory) {
+ Assert.notNull(backingList, "Backing List must not be null");
+ Assert.notNull(elementFactory, "Element factory must not be null");
+ this.backingList = backingList;
+ this.elementFactory = elementFactory;
+ }
+
+
+ public void add(int index, E element) {
+ this.backingList.add(index, element);
+ }
+
+ public boolean add(E o) {
+ return this.backingList.add(o);
+ }
+
+ public boolean addAll(Collection<? extends E> c) {
+ return this.backingList.addAll(c);
+ }
+
+ public boolean addAll(int index, Collection<? extends E> c) {
+ return this.backingList.addAll(index, c);
+ }
+
+ public void clear() {
+ this.backingList.clear();
+ }
+
+ public boolean contains(Object o) {
+ return this.backingList.contains(o);
+ }
+
+ public boolean containsAll(Collection c) {
+ return this.backingList.containsAll(c);
+ }
+
+ /**
+ * Get the element at the supplied index, creating it if there is
+ * no element at that index.
+ */
+ public E get(int index) {
+ int backingListSize = this.backingList.size();
+ E element = null;
+ if (index < backingListSize) {
+ element = this.backingList.get(index);
+ if (element == null) {
+ element = this.elementFactory.createElement(index);
+ this.backingList.set(index, element);
+ }
+ }
+ else {
+ for (int x = backingListSize; x < index; x++) {
+ this.backingList.add(null);
+ }
+ element = this.elementFactory.createElement(index);
+ this.backingList.add(element);
+ }
+ return element;
+ }
+
+ public int indexOf(Object o) {
+ return this.backingList.indexOf(o);
+ }
+
+ public boolean isEmpty() {
+ return this.backingList.isEmpty();
+ }
+
+ public Iterator<E> iterator() {
+ return this.backingList.iterator();
+ }
+
+ public int lastIndexOf(Object o) {
+ return this.backingList.lastIndexOf(o);
+ }
+
+ public ListIterator<E> listIterator() {
+ return this.backingList.listIterator();
+ }
+
+ public ListIterator<E> listIterator(int index) {
+ return this.backingList.listIterator(index);
+ }
+
+ public E remove(int index) {
+ return this.backingList.remove(index);
+ }
+
+ public boolean remove(Object o) {
+ return this.backingList.remove(o);
+ }
+
+ public boolean removeAll(Collection<?> c) {
+ return this.backingList.removeAll(c);
+ }
+
+ public boolean retainAll(Collection<?> c) {
+ return this.backingList.retainAll(c);
+ }
+
+ public E set(int index, E element) {
+ return this.backingList.set(index, element);
+ }
+
+ public int size() {
+ return this.backingList.size();
+ }
+
+ public List<E> subList(int fromIndex, int toIndex) {
+ return this.backingList.subList(fromIndex, toIndex);
+ }
+
+ public Object[] toArray() {
+ return this.backingList.toArray();
+ }
+
+ public <T> T[] toArray(T[] a) {
+ return this.backingList.toArray(a);
+ }
+
+
+ @Override
+ public boolean equals(Object other) {
+ return this.backingList.equals(other);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.backingList.hashCode();
+ }
+
+
+ /**
+ * Factory interface for creating elements for an index-based access
+ * data structure such as a {@link java.util.List}.
+ */
+ public interface ElementFactory<E> {
+
+ /**
+ * Create the element for the supplied index.
+ * @return the element object
+ * @throws ElementInstantiationException if the instantiation process failed
+ * (any exception thrown by a target constructor should be propagated as-is)
+ */
+ E createElement(int index) throws ElementInstantiationException;
+ }
+
+
+ /**
+ * Exception to be thrown from ElementFactory.
+ */
+ public static class ElementInstantiationException extends RuntimeException {
+
+ public ElementInstantiationException(String msg) {
+ super(msg);
+ }
+ }
+
+
+ /**
+ * Reflective implementation of the ElementFactory interface,
+ * using {@code Class.newInstance()} on a given element class.
+ * @see Class#newInstance()
+ */
+ private static class ReflectiveElementFactory<E> implements ElementFactory<E>, Serializable {
+
+ private final Class<? extends E> elementClass;
+
+ public ReflectiveElementFactory(Class<? extends E> elementClass) {
+ Assert.notNull(elementClass, "Element clas must not be null");
+ Assert.isTrue(!elementClass.isInterface(), "Element class must not be an interface type");
+ Assert.isTrue(!Modifier.isAbstract(elementClass.getModifiers()), "Element class cannot be an abstract class");
+ this.elementClass = elementClass;
+ }
+
+ public E createElement(int index) {
+ try {
+ return this.elementClass.newInstance();
+ }
+ catch (InstantiationException ex) {
+ throw new ElementInstantiationException("Unable to instantiate element class [" +
+ this.elementClass.getName() + "]. Root cause is " + ex);
+ }
+ catch (IllegalAccessException ex) {
+ throw new ElementInstantiationException("Cannot access element class [" +
+ this.elementClass.getName() + "]. Root cause is " + ex);
+ }
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/CachingMapDecorator.java b/spring-core/src/main/java/org/springframework/util/CachingMapDecorator.java
new file mode 100644
index 00000000..d1290588
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/CachingMapDecorator.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright 2002-2012 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.util;
+
+import java.io.Serializable;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+/**
+ * A simple decorator for a Map, encapsulating the workflow for caching
+ * expensive values in a target Map. Supports caching weak or strong keys.
+ *
+ * <p>This class is an abstract template. Caching Map implementations
+ * should subclass and override the {@code create(key)} method which
+ * encapsulates expensive creation of a new object.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ * @deprecated as of Spring 3.2, to be removed along with LabeledEnum support
+ */
+@Deprecated
+@SuppressWarnings("serial")
+public abstract class CachingMapDecorator<K, V> implements Map<K, V>, Serializable {
+
+ private static Object NULL_VALUE = new Object();
+
+
+ private final Map<K, Object> targetMap;
+
+ private final boolean synchronize;
+
+ private final boolean weak;
+
+
+ /**
+ * Create a CachingMapDecorator with strong keys,
+ * using an underlying synchronized Map.
+ */
+ public CachingMapDecorator() {
+ this(false);
+ }
+
+ /**
+ * Create a CachingMapDecorator,
+ * using an underlying synchronized Map.
+ * @param weak whether to use weak references for keys and values
+ */
+ public CachingMapDecorator(boolean weak) {
+ Map<K, Object> internalMap = (weak ? new WeakHashMap<K, Object>() : new HashMap<K, Object>());
+ this.targetMap = Collections.synchronizedMap(internalMap);
+ this.synchronize = true;
+ this.weak = weak;
+ }
+
+ /**
+ * Create a CachingMapDecorator with initial size,
+ * using an underlying synchronized Map.
+ * @param weak whether to use weak references for keys and values
+ * @param size the initial cache size
+ */
+ public CachingMapDecorator(boolean weak, int size) {
+ Map<K, Object> internalMap = weak ? new WeakHashMap<K, Object> (size) : new HashMap<K, Object>(size);
+ this.targetMap = Collections.synchronizedMap(internalMap);
+ this.synchronize = true;
+ this.weak = weak;
+ }
+
+ /**
+ * Create a CachingMapDecorator for the given Map.
+ * <p>The passed-in Map won't get synchronized explicitly,
+ * so make sure to pass in a properly synchronized Map, if desired.
+ * @param targetMap the Map to decorate
+ */
+ public CachingMapDecorator(Map<K, V> targetMap) {
+ this(targetMap, false, false);
+ }
+
+ /**
+ * Create a CachingMapDecorator for the given Map.
+ * <p>The passed-in Map won't get synchronized explicitly unless
+ * you specify "synchronize" as "true".
+ * @param targetMap the Map to decorate
+ * @param synchronize whether to synchronize on the given Map
+ * @param weak whether to use weak references for values
+ */
+ @SuppressWarnings("unchecked")
+ public CachingMapDecorator(Map<K, V> targetMap, boolean synchronize, boolean weak) {
+ Assert.notNull(targetMap, "'targetMap' must not be null");
+ this.targetMap = (Map<K, Object>) (synchronize ? Collections.synchronizedMap(targetMap) : targetMap);
+ this.synchronize = synchronize;
+ this.weak = weak;
+ }
+
+
+ public int size() {
+ return this.targetMap.size();
+ }
+
+ public boolean isEmpty() {
+ return this.targetMap.isEmpty();
+ }
+
+ public boolean containsKey(Object key) {
+ return this.targetMap.containsKey(key);
+ }
+
+ public boolean containsValue(Object value) {
+ Object valueToCheck = (value != null ? value : NULL_VALUE);
+ if (this.synchronize) {
+ synchronized (this.targetMap) {
+ return containsValueOrReference(valueToCheck);
+ }
+ }
+ else {
+ return containsValueOrReference(valueToCheck);
+ }
+ }
+
+ private boolean containsValueOrReference(Object value) {
+ if (this.targetMap.containsValue(value)) {
+ return true;
+ }
+ for (Object mapVal : this.targetMap.values()) {
+ if (mapVal instanceof Reference && value.equals(((Reference) mapVal).get())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public V remove(Object key) {
+ return unwrapReturnValue(this.targetMap.remove(key));
+ }
+
+ @SuppressWarnings("unchecked")
+ private V unwrapReturnValue(Object value) {
+ Object returnValue = value;
+ if (returnValue instanceof Reference) {
+ returnValue = ((Reference) returnValue).get();
+ }
+ return (returnValue == NULL_VALUE ? null : (V) returnValue);
+ }
+
+ public void putAll(Map<? extends K, ? extends V> map) {
+ this.targetMap.putAll(map);
+ }
+
+ public void clear() {
+ this.targetMap.clear();
+ }
+
+ public Set<K> keySet() {
+ if (this.synchronize) {
+ synchronized (this.targetMap) {
+ return new LinkedHashSet<K>(this.targetMap.keySet());
+ }
+ }
+ else {
+ return new LinkedHashSet<K>(this.targetMap.keySet());
+ }
+ }
+
+ public Collection<V> values() {
+ if (this.synchronize) {
+ synchronized (this.targetMap) {
+ return valuesCopy();
+ }
+ }
+ else {
+ return valuesCopy();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private Collection<V> valuesCopy() {
+ LinkedList<V> values = new LinkedList<V>();
+ for (Iterator<Object> it = this.targetMap.values().iterator(); it.hasNext();) {
+ Object value = it.next();
+ if (value instanceof Reference) {
+ value = ((Reference) value).get();
+ if (value == null) {
+ it.remove();
+ continue;
+ }
+ }
+ values.add(value == NULL_VALUE ? null : (V) value);
+ }
+ return values;
+ }
+
+ public Set<Map.Entry<K, V>> entrySet() {
+ if (this.synchronize) {
+ synchronized (this.targetMap) {
+ return entryCopy();
+ }
+ }
+ else {
+ return entryCopy();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private Set<Map.Entry<K, V>> entryCopy() {
+ Map<K,V> entries = new LinkedHashMap<K, V>();
+ for (Iterator<Entry<K, Object>> it = this.targetMap.entrySet().iterator(); it.hasNext();) {
+ Entry<K, Object> entry = it.next();
+ Object value = entry.getValue();
+ if (value instanceof Reference) {
+ value = ((Reference) value).get();
+ if (value == null) {
+ it.remove();
+ continue;
+ }
+ }
+ entries.put(entry.getKey(), value == NULL_VALUE ? null : (V) value);
+ }
+ return entries.entrySet();
+ }
+
+
+ /**
+ * Put an object into the cache, possibly wrapping it with a weak
+ * reference.
+ * @see #useWeakValue(Object, Object)
+ */
+ public V put(K key, V value) {
+ Object newValue = value;
+ if (value == null) {
+ newValue = NULL_VALUE;
+ }
+ else if (useWeakValue(key, value)) {
+ newValue = new WeakReference<Object>(newValue);
+ }
+ return unwrapReturnValue(this.targetMap.put(key, newValue));
+ }
+
+ /**
+ * Decide whether to use a weak reference for the value of
+ * the given key-value pair.
+ * @param key the candidate key
+ * @param value the candidate value
+ * @return {@code true} in order to use a weak reference;
+ * {@code false} otherwise.
+ */
+ protected boolean useWeakValue(K key, V value) {
+ return this.weak;
+ }
+
+ /**
+ * Get value for key.
+ * Creates and caches value if it doesn't already exist in the cache.
+ * <p>This implementation is <i>not</i> synchronized: This is highly
+ * concurrent but does not guarantee unique instances in the cache,
+ * as multiple values for the same key could get created in parallel.
+ * Consider overriding this method to synchronize it, if desired.
+ * @see #create(Object)
+ */
+ @SuppressWarnings("unchecked")
+ public V get(Object key) {
+ Object value = this.targetMap.get(key);
+ if (value instanceof Reference) {
+ value = ((Reference) value).get();
+ }
+ if (value == null) {
+ V newValue = create((K) key);
+ put((K) key, newValue);
+ return newValue;
+ }
+ return (value == NULL_VALUE ? null : (V) value);
+ }
+
+ /**
+ * Create a value to cache for the given key.
+ * Called by {@code get} if there is no value cached already.
+ * @param key the cache key
+ * @see #get(Object)
+ */
+ protected abstract V create(K key);
+
+
+ @Override
+ public String toString() {
+ return "CachingMapDecorator [" + getClass().getName() + "]:" + this.targetMap;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/ClassUtils.java b/spring-core/src/main/java/org/springframework/util/ClassUtils.java
new file mode 100644
index 00000000..4bac3488
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java
@@ -0,0 +1,1251 @@
+/*
+ * Copyright 2002-2014 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.util;
+
+import java.beans.Introspector;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Miscellaneous class utility methods.
+ * Mainly for internal use within the framework.
+ *
+ * @author Juergen Hoeller
+ * @author Keith Donald
+ * @author Rob Harrop
+ * @author Sam Brannen
+ * @since 1.1
+ * @see TypeUtils
+ * @see ReflectionUtils
+ */
+public abstract class ClassUtils {
+
+ /** Suffix for array class names: "[]" */
+ public static final String ARRAY_SUFFIX = "[]";
+
+ /** Prefix for internal array class names: "[" */
+ private static final String INTERNAL_ARRAY_PREFIX = "[";
+
+ /** Prefix for internal non-primitive array class names: "[L" */
+ private static final String NON_PRIMITIVE_ARRAY_PREFIX = "[L";
+
+ /** The package separator character '.' */
+ private static final char PACKAGE_SEPARATOR = '.';
+
+ /** The inner class separator character '$' */
+ private static final char INNER_CLASS_SEPARATOR = '$';
+
+ /** The CGLIB class separator character "$$" */
+ public static final String CGLIB_CLASS_SEPARATOR = "$$";
+
+ /** The ".class" file suffix */
+ public static final String CLASS_FILE_SUFFIX = ".class";
+
+
+ /**
+ * Map with primitive wrapper type as key and corresponding primitive
+ * type as value, for example: Integer.class -> int.class.
+ */
+ private static final Map<Class<?>, Class<?>> primitiveWrapperTypeMap = new HashMap<Class<?>, Class<?>>(8);
+
+ /**
+ * Map with primitive type as key and corresponding wrapper
+ * type as value, for example: int.class -> Integer.class.
+ */
+ private static final Map<Class<?>, Class<?>> primitiveTypeToWrapperMap = new HashMap<Class<?>, Class<?>>(8);
+
+ /**
+ * Map with primitive type name as key and corresponding primitive
+ * type as value, for example: "int" -> "int.class".
+ */
+ private static final Map<String, Class<?>> primitiveTypeNameMap = new HashMap<String, Class<?>>(32);
+
+ /**
+ * Map with common "java.lang" class name as key and corresponding Class as value.
+ * Primarily for efficient deserialization of remote invocations.
+ */
+ private static final Map<String, Class<?>> commonClassCache = new HashMap<String, Class<?>>(32);
+
+
+ static {
+ primitiveWrapperTypeMap.put(Boolean.class, boolean.class);
+ primitiveWrapperTypeMap.put(Byte.class, byte.class);
+ primitiveWrapperTypeMap.put(Character.class, char.class);
+ primitiveWrapperTypeMap.put(Double.class, double.class);
+ primitiveWrapperTypeMap.put(Float.class, float.class);
+ primitiveWrapperTypeMap.put(Integer.class, int.class);
+ primitiveWrapperTypeMap.put(Long.class, long.class);
+ primitiveWrapperTypeMap.put(Short.class, short.class);
+
+ for (Map.Entry<Class<?>, Class<?>> entry : primitiveWrapperTypeMap.entrySet()) {
+ primitiveTypeToWrapperMap.put(entry.getValue(), entry.getKey());
+ registerCommonClasses(entry.getKey());
+ }
+
+ Set<Class<?>> primitiveTypes = new HashSet<Class<?>>(32);
+ primitiveTypes.addAll(primitiveWrapperTypeMap.values());
+ primitiveTypes.addAll(Arrays.asList(new Class<?>[] {
+ boolean[].class, byte[].class, char[].class, double[].class,
+ float[].class, int[].class, long[].class, short[].class}));
+ primitiveTypes.add(void.class);
+ for (Class<?> primitiveType : primitiveTypes) {
+ primitiveTypeNameMap.put(primitiveType.getName(), primitiveType);
+ }
+
+ registerCommonClasses(Boolean[].class, Byte[].class, Character[].class, Double[].class,
+ Float[].class, Integer[].class, Long[].class, Short[].class);
+ registerCommonClasses(Number.class, Number[].class, String.class, String[].class,
+ Object.class, Object[].class, Class.class, Class[].class);
+ registerCommonClasses(Throwable.class, Exception.class, RuntimeException.class,
+ Error.class, StackTraceElement.class, StackTraceElement[].class);
+ }
+
+
+ /**
+ * Register the given common classes with the ClassUtils cache.
+ */
+ private static void registerCommonClasses(Class<?>... commonClasses) {
+ for (Class<?> clazz : commonClasses) {
+ commonClassCache.put(clazz.getName(), clazz);
+ }
+ }
+
+ /**
+ * Return the default ClassLoader to use: typically the thread context
+ * ClassLoader, if available; the ClassLoader that loaded the ClassUtils
+ * class will be used as fallback.
+ * <p>Call this method if you intend to use the thread context ClassLoader
+ * in a scenario where you clearly prefer a non-null ClassLoader reference:
+ * for example, for class path resource loading (but not necessarily for
+ * {@code Class.forName}, which accepts a {@code null} ClassLoader
+ * reference as well).
+ * @return the default ClassLoader (only {@code null} if even the system
+ * ClassLoader isn't accessible)
+ * @see Thread#getContextClassLoader()
+ * @see ClassLoader#getSystemClassLoader()
+ */
+ public static ClassLoader getDefaultClassLoader() {
+ ClassLoader cl = null;
+ try {
+ cl = Thread.currentThread().getContextClassLoader();
+ }
+ catch (Throwable ex) {
+ // Cannot access thread context ClassLoader - falling back...
+ }
+ if (cl == null) {
+ // No thread context class loader -> use class loader of this class.
+ cl = ClassUtils.class.getClassLoader();
+ if (cl == null) {
+ // getClassLoader() returning null indicates the bootstrap ClassLoader
+ try {
+ cl = ClassLoader.getSystemClassLoader();
+ }
+ catch (Throwable ex) {
+ // Cannot access system ClassLoader - oh well, maybe the caller can live with null...
+ }
+ }
+ }
+ return cl;
+ }
+
+ /**
+ * Override the thread context ClassLoader with the environment's bean ClassLoader
+ * if necessary, i.e. if the bean ClassLoader is not equivalent to the thread
+ * context ClassLoader already.
+ * @param classLoaderToUse the actual ClassLoader to use for the thread context
+ * @return the original thread context ClassLoader, or {@code null} if not overridden
+ */
+ public static ClassLoader overrideThreadContextClassLoader(ClassLoader classLoaderToUse) {
+ Thread currentThread = Thread.currentThread();
+ ClassLoader threadContextClassLoader = currentThread.getContextClassLoader();
+ if (classLoaderToUse != null && !classLoaderToUse.equals(threadContextClassLoader)) {
+ currentThread.setContextClassLoader(classLoaderToUse);
+ return threadContextClassLoader;
+ }
+ else {
+ return null;
+ }
+ }
+
+ /**
+ * Replacement for {@code Class.forName()} that also returns Class instances
+ * for primitives (like "int") and array class names (like "String[]").
+ * <p>Always uses the default class loader: that is, preferably the thread context
+ * class loader, or the ClassLoader that loaded the ClassUtils class as fallback.
+ * @param name the name of the Class
+ * @return Class instance for the supplied name
+ * @throws ClassNotFoundException if the class was not found
+ * @throws LinkageError if the class file could not be loaded
+ * @see Class#forName(String, boolean, ClassLoader)
+ * @see #getDefaultClassLoader()
+ * @deprecated as of Spring 3.0, in favor of specifying a ClassLoader explicitly:
+ * see {@link #forName(String, ClassLoader)}
+ */
+ @Deprecated
+ public static Class<?> forName(String name) throws ClassNotFoundException, LinkageError {
+ return forName(name, getDefaultClassLoader());
+ }
+
+ /**
+ * Replacement for {@code Class.forName()} that also returns Class instances
+ * for primitives (e.g. "int") and array class names (e.g. "String[]").
+ * Furthermore, it is also capable of resolving inner class names in Java source
+ * style (e.g. "java.lang.Thread.State" instead of "java.lang.Thread$State").
+ * @param name the name of the Class
+ * @param classLoader the class loader to use
+ * (may be {@code null}, which indicates the default class loader)
+ * @return Class instance for the supplied name
+ * @throws ClassNotFoundException if the class was not found
+ * @throws LinkageError if the class file could not be loaded
+ * @see Class#forName(String, boolean, ClassLoader)
+ */
+ public static Class<?> forName(String name, ClassLoader classLoader) throws ClassNotFoundException, LinkageError {
+ Assert.notNull(name, "Name must not be null");
+
+ Class<?> clazz = resolvePrimitiveClassName(name);
+ if (clazz == null) {
+ clazz = commonClassCache.get(name);
+ }
+ if (clazz != null) {
+ return clazz;
+ }
+
+ // "java.lang.String[]" style arrays
+ if (name.endsWith(ARRAY_SUFFIX)) {
+ String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length());
+ Class<?> elementClass = forName(elementClassName, classLoader);
+ return Array.newInstance(elementClass, 0).getClass();
+ }
+
+ // "[Ljava.lang.String;" style arrays
+ if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) {
+ String elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length(), name.length() - 1);
+ Class<?> elementClass = forName(elementName, classLoader);
+ return Array.newInstance(elementClass, 0).getClass();
+ }
+
+ // "[[I" or "[[Ljava.lang.String;" style arrays
+ if (name.startsWith(INTERNAL_ARRAY_PREFIX)) {
+ String elementName = name.substring(INTERNAL_ARRAY_PREFIX.length());
+ Class<?> elementClass = forName(elementName, classLoader);
+ return Array.newInstance(elementClass, 0).getClass();
+ }
+
+ ClassLoader clToUse = classLoader;
+ if (clToUse == null) {
+ clToUse = getDefaultClassLoader();
+ }
+ try {
+ return (clToUse != null ? clToUse.loadClass(name) : Class.forName(name));
+ }
+ catch (ClassNotFoundException ex) {
+ int lastDotIndex = name.lastIndexOf('.');
+ if (lastDotIndex != -1) {
+ String innerClassName = name.substring(0, lastDotIndex) + '$' + name.substring(lastDotIndex + 1);
+ try {
+ return (clToUse != null ? clToUse.loadClass(innerClassName) : Class.forName(innerClassName));
+ }
+ catch (ClassNotFoundException ex2) {
+ // swallow - let original exception get through
+ }
+ }
+ throw ex;
+ }
+ }
+
+ /**
+ * Resolve the given class name into a Class instance. Supports
+ * primitives (like "int") and array class names (like "String[]").
+ * <p>This is effectively equivalent to the {@code forName}
+ * method with the same arguments, with the only difference being
+ * the exceptions thrown in case of class loading failure.
+ * @param className the name of the Class
+ * @param classLoader the class loader to use
+ * (may be {@code null}, which indicates the default class loader)
+ * @return Class instance for the supplied name
+ * @throws IllegalArgumentException if the class name was not resolvable
+ * (that is, the class could not be found or the class file could not be loaded)
+ * @see #forName(String, ClassLoader)
+ */
+ public static Class<?> resolveClassName(String className, ClassLoader classLoader) throws IllegalArgumentException {
+ try {
+ return forName(className, classLoader);
+ }
+ catch (ClassNotFoundException ex) {
+ throw new IllegalArgumentException("Cannot find class [" + className + "]", ex);
+ }
+ catch (LinkageError ex) {
+ throw new IllegalArgumentException(
+ "Error loading class [" + className + "]: problem with class file or dependent class.", ex);
+ }
+ }
+
+ /**
+ * Resolve the given class name as primitive class, if appropriate,
+ * according to the JVM's naming rules for primitive classes.
+ * <p>Also supports the JVM's internal class names for primitive arrays.
+ * Does <i>not</i> support the "[]" suffix notation for primitive arrays;
+ * this is only supported by {@link #forName(String, ClassLoader)}.
+ * @param name the name of the potentially primitive class
+ * @return the primitive class, or {@code null} if the name does not denote
+ * a primitive class or primitive array class
+ */
+ public static Class<?> resolvePrimitiveClassName(String name) {
+ Class<?> result = null;
+ // Most class names will be quite long, considering that they
+ // SHOULD sit in a package, so a length check is worthwhile.
+ if (name != null && name.length() <= 8) {
+ // Could be a primitive - likely.
+ result = primitiveTypeNameMap.get(name);
+ }
+ return result;
+ }
+
+ /**
+ * Determine whether the {@link Class} identified by the supplied name is present
+ * and can be loaded. Will return {@code false} if either the class or
+ * one of its dependencies is not present or cannot be loaded.
+ * @param className the name of the class to check
+ * @return whether the specified class is present
+ * @deprecated as of Spring 2.5, in favor of {@link #isPresent(String, ClassLoader)}
+ */
+ @Deprecated
+ public static boolean isPresent(String className) {
+ return isPresent(className, getDefaultClassLoader());
+ }
+
+ /**
+ * Determine whether the {@link Class} identified by the supplied name is present
+ * and can be loaded. Will return {@code false} if either the class or
+ * one of its dependencies is not present or cannot be loaded.
+ * @param className the name of the class to check
+ * @param classLoader the class loader to use
+ * (may be {@code null}, which indicates the default class loader)
+ * @return whether the specified class is present
+ */
+ public static boolean isPresent(String className, ClassLoader classLoader) {
+ try {
+ forName(className, classLoader);
+ return true;
+ }
+ catch (Throwable ex) {
+ // Class or one of its dependencies is not present...
+ return false;
+ }
+ }
+
+ /**
+ * Return the user-defined class for the given instance: usually simply
+ * the class of the given instance, but the original class in case of a
+ * CGLIB-generated subclass.
+ * @param instance the instance to check
+ * @return the user-defined class
+ */
+ public static Class<?> getUserClass(Object instance) {
+ Assert.notNull(instance, "Instance must not be null");
+ return getUserClass(instance.getClass());
+ }
+
+ /**
+ * Return the user-defined class for the given class: usually simply the given
+ * class, but the original class in case of a CGLIB-generated subclass.
+ * @param clazz the class to check
+ * @return the user-defined class
+ */
+ public static Class<?> getUserClass(Class<?> clazz) {
+ if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
+ Class<?> superClass = clazz.getSuperclass();
+ if (superClass != null && !Object.class.equals(superClass)) {
+ return superClass;
+ }
+ }
+ return clazz;
+ }
+
+ /**
+ * Check whether the given class is cache-safe in the given context,
+ * i.e. whether it is loaded by the given ClassLoader or a parent of it.
+ * @param clazz the class to analyze
+ * @param classLoader the ClassLoader to potentially cache metadata in
+ */
+ public static boolean isCacheSafe(Class<?> clazz, ClassLoader classLoader) {
+ Assert.notNull(clazz, "Class must not be null");
+ try {
+ ClassLoader target = clazz.getClassLoader();
+ if (target == null) {
+ return true;
+ }
+ ClassLoader cur = classLoader;
+ if (cur == target) {
+ return true;
+ }
+ while (cur != null) {
+ cur = cur.getParent();
+ if (cur == target) {
+ return true;
+ }
+ }
+ return false;
+ }
+ catch (SecurityException ex) {
+ // Probably from the system ClassLoader - let's consider it safe.
+ return true;
+ }
+ }
+
+
+ /**
+ * Get the class name without the qualified package name.
+ * @param className the className to get the short name for
+ * @return the class name of the class without the package name
+ * @throws IllegalArgumentException if the className is empty
+ */
+ public static String getShortName(String className) {
+ Assert.hasLength(className, "Class name must not be empty");
+ int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR);
+ int nameEndIndex = className.indexOf(CGLIB_CLASS_SEPARATOR);
+ if (nameEndIndex == -1) {
+ nameEndIndex = className.length();
+ }
+ String shortName = className.substring(lastDotIndex + 1, nameEndIndex);
+ shortName = shortName.replace(INNER_CLASS_SEPARATOR, PACKAGE_SEPARATOR);
+ return shortName;
+ }
+
+ /**
+ * Get the class name without the qualified package name.
+ * @param clazz the class to get the short name for
+ * @return the class name of the class without the package name
+ */
+ public static String getShortName(Class<?> clazz) {
+ return getShortName(getQualifiedName(clazz));
+ }
+
+ /**
+ * Return the short string name of a Java class in uncapitalized JavaBeans
+ * property format. Strips the outer class name in case of an inner class.
+ * @param clazz the class
+ * @return the short name rendered in a standard JavaBeans property format
+ * @see java.beans.Introspector#decapitalize(String)
+ */
+ public static String getShortNameAsProperty(Class<?> clazz) {
+ String shortName = ClassUtils.getShortName(clazz);
+ int dotIndex = shortName.lastIndexOf('.');
+ shortName = (dotIndex != -1 ? shortName.substring(dotIndex + 1) : shortName);
+ return Introspector.decapitalize(shortName);
+ }
+
+ /**
+ * Determine the name of the class file, relative to the containing
+ * package: e.g. "String.class"
+ * @param clazz the class
+ * @return the file name of the ".class" file
+ */
+ public static String getClassFileName(Class<?> clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ String className = clazz.getName();
+ int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR);
+ return className.substring(lastDotIndex + 1) + CLASS_FILE_SUFFIX;
+ }
+
+ /**
+ * Determine the name of the package of the given class,
+ * e.g. "java.lang" for the {@code java.lang.String} class.
+ * @param clazz the class
+ * @return the package name, or the empty String if the class
+ * is defined in the default package
+ */
+ public static String getPackageName(Class<?> clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ return getPackageName(clazz.getName());
+ }
+
+ /**
+ * Determine the name of the package of the given fully-qualified class name,
+ * e.g. "java.lang" for the {@code java.lang.String} class name.
+ * @param fqClassName the fully-qualified class name
+ * @return the package name, or the empty String if the class
+ * is defined in the default package
+ */
+ public static String getPackageName(String fqClassName) {
+ Assert.notNull(fqClassName, "Class name must not be null");
+ int lastDotIndex = fqClassName.lastIndexOf(PACKAGE_SEPARATOR);
+ return (lastDotIndex != -1 ? fqClassName.substring(0, lastDotIndex) : "");
+ }
+
+ /**
+ * Return the qualified name of the given class: usually simply
+ * the class name, but component type class name + "[]" for arrays.
+ * @param clazz the class
+ * @return the qualified name of the class
+ */
+ public static String getQualifiedName(Class<?> clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ if (clazz.isArray()) {
+ return getQualifiedNameForArray(clazz);
+ }
+ else {
+ return clazz.getName();
+ }
+ }
+
+ /**
+ * Build a nice qualified name for an array:
+ * component type class name + "[]".
+ * @param clazz the array class
+ * @return a qualified name for the array class
+ */
+ private static String getQualifiedNameForArray(Class<?> clazz) {
+ StringBuilder result = new StringBuilder();
+ while (clazz.isArray()) {
+ clazz = clazz.getComponentType();
+ result.append(ClassUtils.ARRAY_SUFFIX);
+ }
+ result.insert(0, clazz.getName());
+ return result.toString();
+ }
+
+ /**
+ * Return the qualified name of the given method, consisting of
+ * fully qualified interface/class name + "." + method name.
+ * @param method the method
+ * @return the qualified name of the method
+ */
+ public static String getQualifiedMethodName(Method method) {
+ Assert.notNull(method, "Method must not be null");
+ return method.getDeclaringClass().getName() + "." + method.getName();
+ }
+
+ /**
+ * Return a descriptive name for the given object's type: usually simply
+ * the class name, but component type class name + "[]" for arrays,
+ * and an appended list of implemented interfaces for JDK proxies.
+ * @param value the value to introspect
+ * @return the qualified name of the class
+ */
+ public static String getDescriptiveType(Object value) {
+ if (value == null) {
+ return null;
+ }
+ Class<?> clazz = value.getClass();
+ if (Proxy.isProxyClass(clazz)) {
+ StringBuilder result = new StringBuilder(clazz.getName());
+ result.append(" implementing ");
+ Class<?>[] ifcs = clazz.getInterfaces();
+ for (int i = 0; i < ifcs.length; i++) {
+ result.append(ifcs[i].getName());
+ if (i < ifcs.length - 1) {
+ result.append(',');
+ }
+ }
+ return result.toString();
+ }
+ else if (clazz.isArray()) {
+ return getQualifiedNameForArray(clazz);
+ }
+ else {
+ return clazz.getName();
+ }
+ }
+
+ /**
+ * Check whether the given class matches the user-specified type name.
+ * @param clazz the class to check
+ * @param typeName the type name to match
+ */
+ public static boolean matchesTypeName(Class<?> clazz, String typeName) {
+ return (typeName != null &&
+ (typeName.equals(clazz.getName()) || typeName.equals(clazz.getSimpleName()) ||
+ (clazz.isArray() && typeName.equals(getQualifiedNameForArray(clazz)))));
+ }
+
+
+ /**
+ * Determine whether the given class has a public constructor with the given signature.
+ * <p>Essentially translates {@code NoSuchMethodException} to "false".
+ * @param clazz the clazz to analyze
+ * @param paramTypes the parameter types of the method
+ * @return whether the class has a corresponding constructor
+ * @see Class#getMethod
+ */
+ public static boolean hasConstructor(Class<?> clazz, Class<?>... paramTypes) {
+ return (getConstructorIfAvailable(clazz, paramTypes) != null);
+ }
+
+ /**
+ * Determine whether the given class has a public constructor with the given signature,
+ * and return it if available (else return {@code null}).
+ * <p>Essentially translates {@code NoSuchMethodException} to {@code null}.
+ * @param clazz the clazz to analyze
+ * @param paramTypes the parameter types of the method
+ * @return the constructor, or {@code null} if not found
+ * @see Class#getConstructor
+ */
+ public static <T> Constructor<T> getConstructorIfAvailable(Class<T> clazz, Class<?>... paramTypes) {
+ Assert.notNull(clazz, "Class must not be null");
+ try {
+ return clazz.getConstructor(paramTypes);
+ }
+ catch (NoSuchMethodException ex) {
+ return null;
+ }
+ }
+
+ /**
+ * Determine whether the given class has a public method with the given signature.
+ * <p>Essentially translates {@code NoSuchMethodException} to "false".
+ * @param clazz the clazz to analyze
+ * @param methodName the name of the method
+ * @param paramTypes the parameter types of the method
+ * @return whether the class has a corresponding method
+ * @see Class#getMethod
+ */
+ public static boolean hasMethod(Class<?> clazz, String methodName, Class<?>... paramTypes) {
+ return (getMethodIfAvailable(clazz, methodName, paramTypes) != null);
+ }
+
+ /**
+ * Determine whether the given class has a public method with the given signature,
+ * and return it if available (else throws an {@code IllegalStateException}).
+ * <p>In case of any signature specified, only returns the method if there is a
+ * unique candidate, i.e. a single public method with the specified name.
+ * <p>Essentially translates {@code NoSuchMethodException} to {@code IllegalStateException}.
+ * @param clazz the clazz to analyze
+ * @param methodName the name of the method
+ * @param paramTypes the parameter types of the method
+ * (may be {@code null} to indicate any signature)
+ * @return the method (never {@code null})
+ * @throws IllegalStateException if the method has not been found
+ * @see Class#getMethod
+ */
+ public static Method getMethod(Class<?> clazz, String methodName, Class<?>... paramTypes) {
+ Assert.notNull(clazz, "Class must not be null");
+ Assert.notNull(methodName, "Method name must not be null");
+ if (paramTypes != null) {
+ try {
+ return clazz.getMethod(methodName, paramTypes);
+ }
+ catch (NoSuchMethodException ex) {
+ throw new IllegalStateException("Expected method not found: " + ex);
+ }
+ }
+ else {
+ Set<Method> candidates = new HashSet<Method>(1);
+ Method[] methods = clazz.getMethods();
+ for (Method method : methods) {
+ if (methodName.equals(method.getName())) {
+ candidates.add(method);
+ }
+ }
+ if (candidates.size() == 1) {
+ return candidates.iterator().next();
+ }
+ else if (candidates.isEmpty()) {
+ throw new IllegalStateException("Expected method not found: " + clazz + "." + methodName);
+ }
+ else {
+ throw new IllegalStateException("No unique method found: " + clazz + "." + methodName);
+ }
+ }
+ }
+
+ /**
+ * Determine whether the given class has a public method with the given signature,
+ * and return it if available (else return {@code null}).
+ * <p>In case of any signature specified, only returns the method if there is a
+ * unique candidate, i.e. a single public method with the specified name.
+ * <p>Essentially translates {@code NoSuchMethodException} to {@code null}.
+ * @param clazz the clazz to analyze
+ * @param methodName the name of the method
+ * @param paramTypes the parameter types of the method
+ * (may be {@code null} to indicate any signature)
+ * @return the method, or {@code null} if not found
+ * @see Class#getMethod
+ */
+ public static Method getMethodIfAvailable(Class<?> clazz, String methodName, Class<?>... paramTypes) {
+ Assert.notNull(clazz, "Class must not be null");
+ Assert.notNull(methodName, "Method name must not be null");
+ if (paramTypes != null) {
+ try {
+ return clazz.getMethod(methodName, paramTypes);
+ }
+ catch (NoSuchMethodException ex) {
+ return null;
+ }
+ }
+ else {
+ Set<Method> candidates = new HashSet<Method>(1);
+ Method[] methods = clazz.getMethods();
+ for (Method method : methods) {
+ if (methodName.equals(method.getName())) {
+ candidates.add(method);
+ }
+ }
+ if (candidates.size() == 1) {
+ return candidates.iterator().next();
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Return the number of methods with a given name (with any argument types),
+ * for the given class and/or its superclasses. Includes non-public methods.
+ * @param clazz the clazz to check
+ * @param methodName the name of the method
+ * @return the number of methods with the given name
+ */
+ public static int getMethodCountForName(Class<?> clazz, String methodName) {
+ Assert.notNull(clazz, "Class must not be null");
+ Assert.notNull(methodName, "Method name must not be null");
+ int count = 0;
+ Method[] declaredMethods = clazz.getDeclaredMethods();
+ for (Method method : declaredMethods) {
+ if (methodName.equals(method.getName())) {
+ count++;
+ }
+ }
+ Class<?>[] ifcs = clazz.getInterfaces();
+ for (Class<?> ifc : ifcs) {
+ count += getMethodCountForName(ifc, methodName);
+ }
+ if (clazz.getSuperclass() != null) {
+ count += getMethodCountForName(clazz.getSuperclass(), methodName);
+ }
+ return count;
+ }
+
+ /**
+ * Does the given class or one of its superclasses at least have one or more
+ * methods with the supplied name (with any argument types)?
+ * Includes non-public methods.
+ * @param clazz the clazz to check
+ * @param methodName the name of the method
+ * @return whether there is at least one method with the given name
+ */
+ public static boolean hasAtLeastOneMethodWithName(Class<?> clazz, String methodName) {
+ Assert.notNull(clazz, "Class must not be null");
+ Assert.notNull(methodName, "Method name must not be null");
+ Method[] declaredMethods = clazz.getDeclaredMethods();
+ for (Method method : declaredMethods) {
+ if (method.getName().equals(methodName)) {
+ return true;
+ }
+ }
+ Class<?>[] ifcs = clazz.getInterfaces();
+ for (Class<?> ifc : ifcs) {
+ if (hasAtLeastOneMethodWithName(ifc, methodName)) {
+ return true;
+ }
+ }
+ return (clazz.getSuperclass() != null && hasAtLeastOneMethodWithName(clazz.getSuperclass(), methodName));
+ }
+
+ /**
+ * Given a method, which may come from an interface, and a target class used
+ * in the current reflective invocation, find the corresponding target method
+ * if there is one. E.g. the method may be {@code IFoo.bar()} and the
+ * target class may be {@code DefaultFoo}. In this case, the method may be
+ * {@code DefaultFoo.bar()}. This enables attributes on that method to be found.
+ * <p><b>NOTE:</b> In contrast to {@link org.springframework.aop.support.AopUtils#getMostSpecificMethod},
+ * this method does <i>not</i> resolve Java 5 bridge methods automatically.
+ * Call {@link org.springframework.core.BridgeMethodResolver#findBridgedMethod}
+ * if bridge method resolution is desirable (e.g. for obtaining metadata from
+ * the original method definition).
+ * <p><b>NOTE:</b> Since Spring 3.1.1, if Java security settings disallow reflective
+ * access (e.g. calls to {@code Class#getDeclaredMethods} etc, this implementation
+ * will fall back to returning the originally provided method.
+ * @param method the method to be invoked, which may come from an interface
+ * @param targetClass the target class for the current invocation.
+ * May be {@code null} or may not even implement the method.
+ * @return the specific target method, or the original method if the
+ * {@code targetClass} doesn't implement it or is {@code null}
+ */
+ public static Method getMostSpecificMethod(Method method, Class<?> targetClass) {
+ if (method != null && isOverridable(method, targetClass) &&
+ targetClass != null && !targetClass.equals(method.getDeclaringClass())) {
+ try {
+ if (Modifier.isPublic(method.getModifiers())) {
+ try {
+ return targetClass.getMethod(method.getName(), method.getParameterTypes());
+ }
+ catch (NoSuchMethodException ex) {
+ return method;
+ }
+ }
+ else {
+ Method specificMethod =
+ ReflectionUtils.findMethod(targetClass, method.getName(), method.getParameterTypes());
+ return (specificMethod != null ? specificMethod : method);
+ }
+ }
+ catch (SecurityException ex) {
+ // Security settings are disallowing reflective access; fall back to 'method' below.
+ }
+ }
+ return method;
+ }
+
+ /**
+ * Determine whether the given method is overridable in the given target class.
+ * @param method the method to check
+ * @param targetClass the target class to check against
+ */
+ private static boolean isOverridable(Method method, Class<?> targetClass) {
+ if (Modifier.isPrivate(method.getModifiers())) {
+ return false;
+ }
+ if (Modifier.isPublic(method.getModifiers()) || Modifier.isProtected(method.getModifiers())) {
+ return true;
+ }
+ return getPackageName(method.getDeclaringClass()).equals(getPackageName(targetClass));
+ }
+
+ /**
+ * Return a public static method of a class.
+ * @param methodName the static method name
+ * @param clazz the class which defines the method
+ * @param args the parameter types to the method
+ * @return the static method, or {@code null} if no static method was found
+ * @throws IllegalArgumentException if the method name is blank or the clazz is null
+ */
+ public static Method getStaticMethod(Class<?> clazz, String methodName, Class<?>... args) {
+ Assert.notNull(clazz, "Class must not be null");
+ Assert.notNull(methodName, "Method name must not be null");
+ try {
+ Method method = clazz.getMethod(methodName, args);
+ return Modifier.isStatic(method.getModifiers()) ? method : null;
+ }
+ catch (NoSuchMethodException ex) {
+ return null;
+ }
+ }
+
+
+ /**
+ * Check if the given class represents a primitive wrapper,
+ * i.e. Boolean, Byte, Character, Short, Integer, Long, Float, or Double.
+ * @param clazz the class to check
+ * @return whether the given class is a primitive wrapper class
+ */
+ public static boolean isPrimitiveWrapper(Class<?> clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ return primitiveWrapperTypeMap.containsKey(clazz);
+ }
+
+ /**
+ * Check if the given class represents a primitive (i.e. boolean, byte,
+ * char, short, int, long, float, or double) or a primitive wrapper
+ * (i.e. Boolean, Byte, Character, Short, Integer, Long, Float, or Double).
+ * @param clazz the class to check
+ * @return whether the given class is a primitive or primitive wrapper class
+ */
+ public static boolean isPrimitiveOrWrapper(Class<?> clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ return (clazz.isPrimitive() || isPrimitiveWrapper(clazz));
+ }
+
+ /**
+ * Check if the given class represents an array of primitives,
+ * i.e. boolean, byte, char, short, int, long, float, or double.
+ * @param clazz the class to check
+ * @return whether the given class is a primitive array class
+ */
+ public static boolean isPrimitiveArray(Class<?> clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ return (clazz.isArray() && clazz.getComponentType().isPrimitive());
+ }
+
+ /**
+ * Check if the given class represents an array of primitive wrappers,
+ * i.e. Boolean, Byte, Character, Short, Integer, Long, Float, or Double.
+ * @param clazz the class to check
+ * @return whether the given class is a primitive wrapper array class
+ */
+ public static boolean isPrimitiveWrapperArray(Class<?> clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ return (clazz.isArray() && isPrimitiveWrapper(clazz.getComponentType()));
+ }
+
+ /**
+ * Resolve the given class if it is a primitive class,
+ * returning the corresponding primitive wrapper type instead.
+ * @param clazz the class to check
+ * @return the original class, or a primitive wrapper for the original primitive type
+ */
+ public static Class<?> resolvePrimitiveIfNecessary(Class<?> clazz) {
+ Assert.notNull(clazz, "Class must not be null");
+ return (clazz.isPrimitive() && clazz != void.class? primitiveTypeToWrapperMap.get(clazz) : clazz);
+ }
+
+ /**
+ * Check if the right-hand side type may be assigned to the left-hand side
+ * type, assuming setting by reflection. Considers primitive wrapper
+ * classes as assignable to the corresponding primitive types.
+ * @param lhsType the target type
+ * @param rhsType the value type that should be assigned to the target type
+ * @return if the target type is assignable from the value type
+ * @see TypeUtils#isAssignable
+ */
+ public static boolean isAssignable(Class<?> lhsType, Class<?> rhsType) {
+ Assert.notNull(lhsType, "Left-hand side type must not be null");
+ Assert.notNull(rhsType, "Right-hand side type must not be null");
+ if (lhsType.isAssignableFrom(rhsType)) {
+ return true;
+ }
+ if (lhsType.isPrimitive()) {
+ Class<?> resolvedPrimitive = primitiveWrapperTypeMap.get(rhsType);
+ if (resolvedPrimitive != null && lhsType.equals(resolvedPrimitive)) {
+ return true;
+ }
+ }
+ else {
+ Class<?> resolvedWrapper = primitiveTypeToWrapperMap.get(rhsType);
+ if (resolvedWrapper != null && lhsType.isAssignableFrom(resolvedWrapper)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Determine if the given type is assignable from the given value,
+ * assuming setting by reflection. Considers primitive wrapper classes
+ * as assignable to the corresponding primitive types.
+ * @param type the target type
+ * @param value the value that should be assigned to the type
+ * @return if the type is assignable from the value
+ */
+ public static boolean isAssignableValue(Class<?> type, Object value) {
+ Assert.notNull(type, "Type must not be null");
+ return (value != null ? isAssignable(type, value.getClass()) : !type.isPrimitive());
+ }
+
+
+ /**
+ * Convert a "/"-based resource path to a "."-based fully qualified class name.
+ * @param resourcePath the resource path pointing to a class
+ * @return the corresponding fully qualified class name
+ */
+ public static String convertResourcePathToClassName(String resourcePath) {
+ Assert.notNull(resourcePath, "Resource path must not be null");
+ return resourcePath.replace('/', '.');
+ }
+
+ /**
+ * Convert a "."-based fully qualified class name to a "/"-based resource path.
+ * @param className the fully qualified class name
+ * @return the corresponding resource path, pointing to the class
+ */
+ public static String convertClassNameToResourcePath(String className) {
+ Assert.notNull(className, "Class name must not be null");
+ return className.replace('.', '/');
+ }
+
+ /**
+ * Return a path suitable for use with {@code ClassLoader.getResource}
+ * (also suitable for use with {@code Class.getResource} by prepending a
+ * slash ('/') to the return value). Built by taking the package of the specified
+ * class file, converting all dots ('.') to slashes ('/'), adding a trailing slash
+ * if necessary, and concatenating the specified resource name to this.
+ * <br/>As such, this function may be used to build a path suitable for
+ * loading a resource file that is in the same package as a class file,
+ * although {@link org.springframework.core.io.ClassPathResource} is usually
+ * even more convenient.
+ * @param clazz the Class whose package will be used as the base
+ * @param resourceName the resource name to append. A leading slash is optional.
+ * @return the built-up resource path
+ * @see ClassLoader#getResource
+ * @see Class#getResource
+ */
+ public static String addResourcePathToPackagePath(Class<?> clazz, String resourceName) {
+ Assert.notNull(resourceName, "Resource name must not be null");
+ if (!resourceName.startsWith("/")) {
+ return classPackageAsResourcePath(clazz) + "/" + resourceName;
+ }
+ return classPackageAsResourcePath(clazz) + resourceName;
+ }
+
+ /**
+ * Given an input class object, return a string which consists of the
+ * class's package name as a pathname, i.e., all dots ('.') are replaced by
+ * slashes ('/'). Neither a leading nor trailing slash is added. The result
+ * could be concatenated with a slash and the name of a resource and fed
+ * directly to {@code ClassLoader.getResource()}. For it to be fed to
+ * {@code Class.getResource} instead, a leading slash would also have
+ * to be prepended to the returned value.
+ * @param clazz the input class. A {@code null} value or the default
+ * (empty) package will result in an empty string ("") being returned.
+ * @return a path which represents the package name
+ * @see ClassLoader#getResource
+ * @see Class#getResource
+ */
+ public static String classPackageAsResourcePath(Class<?> clazz) {
+ if (clazz == null) {
+ return "";
+ }
+ String className = clazz.getName();
+ int packageEndIndex = className.lastIndexOf('.');
+ if (packageEndIndex == -1) {
+ return "";
+ }
+ String packageName = className.substring(0, packageEndIndex);
+ return packageName.replace('.', '/');
+ }
+
+ /**
+ * Build a String that consists of the names of the classes/interfaces
+ * in the given array.
+ * <p>Basically like {@code AbstractCollection.toString()}, but stripping
+ * the "class "/"interface " prefix before every class name.
+ * @param classes a Collection of Class objects (may be {@code null})
+ * @return a String of form "[com.foo.Bar, com.foo.Baz]"
+ * @see java.util.AbstractCollection#toString()
+ */
+ public static String classNamesToString(Class... classes) {
+ return classNamesToString(Arrays.asList(classes));
+ }
+
+ /**
+ * Build a String that consists of the names of the classes/interfaces
+ * in the given collection.
+ * <p>Basically like {@code AbstractCollection.toString()}, but stripping
+ * the "class "/"interface " prefix before every class name.
+ * @param classes a Collection of Class objects (may be {@code null})
+ * @return a String of form "[com.foo.Bar, com.foo.Baz]"
+ * @see java.util.AbstractCollection#toString()
+ */
+ public static String classNamesToString(Collection<Class> classes) {
+ if (CollectionUtils.isEmpty(classes)) {
+ return "[]";
+ }
+ StringBuilder sb = new StringBuilder("[");
+ for (Iterator<Class> it = classes.iterator(); it.hasNext(); ) {
+ Class clazz = it.next();
+ sb.append(clazz.getName());
+ if (it.hasNext()) {
+ sb.append(", ");
+ }
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ /**
+ * Copy the given Collection into a Class array.
+ * The Collection must contain Class elements only.
+ * @param collection the Collection to copy
+ * @return the Class array ({@code null} if the passed-in
+ * Collection was {@code null})
+ */
+ public static Class<?>[] toClassArray(Collection<Class<?>> collection) {
+ if (collection == null) {
+ return null;
+ }
+ return collection.toArray(new Class<?>[collection.size()]);
+ }
+
+ /**
+ * Return all interfaces that the given instance implements as array,
+ * including ones implemented by superclasses.
+ * @param instance the instance to analyze for interfaces
+ * @return all interfaces that the given instance implements as array
+ */
+ public static Class<?>[] getAllInterfaces(Object instance) {
+ Assert.notNull(instance, "Instance must not be null");
+ return getAllInterfacesForClass(instance.getClass());
+ }
+
+ /**
+ * Return all interfaces that the given class implements as array,
+ * including ones implemented by superclasses.
+ * <p>If the class itself is an interface, it gets returned as sole interface.
+ * @param clazz the class to analyze for interfaces
+ * @return all interfaces that the given object implements as array
+ */
+ public static Class<?>[] getAllInterfacesForClass(Class<?> clazz) {
+ return getAllInterfacesForClass(clazz, null);
+ }
+
+ /**
+ * Return all interfaces that the given class implements as array,
+ * including ones implemented by superclasses.
+ * <p>If the class itself is an interface, it gets returned as sole interface.
+ * @param clazz the class to analyze for interfaces
+ * @param classLoader the ClassLoader that the interfaces need to be visible in
+ * (may be {@code null} when accepting all declared interfaces)
+ * @return all interfaces that the given object implements as array
+ */
+ public static Class<?>[] getAllInterfacesForClass(Class<?> clazz, ClassLoader classLoader) {
+ Set<Class> ifcs = getAllInterfacesForClassAsSet(clazz, classLoader);
+ return ifcs.toArray(new Class[ifcs.size()]);
+ }
+
+ /**
+ * Return all interfaces that the given instance implements as Set,
+ * including ones implemented by superclasses.
+ * @param instance the instance to analyze for interfaces
+ * @return all interfaces that the given instance implements as Set
+ */
+ public static Set<Class> getAllInterfacesAsSet(Object instance) {
+ Assert.notNull(instance, "Instance must not be null");
+ return getAllInterfacesForClassAsSet(instance.getClass());
+ }
+
+ /**
+ * Return all interfaces that the given class implements as Set,
+ * including ones implemented by superclasses.
+ * <p>If the class itself is an interface, it gets returned as sole interface.
+ * @param clazz the class to analyze for interfaces
+ * @return all interfaces that the given object implements as Set
+ */
+ public static Set<Class> getAllInterfacesForClassAsSet(Class clazz) {
+ return getAllInterfacesForClassAsSet(clazz, null);
+ }
+
+ /**
+ * Return all interfaces that the given class implements as Set,
+ * including ones implemented by superclasses.
+ * <p>If the class itself is an interface, it gets returned as sole interface.
+ * @param clazz the class to analyze for interfaces
+ * @param classLoader the ClassLoader that the interfaces need to be visible in
+ * (may be {@code null} when accepting all declared interfaces)
+ * @return all interfaces that the given object implements as Set
+ */
+ public static Set<Class> getAllInterfacesForClassAsSet(Class clazz, ClassLoader classLoader) {
+ Assert.notNull(clazz, "Class must not be null");
+ if (clazz.isInterface() && isVisible(clazz, classLoader)) {
+ return Collections.singleton(clazz);
+ }
+ Set<Class> interfaces = new LinkedHashSet<Class>();
+ while (clazz != null) {
+ Class<?>[] ifcs = clazz.getInterfaces();
+ for (Class<?> ifc : ifcs) {
+ interfaces.addAll(getAllInterfacesForClassAsSet(ifc, classLoader));
+ }
+ clazz = clazz.getSuperclass();
+ }
+ return interfaces;
+ }
+
+ /**
+ * Create a composite interface Class for the given interfaces,
+ * implementing the given interfaces in one single Class.
+ * <p>This implementation builds a JDK proxy class for the given interfaces.
+ * @param interfaces the interfaces to merge
+ * @param classLoader the ClassLoader to create the composite Class in
+ * @return the merged interface as Class
+ * @see java.lang.reflect.Proxy#getProxyClass
+ */
+ public static Class<?> createCompositeInterface(Class<?>[] interfaces, ClassLoader classLoader) {
+ Assert.notEmpty(interfaces, "Interfaces must not be empty");
+ Assert.notNull(classLoader, "ClassLoader must not be null");
+ return Proxy.getProxyClass(classLoader, interfaces);
+ }
+
+ /**
+ * Determine the common ancestor of the given classes, if any.
+ * @param clazz1 the class to introspect
+ * @param clazz2 the other class to introspect
+ * @return the common ancestor (i.e. common superclass, one interface
+ * extending the other), or {@code null} if none found. If any of the
+ * given classes is {@code null}, the other class will be returned.
+ * @since 3.2.6
+ */
+ public static Class<?> determineCommonAncestor(Class<?> clazz1, Class<?> clazz2) {
+ if (clazz1 == null) {
+ return clazz2;
+ }
+ if (clazz2 == null) {
+ return clazz1;
+ }
+ if (clazz1.isAssignableFrom(clazz2)) {
+ return clazz1;
+ }
+ if (clazz2.isAssignableFrom(clazz1)) {
+ return clazz2;
+ }
+ Class<?> ancestor = clazz1;
+ do {
+ ancestor = ancestor.getSuperclass();
+ if (ancestor == null || Object.class.equals(ancestor)) {
+ return null;
+ }
+ }
+ while (!ancestor.isAssignableFrom(clazz2));
+ return ancestor;
+ }
+
+ /**
+ * Check whether the given class is visible in the given ClassLoader.
+ * @param clazz the class to check (typically an interface)
+ * @param classLoader the ClassLoader to check against (may be {@code null},
+ * in which case this method will always return {@code true})
+ */
+ public static boolean isVisible(Class<?> clazz, ClassLoader classLoader) {
+ if (classLoader == null) {
+ return true;
+ }
+ try {
+ Class<?> actualClass = classLoader.loadClass(clazz.getName());
+ return (clazz == actualClass);
+ // Else: different interface class found...
+ }
+ catch (ClassNotFoundException ex) {
+ // No interface class found...
+ return false;
+ }
+ }
+
+ /**
+ * Check whether the given object is a CGLIB proxy.
+ * @param object the object to check
+ * @see org.springframework.aop.support.AopUtils#isCglibProxy(Object)
+ */
+ public static boolean isCglibProxy(Object object) {
+ return ClassUtils.isCglibProxyClass(object.getClass());
+ }
+
+ /**
+ * Check whether the specified class is a CGLIB-generated class.
+ * @param clazz the class to check
+ */
+ public static boolean isCglibProxyClass(Class<?> clazz) {
+ return (clazz != null && isCglibProxyClassName(clazz.getName()));
+ }
+
+ /**
+ * Check whether the specified class name is a CGLIB-generated class.
+ * @param className the class name to check
+ */
+ public static boolean isCglibProxyClassName(String className) {
+ return (className != null && className.contains(CGLIB_CLASS_SEPARATOR));
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/CollectionUtils.java b/spring-core/src/main/java/org/springframework/util/CollectionUtils.java
new file mode 100644
index 00000000..5e74473f
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/CollectionUtils.java
@@ -0,0 +1,500 @@
+/*
+ * Copyright 2002-2012 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.util;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * Miscellaneous collection utility methods.
+ * Mainly for internal use within the framework.
+ *
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @author Arjen Poutsma
+ * @since 1.1.3
+ */
+public abstract class CollectionUtils {
+
+ /**
+ * Return {@code true} if the supplied Collection is {@code null}
+ * or empty. Otherwise, return {@code false}.
+ * @param collection the Collection to check
+ * @return whether the given Collection is empty
+ */
+ public static boolean isEmpty(Collection collection) {
+ return (collection == null || collection.isEmpty());
+ }
+
+ /**
+ * Return {@code true} if the supplied Map is {@code null}
+ * or empty. Otherwise, return {@code false}.
+ * @param map the Map to check
+ * @return whether the given Map is empty
+ */
+ public static boolean isEmpty(Map map) {
+ return (map == null || map.isEmpty());
+ }
+
+ /**
+ * Convert the supplied array into a List. A primitive array gets
+ * converted into a List of the appropriate wrapper type.
+ * <p>A {@code null} source value will be converted to an
+ * empty List.
+ * @param source the (potentially primitive) array
+ * @return the converted List result
+ * @see ObjectUtils#toObjectArray(Object)
+ */
+ public static List arrayToList(Object source) {
+ return Arrays.asList(ObjectUtils.toObjectArray(source));
+ }
+
+ /**
+ * Merge the given array into the given Collection.
+ * @param array the array to merge (may be {@code null})
+ * @param collection the target Collection to merge the array into
+ */
+ @SuppressWarnings("unchecked")
+ public static void mergeArrayIntoCollection(Object array, Collection collection) {
+ if (collection == null) {
+ throw new IllegalArgumentException("Collection must not be null");
+ }
+ Object[] arr = ObjectUtils.toObjectArray(array);
+ for (Object elem : arr) {
+ collection.add(elem);
+ }
+ }
+
+ /**
+ * Merge the given Properties instance into the given Map,
+ * copying all properties (key-value pairs) over.
+ * <p>Uses {@code Properties.propertyNames()} to even catch
+ * default properties linked into the original Properties instance.
+ * @param props the Properties instance to merge (may be {@code null})
+ * @param map the target Map to merge the properties into
+ */
+ @SuppressWarnings("unchecked")
+ public static void mergePropertiesIntoMap(Properties props, Map map) {
+ if (map == null) {
+ throw new IllegalArgumentException("Map must not be null");
+ }
+ if (props != null) {
+ for (Enumeration en = props.propertyNames(); en.hasMoreElements();) {
+ String key = (String) en.nextElement();
+ Object value = props.getProperty(key);
+ if (value == null) {
+ // Potentially a non-String value...
+ value = props.get(key);
+ }
+ map.put(key, value);
+ }
+ }
+ }
+
+
+ /**
+ * Check whether the given Iterator contains the given element.
+ * @param iterator the Iterator to check
+ * @param element the element to look for
+ * @return {@code true} if found, {@code false} else
+ */
+ public static boolean contains(Iterator iterator, Object element) {
+ if (iterator != null) {
+ while (iterator.hasNext()) {
+ Object candidate = iterator.next();
+ if (ObjectUtils.nullSafeEquals(candidate, element)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the given Enumeration contains the given element.
+ * @param enumeration the Enumeration to check
+ * @param element the element to look for
+ * @return {@code true} if found, {@code false} else
+ */
+ public static boolean contains(Enumeration enumeration, Object element) {
+ if (enumeration != null) {
+ while (enumeration.hasMoreElements()) {
+ Object candidate = enumeration.nextElement();
+ if (ObjectUtils.nullSafeEquals(candidate, element)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the given Collection contains the given element instance.
+ * <p>Enforces the given instance to be present, rather than returning
+ * {@code true} for an equal element as well.
+ * @param collection the Collection to check
+ * @param element the element to look for
+ * @return {@code true} if found, {@code false} else
+ */
+ public static boolean containsInstance(Collection collection, Object element) {
+ if (collection != null) {
+ for (Object candidate : collection) {
+ if (candidate == element) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return {@code true} if any element in '{@code candidates}' is
+ * contained in '{@code source}'; otherwise returns {@code false}.
+ * @param source the source Collection
+ * @param candidates the candidates to search for
+ * @return whether any of the candidates has been found
+ */
+ public static boolean containsAny(Collection source, Collection candidates) {
+ if (isEmpty(source) || isEmpty(candidates)) {
+ return false;
+ }
+ for (Object candidate : candidates) {
+ if (source.contains(candidate)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return the first element in '{@code candidates}' that is contained in
+ * '{@code source}'. If no element in '{@code candidates}' is present in
+ * '{@code source}' returns {@code null}. Iteration order is
+ * {@link Collection} implementation specific.
+ * @param source the source Collection
+ * @param candidates the candidates to search for
+ * @return the first present object, or {@code null} if not found
+ */
+ public static Object findFirstMatch(Collection source, Collection candidates) {
+ if (isEmpty(source) || isEmpty(candidates)) {
+ return null;
+ }
+ for (Object candidate : candidates) {
+ if (source.contains(candidate)) {
+ return candidate;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Find a single value of the given type in the given Collection.
+ * @param collection the Collection to search
+ * @param type the type to look for
+ * @return a value of the given type found if there is a clear match,
+ * or {@code null} if none or more than one such value found
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T findValueOfType(Collection<?> collection, Class<T> type) {
+ if (isEmpty(collection)) {
+ return null;
+ }
+ T value = null;
+ for (Object element : collection) {
+ if (type == null || type.isInstance(element)) {
+ if (value != null) {
+ // More than one value found... no clear single value.
+ return null;
+ }
+ value = (T) element;
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Find a single value of one of the given types in the given Collection:
+ * searching the Collection for a value of the first type, then
+ * searching for a value of the second type, etc.
+ * @param collection the collection to search
+ * @param types the types to look for, in prioritized order
+ * @return a value of one of the given types found if there is a clear match,
+ * or {@code null} if none or more than one such value found
+ */
+ public static Object findValueOfType(Collection<?> collection, Class<?>[] types) {
+ if (isEmpty(collection) || ObjectUtils.isEmpty(types)) {
+ return null;
+ }
+ for (Class<?> type : types) {
+ Object value = findValueOfType(collection, type);
+ if (value != null) {
+ return value;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Determine whether the given Collection only contains a single unique object.
+ * @param collection the Collection to check
+ * @return {@code true} if the collection contains a single reference or
+ * multiple references to the same instance, {@code false} else
+ */
+ public static boolean hasUniqueObject(Collection collection) {
+ if (isEmpty(collection)) {
+ return false;
+ }
+ boolean hasCandidate = false;
+ Object candidate = null;
+ for (Object elem : collection) {
+ if (!hasCandidate) {
+ hasCandidate = true;
+ candidate = elem;
+ }
+ else if (candidate != elem) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Find the common element type of the given Collection, if any.
+ * @param collection the Collection to check
+ * @return the common element type, or {@code null} if no clear
+ * common type has been found (or the collection was empty)
+ */
+ public static Class<?> findCommonElementType(Collection collection) {
+ if (isEmpty(collection)) {
+ return null;
+ }
+ Class<?> candidate = null;
+ for (Object val : collection) {
+ if (val != null) {
+ if (candidate == null) {
+ candidate = val.getClass();
+ }
+ else if (candidate != val.getClass()) {
+ return null;
+ }
+ }
+ }
+ return candidate;
+ }
+
+ /**
+ * Marshal the elements from the given enumeration into an array of the given type.
+ * Enumeration elements must be assignable to the type of the given array. The array
+ * returned will be a different instance than the array given.
+ */
+ public static <A,E extends A> A[] toArray(Enumeration<E> enumeration, A[] array) {
+ ArrayList<A> elements = new ArrayList<A>();
+ while (enumeration.hasMoreElements()) {
+ elements.add(enumeration.nextElement());
+ }
+ return elements.toArray(array);
+ }
+
+ /**
+ * Adapt an enumeration to an iterator.
+ * @param enumeration the enumeration
+ * @return the iterator
+ */
+ public static <E> Iterator<E> toIterator(Enumeration<E> enumeration) {
+ return new EnumerationIterator<E>(enumeration);
+ }
+
+ /**
+ * Adapts a {@code Map<K, List<V>>} to an {@code MultiValueMap<K,V>}.
+ *
+ * @param map the map
+ * @return the multi-value map
+ */
+ public static <K, V> MultiValueMap<K, V> toMultiValueMap(Map<K, List<V>> map) {
+ return new MultiValueMapAdapter<K, V>(map);
+
+ }
+
+ /**
+ * Returns an unmodifiable view of the specified multi-value map.
+ *
+ * @param map the map for which an unmodifiable view is to be returned.
+ * @return an unmodifiable view of the specified multi-value map.
+ */
+ public static <K,V> MultiValueMap<K,V> unmodifiableMultiValueMap(MultiValueMap<? extends K, ? extends V> map) {
+ Assert.notNull(map, "'map' must not be null");
+ Map<K, List<V>> result = new LinkedHashMap<K, List<V>>(map.size());
+ for (Map.Entry<? extends K, ? extends List<? extends V>> entry : map.entrySet()) {
+ List<V> values = Collections.unmodifiableList(entry.getValue());
+ result.put(entry.getKey(), values);
+ }
+ Map<K, List<V>> unmodifiableMap = Collections.unmodifiableMap(result);
+ return toMultiValueMap(unmodifiableMap);
+ }
+
+
+
+ /**
+ * Iterator wrapping an Enumeration.
+ */
+ private static class EnumerationIterator<E> implements Iterator<E> {
+
+ private Enumeration<E> enumeration;
+
+ public EnumerationIterator(Enumeration<E> enumeration) {
+ this.enumeration = enumeration;
+ }
+
+ public boolean hasNext() {
+ return this.enumeration.hasMoreElements();
+ }
+
+ public E next() {
+ return this.enumeration.nextElement();
+ }
+
+ public void remove() throws UnsupportedOperationException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+ }
+
+ /**
+ * Adapts a Map to the MultiValueMap contract.
+ */
+ @SuppressWarnings("serial")
+ private static class MultiValueMapAdapter<K, V> implements MultiValueMap<K, V>, Serializable {
+
+ private final Map<K, List<V>> map;
+
+ public MultiValueMapAdapter(Map<K, List<V>> map) {
+ Assert.notNull(map, "'map' must not be null");
+ this.map = map;
+ }
+
+ public void add(K key, V value) {
+ List<V> values = this.map.get(key);
+ if (values == null) {
+ values = new LinkedList<V>();
+ this.map.put(key, values);
+ }
+ values.add(value);
+ }
+
+ public V getFirst(K key) {
+ List<V> values = this.map.get(key);
+ return (values != null ? values.get(0) : null);
+ }
+
+ public void set(K key, V value) {
+ List<V> values = new LinkedList<V>();
+ values.add(value);
+ this.map.put(key, values);
+ }
+
+ public void setAll(Map<K, V> values) {
+ for (Entry<K, V> entry : values.entrySet()) {
+ set(entry.getKey(), entry.getValue());
+ }
+ }
+
+ public Map<K, V> toSingleValueMap() {
+ LinkedHashMap<K, V> singleValueMap = new LinkedHashMap<K,V>(this.map.size());
+ for (Entry<K, List<V>> entry : map.entrySet()) {
+ singleValueMap.put(entry.getKey(), entry.getValue().get(0));
+ }
+ return singleValueMap;
+ }
+
+ public int size() {
+ return this.map.size();
+ }
+
+ public boolean isEmpty() {
+ return this.map.isEmpty();
+ }
+
+ public boolean containsKey(Object key) {
+ return this.map.containsKey(key);
+ }
+
+ public boolean containsValue(Object value) {
+ return this.map.containsValue(value);
+ }
+
+ public List<V> get(Object key) {
+ return this.map.get(key);
+ }
+
+ public List<V> put(K key, List<V> value) {
+ return this.map.put(key, value);
+ }
+
+ public List<V> remove(Object key) {
+ return this.map.remove(key);
+ }
+
+ public void putAll(Map<? extends K, ? extends List<V>> m) {
+ this.map.putAll(m);
+ }
+
+ public void clear() {
+ this.map.clear();
+ }
+
+ public Set<K> keySet() {
+ return this.map.keySet();
+ }
+
+ public Collection<List<V>> values() {
+ return this.map.values();
+ }
+
+ public Set<Entry<K, List<V>>> entrySet() {
+ return this.map.entrySet();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ return map.equals(other);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.map.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return this.map.toString();
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/CommonsLogWriter.java b/spring-core/src/main/java/org/springframework/util/CommonsLogWriter.java
new file mode 100644
index 00000000..30bdb086
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/CommonsLogWriter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2012 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.util;
+
+import java.io.Writer;
+
+import org.apache.commons.logging.Log;
+
+/**
+ * {@code java.io.Writer} adapter for a Commons Logging {@code Log}.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.1
+ */
+public class CommonsLogWriter extends Writer {
+
+ private final Log logger;
+
+ private final StringBuilder buffer = new StringBuilder();
+
+
+ /**
+ * Create a new CommonsLogWriter for the given Commons Logging logger.
+ * @param logger the Commons Logging logger to write to
+ */
+ public CommonsLogWriter(Log logger) {
+ Assert.notNull(logger, "Logger must not be null");
+ this.logger = logger;
+ }
+
+
+ public void write(char ch) {
+ if (ch == '\n' && this.buffer.length() > 0) {
+ this.logger.debug(this.buffer.toString());
+ this.buffer.setLength(0);
+ }
+ else {
+ this.buffer.append(ch);
+ }
+ }
+
+ @Override
+ public void write(char[] buffer, int offset, int length) {
+ for (int i = 0; i < length; i++) {
+ char ch = buffer[offset + i];
+ if (ch == '\n' && this.buffer.length() > 0) {
+ this.logger.debug(this.buffer.toString());
+ this.buffer.setLength(0);
+ }
+ else {
+ this.buffer.append(ch);
+ }
+ }
+ }
+
+ @Override
+ public void flush() {
+ }
+
+ @Override
+ public void close() {
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/CompositeIterator.java b/spring-core/src/main/java/org/springframework/util/CompositeIterator.java
new file mode 100644
index 00000000..60b1571c
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/CompositeIterator.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2002-2014 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.util;
+
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+/**
+ * Composite iterator that combines multiple other iterators,
+ * as registered via {@link #add(Iterator)}.
+ *
+ * <p>This implementation maintains a linked set of iterators
+ * which are invoked in sequence until all iterators are exhausted.
+ *
+ * @author Erwin Vervaet
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class CompositeIterator<E> implements Iterator<E> {
+
+ private final Set<Iterator<E>> iterators = new LinkedHashSet<Iterator<E>>();
+
+ private boolean inUse = false;
+
+
+ /**
+ * Add given iterator to this composite.
+ */
+ public void add(Iterator<E> iterator) {
+ Assert.state(!this.inUse, "You can no longer add iterators to a composite iterator that's already in use");
+ if (this.iterators.contains(iterator)) {
+ throw new IllegalArgumentException("You cannot add the same iterator twice");
+ }
+ this.iterators.add(iterator);
+ }
+
+ public boolean hasNext() {
+ this.inUse = true;
+ for (Iterator<E> iterator : this.iterators) {
+ if (iterator.hasNext()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public E next() {
+ this.inUse = true;
+ for (Iterator<E> iterator : this.iterators) {
+ if (iterator.hasNext()) {
+ return iterator.next();
+ }
+ }
+ throw new NoSuchElementException("All iterators exhausted");
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException("CompositeIterator does not support remove()");
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/ConcurrencyThrottleSupport.java b/spring-core/src/main/java/org/springframework/util/ConcurrencyThrottleSupport.java
new file mode 100644
index 00000000..56abcec7
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/ConcurrencyThrottleSupport.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2002-2012 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.util;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Support class for throttling concurrent access to a specific resource.
+ *
+ * <p>Designed for use as a base class, with the subclass invoking
+ * the {@link #beforeAccess()} and {@link #afterAccess()} methods at
+ * appropriate points of its workflow. Note that {@code afterAccess}
+ * should usually be called in a finally block!
+ *
+ * <p>The default concurrency limit of this support class is -1
+ * ("unbounded concurrency"). Subclasses may override this default;
+ * check the javadoc of the concrete class that you're using.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2.5
+ * @see #setConcurrencyLimit
+ * @see #beforeAccess()
+ * @see #afterAccess()
+ * @see org.springframework.aop.interceptor.ConcurrencyThrottleInterceptor
+ * @see java.io.Serializable
+ */
+@SuppressWarnings("serial")
+public abstract class ConcurrencyThrottleSupport implements Serializable {
+
+ /**
+ * Permit any number of concurrent invocations: that is, don't throttle concurrency.
+ */
+ public static final int UNBOUNDED_CONCURRENCY = -1;
+
+ /**
+ * Switch concurrency 'off': that is, don't allow any concurrent invocations.
+ */
+ public static final int NO_CONCURRENCY = 0;
+
+
+ /** Transient to optimize serialization */
+ protected transient Log logger = LogFactory.getLog(getClass());
+
+ private transient Object monitor = new Object();
+
+ private int concurrencyLimit = UNBOUNDED_CONCURRENCY;
+
+ private int concurrencyCount = 0;
+
+
+ /**
+ * Set the maximum number of concurrent access attempts allowed.
+ * -1 indicates unbounded concurrency.
+ * <p>In principle, this limit can be changed at runtime,
+ * although it is generally designed as a config time setting.
+ * <p>NOTE: Do not switch between -1 and any concrete limit at runtime,
+ * as this will lead to inconsistent concurrency counts: A limit
+ * of -1 effectively turns off concurrency counting completely.
+ */
+ public void setConcurrencyLimit(int concurrencyLimit) {
+ this.concurrencyLimit = concurrencyLimit;
+ }
+
+ /**
+ * Return the maximum number of concurrent access attempts allowed.
+ */
+ public int getConcurrencyLimit() {
+ return this.concurrencyLimit;
+ }
+
+ /**
+ * Return whether this throttle is currently active.
+ * @return {@code true} if the concurrency limit for this instance is active
+ * @see #getConcurrencyLimit()
+ */
+ public boolean isThrottleActive() {
+ return (this.concurrencyLimit > 0);
+ }
+
+
+ /**
+ * To be invoked before the main execution logic of concrete subclasses.
+ * <p>This implementation applies the concurrency throttle.
+ * @see #afterAccess()
+ */
+ protected void beforeAccess() {
+ if (this.concurrencyLimit == NO_CONCURRENCY) {
+ throw new IllegalStateException(
+ "Currently no invocations allowed - concurrency limit set to NO_CONCURRENCY");
+ }
+ if (this.concurrencyLimit > 0) {
+ boolean debug = logger.isDebugEnabled();
+ synchronized (this.monitor) {
+ boolean interrupted = false;
+ while (this.concurrencyCount >= this.concurrencyLimit) {
+ if (interrupted) {
+ throw new IllegalStateException("Thread was interrupted while waiting for invocation access, " +
+ "but concurrency limit still does not allow for entering");
+ }
+ if (debug) {
+ logger.debug("Concurrency count " + this.concurrencyCount +
+ " has reached limit " + this.concurrencyLimit + " - blocking");
+ }
+ try {
+ this.monitor.wait();
+ }
+ catch (InterruptedException ex) {
+ // Re-interrupt current thread, to allow other threads to react.
+ Thread.currentThread().interrupt();
+ interrupted = true;
+ }
+ }
+ if (debug) {
+ logger.debug("Entering throttle at concurrency count " + this.concurrencyCount);
+ }
+ this.concurrencyCount++;
+ }
+ }
+ }
+
+ /**
+ * To be invoked after the main execution logic of concrete subclasses.
+ * @see #beforeAccess()
+ */
+ protected void afterAccess() {
+ if (this.concurrencyLimit >= 0) {
+ synchronized (this.monitor) {
+ this.concurrencyCount--;
+ if (logger.isDebugEnabled()) {
+ logger.debug("Returning from throttle at concurrency count " + this.concurrencyCount);
+ }
+ this.monitor.notify();
+ }
+ }
+ }
+
+
+ //---------------------------------------------------------------------
+ // Serialization support
+ //---------------------------------------------------------------------
+
+ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
+ // Rely on default serialization, just initialize state after deserialization.
+ ois.defaultReadObject();
+
+ // Initialize transient fields.
+ this.logger = LogFactory.getLog(getClass());
+ this.monitor = new Object();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java b/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java
new file mode 100644
index 00000000..c7324610
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java
@@ -0,0 +1,1006 @@
+/*
+ * Copyright 2002-2014 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.util;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Array;
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A {@link ConcurrentHashMap} that uses {@link ReferenceType#SOFT soft} or
+ * {@linkplain ReferenceType#WEAK weak} references for both {@code keys} and {@code values}.
+ *
+ * <p>This class can be used as an alternative to
+ * {@code Collections.synchronizedMap(new WeakHashMap<K, Reference<V>>())} in order to
+ * support better performance when accessed concurrently. This implementation follows the
+ * same design constraints as {@link ConcurrentHashMap} with the exception that
+ * {@code null} values and {@code null} keys are supported.
+ *
+ * <p><b>NOTE:</b> The use of references means that there is no guarantee that items
+ * placed into the map will be subsequently available. The garbage collector may discard
+ * references at any time, so it may appear that an unknown thread is silently removing
+ * entries.
+ *
+ * <p>If not explicitly specified, this implementation will use
+ * {@linkplain SoftReference soft entry references}.
+ *
+ * @param <K> The key type
+ * @param <V> The value type
+ * @author Phillip Webb
+ * @since 3.2
+ */
+public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
+
+ private static final int DEFAULT_INITIAL_CAPACITY = 16;
+
+ private static final float DEFAULT_LOAD_FACTOR = 0.75f;
+
+ private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
+
+ private static final ReferenceType DEFAULT_REFERENCE_TYPE = ReferenceType.SOFT;
+
+ private static final int MAXIMUM_CONCURRENCY_LEVEL = 1 << 16;
+
+ private static final int MAXIMUM_SEGMENT_SIZE = 1 << 30;
+
+
+ /**
+ * Array of segments indexed using the high order bits from the hash.
+ */
+ private final Segment[] segments;
+
+ /**
+ * When the average number of references per table exceeds this value resize will be attempted.
+ */
+ private final float loadFactor;
+
+ /**
+ * The reference type: SOFT or WEAK.
+ */
+ private final ReferenceType referenceType;
+
+ /**
+ * The shift value used to calculate the size of the segments array and an index from the hash.
+ */
+ private final int shift;
+
+ /**
+ * Late binding entry set.
+ */
+ private Set<Map.Entry<K, V>> entrySet;
+
+
+ /**
+ * Create a new {@code ConcurrentReferenceHashMap} instance.
+ */
+ public ConcurrentReferenceHashMap() {
+ this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE);
+ }
+
+ /**
+ * Create a new {@code ConcurrentReferenceHashMap} instance.
+ * @param initialCapacity the initial capacity of the map
+ */
+ public ConcurrentReferenceHashMap(int initialCapacity) {
+ this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE);
+ }
+
+ /**
+ * Create a new {@code ConcurrentReferenceHashMap} instance.
+ * @param initialCapacity the initial capacity of the map
+ * @param loadFactor the load factor. When the average number of references per table
+ * exceeds this value resize will be attempted
+ */
+ public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor) {
+ this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE);
+ }
+
+ /**
+ * Create a new {@code ConcurrentReferenceHashMap} instance.
+ * @param initialCapacity the initial capacity of the map
+ * @param concurrencyLevel the expected number of threads that will concurrently
+ * write to the map
+ */
+ public ConcurrentReferenceHashMap(int initialCapacity, int concurrencyLevel) {
+ this(initialCapacity, DEFAULT_LOAD_FACTOR, concurrencyLevel, DEFAULT_REFERENCE_TYPE);
+ }
+
+ /**
+ * Create a new {@code ConcurrentReferenceHashMap} instance.
+ * @param initialCapacity the initial capacity of the map
+ * @param referenceType the reference type used for entries (soft or weak)
+ */
+ public ConcurrentReferenceHashMap(int initialCapacity, ReferenceType referenceType) {
+ this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, referenceType);
+ }
+
+ /**
+ * Create a new {@code ConcurrentReferenceHashMap} instance.
+ * @param initialCapacity the initial capacity of the map
+ * @param loadFactor the load factor. When the average number of references per
+ * table exceeds this value, resize will be attempted.
+ * @param concurrencyLevel the expected number of threads that will concurrently
+ * write to the map
+ */
+ public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
+ this(initialCapacity, loadFactor, concurrencyLevel, DEFAULT_REFERENCE_TYPE);
+ }
+
+ /**
+ * Create a new {@code ConcurrentReferenceHashMap} instance.
+ * @param initialCapacity the initial capacity of the map
+ * @param loadFactor the load factor. When the average number of references per
+ * table exceeds this value, resize will be attempted.
+ * @param concurrencyLevel the expected number of threads that will concurrently
+ * write to the map
+ * @param referenceType the reference type used for entries (soft or weak)
+ */
+ @SuppressWarnings("unchecked")
+ public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel,
+ ReferenceType referenceType) {
+
+ Assert.isTrue(initialCapacity >= 0, "Initial capacity must not be negative");
+ Assert.isTrue(loadFactor > 0f, "Load factor must be positive");
+ Assert.isTrue(concurrencyLevel > 0, "Concurrency level must be positive");
+ Assert.notNull(referenceType, "Reference type must not be null");
+ this.loadFactor = loadFactor;
+ this.shift = calculateShift(concurrencyLevel, MAXIMUM_CONCURRENCY_LEVEL);
+ int size = 1 << this.shift;
+ this.referenceType = referenceType;
+ int roundedUpSegmentCapacity = (int) ((initialCapacity + size - 1L) / size);
+ this.segments = (Segment[]) Array.newInstance(Segment.class, size);
+ for (int i = 0; i < this.segments.length; i++) {
+ this.segments[i] = new Segment(roundedUpSegmentCapacity);
+ }
+ }
+
+
+ protected final float getLoadFactor() {
+ return this.loadFactor;
+ }
+
+ protected final int getSegmentsSize() {
+ return this.segments.length;
+ }
+
+ protected final Segment getSegment(int index) {
+ return this.segments[index];
+ }
+
+ /**
+ * Factory method that returns the {@link ReferenceManager}.
+ * This method will be called once for each {@link Segment}.
+ * @return a new reference manager
+ */
+ protected ReferenceManager createReferenceManager() {
+ return new ReferenceManager();
+ }
+
+ /**
+ * Get the hash for a given object, apply an additional hash function to reduce
+ * collisions. This implementation uses the same Wang/Jenkins algorithm as
+ * {@link ConcurrentHashMap}. Subclasses can override to provide alternative hashing.
+ * @param o the object to hash (may be null)
+ * @return the resulting hash code
+ */
+ protected int getHash(Object o) {
+ int hash = o == null ? 0 : o.hashCode();
+ hash += (hash << 15) ^ 0xffffcd7d;
+ hash ^= (hash >>> 10);
+ hash += (hash << 3);
+ hash ^= (hash >>> 6);
+ hash += (hash << 2) + (hash << 14);
+ hash ^= (hash >>> 16);
+ return hash;
+ }
+
+ @Override
+ public V get(Object key) {
+ Reference<K, V> reference = getReference(key, Restructure.WHEN_NECESSARY);
+ Entry<K, V> entry = (reference != null ? reference.get() : null);
+ return (entry != null ? entry.getValue() : null);
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ Reference<K, V> reference = getReference(key, Restructure.WHEN_NECESSARY);
+ Entry<K, V> entry = (reference != null ? reference.get() : null);
+ return (entry != null && ObjectUtils.nullSafeEquals(entry.getKey(), key));
+ }
+
+ /**
+ * Return a {@link Reference} to the {@link Entry} for the specified {@code key},
+ * or {@code null} if not found.
+ * @param key the key (can be {@code null})
+ * @param restructure types of restructure allowed during this call
+ * @return the reference, or {@code null} if not found
+ */
+ protected final Reference<K, V> getReference(Object key, Restructure restructure) {
+ int hash = getHash(key);
+ return getSegmentForHash(hash).getReference(key, hash, restructure);
+ }
+
+ @Override
+ public V put(K key, V value) {
+ return put(key, value, true);
+ }
+
+ public V putIfAbsent(K key, V value) {
+ return put(key, value, false);
+ }
+
+ private V put(final K key, final V value, final boolean overwriteExisting) {
+ return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
+ @Override
+ protected V execute(Reference<K, V> reference, Entry<K, V> entry, Entries entries) {
+ if (entry != null) {
+ V previousValue = entry.getValue();
+ if (overwriteExisting) {
+ entry.setValue(value);
+ }
+ return previousValue;
+ }
+ entries.add(value);
+ return null;
+ }
+ });
+ }
+
+ @Override
+ public V remove(Object key) {
+ return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
+ @Override
+ protected V execute(Reference<K, V> reference, Entry<K, V> entry) {
+ if (entry != null) {
+ reference.release();
+ return entry.value;
+ }
+ return null;
+ }
+ });
+ }
+
+ public boolean remove(Object key, final Object value) {
+ return doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
+ @Override
+ protected Boolean execute(Reference<K, V> reference, Entry<K, V> entry) {
+ if (entry != null && ObjectUtils.nullSafeEquals(entry.getValue(), value)) {
+ reference.release();
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+
+ public boolean replace(K key, final V oldValue, final V newValue) {
+ return doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
+ @Override
+ protected Boolean execute(Reference<K, V> reference, Entry<K, V> entry) {
+ if (entry != null && ObjectUtils.nullSafeEquals(entry.getValue(), oldValue)) {
+ entry.setValue(newValue);
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+
+ public V replace(K key, final V value) {
+ return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
+ @Override
+ protected V execute(Reference<K, V> reference, Entry<K, V> entry) {
+ if (entry != null) {
+ V previousValue = entry.getValue();
+ entry.setValue(value);
+ return previousValue;
+ }
+ return null;
+ }
+ });
+ }
+
+ @Override
+ public void clear() {
+ for (Segment segment : this.segments) {
+ segment.clear();
+ }
+ }
+
+ /**
+ * Remove any entries that have been garbage collected and are no longer referenced.
+ * Under normal circumstances garbage collected entries are automatically purged as
+ * items are added or removed from the Map. This method can be used to force a purge,
+ * and is useful when the Map is read frequently but updated less often.
+ */
+ public void purgeUnreferencedEntries() {
+ for (Segment segment : this.segments) {
+ segment.restructureIfNecessary(false);
+ }
+ }
+
+
+ @Override
+ public int size() {
+ int size = 0;
+ for (Segment segment : this.segments) {
+ size += segment.getCount();
+ }
+ return size;
+ }
+
+ @Override
+ public Set<java.util.Map.Entry<K, V>> entrySet() {
+ if (this.entrySet == null) {
+ this.entrySet = new EntrySet();
+ }
+ return this.entrySet;
+ }
+
+ private <T> T doTask(Object key, Task<T> task) {
+ int hash = getHash(key);
+ return getSegmentForHash(hash).doTask(hash, key, task);
+ }
+
+ private Segment getSegmentForHash(int hash) {
+ return this.segments[(hash >>> (32 - this.shift)) & (this.segments.length - 1)];
+ }
+
+ /**
+ * Calculate a shift value that can be used to create a power-of-two value between
+ * the specified maximum and minimum values.
+ * @param minimumValue the minimum value
+ * @param maximumValue the maximum value
+ * @return the calculated shift (use {@code 1 << shift} to obtain a value)
+ */
+ protected static int calculateShift(int minimumValue, int maximumValue) {
+ int shift = 0;
+ int value = 1;
+ while (value < minimumValue && value < maximumValue) {
+ value <<= 1;
+ shift++;
+ }
+ return shift;
+ }
+
+
+ /**
+ * Various reference types supported by this map.
+ */
+ public static enum ReferenceType {
+
+ /** Use {@link SoftReference}s */
+ SOFT,
+
+ /** Use {@link WeakReference}s */
+ WEAK
+ }
+
+
+ /**
+ * A single segment used to divide the map to allow better concurrent performance.
+ */
+ @SuppressWarnings("serial")
+ protected final class Segment extends ReentrantLock {
+
+ private final ReferenceManager referenceManager;
+
+ private final int initialSize;
+
+ /**
+ * Array of references indexed using the low order bits from the hash. This
+ * property should only be set via {@link #setReferences} to ensure that the
+ * {@code resizeThreshold} is maintained.
+ */
+ private volatile Reference<K, V>[] references;
+
+ /**
+ * The total number of references contained in this segment. This includes chained
+ * references and references that have been garbage collected but not purged.
+ */
+ private volatile int count = 0;
+
+ /**
+ * The threshold when resizing of the references should occur. When {@code count}
+ * exceeds this value references will be resized.
+ */
+ private int resizeThreshold;
+
+ public Segment(int initialCapacity) {
+ this.referenceManager = createReferenceManager();
+ this.initialSize = 1 << calculateShift(initialCapacity, MAXIMUM_SEGMENT_SIZE);
+ setReferences(createReferenceArray(this.initialSize));
+ }
+
+ public Reference<K, V> getReference(Object key, int hash, Restructure restructure) {
+ if (restructure == Restructure.WHEN_NECESSARY) {
+ restructureIfNecessary(false);
+ }
+ if (this.count == 0) {
+ return null;
+ }
+ // Use a local copy to protect against other threads writing
+ Reference<K, V>[] references = this.references;
+ int index = getIndex(hash, references);
+ Reference<K, V> head = references[index];
+ return findInChain(head, key, hash);
+ }
+
+ /**
+ * Apply an update operation to this segment.
+ * The segment will be locked during the update.
+ * @param hash the hash of the key
+ * @param key the key
+ * @param task the update operation
+ * @return the result of the operation
+ */
+ public <T> T doTask(final int hash, final Object key, final Task<T> task) {
+ boolean resize = task.hasOption(TaskOption.RESIZE);
+ if (task.hasOption(TaskOption.RESTRUCTURE_BEFORE)) {
+ restructureIfNecessary(resize);
+ }
+ if (task.hasOption(TaskOption.SKIP_IF_EMPTY) && this.count == 0) {
+ return task.execute(null, null, null);
+ }
+ lock();
+ try {
+ final int index = getIndex(hash, this.references);
+ final Reference<K, V> head = this.references[index];
+ Reference<K, V> reference = findInChain(head, key, hash);
+ Entry<K, V> entry = (reference != null ? reference.get() : null);
+ Entries entries = new Entries() {
+ @Override
+ public void add(V value) {
+ @SuppressWarnings("unchecked")
+ Entry<K, V> newEntry = new Entry<K, V>((K) key, value);
+ Reference<K, V> newReference = Segment.this.referenceManager.createReference(newEntry, hash, head);
+ Segment.this.references[index] = newReference;
+ Segment.this.count++;
+ }
+ };
+ return task.execute(reference, entry, entries);
+ }
+ finally {
+ unlock();
+ if (task.hasOption(TaskOption.RESTRUCTURE_AFTER)) {
+ restructureIfNecessary(resize);
+ }
+ }
+ }
+
+ /**
+ * Clear all items from this segment.
+ */
+ public void clear() {
+ if (this.count == 0) {
+ return;
+ }
+ lock();
+ try {
+ setReferences(createReferenceArray(this.initialSize));
+ this.count = 0;
+ }
+ finally {
+ unlock();
+ }
+ }
+
+ /**
+ * Restructure the underlying data structure when it becomes necessary. This
+ * method can increase the size of the references table as well as purge any
+ * references that have been garbage collected.
+ * @param allowResize if resizing is permitted
+ */
+ protected final void restructureIfNecessary(boolean allowResize) {
+ boolean needsResize = ((this.count > 0) && (this.count >= this.resizeThreshold));
+ Reference<K, V> reference = this.referenceManager.pollForPurge();
+ if ((reference != null) || (needsResize && allowResize)) {
+ lock();
+ try {
+ int countAfterRestructure = this.count;
+
+ Set<Reference<K, V>> toPurge = Collections.emptySet();
+ if (reference != null) {
+ toPurge = new HashSet<Reference<K, V>>();
+ while (reference != null) {
+ toPurge.add(reference);
+ reference = this.referenceManager.pollForPurge();
+ }
+ }
+ countAfterRestructure -= toPurge.size();
+
+ // Recalculate taking into account count inside lock and items that
+ // will be purged
+ needsResize = (countAfterRestructure > 0 && countAfterRestructure >= this.resizeThreshold);
+ boolean resizing = false;
+ int restructureSize = this.references.length;
+ if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) {
+ restructureSize <<= 1;
+ resizing = true;
+ }
+
+ // Either create a new table or reuse the existing one
+ Reference<K, V>[] restructured = (resizing ? createReferenceArray(restructureSize) : this.references);
+
+ // Restructure
+ for (int i = 0; i < this.references.length; i++) {
+ reference = this.references[i];
+ if (!resizing) {
+ restructured[i] = null;
+ }
+ while (reference != null) {
+ if (!toPurge.contains(reference) && (reference.get() != null)) {
+ int index = getIndex(reference.getHash(), restructured);
+ restructured[index] = this.referenceManager.createReference(
+ reference.get(), reference.getHash(),
+ restructured[index]);
+ }
+ reference = reference.getNext();
+ }
+ }
+
+ // Replace volatile members
+ if (resizing) {
+ setReferences(restructured);
+ }
+ this.count = Math.max(countAfterRestructure, 0);
+ }
+ finally {
+ unlock();
+ }
+ }
+ }
+
+ private Reference<K, V> findInChain(Reference<K, V> reference, Object key, int hash) {
+ while (reference != null) {
+ if (reference.getHash() == hash) {
+ Entry<K, V> entry = reference.get();
+ if (entry != null) {
+ K entryKey = entry.getKey();
+ if (entryKey == key || entryKey.equals(key)) {
+ return reference;
+ }
+ }
+ }
+ reference = reference.getNext();
+ }
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Reference<K, V>[] createReferenceArray(int size) {
+ return (Reference<K, V>[]) Array.newInstance(Reference.class, size);
+ }
+
+ private int getIndex(int hash, Reference<K, V>[] references) {
+ return (hash & (references.length - 1));
+ }
+
+ /**
+ * Replace the references with a new value, recalculating the resizeThreshold.
+ * @param references the new references
+ */
+ private void setReferences(Reference<K, V>[] references) {
+ this.references = references;
+ this.resizeThreshold = (int) (references.length * getLoadFactor());
+ }
+
+ /**
+ * @return the size of the current references array
+ */
+ public final int getSize() {
+ return this.references.length;
+ }
+
+ /**
+ * @return the total number of references in this segment
+ */
+ public final int getCount() {
+ return this.count;
+ }
+ }
+
+
+ /**
+ * A reference to an {@link Entry} contained in the map. Implementations are usually
+ * wrappers around specific Java reference implementations (e.g., {@link SoftReference}).
+ */
+ protected static interface Reference<K, V> {
+
+ /**
+ * Returns the referenced entry or {@code null} if the entry is no longer
+ * available.
+ * @return the entry or {@code null}
+ */
+ Entry<K, V> get();
+
+ /**
+ * Returns the hash for the reference.
+ * @return the hash
+ */
+ int getHash();
+
+ /**
+ * Returns the next reference in the chain or {@code null}
+ * @return the next reference of {@code null}
+ */
+ Reference<K, V> getNext();
+
+ /**
+ * Release this entry and ensure that it will be returned from
+ * {@code ReferenceManager#pollForPurge()}.
+ */
+ void release();
+ }
+
+
+ /**
+ * A single map entry.
+ */
+ protected static final class Entry<K, V> implements Map.Entry<K, V> {
+
+ private final K key;
+
+ private volatile V value;
+
+ public Entry(K key, V value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ public K getKey() {
+ return this.key;
+ }
+
+ public V getValue() {
+ return this.value;
+ }
+
+ public V setValue(V value) {
+ V previous = this.value;
+ this.value = value;
+ return previous;
+ }
+
+ @Override
+ public String toString() {
+ return (this.key + "=" + this.value);
+ }
+
+ @Override
+ @SuppressWarnings("rawtypes")
+ public final boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof Map.Entry)) {
+ return false;
+ }
+ Map.Entry otherEntry = (Map.Entry) other;
+ return (ObjectUtils.nullSafeEquals(getKey(), otherEntry.getKey()) &&
+ ObjectUtils.nullSafeEquals(getValue(), otherEntry.getValue()));
+ }
+
+ @Override
+ public final int hashCode() {
+ return (ObjectUtils.nullSafeHashCode(this.key) ^ ObjectUtils.nullSafeHashCode(this.value));
+ }
+ }
+
+
+ /**
+ * A task that can be {@link Segment#doTask run} against a {@link Segment}.
+ */
+ private abstract class Task<T> {
+
+ private final EnumSet<TaskOption> options;
+
+ public Task(TaskOption... options) {
+ this.options = (options.length == 0 ? EnumSet.noneOf(TaskOption.class) : EnumSet.of(options[0], options));
+ }
+
+ public boolean hasOption(TaskOption option) {
+ return this.options.contains(option);
+ }
+
+ /**
+ * Execute the task.
+ * @param reference the found reference or {@code null}
+ * @param entry the found entry or {@code null}
+ * @param entries access to the underlying entries
+ * @return the result of the task
+ * @see #execute(Reference, Entry)
+ */
+ protected T execute(Reference<K, V> reference, Entry<K, V> entry, Entries entries) {
+ return execute(reference, entry);
+ }
+
+ /**
+ * Convenience method that can be used for tasks that do not need access to {@link Entries}.
+ * @param reference the found reference or {@code null}
+ * @param entry the found entry or {@code null}
+ * @return the result of the task
+ * @see #execute(Reference, Entry, Entries)
+ */
+ protected T execute(Reference<K, V> reference, Entry<K, V> entry) {
+ return null;
+ }
+ }
+
+
+ /**
+ * Various options supported by a {@code Task}.
+ */
+ private static enum TaskOption {
+
+ RESTRUCTURE_BEFORE, RESTRUCTURE_AFTER, SKIP_IF_EMPTY, RESIZE
+ }
+
+
+ /**
+ * Allows a task access to {@link Segment} entries.
+ */
+ private abstract class Entries {
+
+ /**
+ * Add a new entry with the specified value.
+ * @param value the value to add
+ */
+ public abstract void add(V value);
+ }
+
+
+ /**
+ * Internal entry-set implementation.
+ */
+ private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
+
+ @Override
+ public Iterator<Map.Entry<K, V>> iterator() {
+ return new EntryIterator();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ if (o != null && o instanceof Map.Entry<?, ?>) {
+ Map.Entry<?, ?> entry = (java.util.Map.Entry<?, ?>) o;
+ Reference<K, V> reference = ConcurrentReferenceHashMap.this.getReference(entry.getKey(), Restructure.NEVER);
+ Entry<K, V> other = (reference != null ? reference.get() : null);
+ if (other != null) {
+ return ObjectUtils.nullSafeEquals(entry.getValue(), other.getValue());
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ if (o instanceof Map.Entry<?, ?>) {
+ Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
+ return ConcurrentReferenceHashMap.this.remove(entry.getKey(), entry.getValue());
+ }
+ return false;
+ }
+
+ @Override
+ public int size() {
+ return ConcurrentReferenceHashMap.this.size();
+ }
+
+ @Override
+ public void clear() {
+ ConcurrentReferenceHashMap.this.clear();
+ }
+ }
+
+
+ /**
+ * Internal entry iterator implementation.
+ */
+ private class EntryIterator implements Iterator<Map.Entry<K, V>> {
+
+ private int segmentIndex;
+
+ private int referenceIndex;
+
+ private Reference<K, V>[] references;
+
+ private Reference<K, V> reference;
+
+ private Entry<K, V> next;
+
+ private Entry<K, V> last;
+
+ public EntryIterator() {
+ moveToNextSegment();
+ }
+
+ public boolean hasNext() {
+ getNextIfNecessary();
+ return (this.next != null);
+ }
+
+ public Entry<K, V> next() {
+ getNextIfNecessary();
+ if (this.next == null) {
+ throw new NoSuchElementException();
+ }
+ this.last = this.next;
+ this.next = null;
+ return this.last;
+ }
+
+ private void getNextIfNecessary() {
+ while (this.next == null) {
+ moveToNextReference();
+ if (this.reference == null) {
+ return;
+ }
+ this.next = this.reference.get();
+ }
+ }
+
+ private void moveToNextReference() {
+ if (this.reference != null) {
+ this.reference = this.reference.getNext();
+ }
+ while (this.reference == null && this.references != null) {
+ if (this.referenceIndex >= this.references.length) {
+ moveToNextSegment();
+ this.referenceIndex = 0;
+ }
+ else {
+ this.reference = this.references[this.referenceIndex];
+ this.referenceIndex++;
+ }
+ }
+ }
+
+ private void moveToNextSegment() {
+ this.reference = null;
+ this.references = null;
+ if (this.segmentIndex < ConcurrentReferenceHashMap.this.segments.length) {
+ this.references = ConcurrentReferenceHashMap.this.segments[this.segmentIndex].references;
+ this.segmentIndex++;
+ }
+ }
+
+ public void remove() {
+ Assert.state(this.last != null);
+ ConcurrentReferenceHashMap.this.remove(this.last.getKey());
+ }
+ }
+
+
+ /**
+ * The types of restructuring that can be performed.
+ */
+ protected static enum Restructure {
+
+ WHEN_NECESSARY, NEVER
+ }
+
+
+ /**
+ * Strategy class used to manage {@link Reference}s. This class can be overridden if
+ * alternative reference types need to be supported.
+ */
+ protected class ReferenceManager {
+
+ private final ReferenceQueue<Entry<K, V>> queue = new ReferenceQueue<Entry<K, V>>();
+
+ /**
+ * Factory method used to create a new {@link Reference}.
+ * @param entry the entry contained in the reference
+ * @param hash the hash
+ * @param next the next reference in the chain or {@code null}
+ * @return a new {@link Reference}
+ */
+ public Reference<K, V> createReference(Entry<K, V> entry, int hash, Reference<K, V> next) {
+ if (ConcurrentReferenceHashMap.this.referenceType == ReferenceType.WEAK) {
+ return new WeakEntryReference<K, V>(entry, hash, next, this.queue);
+ }
+ return new SoftEntryReference<K, V>(entry, hash, next, this.queue);
+ }
+
+ /**
+ * Return any reference that has been garbage collected and can be purged from the
+ * underlying structure or {@code null} if no references need purging. This
+ * method must be thread safe and ideally should not block when returning
+ * {@code null}. References should be returned once and only once.
+ * @return a reference to purge or {@code null}
+ */
+ @SuppressWarnings("unchecked")
+ public Reference<K, V> pollForPurge() {
+ return (Reference<K, V>) this.queue.poll();
+ }
+ }
+
+
+ /**
+ * Internal {@link Reference} implementation for {@link SoftReference}s.
+ */
+ private static final class SoftEntryReference<K, V> extends SoftReference<Entry<K, V>> implements Reference<K, V> {
+
+ private final int hash;
+
+ private final Reference<K, V> nextReference;
+
+ public SoftEntryReference(Entry<K, V> entry, int hash, Reference<K, V> next, ReferenceQueue<Entry<K, V>> queue) {
+ super(entry, queue);
+ this.hash = hash;
+ this.nextReference = next;
+ }
+
+ public int getHash() {
+ return this.hash;
+ }
+
+ public Reference<K, V> getNext() {
+ return this.nextReference;
+ }
+
+ public void release() {
+ enqueue();
+ clear();
+ }
+ }
+
+
+ /**
+ * Internal {@link Reference} implementation for {@link WeakReference}s.
+ */
+ private static final class WeakEntryReference<K, V> extends WeakReference<Entry<K, V>> implements Reference<K, V> {
+
+ private final int hash;
+
+ private final Reference<K, V> nextReference;
+
+ public WeakEntryReference(Entry<K, V> entry, int hash, Reference<K, V> next, ReferenceQueue<Entry<K, V>> queue) {
+ super(entry, queue);
+ this.hash = hash;
+ this.nextReference = next;
+ }
+
+ public int getHash() {
+ return this.hash;
+ }
+
+ public Reference<K, V> getNext() {
+ return this.nextReference;
+ }
+
+ public void release() {
+ enqueue();
+ clear();
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/CustomizableThreadCreator.java b/spring-core/src/main/java/org/springframework/util/CustomizableThreadCreator.java
new file mode 100644
index 00000000..969b5e3d
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/CustomizableThreadCreator.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2002-2014 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.util;
+
+import java.io.Serializable;
+
+/**
+ * Simple customizable helper class for creating new {@link Thread} instances.
+ * Provides various bean properties: thread name prefix, thread priority, etc.
+ *
+ * <p>Serves as base class for thread factories such as
+ * {@link org.springframework.scheduling.concurrent.CustomizableThreadFactory}.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0.3
+ * @see org.springframework.scheduling.concurrent.CustomizableThreadFactory
+ */
+@SuppressWarnings("serial")
+public class CustomizableThreadCreator implements Serializable {
+
+ private String threadNamePrefix;
+
+ private int threadPriority = Thread.NORM_PRIORITY;
+
+ private boolean daemon = false;
+
+ private ThreadGroup threadGroup;
+
+ private int threadCount = 0;
+
+ private final Object threadCountMonitor = new SerializableMonitor();
+
+
+ /**
+ * Create a new CustomizableThreadCreator with default thread name prefix.
+ */
+ public CustomizableThreadCreator() {
+ this.threadNamePrefix = getDefaultThreadNamePrefix();
+ }
+
+ /**
+ * Create a new CustomizableThreadCreator with the given thread name prefix.
+ * @param threadNamePrefix the prefix to use for the names of newly created threads
+ */
+ public CustomizableThreadCreator(String threadNamePrefix) {
+ this.threadNamePrefix = (threadNamePrefix != null ? threadNamePrefix : getDefaultThreadNamePrefix());
+ }
+
+
+ /**
+ * Specify the prefix to use for the names of newly created threads.
+ * Default is "SimpleAsyncTaskExecutor-".
+ */
+ public void setThreadNamePrefix(String threadNamePrefix) {
+ this.threadNamePrefix = (threadNamePrefix != null ? threadNamePrefix : getDefaultThreadNamePrefix());
+ }
+
+ /**
+ * Return the thread name prefix to use for the names of newly
+ * created threads.
+ */
+ public String getThreadNamePrefix() {
+ return this.threadNamePrefix;
+ }
+
+ /**
+ * Set the priority of the threads that this factory creates.
+ * Default is 5.
+ * @see java.lang.Thread#NORM_PRIORITY
+ */
+ public void setThreadPriority(int threadPriority) {
+ this.threadPriority = threadPriority;
+ }
+
+ /**
+ * Return the priority of the threads that this factory creates.
+ */
+ public int getThreadPriority() {
+ return this.threadPriority;
+ }
+
+ /**
+ * Set whether this factory is supposed to create daemon threads,
+ * just executing as long as the application itself is running.
+ * <p>Default is "false": Concrete factories usually support explicit cancelling.
+ * Hence, if the application shuts down, Runnables will by default finish their
+ * execution.
+ * <p>Specify "true" for eager shutdown of threads which still actively execute
+ * a {@link Runnable} at the time that the application itself shuts down.
+ * @see java.lang.Thread#setDaemon
+ */
+ public void setDaemon(boolean daemon) {
+ this.daemon = daemon;
+ }
+
+ /**
+ * Return whether this factory should create daemon threads.
+ */
+ public boolean isDaemon() {
+ return this.daemon;
+ }
+
+ /**
+ * Specify the name of the thread group that threads should be created in.
+ * @see #setThreadGroup
+ */
+ public void setThreadGroupName(String name) {
+ this.threadGroup = new ThreadGroup(name);
+ }
+
+ /**
+ * Specify the thread group that threads should be created in.
+ * @see #setThreadGroupName
+ */
+ public void setThreadGroup(ThreadGroup threadGroup) {
+ this.threadGroup = threadGroup;
+ }
+
+ /**
+ * Return the thread group that threads should be created in
+ * (or {@code null} for the default group).
+ */
+ public ThreadGroup getThreadGroup() {
+ return this.threadGroup;
+ }
+
+
+ /**
+ * Template method for the creation of a new {@link Thread}.
+ * <p>The default implementation creates a new Thread for the given
+ * {@link Runnable}, applying an appropriate thread name.
+ * @param runnable the Runnable to execute
+ * @see #nextThreadName()
+ */
+ public Thread createThread(Runnable runnable) {
+ Thread thread = new Thread(getThreadGroup(), runnable, nextThreadName());
+ thread.setPriority(getThreadPriority());
+ thread.setDaemon(isDaemon());
+ return thread;
+ }
+
+ /**
+ * Return the thread name to use for a newly created {@link Thread}.
+ * <p>The default implementation returns the specified thread name prefix
+ * with an increasing thread count appended: e.g. "SimpleAsyncTaskExecutor-0".
+ * @see #getThreadNamePrefix()
+ */
+ protected String nextThreadName() {
+ int threadNumber = 0;
+ synchronized (this.threadCountMonitor) {
+ this.threadCount++;
+ threadNumber = this.threadCount;
+ }
+ return getThreadNamePrefix() + threadNumber;
+ }
+
+ /**
+ * Build the default thread name prefix for this factory.
+ * @return the default thread name prefix (never {@code null})
+ */
+ protected String getDefaultThreadNamePrefix() {
+ return ClassUtils.getShortName(getClass()) + "-";
+ }
+
+
+ /**
+ * Empty class used for a serializable monitor object.
+ */
+ private static class SerializableMonitor implements Serializable {
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/DefaultPropertiesPersister.java b/spring-core/src/main/java/org/springframework/util/DefaultPropertiesPersister.java
new file mode 100644
index 00000000..1337a5a5
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/DefaultPropertiesPersister.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2002-2013 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.util;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Properties;
+
+/**
+ * Default implementation of the {@link PropertiesPersister} interface.
+ * Follows the native parsing of {@code java.util.Properties}.
+ *
+ * <p>Allows for reading from any Reader and writing to any Writer, for example
+ * to specify a charset for a properties file. This is a capability that standard
+ * {@code java.util.Properties} unfortunately lacked up until JDK 1.5:
+ * You were only able to load files using the ISO-8859-1 charset there.
+ *
+ * <p>Loading from and storing to a stream delegates to {@code Properties.load}
+ * and {@code Properties.store}, respectively, to be fully compatible with
+ * the Unicode conversion as implemented by the JDK Properties class. On JDK 1.6,
+ * {@code Properties.load/store} will also be used for readers/writers,
+ * effectively turning this class into a plain backwards compatibility adapter.
+ *
+ * <p>The persistence code that works with Reader/Writer follows the JDK's parsing
+ * strategy but does not implement Unicode conversion, because the Reader/Writer
+ * should already apply proper decoding/encoding of characters. If you use prefer
+ * to escape unicode characters in your properties files, do <i>not</i> specify
+ * an encoding for a Reader/Writer (like ReloadableResourceBundleMessageSource's
+ * "defaultEncoding" and "fileEncodings" properties).
+ *
+ * @author Juergen Hoeller
+ * @since 10.03.2004
+ * @see java.util.Properties
+ * @see java.util.Properties#load
+ * @see java.util.Properties#store
+ */
+public class DefaultPropertiesPersister implements PropertiesPersister {
+
+ // Determine whether Properties.load(Reader) is available (on JDK 1.6+)
+ private static final boolean loadFromReaderAvailable =
+ ClassUtils.hasMethod(Properties.class, "load", new Class[] {Reader.class});
+
+ // Determine whether Properties.store(Writer, String) is available (on JDK 1.6+)
+ private static final boolean storeToWriterAvailable =
+ ClassUtils.hasMethod(Properties.class, "store", new Class[] {Writer.class, String.class});
+
+
+ public void load(Properties props, InputStream is) throws IOException {
+ props.load(is);
+ }
+
+ public void load(Properties props, Reader reader) throws IOException {
+ if (loadFromReaderAvailable) {
+ // On JDK 1.6+
+ props.load(reader);
+ }
+ else {
+ // Fall back to manual parsing.
+ doLoad(props, reader);
+ }
+ }
+
+ protected void doLoad(Properties props, Reader reader) throws IOException {
+ BufferedReader in = new BufferedReader(reader);
+ while (true) {
+ String line = in.readLine();
+ if (line == null) {
+ return;
+ }
+ line = StringUtils.trimLeadingWhitespace(line);
+ if (line.length() > 0) {
+ char firstChar = line.charAt(0);
+ if (firstChar != '#' && firstChar != '!') {
+ while (endsWithContinuationMarker(line)) {
+ String nextLine = in.readLine();
+ line = line.substring(0, line.length() - 1);
+ if (nextLine != null) {
+ line += StringUtils.trimLeadingWhitespace(nextLine);
+ }
+ }
+ int separatorIndex = line.indexOf("=");
+ if (separatorIndex == -1) {
+ separatorIndex = line.indexOf(":");
+ }
+ String key = (separatorIndex != -1 ? line.substring(0, separatorIndex) : line);
+ String value = (separatorIndex != -1) ? line.substring(separatorIndex + 1) : "";
+ key = StringUtils.trimTrailingWhitespace(key);
+ value = StringUtils.trimLeadingWhitespace(value);
+ props.put(unescape(key), unescape(value));
+ }
+ }
+ }
+ }
+
+ protected boolean endsWithContinuationMarker(String line) {
+ boolean evenSlashCount = true;
+ int index = line.length() - 1;
+ while (index >= 0 && line.charAt(index) == '\\') {
+ evenSlashCount = !evenSlashCount;
+ index--;
+ }
+ return !evenSlashCount;
+ }
+
+ protected String unescape(String str) {
+ StringBuilder result = new StringBuilder(str.length());
+ for (int index = 0; index < str.length();) {
+ char c = str.charAt(index++);
+ if (c == '\\') {
+ c = str.charAt(index++);
+ if (c == 't') {
+ c = '\t';
+ }
+ else if (c == 'r') {
+ c = '\r';
+ }
+ else if (c == 'n') {
+ c = '\n';
+ }
+ else if (c == 'f') {
+ c = '\f';
+ }
+ }
+ result.append(c);
+ }
+ return result.toString();
+ }
+
+
+ public void store(Properties props, OutputStream os, String header) throws IOException {
+ props.store(os, header);
+ }
+
+ public void store(Properties props, Writer writer, String header) throws IOException {
+ if (storeToWriterAvailable) {
+ // On JDK 1.6+
+ props.store(writer, header);
+ }
+ else {
+ // Fall back to manual parsing.
+ doStore(props, writer, header);
+ }
+ }
+
+ protected void doStore(Properties props, Writer writer, String header) throws IOException {
+ BufferedWriter out = new BufferedWriter(writer);
+ if (header != null) {
+ out.write("#" + header);
+ out.newLine();
+ }
+ out.write("#" + new Date());
+ out.newLine();
+ for (Enumeration keys = props.keys(); keys.hasMoreElements();) {
+ String key = (String) keys.nextElement();
+ String val = props.getProperty(key);
+ out.write(escape(key, true) + "=" + escape(val, false));
+ out.newLine();
+ }
+ out.flush();
+ }
+
+ protected String escape(String str, boolean isKey) {
+ int len = str.length();
+ StringBuilder result = new StringBuilder(len * 2);
+ for (int index = 0; index < len; index++) {
+ char c = str.charAt(index);
+ switch (c) {
+ case ' ':
+ if (index == 0 || isKey) {
+ result.append('\\');
+ }
+ result.append(' ');
+ break;
+ case '\\':
+ result.append("\\\\");
+ break;
+ case '\t':
+ result.append("\\t");
+ break;
+ case '\n':
+ result.append("\\n");
+ break;
+ case '\r':
+ result.append("\\r");
+ break;
+ case '\f':
+ result.append("\\f");
+ break;
+ default:
+ if ("=: \t\r\n\f#!".indexOf(c) != -1) {
+ result.append('\\');
+ }
+ result.append(c);
+ }
+ }
+ return result.toString();
+ }
+
+
+ public void loadFromXml(Properties props, InputStream is) throws IOException {
+ props.loadFromXML(is);
+ }
+
+ public void storeToXml(Properties props, OutputStream os, String header) throws IOException {
+ props.storeToXML(os, header);
+ }
+
+ public void storeToXml(Properties props, OutputStream os, String header, String encoding) throws IOException {
+ props.storeToXML(os, header, encoding);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/DigestUtils.java b/spring-core/src/main/java/org/springframework/util/DigestUtils.java
new file mode 100644
index 00000000..a4ca743b
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/DigestUtils.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2002-2012 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.util;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Miscellaneous methods for calculating digests.
+ * <p>Mainly for internal use within the framework; consider
+ * <a href="http://commons.apache.org/codec/">Apache Commons Codec</a> for a
+ * more comprehensive suite of digest utilities.
+ *
+ * @author Arjen Poutsma
+ * @since 3.0
+ * @see org.apache.commons.codec.digest.DigestUtils
+ */
+public abstract class DigestUtils {
+
+ private static final String MD5_ALGORITHM_NAME = "MD5";
+
+ private static final char[] HEX_CHARS =
+ {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+ /**
+ * Calculate the MD5 digest of the given bytes.
+ * @param bytes the bytes to calculate the digest over
+ * @return the digest
+ */
+ public static byte[] md5Digest(byte[] bytes) {
+ return digest(MD5_ALGORITHM_NAME, bytes);
+ }
+
+ /**
+ * Return a hexadecimal string representation of the MD5 digest of the given
+ * bytes.
+ * @param bytes the bytes to calculate the digest over
+ * @return a hexadecimal digest string
+ */
+ public static String md5DigestAsHex(byte[] bytes) {
+ return digestAsHexString(MD5_ALGORITHM_NAME, bytes);
+ }
+
+ /**
+ * Append a hexadecimal string representation of the MD5 digest of the given
+ * bytes to the given {@link StringBuilder}.
+ * @param bytes the bytes to calculate the digest over
+ * @param builder the string builder to append the digest to
+ * @return the given string builder
+ */
+ public static StringBuilder appendMd5DigestAsHex(byte[] bytes, StringBuilder builder) {
+ return appendDigestAsHex(MD5_ALGORITHM_NAME, bytes, builder);
+ }
+
+ /**
+ * Creates a new {@link MessageDigest} with the given algorithm. Necessary
+ * because {@code MessageDigest} is not thread-safe.
+ */
+ private static MessageDigest getDigest(String algorithm) {
+ try {
+ return MessageDigest.getInstance(algorithm);
+ }
+ catch (NoSuchAlgorithmException ex) {
+ throw new IllegalStateException("Could not find MessageDigest with algorithm \"" + algorithm + "\"", ex);
+ }
+ }
+
+ private static byte[] digest(String algorithm, byte[] bytes) {
+ return getDigest(algorithm).digest(bytes);
+ }
+
+ private static String digestAsHexString(String algorithm, byte[] bytes) {
+ char[] hexDigest = digestAsHexChars(algorithm, bytes);
+ return new String(hexDigest);
+ }
+
+ private static StringBuilder appendDigestAsHex(String algorithm, byte[] bytes, StringBuilder builder) {
+ char[] hexDigest = digestAsHexChars(algorithm, bytes);
+ return builder.append(hexDigest);
+ }
+
+ private static char[] digestAsHexChars(String algorithm, byte[] bytes) {
+ byte[] digest = digest(algorithm, bytes);
+ return encodeHex(digest);
+ }
+
+ private static char[] encodeHex(byte[] bytes) {
+ char chars[] = new char[32];
+ for (int i = 0; i < chars.length; i = i + 2) {
+ byte b = bytes[i / 2];
+ chars[i] = HEX_CHARS[(b >>> 0x4) & 0xf];
+ chars[i + 1] = HEX_CHARS[b & 0xf];
+ }
+ return chars;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/ErrorHandler.java b/spring-core/src/main/java/org/springframework/util/ErrorHandler.java
new file mode 100644
index 00000000..a43ee8b6
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/ErrorHandler.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2002-2012 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.util;
+
+/**
+ * A strategy for handling errors. This is especially useful for handling
+ * errors that occur during asynchronous execution of tasks that have been
+ * submitted to a TaskScheduler. In such cases, it may not be possible to
+ * throw the error to the original caller.
+ *
+ * @author Mark Fisher
+ * @since 3.0
+ */
+public interface ErrorHandler {
+
+ /**
+ * Handle the given error, possibly rethrowing it as a fatal exception.
+ */
+ void handleError(Throwable t);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/FileCopyUtils.java b/spring-core/src/main/java/org/springframework/util/FileCopyUtils.java
new file mode 100644
index 00000000..2c14da3b
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/FileCopyUtils.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2002-2013 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.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+
+/**
+ * Simple utility methods for file and stream copying. All copy methods use a block size
+ * of 4096 bytes, and close all affected streams when done. A variation of the copy
+ * methods from this class that leave streams open can be found in {@link StreamUtils}.
+ *
+ * <p>Mainly for use within the framework, but also useful for application code.
+ *
+ * @author Juergen Hoeller
+ * @since 06.10.2003
+ * @see StreamUtils
+ */
+public abstract class FileCopyUtils {
+
+ public static final int BUFFER_SIZE = StreamUtils.BUFFER_SIZE;
+
+
+ //---------------------------------------------------------------------
+ // Copy methods for java.io.File
+ //---------------------------------------------------------------------
+
+ /**
+ * Copy the contents of the given input File to the given output File.
+ * @param in the file to copy from
+ * @param out the file to copy to
+ * @return the number of bytes copied
+ * @throws IOException in case of I/O errors
+ */
+ public static int copy(File in, File out) throws IOException {
+ Assert.notNull(in, "No input File specified");
+ Assert.notNull(out, "No output File specified");
+ return copy(new BufferedInputStream(new FileInputStream(in)),
+ new BufferedOutputStream(new FileOutputStream(out)));
+ }
+
+ /**
+ * Copy the contents of the given byte array to the given output File.
+ * @param in the byte array to copy from
+ * @param out the file to copy to
+ * @throws IOException in case of I/O errors
+ */
+ public static void copy(byte[] in, File out) throws IOException {
+ Assert.notNull(in, "No input byte array specified");
+ Assert.notNull(out, "No output File specified");
+ ByteArrayInputStream inStream = new ByteArrayInputStream(in);
+ OutputStream outStream = new BufferedOutputStream(new FileOutputStream(out));
+ copy(inStream, outStream);
+ }
+
+ /**
+ * Copy the contents of the given input File into a new byte array.
+ * @param in the file to copy from
+ * @return the new byte array that has been copied to
+ * @throws IOException in case of I/O errors
+ */
+ public static byte[] copyToByteArray(File in) throws IOException {
+ Assert.notNull(in, "No input File specified");
+ return copyToByteArray(new BufferedInputStream(new FileInputStream(in)));
+ }
+
+
+ //---------------------------------------------------------------------
+ // Copy methods for java.io.InputStream / java.io.OutputStream
+ //---------------------------------------------------------------------
+
+ /**
+ * Copy the contents of the given InputStream to the given OutputStream.
+ * Closes both streams when done.
+ * @param in the stream to copy from
+ * @param out the stream to copy to
+ * @return the number of bytes copied
+ * @throws IOException in case of I/O errors
+ */
+ public static int copy(InputStream in, OutputStream out) throws IOException {
+ Assert.notNull(in, "No InputStream specified");
+ Assert.notNull(out, "No OutputStream specified");
+ try {
+ return StreamUtils.copy(in, out);
+ }
+ finally {
+ try {
+ in.close();
+ }
+ catch (IOException ex) {
+ }
+ try {
+ out.close();
+ }
+ catch (IOException ex) {
+ }
+ }
+ }
+
+ /**
+ * Copy the contents of the given byte array to the given OutputStream.
+ * Closes the stream when done.
+ * @param in the byte array to copy from
+ * @param out the OutputStream to copy to
+ * @throws IOException in case of I/O errors
+ */
+ public static void copy(byte[] in, OutputStream out) throws IOException {
+ Assert.notNull(in, "No input byte array specified");
+ Assert.notNull(out, "No OutputStream specified");
+ try {
+ out.write(in);
+ }
+ finally {
+ try {
+ out.close();
+ }
+ catch (IOException ex) {
+ }
+ }
+ }
+
+ /**
+ * Copy the contents of the given InputStream into a new byte array.
+ * Closes the stream when done.
+ * @param in the stream to copy from
+ * @return the new byte array that has been copied to
+ * @throws IOException in case of I/O errors
+ */
+ public static byte[] copyToByteArray(InputStream in) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE);
+ copy(in, out);
+ return out.toByteArray();
+ }
+
+
+ //---------------------------------------------------------------------
+ // Copy methods for java.io.Reader / java.io.Writer
+ //---------------------------------------------------------------------
+
+ /**
+ * Copy the contents of the given Reader to the given Writer.
+ * Closes both when done.
+ * @param in the Reader to copy from
+ * @param out the Writer to copy to
+ * @return the number of characters copied
+ * @throws IOException in case of I/O errors
+ */
+ public static int copy(Reader in, Writer out) throws IOException {
+ Assert.notNull(in, "No Reader specified");
+ Assert.notNull(out, "No Writer specified");
+ try {
+ int byteCount = 0;
+ char[] buffer = new char[BUFFER_SIZE];
+ int bytesRead = -1;
+ while ((bytesRead = in.read(buffer)) != -1) {
+ out.write(buffer, 0, bytesRead);
+ byteCount += bytesRead;
+ }
+ out.flush();
+ return byteCount;
+ }
+ finally {
+ try {
+ in.close();
+ }
+ catch (IOException ex) {
+ }
+ try {
+ out.close();
+ }
+ catch (IOException ex) {
+ }
+ }
+ }
+
+ /**
+ * Copy the contents of the given String to the given output Writer.
+ * Closes the writer when done.
+ * @param in the String to copy from
+ * @param out the Writer to copy to
+ * @throws IOException in case of I/O errors
+ */
+ public static void copy(String in, Writer out) throws IOException {
+ Assert.notNull(in, "No input String specified");
+ Assert.notNull(out, "No Writer specified");
+ try {
+ out.write(in);
+ }
+ finally {
+ try {
+ out.close();
+ }
+ catch (IOException ex) {
+ }
+ }
+ }
+
+ /**
+ * Copy the contents of the given Reader into a String.
+ * Closes the reader when done.
+ * @param in the reader to copy from
+ * @return the String that has been copied to
+ * @throws IOException in case of I/O errors
+ */
+ public static String copyToString(Reader in) throws IOException {
+ StringWriter out = new StringWriter();
+ copy(in, out);
+ return out.toString();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/FileSystemUtils.java b/spring-core/src/main/java/org/springframework/util/FileSystemUtils.java
new file mode 100644
index 00000000..529b4f01
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/FileSystemUtils.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2002-2012 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.util;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Utility methods for working with the file system.
+ *
+ * @author Rob Harrop
+ * @author Juergen Hoeller
+ * @since 2.5.3
+ */
+public abstract class FileSystemUtils {
+
+ /**
+ * Delete the supplied {@link File} - for directories,
+ * recursively delete any nested directories or files as well.
+ * @param root the root {@code File} to delete
+ * @return {@code true} if the {@code File} was deleted,
+ * otherwise {@code false}
+ */
+ public static boolean deleteRecursively(File root) {
+ if (root != null && root.exists()) {
+ if (root.isDirectory()) {
+ File[] children = root.listFiles();
+ if (children != null) {
+ for (File child : children) {
+ deleteRecursively(child);
+ }
+ }
+ }
+ return root.delete();
+ }
+ return false;
+ }
+
+ /**
+ * Recursively copy the contents of the {@code src} file/directory
+ * to the {@code dest} file/directory.
+ * @param src the source directory
+ * @param dest the destination directory
+ * @throws IOException in the case of I/O errors
+ */
+ public static void copyRecursively(File src, File dest) throws IOException {
+ Assert.isTrue(src != null && (src.isDirectory() || src.isFile()), "Source File must denote a directory or file");
+ Assert.notNull(dest, "Destination File must not be null");
+ doCopyRecursively(src, dest);
+ }
+
+ /**
+ * Actually copy the contents of the {@code src} file/directory
+ * to the {@code dest} file/directory.
+ * @param src the source directory
+ * @param dest the destination directory
+ * @throws IOException in the case of I/O errors
+ */
+ private static void doCopyRecursively(File src, File dest) throws IOException {
+ if (src.isDirectory()) {
+ dest.mkdir();
+ File[] entries = src.listFiles();
+ if (entries == null) {
+ throw new IOException("Could not list files in directory: " + src);
+ }
+ for (File entry : entries) {
+ doCopyRecursively(entry, new File(dest, entry.getName()));
+ }
+ }
+ else if (src.isFile()) {
+ try {
+ dest.createNewFile();
+ }
+ catch (IOException ex) {
+ IOException ioex = new IOException("Failed to create file: " + dest);
+ ioex.initCause(ex);
+ throw ioex;
+ }
+ FileCopyUtils.copy(src, dest);
+ }
+ else {
+ // Special File handle: neither a file not a directory.
+ // Simply skip it when contained in nested directory...
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java b/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java
new file mode 100644
index 00000000..62921593
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2002-2012 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.util;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * {@link LinkedHashMap} variant that stores String keys in a case-insensitive
+ * manner, for example for key-based access in a results table.
+ *
+ * <p>Preserves the original order as well as the original casing of keys,
+ * while allowing for contains, get and remove calls with any case of key.
+ *
+ * <p>Does <i>not</i> support {@code null} keys.
+ *
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class LinkedCaseInsensitiveMap<V> extends LinkedHashMap<String, V> {
+
+ private final Map<String, String> caseInsensitiveKeys;
+
+ private final Locale locale;
+
+
+ /**
+ * Create a new LinkedCaseInsensitiveMap for the default Locale.
+ * @see java.lang.String#toLowerCase()
+ */
+ public LinkedCaseInsensitiveMap() {
+ this(null);
+ }
+
+ /**
+ * Create a new LinkedCaseInsensitiveMap that stores lower-case keys
+ * according to the given Locale.
+ * @param locale the Locale to use for lower-case conversion
+ * @see java.lang.String#toLowerCase(java.util.Locale)
+ */
+ public LinkedCaseInsensitiveMap(Locale locale) {
+ super();
+ this.caseInsensitiveKeys = new HashMap<String, String>();
+ this.locale = (locale != null ? locale : Locale.getDefault());
+ }
+
+ /**
+ * Create a new LinkedCaseInsensitiveMap that wraps a {@link LinkedHashMap}
+ * with the given initial capacity and stores lower-case keys according
+ * to the default Locale.
+ * @param initialCapacity the initial capacity
+ * @see java.lang.String#toLowerCase()
+ */
+ public LinkedCaseInsensitiveMap(int initialCapacity) {
+ this(initialCapacity, null);
+ }
+
+ /**
+ * Create a new LinkedCaseInsensitiveMap that wraps a {@link LinkedHashMap}
+ * with the given initial capacity and stores lower-case keys according
+ * to the given Locale.
+ * @param initialCapacity the initial capacity
+ * @param locale the Locale to use for lower-case conversion
+ * @see java.lang.String#toLowerCase(java.util.Locale)
+ */
+ public LinkedCaseInsensitiveMap(int initialCapacity, Locale locale) {
+ super(initialCapacity);
+ this.caseInsensitiveKeys = new HashMap<String, String>(initialCapacity);
+ this.locale = (locale != null ? locale : Locale.getDefault());
+ }
+
+
+ @Override
+ public V put(String key, V value) {
+ String oldKey = this.caseInsensitiveKeys.put(convertKey(key), key);
+ if (oldKey != null && !oldKey.equals(key)) {
+ super.remove(oldKey);
+ }
+ return super.put(key, value);
+ }
+
+ @Override
+ public void putAll(Map<? extends String, ? extends V> map) {
+ if (map.isEmpty()) {
+ return;
+ }
+ for (Map.Entry<? extends String, ? extends V> entry : map.entrySet()) {
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return (key instanceof String && this.caseInsensitiveKeys.containsKey(convertKey((String) key)));
+ }
+
+ @Override
+ public V get(Object key) {
+ if (key instanceof String) {
+ return super.get(this.caseInsensitiveKeys.get(convertKey((String) key)));
+ }
+ else {
+ return null;
+ }
+ }
+
+ @Override
+ public V remove(Object key) {
+ if (key instanceof String ) {
+ return super.remove(this.caseInsensitiveKeys.remove(convertKey((String) key)));
+ }
+ else {
+ return null;
+ }
+ }
+
+ @Override
+ public void clear() {
+ this.caseInsensitiveKeys.clear();
+ super.clear();
+ }
+
+
+ /**
+ * Convert the given key to a case-insensitive key.
+ * <p>The default implementation converts the key
+ * to lower-case according to this Map's Locale.
+ * @param key the user-specified key
+ * @return the key to use for storing
+ * @see java.lang.String#toLowerCase(java.util.Locale)
+ */
+ protected String convertKey(String key) {
+ return key.toLowerCase(this.locale);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java b/spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java
new file mode 100644
index 00000000..730047f7
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2002-2010 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.util;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Simple implementation of {@link MultiValueMap} that wraps a {@link LinkedHashMap},
+ * storing multiple values in a {@link LinkedList}.
+ *
+ * <p>This Map implementation is generally not thread-safe. It is primarily designed
+ * for data structures exposed from request objects, for use in a single thread only.
+ *
+ * @author Arjen Poutsma
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializable {
+
+ private static final long serialVersionUID = 3801124242820219131L;
+
+ private final Map<K, List<V>> targetMap;
+
+
+ /**
+ * Create a new LinkedMultiValueMap that wraps a {@link LinkedHashMap}.
+ */
+ public LinkedMultiValueMap() {
+ this.targetMap = new LinkedHashMap<K, List<V>>();
+ }
+
+ /**
+ * Create a new LinkedMultiValueMap that wraps a {@link LinkedHashMap}
+ * with the given initial capacity.
+ * @param initialCapacity the initial capacity
+ */
+ public LinkedMultiValueMap(int initialCapacity) {
+ this.targetMap = new LinkedHashMap<K, List<V>>(initialCapacity);
+ }
+
+ /**
+ * Copy constructor: Create a new LinkedMultiValueMap with the same mappings
+ * as the specified Map.
+ * @param otherMap the Map whose mappings are to be placed in this Map
+ */
+ public LinkedMultiValueMap(Map<K, List<V>> otherMap) {
+ this.targetMap = new LinkedHashMap<K, List<V>>(otherMap);
+ }
+
+
+ // MultiValueMap implementation
+
+ public void add(K key, V value) {
+ List<V> values = this.targetMap.get(key);
+ if (values == null) {
+ values = new LinkedList<V>();
+ this.targetMap.put(key, values);
+ }
+ values.add(value);
+ }
+
+ public V getFirst(K key) {
+ List<V> values = this.targetMap.get(key);
+ return (values != null ? values.get(0) : null);
+ }
+
+ public void set(K key, V value) {
+ List<V> values = new LinkedList<V>();
+ values.add(value);
+ this.targetMap.put(key, values);
+ }
+
+ public void setAll(Map<K, V> values) {
+ for (Entry<K, V> entry : values.entrySet()) {
+ set(entry.getKey(), entry.getValue());
+ }
+ }
+
+ public Map<K, V> toSingleValueMap() {
+ LinkedHashMap<K, V> singleValueMap = new LinkedHashMap<K,V>(this.targetMap.size());
+ for (Entry<K, List<V>> entry : targetMap.entrySet()) {
+ singleValueMap.put(entry.getKey(), entry.getValue().get(0));
+ }
+ return singleValueMap;
+ }
+
+
+ // Map implementation
+
+ public int size() {
+ return this.targetMap.size();
+ }
+
+ public boolean isEmpty() {
+ return this.targetMap.isEmpty();
+ }
+
+ public boolean containsKey(Object key) {
+ return this.targetMap.containsKey(key);
+ }
+
+ public boolean containsValue(Object value) {
+ return this.targetMap.containsValue(value);
+ }
+
+ public List<V> get(Object key) {
+ return this.targetMap.get(key);
+ }
+
+ public List<V> put(K key, List<V> value) {
+ return this.targetMap.put(key, value);
+ }
+
+ public List<V> remove(Object key) {
+ return this.targetMap.remove(key);
+ }
+
+ public void putAll(Map<? extends K, ? extends List<V>> m) {
+ this.targetMap.putAll(m);
+ }
+
+ public void clear() {
+ this.targetMap.clear();
+ }
+
+ public Set<K> keySet() {
+ return this.targetMap.keySet();
+ }
+
+ public Collection<List<V>> values() {
+ return this.targetMap.values();
+ }
+
+ public Set<Entry<K, List<V>>> entrySet() {
+ return this.targetMap.entrySet();
+ }
+
+
+ @Override
+ public boolean equals(Object obj) {
+ return this.targetMap.equals(obj);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.targetMap.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return this.targetMap.toString();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/Log4jConfigurer.java b/spring-core/src/main/java/org/springframework/util/Log4jConfigurer.java
new file mode 100644
index 00000000..655bc0ea
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/Log4jConfigurer.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2002-2012 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.util;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.net.URL;
+
+import org.apache.log4j.LogManager;
+import org.apache.log4j.PropertyConfigurator;
+import org.apache.log4j.xml.DOMConfigurator;
+
+/**
+ * Convenience class that features simple methods for custom log4j configuration.
+ *
+ * <p>Only needed for non-default log4j initialization, for example with a custom
+ * config location or a refresh interval. By default, log4j will simply read its
+ * configuration from a "log4j.properties" or "log4j.xml" file in the root of
+ * the classpath.
+ *
+ * <p>For web environments, the analogous Log4jWebConfigurer class can be found
+ * in the web package, reading in its configuration from context-params in
+ * {@code web.xml}. In a J2EE web application, log4j is usually set up
+ * via Log4jConfigListener or Log4jConfigServlet, delegating to
+ * Log4jWebConfigurer underneath.
+ *
+ * @author Juergen Hoeller
+ * @since 13.03.2003
+ * @see org.springframework.web.util.Log4jWebConfigurer
+ * @see org.springframework.web.util.Log4jConfigListener
+ */
+public abstract class Log4jConfigurer {
+
+ /** Pseudo URL prefix for loading from the class path: "classpath:" */
+ public static final String CLASSPATH_URL_PREFIX = "classpath:";
+
+ /** Extension that indicates a log4j XML config file: ".xml" */
+ public static final String XML_FILE_EXTENSION = ".xml";
+
+
+ /**
+ * Initialize log4j from the given file location, with no config file refreshing.
+ * Assumes an XML file in case of a ".xml" file extension, and a properties file
+ * otherwise.
+ * @param location the location of the config file: either a "classpath:" location
+ * (e.g. "classpath:myLog4j.properties"), an absolute file URL
+ * (e.g. "file:C:/log4j.properties), or a plain absolute path in the file system
+ * (e.g. "C:/log4j.properties")
+ * @throws FileNotFoundException if the location specifies an invalid file path
+ */
+ public static void initLogging(String location) throws FileNotFoundException {
+ String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(location);
+ URL url = ResourceUtils.getURL(resolvedLocation);
+ if (resolvedLocation.toLowerCase().endsWith(XML_FILE_EXTENSION)) {
+ DOMConfigurator.configure(url);
+ }
+ else {
+ PropertyConfigurator.configure(url);
+ }
+ }
+
+ /**
+ * Initialize log4j from the given location, with the given refresh interval
+ * for the config file. Assumes an XML file in case of a ".xml" file extension,
+ * and a properties file otherwise.
+ * <p>Log4j's watchdog thread will asynchronously check whether the timestamp
+ * of the config file has changed, using the given interval between checks.
+ * A refresh interval of 1000 milliseconds (one second), which allows to
+ * do on-demand log level changes with immediate effect, is not unfeasible.
+ * <p><b>WARNING:</b> Log4j's watchdog thread does not terminate until VM shutdown;
+ * in particular, it does not terminate on LogManager shutdown. Therefore, it is
+ * recommended to <i>not</i> use config file refreshing in a production J2EE
+ * environment; the watchdog thread would not stop on application shutdown there.
+ * @param location the location of the config file: either a "classpath:" location
+ * (e.g. "classpath:myLog4j.properties"), an absolute file URL
+ * (e.g. "file:C:/log4j.properties), or a plain absolute path in the file system
+ * (e.g. "C:/log4j.properties")
+ * @param refreshInterval interval between config file refresh checks, in milliseconds
+ * @throws FileNotFoundException if the location specifies an invalid file path
+ */
+ public static void initLogging(String location, long refreshInterval) throws FileNotFoundException {
+ String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(location);
+ File file = ResourceUtils.getFile(resolvedLocation);
+ if (!file.exists()) {
+ throw new FileNotFoundException("Log4j config file [" + resolvedLocation + "] not found");
+ }
+ if (resolvedLocation.toLowerCase().endsWith(XML_FILE_EXTENSION)) {
+ DOMConfigurator.configureAndWatch(file.getAbsolutePath(), refreshInterval);
+ }
+ else {
+ PropertyConfigurator.configureAndWatch(file.getAbsolutePath(), refreshInterval);
+ }
+ }
+
+ /**
+ * Shut down log4j, properly releasing all file locks.
+ * <p>This isn't strictly necessary, but recommended for shutting down
+ * log4j in a scenario where the host VM stays alive (for example, when
+ * shutting down an application in a J2EE environment).
+ */
+ public static void shutdownLogging() {
+ LogManager.shutdown();
+ }
+
+ /**
+ * Set the specified system property to the current working directory.
+ * <p>This can be used e.g. for test environments, for applications that leverage
+ * Log4jWebConfigurer's "webAppRootKey" support in a web environment.
+ * @param key system property key to use, as expected in Log4j configuration
+ * (for example: "demo.root", used as "${demo.root}/WEB-INF/demo.log")
+ * @see org.springframework.web.util.Log4jWebConfigurer
+ */
+ public static void setWorkingDirSystemProperty(String key) {
+ System.setProperty(key, new File("").getAbsolutePath());
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/MethodInvoker.java b/spring-core/src/main/java/org/springframework/util/MethodInvoker.java
new file mode 100644
index 00000000..d2331caf
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/MethodInvoker.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2002-2013 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.util;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Helper class that allows for specifying a method to invoke in a declarative
+ * fashion, be it static or non-static.
+ *
+ * <p>Usage: Specify "targetClass"/"targetMethod" or "targetObject"/"targetMethod",
+ * optionally specify arguments, prepare the invoker. Afterwards, you may
+ * invoke the method any number of times, obtaining the invocation result.
+ *
+ * @author Colin Sampaleanu
+ * @author Juergen Hoeller
+ * @since 19.02.2004
+ * @see #prepare
+ * @see #invoke
+ */
+public class MethodInvoker {
+
+ private Class<?> targetClass;
+
+ private Object targetObject;
+
+ private String targetMethod;
+
+ private String staticMethod;
+
+ private Object[] arguments = new Object[0];
+
+ /** The method we will call */
+ private Method methodObject;
+
+
+ /**
+ * Set the target class on which to call the target method.
+ * Only necessary when the target method is static; else,
+ * a target object needs to be specified anyway.
+ * @see #setTargetObject
+ * @see #setTargetMethod
+ */
+ public void setTargetClass(Class<?> targetClass) {
+ this.targetClass = targetClass;
+ }
+
+ /**
+ * Return the target class on which to call the target method.
+ */
+ public Class<?> getTargetClass() {
+ return this.targetClass;
+ }
+
+ /**
+ * Set the target object on which to call the target method.
+ * Only necessary when the target method is not static;
+ * else, a target class is sufficient.
+ * @see #setTargetClass
+ * @see #setTargetMethod
+ */
+ public void setTargetObject(Object targetObject) {
+ this.targetObject = targetObject;
+ if (targetObject != null) {
+ this.targetClass = targetObject.getClass();
+ }
+ }
+
+ /**
+ * Return the target object on which to call the target method.
+ */
+ public Object getTargetObject() {
+ return this.targetObject;
+ }
+
+ /**
+ * Set the name of the method to be invoked.
+ * Refers to either a static method or a non-static method,
+ * depending on a target object being set.
+ * @see #setTargetClass
+ * @see #setTargetObject
+ */
+ public void setTargetMethod(String targetMethod) {
+ this.targetMethod = targetMethod;
+ }
+
+ /**
+ * Return the name of the method to be invoked.
+ */
+ public String getTargetMethod() {
+ return this.targetMethod;
+ }
+
+ /**
+ * Set a fully qualified static method name to invoke,
+ * e.g. "example.MyExampleClass.myExampleMethod".
+ * Convenient alternative to specifying targetClass and targetMethod.
+ * @see #setTargetClass
+ * @see #setTargetMethod
+ */
+ public void setStaticMethod(String staticMethod) {
+ this.staticMethod = staticMethod;
+ }
+
+ /**
+ * Set arguments for the method invocation. If this property is not set,
+ * or the Object array is of length 0, a method with no arguments is assumed.
+ */
+ public void setArguments(Object[] arguments) {
+ this.arguments = (arguments != null ? arguments : new Object[0]);
+ }
+
+ /**
+ * Return the arguments for the method invocation.
+ */
+ public Object[] getArguments() {
+ return this.arguments;
+ }
+
+
+ /**
+ * Prepare the specified method.
+ * The method can be invoked any number of times afterwards.
+ * @see #getPreparedMethod
+ * @see #invoke
+ */
+ public void prepare() throws ClassNotFoundException, NoSuchMethodException {
+ if (this.staticMethod != null) {
+ int lastDotIndex = this.staticMethod.lastIndexOf('.');
+ if (lastDotIndex == -1 || lastDotIndex == this.staticMethod.length()) {
+ throw new IllegalArgumentException(
+ "staticMethod must be a fully qualified class plus method name: " +
+ "e.g. 'example.MyExampleClass.myExampleMethod'");
+ }
+ String className = this.staticMethod.substring(0, lastDotIndex);
+ String methodName = this.staticMethod.substring(lastDotIndex + 1);
+ this.targetClass = resolveClassName(className);
+ this.targetMethod = methodName;
+ }
+
+ Class<?> targetClass = getTargetClass();
+ String targetMethod = getTargetMethod();
+ if (targetClass == null) {
+ throw new IllegalArgumentException("Either 'targetClass' or 'targetObject' is required");
+ }
+ if (targetMethod == null) {
+ throw new IllegalArgumentException("Property 'targetMethod' is required");
+ }
+
+ Object[] arguments = getArguments();
+ Class<?>[] argTypes = new Class<?>[arguments.length];
+ for (int i = 0; i < arguments.length; ++i) {
+ argTypes[i] = (arguments[i] != null ? arguments[i].getClass() : Object.class);
+ }
+
+ // Try to get the exact method first.
+ try {
+ this.methodObject = targetClass.getMethod(targetMethod, argTypes);
+ }
+ catch (NoSuchMethodException ex) {
+ // Just rethrow exception if we can't get any match.
+ this.methodObject = findMatchingMethod();
+ if (this.methodObject == null) {
+ throw ex;
+ }
+ }
+ }
+
+ /**
+ * Resolve the given class name into a Class.
+ * <p>The default implementations uses {@code ClassUtils.forName},
+ * using the thread context class loader.
+ * @param className the class name to resolve
+ * @return the resolved Class
+ * @throws ClassNotFoundException if the class name was invalid
+ */
+ protected Class<?> resolveClassName(String className) throws ClassNotFoundException {
+ return ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
+ }
+
+ /**
+ * Find a matching method with the specified name for the specified arguments.
+ * @return a matching method, or {@code null} if none
+ * @see #getTargetClass()
+ * @see #getTargetMethod()
+ * @see #getArguments()
+ */
+ protected Method findMatchingMethod() {
+ String targetMethod = getTargetMethod();
+ Object[] arguments = getArguments();
+ int argCount = arguments.length;
+
+ Method[] candidates = ReflectionUtils.getAllDeclaredMethods(getTargetClass());
+ int minTypeDiffWeight = Integer.MAX_VALUE;
+ Method matchingMethod = null;
+
+ for (Method candidate : candidates) {
+ if (candidate.getName().equals(targetMethod)) {
+ Class<?>[] paramTypes = candidate.getParameterTypes();
+ if (paramTypes.length == argCount) {
+ int typeDiffWeight = getTypeDifferenceWeight(paramTypes, arguments);
+ if (typeDiffWeight < minTypeDiffWeight) {
+ minTypeDiffWeight = typeDiffWeight;
+ matchingMethod = candidate;
+ }
+ }
+ }
+ }
+
+ return matchingMethod;
+ }
+
+ /**
+ * Return the prepared Method object that will be invoked.
+ * <p>Can for example be used to determine the return type.
+ * @return the prepared Method object (never {@code null})
+ * @throws IllegalStateException if the invoker hasn't been prepared yet
+ * @see #prepare
+ * @see #invoke
+ */
+ public Method getPreparedMethod() throws IllegalStateException {
+ if (this.methodObject == null) {
+ throw new IllegalStateException("prepare() must be called prior to invoke() on MethodInvoker");
+ }
+ return this.methodObject;
+ }
+
+ /**
+ * Return whether this invoker has been prepared already,
+ * i.e. whether it allows access to {@link #getPreparedMethod()} already.
+ */
+ public boolean isPrepared() {
+ return (this.methodObject != null);
+ }
+
+ /**
+ * Invoke the specified method.
+ * <p>The invoker needs to have been prepared before.
+ * @return the object (possibly null) returned by the method invocation,
+ * or {@code null} if the method has a void return type
+ * @throws InvocationTargetException if the target method threw an exception
+ * @throws IllegalAccessException if the target method couldn't be accessed
+ * @see #prepare
+ */
+ public Object invoke() throws InvocationTargetException, IllegalAccessException {
+ // In the static case, target will simply be {@code null}.
+ Object targetObject = getTargetObject();
+ Method preparedMethod = getPreparedMethod();
+ if (targetObject == null && !Modifier.isStatic(preparedMethod.getModifiers())) {
+ throw new IllegalArgumentException("Target method must not be non-static without a target");
+ }
+ ReflectionUtils.makeAccessible(preparedMethod);
+ return preparedMethod.invoke(targetObject, getArguments());
+ }
+
+
+ /**
+ * Algorithm that judges the match between the declared parameter types of a candidate method
+ * and a specific list of arguments that this method is supposed to be invoked with.
+ * <p>Determines a weight that represents the class hierarchy difference between types and
+ * arguments. A direct match, i.e. type Integer -> arg of class Integer, does not increase
+ * the result - all direct matches means weight 0. A match between type Object and arg of
+ * class Integer would increase the weight by 2, due to the superclass 2 steps up in the
+ * hierarchy (i.e. Object) being the last one that still matches the required type Object.
+ * Type Number and class Integer would increase the weight by 1 accordingly, due to the
+ * superclass 1 step up the hierarchy (i.e. Number) still matching the required type Number.
+ * Therefore, with an arg of type Integer, a constructor (Integer) would be preferred to a
+ * constructor (Number) which would in turn be preferred to a constructor (Object).
+ * All argument weights get accumulated.
+ * <p>Note: This is the algorithm used by MethodInvoker itself and also the algorithm
+ * used for constructor and factory method selection in Spring's bean container (in case
+ * of lenient constructor resolution which is the default for regular bean definitions).
+ * @param paramTypes the parameter types to match
+ * @param args the arguments to match
+ * @return the accumulated weight for all arguments
+ */
+ public static int getTypeDifferenceWeight(Class<?>[] paramTypes, Object[] args) {
+ int result = 0;
+ for (int i = 0; i < paramTypes.length; i++) {
+ if (!ClassUtils.isAssignableValue(paramTypes[i], args[i])) {
+ return Integer.MAX_VALUE;
+ }
+ if (args[i] != null) {
+ Class<?> paramType = paramTypes[i];
+ Class<?> superClass = args[i].getClass().getSuperclass();
+ while (superClass != null) {
+ if (paramType.equals(superClass)) {
+ result = result + 2;
+ superClass = null;
+ }
+ else if (ClassUtils.isAssignable(paramType, superClass)) {
+ result = result + 2;
+ superClass = superClass.getSuperclass();
+ }
+ else {
+ superClass = null;
+ }
+ }
+ if (paramType.isInterface()) {
+ result = result + 1;
+ }
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/MultiValueMap.java b/spring-core/src/main/java/org/springframework/util/MultiValueMap.java
new file mode 100644
index 00000000..a526af78
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/MultiValueMap.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2002-2012 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.util;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Extension of the {@code Map} interface that stores multiple values.
+ *
+ * @author Arjen Poutsma
+ * @since 3.0
+ */
+public interface MultiValueMap<K, V> extends Map<K, List<V>> {
+
+ /**
+ * Return the first value for the given key.
+ * @param key the key
+ * @return the first value for the specified key, or {@code null}
+ */
+ V getFirst(K key);
+
+ /**
+ * Add the given single value to the current list of values for the given key.
+ * @param key the key
+ * @param value the value to be added
+ */
+ void add(K key, V value);
+
+ /**
+ * Set the given single value under the given key.
+ * @param key the key
+ * @param value the value to set
+ */
+ void set(K key, V value);
+
+ /**
+ * Set the given values under.
+ * @param values the values.
+ */
+ void setAll(Map<K, V> values);
+
+ /**
+ * Returns the first values contained in this {@code MultiValueMap}.
+ * @return a single value representation of this map
+ */
+ Map<K, V> toSingleValueMap();
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/NumberUtils.java b/spring-core/src/main/java/org/springframework/util/NumberUtils.java
new file mode 100644
index 00000000..47f38a0b
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/NumberUtils.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2002-2012 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.util;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.text.ParseException;
+
+/**
+ * Miscellaneous utility methods for number conversion and parsing.
+ * Mainly for internal use within the framework; consider Jakarta's
+ * Commons Lang for a more comprehensive suite of string utilities.
+ *
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @since 1.1.2
+ */
+public abstract class NumberUtils {
+
+ /**
+ * Convert the given number into an instance of the given target class.
+ * @param number the number to convert
+ * @param targetClass the target class to convert to
+ * @return the converted number
+ * @throws IllegalArgumentException if the target class is not supported
+ * (i.e. not a standard Number subclass as included in the JDK)
+ * @see java.lang.Byte
+ * @see java.lang.Short
+ * @see java.lang.Integer
+ * @see java.lang.Long
+ * @see java.math.BigInteger
+ * @see java.lang.Float
+ * @see java.lang.Double
+ * @see java.math.BigDecimal
+ */
+ @SuppressWarnings("unchecked")
+ public static <T extends Number> T convertNumberToTargetClass(Number number, Class<T> targetClass)
+ throws IllegalArgumentException {
+
+ Assert.notNull(number, "Number must not be null");
+ Assert.notNull(targetClass, "Target class must not be null");
+
+ if (targetClass.isInstance(number)) {
+ return (T) number;
+ }
+ else if (targetClass.equals(Byte.class)) {
+ long value = number.longValue();
+ if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
+ raiseOverflowException(number, targetClass);
+ }
+ return (T) new Byte(number.byteValue());
+ }
+ else if (targetClass.equals(Short.class)) {
+ long value = number.longValue();
+ if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
+ raiseOverflowException(number, targetClass);
+ }
+ return (T) new Short(number.shortValue());
+ }
+ else if (targetClass.equals(Integer.class)) {
+ long value = number.longValue();
+ if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
+ raiseOverflowException(number, targetClass);
+ }
+ return (T) new Integer(number.intValue());
+ }
+ else if (targetClass.equals(Long.class)) {
+ return (T) new Long(number.longValue());
+ }
+ else if (targetClass.equals(BigInteger.class)) {
+ if (number instanceof BigDecimal) {
+ // do not lose precision - use BigDecimal's own conversion
+ return (T) ((BigDecimal) number).toBigInteger();
+ }
+ else {
+ // original value is not a Big* number - use standard long conversion
+ return (T) BigInteger.valueOf(number.longValue());
+ }
+ }
+ else if (targetClass.equals(Float.class)) {
+ return (T) new Float(number.floatValue());
+ }
+ else if (targetClass.equals(Double.class)) {
+ return (T) new Double(number.doubleValue());
+ }
+ else if (targetClass.equals(BigDecimal.class)) {
+ // always use BigDecimal(String) here to avoid unpredictability of BigDecimal(double)
+ // (see BigDecimal javadoc for details)
+ return (T) new BigDecimal(number.toString());
+ }
+ else {
+ throw new IllegalArgumentException("Could not convert number [" + number + "] of type [" +
+ number.getClass().getName() + "] to unknown target class [" + targetClass.getName() + "]");
+ }
+ }
+
+ /**
+ * Raise an overflow exception for the given number and target class.
+ * @param number the number we tried to convert
+ * @param targetClass the target class we tried to convert to
+ */
+ private static void raiseOverflowException(Number number, Class targetClass) {
+ throw new IllegalArgumentException("Could not convert number [" + number + "] of type [" +
+ number.getClass().getName() + "] to target class [" + targetClass.getName() + "]: overflow");
+ }
+
+ /**
+ * Parse the given text into a number instance of the given target class,
+ * using the corresponding {@code decode} / {@code valueOf} methods.
+ * <p>Trims the input {@code String} before attempting to parse the number.
+ * Supports numbers in hex format (with leading "0x", "0X" or "#") as well.
+ * @param text the text to convert
+ * @param targetClass the target class to parse into
+ * @return the parsed number
+ * @throws IllegalArgumentException if the target class is not supported
+ * (i.e. not a standard Number subclass as included in the JDK)
+ * @see Byte#decode
+ * @see Short#decode
+ * @see Integer#decode
+ * @see Long#decode
+ * @see #decodeBigInteger(String)
+ * @see Float#valueOf
+ * @see Double#valueOf
+ * @see java.math.BigDecimal#BigDecimal(String)
+ */
+ @SuppressWarnings("unchecked")
+ public static <T extends Number> T parseNumber(String text, Class<T> targetClass) {
+ Assert.notNull(text, "Text must not be null");
+ Assert.notNull(targetClass, "Target class must not be null");
+ String trimmed = StringUtils.trimAllWhitespace(text);
+
+ if (targetClass.equals(Byte.class)) {
+ return (T) (isHexNumber(trimmed) ? Byte.decode(trimmed) : Byte.valueOf(trimmed));
+ }
+ else if (targetClass.equals(Short.class)) {
+ return (T) (isHexNumber(trimmed) ? Short.decode(trimmed) : Short.valueOf(trimmed));
+ }
+ else if (targetClass.equals(Integer.class)) {
+ return (T) (isHexNumber(trimmed) ? Integer.decode(trimmed) : Integer.valueOf(trimmed));
+ }
+ else if (targetClass.equals(Long.class)) {
+ return (T) (isHexNumber(trimmed) ? Long.decode(trimmed) : Long.valueOf(trimmed));
+ }
+ else if (targetClass.equals(BigInteger.class)) {
+ return (T) (isHexNumber(trimmed) ? decodeBigInteger(trimmed) : new BigInteger(trimmed));
+ }
+ else if (targetClass.equals(Float.class)) {
+ return (T) Float.valueOf(trimmed);
+ }
+ else if (targetClass.equals(Double.class)) {
+ return (T) Double.valueOf(trimmed);
+ }
+ else if (targetClass.equals(BigDecimal.class) || targetClass.equals(Number.class)) {
+ return (T) new BigDecimal(trimmed);
+ }
+ else {
+ throw new IllegalArgumentException(
+ "Cannot convert String [" + text + "] to target class [" + targetClass.getName() + "]");
+ }
+ }
+
+ /**
+ * Parse the given text into a number instance of the given target class,
+ * using the given NumberFormat. Trims the input {@code String}
+ * before attempting to parse the number.
+ * @param text the text to convert
+ * @param targetClass the target class to parse into
+ * @param numberFormat the NumberFormat to use for parsing (if {@code null},
+ * this method falls back to {@code parseNumber(String, Class)})
+ * @return the parsed number
+ * @throws IllegalArgumentException if the target class is not supported
+ * (i.e. not a standard Number subclass as included in the JDK)
+ * @see java.text.NumberFormat#parse
+ * @see #convertNumberToTargetClass
+ * @see #parseNumber(String, Class)
+ */
+ public static <T extends Number> T parseNumber(String text, Class<T> targetClass, NumberFormat numberFormat) {
+ if (numberFormat != null) {
+ Assert.notNull(text, "Text must not be null");
+ Assert.notNull(targetClass, "Target class must not be null");
+ DecimalFormat decimalFormat = null;
+ boolean resetBigDecimal = false;
+ if (numberFormat instanceof DecimalFormat) {
+ decimalFormat = (DecimalFormat) numberFormat;
+ if (BigDecimal.class.equals(targetClass) && !decimalFormat.isParseBigDecimal()) {
+ decimalFormat.setParseBigDecimal(true);
+ resetBigDecimal = true;
+ }
+ }
+ try {
+ Number number = numberFormat.parse(StringUtils.trimAllWhitespace(text));
+ return convertNumberToTargetClass(number, targetClass);
+ }
+ catch (ParseException ex) {
+ throw new IllegalArgumentException("Could not parse number: " + ex.getMessage());
+ }
+ finally {
+ if (resetBigDecimal) {
+ decimalFormat.setParseBigDecimal(false);
+ }
+ }
+ }
+ else {
+ return parseNumber(text, targetClass);
+ }
+ }
+
+ /**
+ * Determine whether the given value String indicates a hex number, i.e. needs to be
+ * passed into {@code Integer.decode} instead of {@code Integer.valueOf} (etc).
+ */
+ private static boolean isHexNumber(String value) {
+ int index = (value.startsWith("-") ? 1 : 0);
+ return (value.startsWith("0x", index) || value.startsWith("0X", index) || value.startsWith("#", index));
+ }
+
+ /**
+ * Decode a {@link java.math.BigInteger} from a {@link String} value.
+ * Supports decimal, hex and octal notation.
+ * @see BigInteger#BigInteger(String, int)
+ */
+ private static BigInteger decodeBigInteger(String value) {
+ int radix = 10;
+ int index = 0;
+ boolean negative = false;
+
+ // Handle minus sign, if present.
+ if (value.startsWith("-")) {
+ negative = true;
+ index++;
+ }
+
+ // Handle radix specifier, if present.
+ if (value.startsWith("0x", index) || value.startsWith("0X", index)) {
+ index += 2;
+ radix = 16;
+ }
+ else if (value.startsWith("#", index)) {
+ index++;
+ radix = 16;
+ }
+ else if (value.startsWith("0", index) && value.length() > 1 + index) {
+ index++;
+ radix = 8;
+ }
+
+ BigInteger result = new BigInteger(value.substring(index), radix);
+ return (negative ? result.negate() : result);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/ObjectUtils.java b/spring-core/src/main/java/org/springframework/util/ObjectUtils.java
new file mode 100644
index 00000000..218d43fd
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/ObjectUtils.java
@@ -0,0 +1,880 @@
+/*
+ * Copyright 2002-2013 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.util;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+
+/**
+ * Miscellaneous object utility methods.
+ * Mainly for internal use within the framework.
+ *
+ * <p>Thanks to Alex Ruiz for contributing several enhancements to this class!
+ *
+ * @author Juergen Hoeller
+ * @author Keith Donald
+ * @author Rod Johnson
+ * @author Rob Harrop
+ * @author Chris Beams
+ * @since 19.03.2004
+ */
+public abstract class ObjectUtils {
+
+ private static final int INITIAL_HASH = 7;
+ private static final int MULTIPLIER = 31;
+
+ private static final String EMPTY_STRING = "";
+ private static final String NULL_STRING = "null";
+ private static final String ARRAY_START = "{";
+ private static final String ARRAY_END = "}";
+ private static final String EMPTY_ARRAY = ARRAY_START + ARRAY_END;
+ private static final String ARRAY_ELEMENT_SEPARATOR = ", ";
+
+
+ /**
+ * Return whether the given throwable is a checked exception:
+ * that is, neither a RuntimeException nor an Error.
+ * @param ex the throwable to check
+ * @return whether the throwable is a checked exception
+ * @see java.lang.Exception
+ * @see java.lang.RuntimeException
+ * @see java.lang.Error
+ */
+ public static boolean isCheckedException(Throwable ex) {
+ return !(ex instanceof RuntimeException || ex instanceof Error);
+ }
+
+ /**
+ * Check whether the given exception is compatible with the specified
+ * exception types, as declared in a throws clause.
+ * @param ex the exception to check
+ * @param declaredExceptions the exception types declared in the throws clause
+ * @return whether the given exception is compatible
+ */
+ public static boolean isCompatibleWithThrowsClause(Throwable ex, Class<?>... declaredExceptions) {
+ if (!isCheckedException(ex)) {
+ return true;
+ }
+ if (declaredExceptions != null) {
+ for (Class<?> declaredException : declaredExceptions) {
+ if (declaredException.isInstance(ex)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Determine whether the given object is an array:
+ * either an Object array or a primitive array.
+ * @param obj the object to check
+ */
+ public static boolean isArray(Object obj) {
+ return (obj != null && obj.getClass().isArray());
+ }
+
+ /**
+ * Determine whether the given array is empty:
+ * i.e. {@code null} or of zero length.
+ * @param array the array to check
+ */
+ public static boolean isEmpty(Object[] array) {
+ return (array == null || array.length == 0);
+ }
+
+ /**
+ * Check whether the given array contains the given element.
+ * @param array the array to check (may be {@code null},
+ * in which case the return value will always be {@code false})
+ * @param element the element to check for
+ * @return whether the element has been found in the given array
+ */
+ public static boolean containsElement(Object[] array, Object element) {
+ if (array == null) {
+ return false;
+ }
+ for (Object arrayEle : array) {
+ if (nullSafeEquals(arrayEle, element)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the given array of enum constants contains a constant with the given name,
+ * ignoring case when determining a match.
+ * @param enumValues the enum values to check, typically the product of a call to MyEnum.values()
+ * @param constant the constant name to find (must not be null or empty string)
+ * @return whether the constant has been found in the given array
+ */
+ public static boolean containsConstant(Enum<?>[] enumValues, String constant) {
+ return containsConstant(enumValues, constant, false);
+ }
+
+ /**
+ * Check whether the given array of enum constants contains a constant with the given name.
+ * @param enumValues the enum values to check, typically the product of a call to MyEnum.values()
+ * @param constant the constant name to find (must not be null or empty string)
+ * @param caseSensitive whether case is significant in determining a match
+ * @return whether the constant has been found in the given array
+ */
+ public static boolean containsConstant(Enum<?>[] enumValues, String constant, boolean caseSensitive) {
+ for (Enum<?> candidate : enumValues) {
+ if (caseSensitive ?
+ candidate.toString().equals(constant) :
+ candidate.toString().equalsIgnoreCase(constant)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Case insensitive alternative to {@link Enum#valueOf(Class, String)}.
+ * @param <E> the concrete Enum type
+ * @param enumValues the array of all Enum constants in question, usually per Enum.values()
+ * @param constant the constant to get the enum value of
+ * @throws IllegalArgumentException if the given constant is not found in the given array
+ * of enum values. Use {@link #containsConstant(Enum[], String)} as a guard to avoid this exception.
+ */
+ public static <E extends Enum<?>> E caseInsensitiveValueOf(E[] enumValues, String constant) {
+ for (E candidate : enumValues) {
+ if (candidate.toString().equalsIgnoreCase(constant)) {
+ return candidate;
+ }
+ }
+ throw new IllegalArgumentException(
+ String.format("constant [%s] does not exist in enum type %s",
+ constant, enumValues.getClass().getComponentType().getName()));
+ }
+
+ /**
+ * Append the given object to the given array, returning a new array
+ * consisting of the input array contents plus the given object.
+ * @param array the array to append to (can be {@code null})
+ * @param obj the object to append
+ * @return the new array (of the same component type; never {@code null})
+ */
+ public static <A, O extends A> A[] addObjectToArray(A[] array, O obj) {
+ Class<?> compType = Object.class;
+ if (array != null) {
+ compType = array.getClass().getComponentType();
+ }
+ else if (obj != null) {
+ compType = obj.getClass();
+ }
+ int newArrLength = (array != null ? array.length + 1 : 1);
+ @SuppressWarnings("unchecked")
+ A[] newArr = (A[]) Array.newInstance(compType, newArrLength);
+ if (array != null) {
+ System.arraycopy(array, 0, newArr, 0, array.length);
+ }
+ newArr[newArr.length - 1] = obj;
+ return newArr;
+ }
+
+ /**
+ * Convert the given array (which may be a primitive array) to an
+ * object array (if necessary of primitive wrapper objects).
+ * <p>A {@code null} source value will be converted to an
+ * empty Object array.
+ * @param source the (potentially primitive) array
+ * @return the corresponding object array (never {@code null})
+ * @throws IllegalArgumentException if the parameter is not an array
+ */
+ public static Object[] toObjectArray(Object source) {
+ if (source instanceof Object[]) {
+ return (Object[]) source;
+ }
+ if (source == null) {
+ return new Object[0];
+ }
+ if (!source.getClass().isArray()) {
+ throw new IllegalArgumentException("Source is not an array: " + source);
+ }
+ int length = Array.getLength(source);
+ if (length == 0) {
+ return new Object[0];
+ }
+ Class<?> wrapperType = Array.get(source, 0).getClass();
+ Object[] newArray = (Object[]) Array.newInstance(wrapperType, length);
+ for (int i = 0; i < length; i++) {
+ newArray[i] = Array.get(source, i);
+ }
+ return newArray;
+ }
+
+
+ //---------------------------------------------------------------------
+ // Convenience methods for content-based equality/hash-code handling
+ //---------------------------------------------------------------------
+
+ /**
+ * Determine if the given objects are equal, returning {@code true}
+ * if both are {@code null} or {@code false} if only one is
+ * {@code null}.
+ * <p>Compares arrays with {@code Arrays.equals}, performing an equality
+ * check based on the array elements rather than the array reference.
+ * @param o1 first Object to compare
+ * @param o2 second Object to compare
+ * @return whether the given objects are equal
+ * @see java.util.Arrays#equals
+ */
+ public static boolean nullSafeEquals(Object o1, Object o2) {
+ if (o1 == o2) {
+ return true;
+ }
+ if (o1 == null || o2 == null) {
+ return false;
+ }
+ if (o1.equals(o2)) {
+ return true;
+ }
+ if (o1.getClass().isArray() && o2.getClass().isArray()) {
+ if (o1 instanceof Object[] && o2 instanceof Object[]) {
+ return Arrays.equals((Object[]) o1, (Object[]) o2);
+ }
+ if (o1 instanceof boolean[] && o2 instanceof boolean[]) {
+ return Arrays.equals((boolean[]) o1, (boolean[]) o2);
+ }
+ if (o1 instanceof byte[] && o2 instanceof byte[]) {
+ return Arrays.equals((byte[]) o1, (byte[]) o2);
+ }
+ if (o1 instanceof char[] && o2 instanceof char[]) {
+ return Arrays.equals((char[]) o1, (char[]) o2);
+ }
+ if (o1 instanceof double[] && o2 instanceof double[]) {
+ return Arrays.equals((double[]) o1, (double[]) o2);
+ }
+ if (o1 instanceof float[] && o2 instanceof float[]) {
+ return Arrays.equals((float[]) o1, (float[]) o2);
+ }
+ if (o1 instanceof int[] && o2 instanceof int[]) {
+ return Arrays.equals((int[]) o1, (int[]) o2);
+ }
+ if (o1 instanceof long[] && o2 instanceof long[]) {
+ return Arrays.equals((long[]) o1, (long[]) o2);
+ }
+ if (o1 instanceof short[] && o2 instanceof short[]) {
+ return Arrays.equals((short[]) o1, (short[]) o2);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return as hash code for the given object; typically the value of
+ * {@code Object#hashCode()}}. If the object is an array,
+ * this method will delegate to any of the {@code nullSafeHashCode}
+ * methods for arrays in this class. If the object is {@code null},
+ * this method returns 0.
+ * @see #nullSafeHashCode(Object[])
+ * @see #nullSafeHashCode(boolean[])
+ * @see #nullSafeHashCode(byte[])
+ * @see #nullSafeHashCode(char[])
+ * @see #nullSafeHashCode(double[])
+ * @see #nullSafeHashCode(float[])
+ * @see #nullSafeHashCode(int[])
+ * @see #nullSafeHashCode(long[])
+ * @see #nullSafeHashCode(short[])
+ */
+ public static int nullSafeHashCode(Object obj) {
+ if (obj == null) {
+ return 0;
+ }
+ if (obj.getClass().isArray()) {
+ if (obj instanceof Object[]) {
+ return nullSafeHashCode((Object[]) obj);
+ }
+ if (obj instanceof boolean[]) {
+ return nullSafeHashCode((boolean[]) obj);
+ }
+ if (obj instanceof byte[]) {
+ return nullSafeHashCode((byte[]) obj);
+ }
+ if (obj instanceof char[]) {
+ return nullSafeHashCode((char[]) obj);
+ }
+ if (obj instanceof double[]) {
+ return nullSafeHashCode((double[]) obj);
+ }
+ if (obj instanceof float[]) {
+ return nullSafeHashCode((float[]) obj);
+ }
+ if (obj instanceof int[]) {
+ return nullSafeHashCode((int[]) obj);
+ }
+ if (obj instanceof long[]) {
+ return nullSafeHashCode((long[]) obj);
+ }
+ if (obj instanceof short[]) {
+ return nullSafeHashCode((short[]) obj);
+ }
+ }
+ return obj.hashCode();
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If {@code array} is {@code null}, this method returns 0.
+ */
+ public static int nullSafeHashCode(Object[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ for (Object element : array) {
+ hash = MULTIPLIER * hash + nullSafeHashCode(element);
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If {@code array} is {@code null}, this method returns 0.
+ */
+ public static int nullSafeHashCode(boolean[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ for (boolean element : array) {
+ hash = MULTIPLIER * hash + hashCode(element);
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If {@code array} is {@code null}, this method returns 0.
+ */
+ public static int nullSafeHashCode(byte[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ for (byte element : array) {
+ hash = MULTIPLIER * hash + element;
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If {@code array} is {@code null}, this method returns 0.
+ */
+ public static int nullSafeHashCode(char[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ for (char element : array) {
+ hash = MULTIPLIER * hash + element;
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If {@code array} is {@code null}, this method returns 0.
+ */
+ public static int nullSafeHashCode(double[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ for (double element : array) {
+ hash = MULTIPLIER * hash + hashCode(element);
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If {@code array} is {@code null}, this method returns 0.
+ */
+ public static int nullSafeHashCode(float[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ for (float element : array) {
+ hash = MULTIPLIER * hash + hashCode(element);
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If {@code array} is {@code null}, this method returns 0.
+ */
+ public static int nullSafeHashCode(int[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ for (int element : array) {
+ hash = MULTIPLIER * hash + element;
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If {@code array} is {@code null}, this method returns 0.
+ */
+ public static int nullSafeHashCode(long[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ for (long element : array) {
+ hash = MULTIPLIER * hash + hashCode(element);
+ }
+ return hash;
+ }
+
+ /**
+ * Return a hash code based on the contents of the specified array.
+ * If {@code array} is {@code null}, this method returns 0.
+ */
+ public static int nullSafeHashCode(short[] array) {
+ if (array == null) {
+ return 0;
+ }
+ int hash = INITIAL_HASH;
+ for (short element : array) {
+ hash = MULTIPLIER * hash + element;
+ }
+ return hash;
+ }
+
+ /**
+ * Return the same value as {@link Boolean#hashCode()}}.
+ * @see Boolean#hashCode()
+ */
+ public static int hashCode(boolean bool) {
+ return (bool ? 1231 : 1237);
+ }
+
+ /**
+ * Return the same value as {@link Double#hashCode()}}.
+ * @see Double#hashCode()
+ */
+ public static int hashCode(double dbl) {
+ return hashCode(Double.doubleToLongBits(dbl));
+ }
+
+ /**
+ * Return the same value as {@link Float#hashCode()}}.
+ * @see Float#hashCode()
+ */
+ public static int hashCode(float flt) {
+ return Float.floatToIntBits(flt);
+ }
+
+ /**
+ * Return the same value as {@link Long#hashCode()}}.
+ * @see Long#hashCode()
+ */
+ public static int hashCode(long lng) {
+ return (int) (lng ^ (lng >>> 32));
+ }
+
+
+ //---------------------------------------------------------------------
+ // Convenience methods for toString output
+ //---------------------------------------------------------------------
+
+ /**
+ * Return a String representation of an object's overall identity.
+ * @param obj the object (may be {@code null})
+ * @return the object's identity as String representation,
+ * or an empty String if the object was {@code null}
+ */
+ public static String identityToString(Object obj) {
+ if (obj == null) {
+ return EMPTY_STRING;
+ }
+ return obj.getClass().getName() + "@" + getIdentityHexString(obj);
+ }
+
+ /**
+ * Return a hex String form of an object's identity hash code.
+ * @param obj the object
+ * @return the object's identity code in hex notation
+ */
+ public static String getIdentityHexString(Object obj) {
+ return Integer.toHexString(System.identityHashCode(obj));
+ }
+
+ /**
+ * Return a content-based String representation if {@code obj} is
+ * not {@code null}; otherwise returns an empty String.
+ * <p>Differs from {@link #nullSafeToString(Object)} in that it returns
+ * an empty String rather than "null" for a {@code null} value.
+ * @param obj the object to build a display String for
+ * @return a display String representation of {@code obj}
+ * @see #nullSafeToString(Object)
+ */
+ public static String getDisplayString(Object obj) {
+ if (obj == null) {
+ return EMPTY_STRING;
+ }
+ return nullSafeToString(obj);
+ }
+
+ /**
+ * Determine the class name for the given object.
+ * <p>Returns {@code "null"} if {@code obj} is {@code null}.
+ * @param obj the object to introspect (may be {@code null})
+ * @return the corresponding class name
+ */
+ public static String nullSafeClassName(Object obj) {
+ return (obj != null ? obj.getClass().getName() : NULL_STRING);
+ }
+
+ /**
+ * Return a String representation of the specified Object.
+ * <p>Builds a String representation of the contents in case of an array.
+ * Returns {@code "null"} if {@code obj} is {@code null}.
+ * @param obj the object to build a String representation for
+ * @return a String representation of {@code obj}
+ */
+ public static String nullSafeToString(Object obj) {
+ if (obj == null) {
+ return NULL_STRING;
+ }
+ if (obj instanceof String) {
+ return (String) obj;
+ }
+ if (obj instanceof Object[]) {
+ return nullSafeToString((Object[]) obj);
+ }
+ if (obj instanceof boolean[]) {
+ return nullSafeToString((boolean[]) obj);
+ }
+ if (obj instanceof byte[]) {
+ return nullSafeToString((byte[]) obj);
+ }
+ if (obj instanceof char[]) {
+ return nullSafeToString((char[]) obj);
+ }
+ if (obj instanceof double[]) {
+ return nullSafeToString((double[]) obj);
+ }
+ if (obj instanceof float[]) {
+ return nullSafeToString((float[]) obj);
+ }
+ if (obj instanceof int[]) {
+ return nullSafeToString((int[]) obj);
+ }
+ if (obj instanceof long[]) {
+ return nullSafeToString((long[]) obj);
+ }
+ if (obj instanceof short[]) {
+ return nullSafeToString((short[]) obj);
+ }
+ String str = obj.toString();
+ return (str != null ? str : EMPTY_STRING);
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ * <p>The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated
+ * by the characters {@code ", "} (a comma followed by a space). Returns
+ * {@code "null"} if {@code array} is {@code null}.
+ * @param array the array to build a String representation for
+ * @return a String representation of {@code array}
+ */
+ public static String nullSafeToString(Object[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ sb.append(ARRAY_START);
+ }
+ else {
+ sb.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+ sb.append(String.valueOf(array[i]));
+ }
+ sb.append(ARRAY_END);
+ return sb.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ * <p>The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated
+ * by the characters {@code ", "} (a comma followed by a space). Returns
+ * {@code "null"} if {@code array} is {@code null}.
+ * @param array the array to build a String representation for
+ * @return a String representation of {@code array}
+ */
+ public static String nullSafeToString(boolean[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ sb.append(ARRAY_START);
+ }
+ else {
+ sb.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+
+ sb.append(array[i]);
+ }
+ sb.append(ARRAY_END);
+ return sb.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ * <p>The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated
+ * by the characters {@code ", "} (a comma followed by a space). Returns
+ * {@code "null"} if {@code array} is {@code null}.
+ * @param array the array to build a String representation for
+ * @return a String representation of {@code array}
+ */
+ public static String nullSafeToString(byte[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ sb.append(ARRAY_START);
+ }
+ else {
+ sb.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+ sb.append(array[i]);
+ }
+ sb.append(ARRAY_END);
+ return sb.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ * <p>The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated
+ * by the characters {@code ", "} (a comma followed by a space). Returns
+ * {@code "null"} if {@code array} is {@code null}.
+ * @param array the array to build a String representation for
+ * @return a String representation of {@code array}
+ */
+ public static String nullSafeToString(char[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ sb.append(ARRAY_START);
+ }
+ else {
+ sb.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+ sb.append("'").append(array[i]).append("'");
+ }
+ sb.append(ARRAY_END);
+ return sb.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ * <p>The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated
+ * by the characters {@code ", "} (a comma followed by a space). Returns
+ * {@code "null"} if {@code array} is {@code null}.
+ * @param array the array to build a String representation for
+ * @return a String representation of {@code array}
+ */
+ public static String nullSafeToString(double[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ sb.append(ARRAY_START);
+ }
+ else {
+ sb.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+
+ sb.append(array[i]);
+ }
+ sb.append(ARRAY_END);
+ return sb.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ * <p>The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated
+ * by the characters {@code ", "} (a comma followed by a space). Returns
+ * {@code "null"} if {@code array} is {@code null}.
+ * @param array the array to build a String representation for
+ * @return a String representation of {@code array}
+ */
+ public static String nullSafeToString(float[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ sb.append(ARRAY_START);
+ }
+ else {
+ sb.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+
+ sb.append(array[i]);
+ }
+ sb.append(ARRAY_END);
+ return sb.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ * <p>The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated
+ * by the characters {@code ", "} (a comma followed by a space). Returns
+ * {@code "null"} if {@code array} is {@code null}.
+ * @param array the array to build a String representation for
+ * @return a String representation of {@code array}
+ */
+ public static String nullSafeToString(int[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ sb.append(ARRAY_START);
+ }
+ else {
+ sb.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+ sb.append(array[i]);
+ }
+ sb.append(ARRAY_END);
+ return sb.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ * <p>The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated
+ * by the characters {@code ", "} (a comma followed by a space). Returns
+ * {@code "null"} if {@code array} is {@code null}.
+ * @param array the array to build a String representation for
+ * @return a String representation of {@code array}
+ */
+ public static String nullSafeToString(long[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ sb.append(ARRAY_START);
+ }
+ else {
+ sb.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+ sb.append(array[i]);
+ }
+ sb.append(ARRAY_END);
+ return sb.toString();
+ }
+
+ /**
+ * Return a String representation of the contents of the specified array.
+ * <p>The String representation consists of a list of the array's elements,
+ * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated
+ * by the characters {@code ", "} (a comma followed by a space). Returns
+ * {@code "null"} if {@code array} is {@code null}.
+ * @param array the array to build a String representation for
+ * @return a String representation of {@code array}
+ */
+ public static String nullSafeToString(short[] array) {
+ if (array == null) {
+ return NULL_STRING;
+ }
+ int length = array.length;
+ if (length == 0) {
+ return EMPTY_ARRAY;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (i == 0) {
+ sb.append(ARRAY_START);
+ }
+ else {
+ sb.append(ARRAY_ELEMENT_SEPARATOR);
+ }
+ sb.append(array[i]);
+ }
+ sb.append(ARRAY_END);
+ return sb.toString();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/PathMatcher.java b/spring-core/src/main/java/org/springframework/util/PathMatcher.java
new file mode 100644
index 00000000..0bea9cd8
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/PathMatcher.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2002-2012 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.util;
+
+import java.util.Comparator;
+import java.util.Map;
+
+/**
+ * Strategy interface for {@code String}-based path matching.
+ *
+ * <p>Used by {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver},
+ * {@link org.springframework.web.servlet.handler.AbstractUrlHandlerMapping},
+ * {@link org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver},
+ * and {@link org.springframework.web.servlet.mvc.WebContentInterceptor}.
+ *
+ * <p>The default implementation is {@link AntPathMatcher}, supporting the
+ * Ant-style pattern syntax.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see AntPathMatcher
+ */
+public interface PathMatcher {
+
+ /**
+ * Does the given {@code path} represent a pattern that can be matched
+ * by an implementation of this interface?
+ * <p>If the return value is {@code false}, then the {@link #match}
+ * method does not have to be used because direct equality comparisons
+ * on the static path Strings will lead to the same result.
+ * @param path the path String to check
+ * @return {@code true} if the given {@code path} represents a pattern
+ */
+ boolean isPattern(String path);
+
+ /**
+ * Match the given {@code path} against the given {@code pattern},
+ * according to this PathMatcher's matching strategy.
+ * @param pattern the pattern to match against
+ * @param path the path String to test
+ * @return {@code true} if the supplied {@code path} matched,
+ * {@code false} if it didn't
+ */
+ boolean match(String pattern, String path);
+
+ /**
+ * Match the given {@code path} against the corresponding part of the given
+ * {@code pattern}, according to this PathMatcher's matching strategy.
+ * <p>Determines whether the pattern at least matches as far as the given base
+ * path goes, assuming that a full path may then match as well.
+ * @param pattern the pattern to match against
+ * @param path the path String to test
+ * @return {@code true} if the supplied {@code path} matched,
+ * {@code false} if it didn't
+ */
+ boolean matchStart(String pattern, String path);
+
+ /**
+ * Given a pattern and a full path, determine the pattern-mapped part.
+ * <p>This method is supposed to find out which part of the path is matched
+ * dynamically through an actual pattern, that is, it strips off a statically
+ * defined leading path from the given full path, returning only the actually
+ * pattern-matched part of the path.
+ * <p>For example: For "myroot/*.html" as pattern and "myroot/myfile.html"
+ * as full path, this method should return "myfile.html". The detailed
+ * determination rules are specified to this PathMatcher's matching strategy.
+ * <p>A simple implementation may return the given full path as-is in case
+ * of an actual pattern, and the empty String in case of the pattern not
+ * containing any dynamic parts (i.e. the {@code pattern} parameter being
+ * a static path that wouldn't qualify as an actual {@link #isPattern pattern}).
+ * A sophisticated implementation will differentiate between the static parts
+ * and the dynamic parts of the given path pattern.
+ * @param pattern the path pattern
+ * @param path the full path to introspect
+ * @return the pattern-mapped part of the given {@code path}
+ * (never {@code null})
+ */
+ String extractPathWithinPattern(String pattern, String path);
+
+ /**
+ * Given a pattern and a full path, extract the URI template variables. URI template
+ * variables are expressed through curly brackets ('{' and '}').
+ * <p>For example: For pattern "/hotels/{hotel}" and path "/hotels/1", this method will
+ * return a map containing "hotel"->"1".
+ * @param pattern the path pattern, possibly containing URI templates
+ * @param path the full path to extract template variables from
+ * @return a map, containing variable names as keys; variables values as values
+ */
+ Map<String, String> extractUriTemplateVariables(String pattern, String path);
+
+ /**
+ * Given a full path, returns a {@link Comparator} suitable for sorting patterns
+ * in order of explicitness for that path.
+ * <p>The full algorithm used depends on the underlying implementation, but generally,
+ * the returned {@code Comparator} will
+ * {@linkplain java.util.Collections#sort(java.util.List, java.util.Comparator) sort}
+ * a list so that more specific patterns come before generic patterns.
+ * @param path the full path to use for comparison
+ * @return a comparator capable of sorting patterns in order of explicitness
+ */
+ Comparator<String> getPatternComparator(String path);
+
+ /**
+ * Combines two patterns into a new pattern that is returned.
+ * <p>The full algorithm used for combining the two pattern depends on the underlying implementation.
+ * @param pattern1 the first pattern
+ * @param pattern2 the second pattern
+ * @return the combination of the two patterns
+ * @throws IllegalArgumentException when the two patterns cannot be combined
+ */
+ String combine(String pattern1, String pattern2);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/PatternMatchUtils.java b/spring-core/src/main/java/org/springframework/util/PatternMatchUtils.java
new file mode 100644
index 00000000..0b7c80a4
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/PatternMatchUtils.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2007 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.util;
+
+/**
+ * Utility methods for simple pattern matching, in particular for
+ * Spring's typical "xxx*", "*xxx" and "*xxx*" pattern styles.
+ *
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public abstract class PatternMatchUtils {
+
+ /**
+ * Match a String against the given pattern, supporting the following simple
+ * pattern styles: "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an
+ * arbitrary number of pattern parts), as well as direct equality.
+ * @param pattern the pattern to match against
+ * @param str the String to match
+ * @return whether the String matches the given pattern
+ */
+ public static boolean simpleMatch(String pattern, String str) {
+ if (pattern == null || str == null) {
+ return false;
+ }
+ int firstIndex = pattern.indexOf('*');
+ if (firstIndex == -1) {
+ return pattern.equals(str);
+ }
+ if (firstIndex == 0) {
+ if (pattern.length() == 1) {
+ return true;
+ }
+ int nextIndex = pattern.indexOf('*', firstIndex + 1);
+ if (nextIndex == -1) {
+ return str.endsWith(pattern.substring(1));
+ }
+ String part = pattern.substring(1, nextIndex);
+ int partIndex = str.indexOf(part);
+ while (partIndex != -1) {
+ if (simpleMatch(pattern.substring(nextIndex), str.substring(partIndex + part.length()))) {
+ return true;
+ }
+ partIndex = str.indexOf(part, partIndex + 1);
+ }
+ return false;
+ }
+ return (str.length() >= firstIndex &&
+ pattern.substring(0, firstIndex).equals(str.substring(0, firstIndex)) &&
+ simpleMatch(pattern.substring(firstIndex), str.substring(firstIndex)));
+ }
+
+ /**
+ * Match a String against the given patterns, supporting the following simple
+ * pattern styles: "xxx*", "*xxx", "*xxx*" and "xxx*yyy" matches (with an
+ * arbitrary number of pattern parts), as well as direct equality.
+ * @param patterns the patterns to match against
+ * @param str the String to match
+ * @return whether the String matches any of the given patterns
+ */
+ public static boolean simpleMatch(String[] patterns, String str) {
+ if (patterns != null) {
+ for (int i = 0; i < patterns.length; i++) {
+ if (simpleMatch(patterns[i], str)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/PropertiesPersister.java b/spring-core/src/main/java/org/springframework/util/PropertiesPersister.java
new file mode 100644
index 00000000..1e4c3c97
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/PropertiesPersister.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2002-2012 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.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Properties;
+
+/**
+ * Strategy interface for persisting {@code java.util.Properties},
+ * allowing for pluggable parsing strategies.
+ *
+ * <p>The default implementation is DefaultPropertiesPersister,
+ * providing the native parsing of {@code java.util.Properties},
+ * but allowing for reading from any Reader and writing to any Writer
+ * (which allows to specify an encoding for a properties file).
+ *
+ * <p>As of Spring 1.2.2, this interface also supports properties XML files,
+ * through the {@code loadFromXml} and {@code storeToXml} methods.
+ * The default implementations delegate to JDK 1.5's corresponding methods.
+ *
+ * @author Juergen Hoeller
+ * @since 10.03.2004
+ * @see DefaultPropertiesPersister
+ * @see java.util.Properties
+ */
+public interface PropertiesPersister {
+
+ /**
+ * Load properties from the given InputStream into the given
+ * Properties object.
+ * @param props the Properties object to load into
+ * @param is the InputStream to load from
+ * @throws IOException in case of I/O errors
+ * @see java.util.Properties#load
+ */
+ void load(Properties props, InputStream is) throws IOException;
+
+ /**
+ * Load properties from the given Reader into the given
+ * Properties object.
+ * @param props the Properties object to load into
+ * @param reader the Reader to load from
+ * @throws IOException in case of I/O errors
+ */
+ void load(Properties props, Reader reader) throws IOException;
+
+
+ /**
+ * Write the contents of the given Properties object to the
+ * given OutputStream.
+ * @param props the Properties object to store
+ * @param os the OutputStream to write to
+ * @param header the description of the property list
+ * @throws IOException in case of I/O errors
+ * @see java.util.Properties#store
+ */
+ void store(Properties props, OutputStream os, String header) throws IOException;
+
+ /**
+ * Write the contents of the given Properties object to the
+ * given Writer.
+ * @param props the Properties object to store
+ * @param writer the Writer to write to
+ * @param header the description of the property list
+ * @throws IOException in case of I/O errors
+ */
+ void store(Properties props, Writer writer, String header) throws IOException;
+
+
+ /**
+ * Load properties from the given XML InputStream into the
+ * given Properties object.
+ * @param props the Properties object to load into
+ * @param is the InputStream to load from
+ * @throws IOException in case of I/O errors
+ * @see java.util.Properties#loadFromXML(java.io.InputStream)
+ */
+ void loadFromXml(Properties props, InputStream is) throws IOException;
+
+ /**
+ * Write the contents of the given Properties object to the
+ * given XML OutputStream.
+ * @param props the Properties object to store
+ * @param os the OutputStream to write to
+ * @param header the description of the property list
+ * @throws IOException in case of I/O errors
+ * @see java.util.Properties#storeToXML(java.io.OutputStream, String)
+ */
+ void storeToXml(Properties props, OutputStream os, String header) throws IOException;
+
+ /**
+ * Write the contents of the given Properties object to the
+ * given XML OutputStream.
+ * @param props the Properties object to store
+ * @param os the OutputStream to write to
+ * @param encoding the encoding to use
+ * @param header the description of the property list
+ * @throws IOException in case of I/O errors
+ * @see java.util.Properties#storeToXML(java.io.OutputStream, String, String)
+ */
+ void storeToXml(Properties props, OutputStream os, String header, String encoding) throws IOException;
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/PropertyPlaceholderHelper.java b/spring-core/src/main/java/org/springframework/util/PropertyPlaceholderHelper.java
new file mode 100644
index 00000000..1ce878f8
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/PropertyPlaceholderHelper.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2002-2014 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.util;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Utility class for working with Strings that have placeholder values in them. A placeholder takes the form
+ * {@code ${name}}. Using {@code PropertyPlaceholderHelper} these placeholders can be substituted for
+ * user-supplied values. <p> Values for substitution can be supplied using a {@link Properties} instance or
+ * using a {@link PlaceholderResolver}.
+ *
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @since 3.0
+ */
+public class PropertyPlaceholderHelper {
+
+ private static final Log logger = LogFactory.getLog(PropertyPlaceholderHelper.class);
+
+ private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<String, String>(4);
+
+ static {
+ wellKnownSimplePrefixes.put("}", "{");
+ wellKnownSimplePrefixes.put("]", "[");
+ wellKnownSimplePrefixes.put(")", "(");
+ }
+
+
+ private final String placeholderPrefix;
+
+ private final String placeholderSuffix;
+
+ private final String simplePrefix;
+
+ private final String valueSeparator;
+
+ private final boolean ignoreUnresolvablePlaceholders;
+
+
+ /**
+ * Creates a new {@code PropertyPlaceholderHelper} that uses the supplied prefix and suffix.
+ * Unresolvable placeholders are ignored.
+ * @param placeholderPrefix the prefix that denotes the start of a placeholder
+ * @param placeholderSuffix the suffix that denotes the end of a placeholder
+ */
+ public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) {
+ this(placeholderPrefix, placeholderSuffix, null, true);
+ }
+
+ /**
+ * Creates a new {@code PropertyPlaceholderHelper} that uses the supplied prefix and suffix.
+ * @param placeholderPrefix the prefix that denotes the start of a placeholder
+ * @param placeholderSuffix the suffix that denotes the end of a placeholder
+ * @param valueSeparator the separating character between the placeholder variable
+ * and the associated default value, if any
+ * @param ignoreUnresolvablePlaceholders indicates whether unresolvable placeholders should
+ * be ignored ({@code true}) or cause an exception ({@code false})
+ */
+ public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix,
+ String valueSeparator, boolean ignoreUnresolvablePlaceholders) {
+
+ Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null");
+ Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null");
+ this.placeholderPrefix = placeholderPrefix;
+ this.placeholderSuffix = placeholderSuffix;
+ String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
+ if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
+ this.simplePrefix = simplePrefixForSuffix;
+ }
+ else {
+ this.simplePrefix = this.placeholderPrefix;
+ }
+ this.valueSeparator = valueSeparator;
+ this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
+ }
+
+
+ /**
+ * Replaces all placeholders of format {@code ${name}} with the corresponding
+ * property from the supplied {@link Properties}.
+ * @param value the value containing the placeholders to be replaced
+ * @param properties the {@code Properties} to use for replacement
+ * @return the supplied value with placeholders replaced inline
+ */
+ public String replacePlaceholders(String value, final Properties properties) {
+ Assert.notNull(properties, "'properties' must not be null");
+ return replacePlaceholders(value, new PlaceholderResolver() {
+ public String resolvePlaceholder(String placeholderName) {
+ return properties.getProperty(placeholderName);
+ }
+ });
+ }
+
+ /**
+ * Replaces all placeholders of format {@code ${name}} with the value returned
+ * from the supplied {@link PlaceholderResolver}.
+ * @param value the value containing the placeholders to be replaced
+ * @param placeholderResolver the {@code PlaceholderResolver} to use for replacement
+ * @return the supplied value with placeholders replaced inline
+ */
+ public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
+ Assert.notNull(value, "'value' must not be null");
+ return parseStringValue(value, placeholderResolver, new HashSet<String>());
+ }
+
+ protected String parseStringValue(
+ String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
+
+ StringBuilder result = new StringBuilder(strVal);
+
+ int startIndex = strVal.indexOf(this.placeholderPrefix);
+ while (startIndex != -1) {
+ int endIndex = findPlaceholderEndIndex(result, startIndex);
+ if (endIndex != -1) {
+ String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
+ String originalPlaceholder = placeholder;
+ if (!visitedPlaceholders.add(originalPlaceholder)) {
+ throw new IllegalArgumentException(
+ "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
+ }
+ // Recursive invocation, parsing placeholders contained in the placeholder key.
+ placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
+ // Now obtain the value for the fully resolved key...
+ String propVal = placeholderResolver.resolvePlaceholder(placeholder);
+ if (propVal == null && this.valueSeparator != null) {
+ int separatorIndex = placeholder.indexOf(this.valueSeparator);
+ if (separatorIndex != -1) {
+ String actualPlaceholder = placeholder.substring(0, separatorIndex);
+ String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
+ propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
+ if (propVal == null) {
+ propVal = defaultValue;
+ }
+ }
+ }
+ if (propVal != null) {
+ // Recursive invocation, parsing placeholders contained in the
+ // previously resolved placeholder value.
+ propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
+ result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
+ if (logger.isTraceEnabled()) {
+ logger.trace("Resolved placeholder '" + placeholder + "'");
+ }
+ startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
+ }
+ else if (this.ignoreUnresolvablePlaceholders) {
+ // Proceed with unprocessed value.
+ startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
+ }
+ else {
+ throw new IllegalArgumentException("Could not resolve placeholder '" +
+ placeholder + "'" + " in string value \"" + strVal + "\"");
+ }
+ visitedPlaceholders.remove(originalPlaceholder);
+ }
+ else {
+ startIndex = -1;
+ }
+ }
+
+ return result.toString();
+ }
+
+ private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
+ int index = startIndex + this.placeholderPrefix.length();
+ int withinNestedPlaceholder = 0;
+ while (index < buf.length()) {
+ if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {
+ if (withinNestedPlaceholder > 0) {
+ withinNestedPlaceholder--;
+ index = index + this.placeholderSuffix.length();
+ }
+ else {
+ return index;
+ }
+ }
+ else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {
+ withinNestedPlaceholder++;
+ index = index + this.simplePrefix.length();
+ }
+ else {
+ index++;
+ }
+ }
+ return -1;
+ }
+
+
+ /**
+ * Strategy interface used to resolve replacement values for placeholders contained in Strings.
+ */
+ public static interface PlaceholderResolver {
+
+ /**
+ * Resolve the supplied placeholder name to the replacement value.
+ * @param placeholderName the name of the placeholder to resolve
+ * @return the replacement value, or {@code null} if no replacement is to be made
+ */
+ String resolvePlaceholder(String placeholderName);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java
new file mode 100644
index 00000000..502d6827
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java
@@ -0,0 +1,727 @@
+/*
+ * Copyright 2002-2014 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.util;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Simple utility class for working with the reflection API and handling
+ * reflection exceptions.
+ *
+ * <p>Only intended for internal use.
+ *
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @author Rod Johnson
+ * @author Costin Leau
+ * @author Sam Brannen
+ * @author Chris Beams
+ * @since 1.2.2
+ */
+public abstract class ReflectionUtils {
+
+ /**
+ * Naming prefix for CGLIB-renamed methods.
+ * @see #isCglibRenamedMethod
+ */
+ private static final String CGLIB_RENAMED_METHOD_PREFIX = "CGLIB$";
+
+ /**
+ * Pattern for detecting CGLIB-renamed methods.
+ * @see #isCglibRenamedMethod
+ */
+ private static final Pattern CGLIB_RENAMED_METHOD_PATTERN = Pattern.compile("(.+)\\$\\d+");
+
+ /**
+ * Cache for {@link Class#getDeclaredMethods()}, allowing for fast resolution.
+ */
+ private static final Map<Class<?>, Method[]> declaredMethodsCache =
+ new ConcurrentReferenceHashMap<Class<?>, Method[]>(256);
+
+
+ /**
+ * Attempt to find a {@link Field field} on the supplied {@link Class} with the
+ * supplied {@code name}. Searches all superclasses up to {@link Object}.
+ * @param clazz the class to introspect
+ * @param name the name of the field
+ * @return the corresponding Field object, or {@code null} if not found
+ */
+ public static Field findField(Class<?> clazz, String name) {
+ return findField(clazz, name, null);
+ }
+
+ /**
+ * Attempt to find a {@link Field field} on the supplied {@link Class} with the
+ * supplied {@code name} and/or {@link Class type}. Searches all superclasses
+ * up to {@link Object}.
+ * @param clazz the class to introspect
+ * @param name the name of the field (may be {@code null} if type is specified)
+ * @param type the type of the field (may be {@code null} if name is specified)
+ * @return the corresponding Field object, or {@code null} if not found
+ */
+ public static Field findField(Class<?> clazz, String name, Class<?> type) {
+ Assert.notNull(clazz, "Class must not be null");
+ Assert.isTrue(name != null || type != null, "Either name or type of the field must be specified");
+ Class<?> searchType = clazz;
+ while (!Object.class.equals(searchType) && searchType != null) {
+ Field[] fields = searchType.getDeclaredFields();
+ for (Field field : fields) {
+ if ((name == null || name.equals(field.getName())) && (type == null || type.equals(field.getType()))) {
+ return field;
+ }
+ }
+ searchType = searchType.getSuperclass();
+ }
+ return null;
+ }
+
+ /**
+ * Set the field represented by the supplied {@link Field field object} on the
+ * specified {@link Object target object} to the specified {@code value}.
+ * In accordance with {@link Field#set(Object, Object)} semantics, the new value
+ * is automatically unwrapped if the underlying field has a primitive type.
+ * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}.
+ * @param field the field to set
+ * @param target the target object on which to set the field
+ * @param value the value to set; may be {@code null}
+ */
+ public static void setField(Field field, Object target, Object value) {
+ try {
+ field.set(target, value);
+ }
+ catch (IllegalAccessException ex) {
+ handleReflectionException(ex);
+ throw new IllegalStateException(
+ "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage());
+ }
+ }
+
+ /**
+ * Get the field represented by the supplied {@link Field field object} on the
+ * specified {@link Object target object}. In accordance with {@link Field#get(Object)}
+ * semantics, the returned value is automatically wrapped if the underlying field
+ * has a primitive type.
+ * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}.
+ * @param field the field to get
+ * @param target the target object from which to get the field
+ * @return the field's current value
+ */
+ public static Object getField(Field field, Object target) {
+ try {
+ return field.get(target);
+ }
+ catch (IllegalAccessException ex) {
+ handleReflectionException(ex);
+ throw new IllegalStateException(
+ "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage());
+ }
+ }
+
+ /**
+ * Attempt to find a {@link Method} on the supplied class with the supplied name
+ * and no parameters. Searches all superclasses up to {@code Object}.
+ * <p>Returns {@code null} if no {@link Method} can be found.
+ * @param clazz the class to introspect
+ * @param name the name of the method
+ * @return the Method object, or {@code null} if none found
+ */
+ public static Method findMethod(Class<?> clazz, String name) {
+ return findMethod(clazz, name, new Class<?>[0]);
+ }
+
+ /**
+ * Attempt to find a {@link Method} on the supplied class with the supplied name
+ * and parameter types. Searches all superclasses up to {@code Object}.
+ * <p>Returns {@code null} if no {@link Method} can be found.
+ * @param clazz the class to introspect
+ * @param name the name of the method
+ * @param paramTypes the parameter types of the method
+ * (may be {@code null} to indicate any signature)
+ * @return the Method object, or {@code null} if none found
+ */
+ public static Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes) {
+ Assert.notNull(clazz, "Class must not be null");
+ Assert.notNull(name, "Method name must not be null");
+ Class<?> searchType = clazz;
+ while (searchType != null) {
+ Method[] methods = (searchType.isInterface() ? searchType.getMethods() : getDeclaredMethods(searchType));
+ for (Method method : methods) {
+ if (name.equals(method.getName()) &&
+ (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) {
+ return method;
+ }
+ }
+ searchType = searchType.getSuperclass();
+ }
+ return null;
+ }
+
+ /**
+ * Invoke the specified {@link Method} against the supplied target object with no arguments.
+ * The target object can be {@code null} when invoking a static {@link Method}.
+ * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}.
+ * @param method the method to invoke
+ * @param target the target object to invoke the method on
+ * @return the invocation result, if any
+ * @see #invokeMethod(java.lang.reflect.Method, Object, Object[])
+ */
+ public static Object invokeMethod(Method method, Object target) {
+ return invokeMethod(method, target, new Object[0]);
+ }
+
+ /**
+ * Invoke the specified {@link Method} against the supplied target object with the
+ * supplied arguments. The target object can be {@code null} when invoking a
+ * static {@link Method}.
+ * <p>Thrown exceptions are handled via a call to {@link #handleReflectionException}.
+ * @param method the method to invoke
+ * @param target the target object to invoke the method on
+ * @param args the invocation arguments (may be {@code null})
+ * @return the invocation result, if any
+ */
+ public static Object invokeMethod(Method method, Object target, Object... args) {
+ try {
+ return method.invoke(target, args);
+ }
+ catch (Exception ex) {
+ handleReflectionException(ex);
+ }
+ throw new IllegalStateException("Should never get here");
+ }
+
+ /**
+ * Invoke the specified JDBC API {@link Method} against the supplied target
+ * object with no arguments.
+ * @param method the method to invoke
+ * @param target the target object to invoke the method on
+ * @return the invocation result, if any
+ * @throws SQLException the JDBC API SQLException to rethrow (if any)
+ * @see #invokeJdbcMethod(java.lang.reflect.Method, Object, Object[])
+ */
+ public static Object invokeJdbcMethod(Method method, Object target) throws SQLException {
+ return invokeJdbcMethod(method, target, new Object[0]);
+ }
+
+ /**
+ * Invoke the specified JDBC API {@link Method} against the supplied target
+ * object with the supplied arguments.
+ * @param method the method to invoke
+ * @param target the target object to invoke the method on
+ * @param args the invocation arguments (may be {@code null})
+ * @return the invocation result, if any
+ * @throws SQLException the JDBC API SQLException to rethrow (if any)
+ * @see #invokeMethod(java.lang.reflect.Method, Object, Object[])
+ */
+ public static Object invokeJdbcMethod(Method method, Object target, Object... args) throws SQLException {
+ try {
+ return method.invoke(target, args);
+ }
+ catch (IllegalAccessException ex) {
+ handleReflectionException(ex);
+ }
+ catch (InvocationTargetException ex) {
+ if (ex.getTargetException() instanceof SQLException) {
+ throw (SQLException) ex.getTargetException();
+ }
+ handleInvocationTargetException(ex);
+ }
+ throw new IllegalStateException("Should never get here");
+ }
+
+ /**
+ * Handle the given reflection exception. Should only be called if no
+ * checked exception is expected to be thrown by the target method.
+ * <p>Throws the underlying RuntimeException or Error in case of an
+ * InvocationTargetException with such a root cause. Throws an
+ * IllegalStateException with an appropriate message else.
+ * @param ex the reflection exception to handle
+ */
+ public static void handleReflectionException(Exception ex) {
+ if (ex instanceof NoSuchMethodException) {
+ throw new IllegalStateException("Method not found: " + ex.getMessage());
+ }
+ if (ex instanceof IllegalAccessException) {
+ throw new IllegalStateException("Could not access method: " + ex.getMessage());
+ }
+ if (ex instanceof InvocationTargetException) {
+ handleInvocationTargetException((InvocationTargetException) ex);
+ }
+ if (ex instanceof RuntimeException) {
+ throw (RuntimeException) ex;
+ }
+ throw new UndeclaredThrowableException(ex);
+ }
+
+ /**
+ * Handle the given invocation target exception. Should only be called if no
+ * checked exception is expected to be thrown by the target method.
+ * <p>Throws the underlying RuntimeException or Error in case of such a root
+ * cause. Throws an IllegalStateException else.
+ * @param ex the invocation target exception to handle
+ */
+ public static void handleInvocationTargetException(InvocationTargetException ex) {
+ rethrowRuntimeException(ex.getTargetException());
+ }
+
+ /**
+ * Rethrow the given {@link Throwable exception}, which is presumably the
+ * <em>target exception</em> of an {@link InvocationTargetException}. Should
+ * only be called if no checked exception is expected to be thrown by the
+ * target method.
+ * <p>Rethrows the underlying exception cast to an {@link RuntimeException} or
+ * {@link Error} if appropriate; otherwise, throws an
+ * {@link IllegalStateException}.
+ * @param ex the exception to rethrow
+ * @throws RuntimeException the rethrown exception
+ */
+ public static void rethrowRuntimeException(Throwable ex) {
+ if (ex instanceof RuntimeException) {
+ throw (RuntimeException) ex;
+ }
+ if (ex instanceof Error) {
+ throw (Error) ex;
+ }
+ throw new UndeclaredThrowableException(ex);
+ }
+
+ /**
+ * Rethrow the given {@link Throwable exception}, which is presumably the
+ * <em>target exception</em> of an {@link InvocationTargetException}. Should
+ * only be called if no checked exception is expected to be thrown by the
+ * target method.
+ * <p>Rethrows the underlying exception cast to an {@link Exception} or
+ * {@link Error} if appropriate; otherwise, throws an
+ * {@link IllegalStateException}.
+ * @param ex the exception to rethrow
+ * @throws Exception the rethrown exception (in case of a checked exception)
+ */
+ public static void rethrowException(Throwable ex) throws Exception {
+ if (ex instanceof Exception) {
+ throw (Exception) ex;
+ }
+ if (ex instanceof Error) {
+ throw (Error) ex;
+ }
+ throw new UndeclaredThrowableException(ex);
+ }
+
+ /**
+ * Determine whether the given method explicitly declares the given
+ * exception or one of its superclasses, which means that an exception of
+ * that type can be propagated as-is within a reflective invocation.
+ * @param method the declaring method
+ * @param exceptionType the exception to throw
+ * @return {@code true} if the exception can be thrown as-is;
+ * {@code false} if it needs to be wrapped
+ */
+ public static boolean declaresException(Method method, Class<?> exceptionType) {
+ Assert.notNull(method, "Method must not be null");
+ Class<?>[] declaredExceptions = method.getExceptionTypes();
+ for (Class<?> declaredException : declaredExceptions) {
+ if (declaredException.isAssignableFrom(exceptionType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Determine whether the given field is a "public static final" constant.
+ * @param field the field to check
+ */
+ public static boolean isPublicStaticFinal(Field field) {
+ int modifiers = field.getModifiers();
+ return (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers));
+ }
+
+ /**
+ * Determine whether the given method is an "equals" method.
+ * @see java.lang.Object#equals(Object)
+ */
+ public static boolean isEqualsMethod(Method method) {
+ if (method == null || !method.getName().equals("equals")) {
+ return false;
+ }
+ Class<?>[] paramTypes = method.getParameterTypes();
+ return (paramTypes.length == 1 && paramTypes[0] == Object.class);
+ }
+
+ /**
+ * Determine whether the given method is a "hashCode" method.
+ * @see java.lang.Object#hashCode()
+ */
+ public static boolean isHashCodeMethod(Method method) {
+ return (method != null && method.getName().equals("hashCode") && method.getParameterTypes().length == 0);
+ }
+
+ /**
+ * Determine whether the given method is a "toString" method.
+ * @see java.lang.Object#toString()
+ */
+ public static boolean isToStringMethod(Method method) {
+ return (method != null && method.getName().equals("toString") && method.getParameterTypes().length == 0);
+ }
+
+ /**
+ * Determine whether the given method is originally declared by {@link java.lang.Object}.
+ */
+ public static boolean isObjectMethod(Method method) {
+ if (method == null) {
+ return false;
+ }
+ try {
+ Object.class.getDeclaredMethod(method.getName(), method.getParameterTypes());
+ return true;
+ }
+ catch (Exception ex) {
+ return false;
+ }
+ }
+
+ /**
+ * Determine whether the given method is a CGLIB 'renamed' method,
+ * following the pattern "CGLIB$methodName$0".
+ * @param renamedMethod the method to check
+ * @see org.springframework.cglib.proxy.Enhancer#rename
+ */
+ public static boolean isCglibRenamedMethod(Method renamedMethod) {
+ String name = renamedMethod.getName();
+ return (name.startsWith(CGLIB_RENAMED_METHOD_PREFIX) &&
+ CGLIB_RENAMED_METHOD_PATTERN.matcher(name.substring(CGLIB_RENAMED_METHOD_PREFIX.length())).matches());
+ }
+
+ /**
+ * Make the given field accessible, explicitly setting it accessible if
+ * necessary. The {@code setAccessible(true)} method is only called
+ * when actually necessary, to avoid unnecessary conflicts with a JVM
+ * SecurityManager (if active).
+ * @param field the field to make accessible
+ * @see java.lang.reflect.Field#setAccessible
+ */
+ public static void makeAccessible(Field field) {
+ if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) ||
+ Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) {
+ field.setAccessible(true);
+ }
+ }
+
+ /**
+ * Make the given method accessible, explicitly setting it accessible if
+ * necessary. The {@code setAccessible(true)} method is only called
+ * when actually necessary, to avoid unnecessary conflicts with a JVM
+ * SecurityManager (if active).
+ * @param method the method to make accessible
+ * @see java.lang.reflect.Method#setAccessible
+ */
+ public static void makeAccessible(Method method) {
+ if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) &&
+ !method.isAccessible()) {
+ method.setAccessible(true);
+ }
+ }
+
+ /**
+ * Make the given constructor accessible, explicitly setting it accessible
+ * if necessary. The {@code setAccessible(true)} method is only called
+ * when actually necessary, to avoid unnecessary conflicts with a JVM
+ * SecurityManager (if active).
+ * @param ctor the constructor to make accessible
+ * @see java.lang.reflect.Constructor#setAccessible
+ */
+ public static void makeAccessible(Constructor<?> ctor) {
+ if ((!Modifier.isPublic(ctor.getModifiers()) || !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) &&
+ !ctor.isAccessible()) {
+ ctor.setAccessible(true);
+ }
+ }
+
+ /**
+ * Perform the given callback operation on all matching methods of the given
+ * class and superclasses.
+ * <p>The same named method occurring on subclass and superclass will appear
+ * twice, unless excluded by a {@link MethodFilter}.
+ * @param clazz class to start looking at
+ * @param mc the callback to invoke for each method
+ * @see #doWithMethods(Class, MethodCallback, MethodFilter)
+ */
+ public static void doWithMethods(Class<?> clazz, MethodCallback mc) throws IllegalArgumentException {
+ doWithMethods(clazz, mc, null);
+ }
+
+ /**
+ * Perform the given callback operation on all matching methods of the given
+ * class and superclasses (or given interface and super-interfaces).
+ * <p>The same named method occurring on subclass and superclass will appear
+ * twice, unless excluded by the specified {@link MethodFilter}.
+ * @param clazz class to start looking at
+ * @param mc the callback to invoke for each method
+ * @param mf the filter that determines the methods to apply the callback to
+ */
+ public static void doWithMethods(Class<?> clazz, MethodCallback mc, MethodFilter mf)
+ throws IllegalArgumentException {
+
+ // Keep backing up the inheritance hierarchy.
+ Method[] methods = getDeclaredMethods(clazz);
+ for (Method method : methods) {
+ if (mf != null && !mf.matches(method)) {
+ continue;
+ }
+ try {
+ mc.doWith(method);
+ }
+ catch (IllegalAccessException ex) {
+ throw new IllegalStateException("Shouldn't be illegal to access method '" + method.getName() + "': " + ex);
+ }
+ }
+ if (clazz.getSuperclass() != null) {
+ doWithMethods(clazz.getSuperclass(), mc, mf);
+ }
+ else if (clazz.isInterface()) {
+ for (Class<?> superIfc : clazz.getInterfaces()) {
+ doWithMethods(superIfc, mc, mf);
+ }
+ }
+ }
+
+ /**
+ * Get all declared methods on the leaf class and all superclasses. Leaf
+ * class methods are included first.
+ */
+ public static Method[] getAllDeclaredMethods(Class<?> leafClass) throws IllegalArgumentException {
+ final List<Method> methods = new ArrayList<Method>(32);
+ doWithMethods(leafClass, new MethodCallback() {
+ public void doWith(Method method) {
+ methods.add(method);
+ }
+ });
+ return methods.toArray(new Method[methods.size()]);
+ }
+
+ /**
+ * Get the unique set of declared methods on the leaf class and all superclasses. Leaf
+ * class methods are included first and while traversing the superclass hierarchy any methods found
+ * with signatures matching a method already included are filtered out.
+ */
+ public static Method[] getUniqueDeclaredMethods(Class<?> leafClass) throws IllegalArgumentException {
+ final List<Method> methods = new ArrayList<Method>(32);
+ doWithMethods(leafClass, new MethodCallback() {
+ public void doWith(Method method) {
+ boolean knownSignature = false;
+ Method methodBeingOverriddenWithCovariantReturnType = null;
+ for (Method existingMethod : methods) {
+ if (method.getName().equals(existingMethod.getName()) &&
+ Arrays.equals(method.getParameterTypes(), existingMethod.getParameterTypes())) {
+ // Is this a covariant return type situation?
+ if (existingMethod.getReturnType() != method.getReturnType() &&
+ existingMethod.getReturnType().isAssignableFrom(method.getReturnType())) {
+ methodBeingOverriddenWithCovariantReturnType = existingMethod;
+ }
+ else {
+ knownSignature = true;
+ }
+ break;
+ }
+ }
+ if (methodBeingOverriddenWithCovariantReturnType != null) {
+ methods.remove(methodBeingOverriddenWithCovariantReturnType);
+ }
+ if (!knownSignature && !isCglibRenamedMethod(method)) {
+ methods.add(method);
+ }
+ }
+ });
+ return methods.toArray(new Method[methods.size()]);
+ }
+
+ /**
+ * This method retrieves {@link Class#getDeclaredMethods()} from a local cache
+ * in order to avoid the JVM's SecurityManager check and defensive array copying.
+ */
+ private static Method[] getDeclaredMethods(Class<?> clazz) {
+ Method[] result = declaredMethodsCache.get(clazz);
+ if (result == null) {
+ result = clazz.getDeclaredMethods();
+ declaredMethodsCache.put(clazz, result);
+ }
+ return result;
+ }
+
+ /**
+ * Invoke the given callback on all fields in the target class, going up the
+ * class hierarchy to get all declared fields.
+ * @param clazz the target class to analyze
+ * @param fc the callback to invoke for each field
+ */
+ public static void doWithFields(Class<?> clazz, FieldCallback fc) throws IllegalArgumentException {
+ doWithFields(clazz, fc, null);
+ }
+
+ /**
+ * Invoke the given callback on all fields in the target class, going up the
+ * class hierarchy to get all declared fields.
+ * @param clazz the target class to analyze
+ * @param fc the callback to invoke for each field
+ * @param ff the filter that determines the fields to apply the callback to
+ */
+ public static void doWithFields(Class<?> clazz, FieldCallback fc, FieldFilter ff)
+ throws IllegalArgumentException {
+
+ // Keep backing up the inheritance hierarchy.
+ Class<?> targetClass = clazz;
+ do {
+ Field[] fields = targetClass.getDeclaredFields();
+ for (Field field : fields) {
+ // Skip static and final fields.
+ if (ff != null && !ff.matches(field)) {
+ continue;
+ }
+ try {
+ fc.doWith(field);
+ }
+ catch (IllegalAccessException ex) {
+ throw new IllegalStateException("Shouldn't be illegal to access field '" + field.getName() + "': " + ex);
+ }
+ }
+ targetClass = targetClass.getSuperclass();
+ }
+ while (targetClass != null && targetClass != Object.class);
+ }
+
+ /**
+ * Given the source object and the destination, which must be the same class
+ * or a subclass, copy all fields, including inherited fields. Designed to
+ * work on objects with public no-arg constructors.
+ * @throws IllegalArgumentException if the arguments are incompatible
+ */
+ public static void shallowCopyFieldState(final Object src, final Object dest) throws IllegalArgumentException {
+ if (src == null) {
+ throw new IllegalArgumentException("Source for field copy cannot be null");
+ }
+ if (dest == null) {
+ throw new IllegalArgumentException("Destination for field copy cannot be null");
+ }
+ if (!src.getClass().isAssignableFrom(dest.getClass())) {
+ throw new IllegalArgumentException("Destination class [" + dest.getClass().getName() +
+ "] must be same or subclass as source class [" + src.getClass().getName() + "]");
+ }
+ doWithFields(src.getClass(), new FieldCallback() {
+ public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
+ makeAccessible(field);
+ Object srcValue = field.get(src);
+ field.set(dest, srcValue);
+ }
+ }, COPYABLE_FIELDS);
+ }
+
+
+ /**
+ * Action to take on each method.
+ */
+ public interface MethodCallback {
+
+ /**
+ * Perform an operation using the given method.
+ * @param method the method to operate on
+ */
+ void doWith(Method method) throws IllegalArgumentException, IllegalAccessException;
+ }
+
+
+ /**
+ * Callback optionally used to filter methods to be operated on by a method callback.
+ */
+ public interface MethodFilter {
+
+ /**
+ * Determine whether the given method matches.
+ * @param method the method to check
+ */
+ boolean matches(Method method);
+ }
+
+
+ /**
+ * Callback interface invoked on each field in the hierarchy.
+ */
+ public interface FieldCallback {
+
+ /**
+ * Perform an operation using the given field.
+ * @param field the field to operate on
+ */
+ void doWith(Field field) throws IllegalArgumentException, IllegalAccessException;
+ }
+
+
+ /**
+ * Callback optionally used to filter fields to be operated on by a field callback.
+ */
+ public interface FieldFilter {
+
+ /**
+ * Determine whether the given field matches.
+ * @param field the field to check
+ */
+ boolean matches(Field field);
+ }
+
+
+ /**
+ * Pre-built FieldFilter that matches all non-static, non-final fields.
+ */
+ public static FieldFilter COPYABLE_FIELDS = new FieldFilter() {
+
+ public boolean matches(Field field) {
+ return !(Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers()));
+ }
+ };
+
+
+ /**
+ * Pre-built MethodFilter that matches all non-bridge methods.
+ */
+ public static MethodFilter NON_BRIDGED_METHODS = new MethodFilter() {
+
+ public boolean matches(Method method) {
+ return !method.isBridge();
+ }
+ };
+
+
+ /**
+ * Pre-built MethodFilter that matches all non-bridge methods
+ * which are not declared on {@code java.lang.Object}.
+ */
+ public static MethodFilter USER_DECLARED_METHODS = new MethodFilter() {
+
+ public boolean matches(Method method) {
+ return (!method.isBridge() && method.getDeclaringClass() != Object.class);
+ }
+ };
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/ResourceUtils.java b/spring-core/src/main/java/org/springframework/util/ResourceUtils.java
new file mode 100644
index 00000000..ce552eb0
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/ResourceUtils.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright 2002-2014 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.util;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+
+/**
+ * Utility methods for resolving resource locations to files in the
+ * file system. Mainly for internal use within the framework.
+ *
+ * <p>Consider using Spring's Resource abstraction in the core package
+ * for handling all kinds of file resources in a uniform manner.
+ * {@link org.springframework.core.io.ResourceLoader}'s {@code getResource()}
+ * method can resolve any location to a {@link org.springframework.core.io.Resource}
+ * object, which in turn allows one to obtain a {@code java.io.File} in the
+ * file system through its {@code getFile()} method.
+ *
+ * <p>The main reason for these utility methods for resource location handling
+ * is to support {@link Log4jConfigurer}, which must be able to resolve
+ * resource locations <i>before the logging system has been initialized</i>.
+ * Spring's {@code Resource} abstraction in the core package, on the other hand,
+ * already expects the logging system to be available.
+ *
+ * @author Juergen Hoeller
+ * @since 1.1.5
+ * @see org.springframework.core.io.Resource
+ * @see org.springframework.core.io.ClassPathResource
+ * @see org.springframework.core.io.FileSystemResource
+ * @see org.springframework.core.io.UrlResource
+ * @see org.springframework.core.io.ResourceLoader
+ */
+public abstract class ResourceUtils {
+
+ /** Pseudo URL prefix for loading from the class path: "classpath:" */
+ public static final String CLASSPATH_URL_PREFIX = "classpath:";
+
+ /** URL prefix for loading from the file system: "file:" */
+ public static final String FILE_URL_PREFIX = "file:";
+
+ /** URL protocol for a file in the file system: "file" */
+ public static final String URL_PROTOCOL_FILE = "file";
+
+ /** URL protocol for an entry from a jar file: "jar" */
+ public static final String URL_PROTOCOL_JAR = "jar";
+
+ /** URL protocol for an entry from a zip file: "zip" */
+ public static final String URL_PROTOCOL_ZIP = "zip";
+
+ /** URL protocol for an entry from a WebSphere jar file: "wsjar" */
+ public static final String URL_PROTOCOL_WSJAR = "wsjar";
+
+ /** URL protocol for an entry from a JBoss jar file: "vfszip" */
+ public static final String URL_PROTOCOL_VFSZIP = "vfszip";
+
+ /** URL protocol for a JBoss file system resource: "vfsfile" */
+ public static final String URL_PROTOCOL_VFSFILE = "vfsfile";
+
+ /** URL protocol for a general JBoss VFS resource: "vfs" */
+ public static final String URL_PROTOCOL_VFS = "vfs";
+
+ /** URL protocol for an entry from an OC4J jar file: "code-source" */
+ public static final String URL_PROTOCOL_CODE_SOURCE = "code-source";
+
+ /** Separator between JAR URL and file path within the JAR */
+ public static final String JAR_URL_SEPARATOR = "!/";
+
+
+ /**
+ * Return whether the given resource location is a URL:
+ * either a special "classpath" pseudo URL or a standard URL.
+ * @param resourceLocation the location String to check
+ * @return whether the location qualifies as a URL
+ * @see #CLASSPATH_URL_PREFIX
+ * @see java.net.URL
+ */
+ public static boolean isUrl(String resourceLocation) {
+ if (resourceLocation == null) {
+ return false;
+ }
+ if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) {
+ return true;
+ }
+ try {
+ new URL(resourceLocation);
+ return true;
+ }
+ catch (MalformedURLException ex) {
+ return false;
+ }
+ }
+
+ /**
+ * Resolve the given resource location to a {@code java.net.URL}.
+ * <p>Does not check whether the URL actually exists; simply returns
+ * the URL that the given location would correspond to.
+ * @param resourceLocation the resource location to resolve: either a
+ * "classpath:" pseudo URL, a "file:" URL, or a plain file path
+ * @return a corresponding URL object
+ * @throws FileNotFoundException if the resource cannot be resolved to a URL
+ */
+ public static URL getURL(String resourceLocation) throws FileNotFoundException {
+ Assert.notNull(resourceLocation, "Resource location must not be null");
+ if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) {
+ String path = resourceLocation.substring(CLASSPATH_URL_PREFIX.length());
+ ClassLoader cl = ClassUtils.getDefaultClassLoader();
+ URL url = (cl != null ? cl.getResource(path) : ClassLoader.getSystemResource(path));
+ if (url == null) {
+ String description = "class path resource [" + path + "]";
+ throw new FileNotFoundException(
+ description + " cannot be resolved to URL because it does not exist");
+ }
+ return url;
+ }
+ try {
+ // try URL
+ return new URL(resourceLocation);
+ }
+ catch (MalformedURLException ex) {
+ // no URL -> treat as file path
+ try {
+ return new File(resourceLocation).toURI().toURL();
+ }
+ catch (MalformedURLException ex2) {
+ throw new FileNotFoundException("Resource location [" + resourceLocation +
+ "] is neither a URL not a well-formed file path");
+ }
+ }
+ }
+
+ /**
+ * Resolve the given resource location to a {@code java.io.File},
+ * i.e. to a file in the file system.
+ * <p>Does not check whether the file actually exists; simply returns
+ * the File that the given location would correspond to.
+ * @param resourceLocation the resource location to resolve: either a
+ * "classpath:" pseudo URL, a "file:" URL, or a plain file path
+ * @return a corresponding File object
+ * @throws FileNotFoundException if the resource cannot be resolved to
+ * a file in the file system
+ */
+ public static File getFile(String resourceLocation) throws FileNotFoundException {
+ Assert.notNull(resourceLocation, "Resource location must not be null");
+ if (resourceLocation.startsWith(CLASSPATH_URL_PREFIX)) {
+ String path = resourceLocation.substring(CLASSPATH_URL_PREFIX.length());
+ String description = "class path resource [" + path + "]";
+ ClassLoader cl = ClassUtils.getDefaultClassLoader();
+ URL url = (cl != null ? cl.getResource(path) : ClassLoader.getSystemResource(path));
+ if (url == null) {
+ throw new FileNotFoundException(
+ description + " cannot be resolved to absolute file path " +
+ "because it does not reside in the file system");
+ }
+ return getFile(url, description);
+ }
+ try {
+ // try URL
+ return getFile(new URL(resourceLocation));
+ }
+ catch (MalformedURLException ex) {
+ // no URL -> treat as file path
+ return new File(resourceLocation);
+ }
+ }
+
+ /**
+ * Resolve the given resource URL to a {@code java.io.File},
+ * i.e. to a file in the file system.
+ * @param resourceUrl the resource URL to resolve
+ * @return a corresponding File object
+ * @throws FileNotFoundException if the URL cannot be resolved to
+ * a file in the file system
+ */
+ public static File getFile(URL resourceUrl) throws FileNotFoundException {
+ return getFile(resourceUrl, "URL");
+ }
+
+ /**
+ * Resolve the given resource URL to a {@code java.io.File},
+ * i.e. to a file in the file system.
+ * @param resourceUrl the resource URL to resolve
+ * @param description a description of the original resource that
+ * the URL was created for (for example, a class path location)
+ * @return a corresponding File object
+ * @throws FileNotFoundException if the URL cannot be resolved to
+ * a file in the file system
+ */
+ public static File getFile(URL resourceUrl, String description) throws FileNotFoundException {
+ Assert.notNull(resourceUrl, "Resource URL must not be null");
+ if (!URL_PROTOCOL_FILE.equals(resourceUrl.getProtocol())) {
+ throw new FileNotFoundException(
+ description + " cannot be resolved to absolute file path " +
+ "because it does not reside in the file system: " + resourceUrl);
+ }
+ try {
+ return new File(toURI(resourceUrl).getSchemeSpecificPart());
+ }
+ catch (URISyntaxException ex) {
+ // Fallback for URLs that are not valid URIs (should hardly ever happen).
+ return new File(resourceUrl.getFile());
+ }
+ }
+
+ /**
+ * Resolve the given resource URI to a {@code java.io.File},
+ * i.e. to a file in the file system.
+ * @param resourceUri the resource URI to resolve
+ * @return a corresponding File object
+ * @throws FileNotFoundException if the URL cannot be resolved to
+ * a file in the file system
+ */
+ public static File getFile(URI resourceUri) throws FileNotFoundException {
+ return getFile(resourceUri, "URI");
+ }
+
+ /**
+ * Resolve the given resource URI to a {@code java.io.File},
+ * i.e. to a file in the file system.
+ * @param resourceUri the resource URI to resolve
+ * @param description a description of the original resource that
+ * the URI was created for (for example, a class path location)
+ * @return a corresponding File object
+ * @throws FileNotFoundException if the URL cannot be resolved to
+ * a file in the file system
+ */
+ public static File getFile(URI resourceUri, String description) throws FileNotFoundException {
+ Assert.notNull(resourceUri, "Resource URI must not be null");
+ if (!URL_PROTOCOL_FILE.equals(resourceUri.getScheme())) {
+ throw new FileNotFoundException(
+ description + " cannot be resolved to absolute file path " +
+ "because it does not reside in the file system: " + resourceUri);
+ }
+ return new File(resourceUri.getSchemeSpecificPart());
+ }
+
+ /**
+ * Determine whether the given URL points to a resource in the file system,
+ * that is, has protocol "file", "vfsfile" or "vfs".
+ * @param url the URL to check
+ * @return whether the URL has been identified as a file system URL
+ */
+ public static boolean isFileURL(URL url) {
+ String protocol = url.getProtocol();
+ return (URL_PROTOCOL_FILE.equals(protocol) || URL_PROTOCOL_VFSFILE.equals(protocol) ||
+ URL_PROTOCOL_VFS.equals(protocol));
+ }
+
+ /**
+ * Determine whether the given URL points to a resource in a jar file,
+ * that is, has protocol "jar", "zip", "vfszip", "wsjar" or "code-source".
+ * <p>"zip" and "wsjar" are used by WebLogic Server and WebSphere, respectively,
+ * but can be treated like jar files. The same applies to "code-source" URLs on
+ * OC4J, provided that the path contains a jar separator.
+ * @param url the URL to check
+ * @return whether the URL has been identified as a JAR URL
+ */
+ public static boolean isJarURL(URL url) {
+ String protocol = url.getProtocol();
+ return (URL_PROTOCOL_JAR.equals(protocol) || URL_PROTOCOL_ZIP.equals(protocol) ||
+ URL_PROTOCOL_VFSZIP.equals(protocol) || URL_PROTOCOL_WSJAR.equals(protocol) ||
+ (URL_PROTOCOL_CODE_SOURCE.equals(protocol) && url.getPath().contains(JAR_URL_SEPARATOR)));
+ }
+
+ /**
+ * Extract the URL for the actual jar file from the given URL
+ * (which may point to a resource in a jar file or to a jar file itself).
+ * @param jarUrl the original URL
+ * @return the URL for the actual jar file
+ * @throws MalformedURLException if no valid jar file URL could be extracted
+ */
+ public static URL extractJarFileURL(URL jarUrl) throws MalformedURLException {
+ String urlFile = jarUrl.getFile();
+ int separatorIndex = urlFile.indexOf(JAR_URL_SEPARATOR);
+ if (separatorIndex != -1) {
+ String jarFile = urlFile.substring(0, separatorIndex);
+ try {
+ return new URL(jarFile);
+ }
+ catch (MalformedURLException ex) {
+ // Probably no protocol in original jar URL, like "jar:C:/mypath/myjar.jar".
+ // This usually indicates that the jar file resides in the file system.
+ if (!jarFile.startsWith("/")) {
+ jarFile = "/" + jarFile;
+ }
+ return new URL(FILE_URL_PREFIX + jarFile);
+ }
+ }
+ else {
+ return jarUrl;
+ }
+ }
+
+ /**
+ * Create a URI instance for the given URL,
+ * replacing spaces with "%20" URI encoding first.
+ * <p>Furthermore, this method works on JDK 1.4 as well,
+ * in contrast to the {@code URL.toURI()} method.
+ * @param url the URL to convert into a URI instance
+ * @return the URI instance
+ * @throws URISyntaxException if the URL wasn't a valid URI
+ * @see java.net.URL#toURI()
+ */
+ public static URI toURI(URL url) throws URISyntaxException {
+ return toURI(url.toString());
+ }
+
+ /**
+ * Create a URI instance for the given location String,
+ * replacing spaces with "%20" URI encoding first.
+ * @param location the location String to convert into a URI instance
+ * @return the URI instance
+ * @throws URISyntaxException if the location wasn't a valid URI
+ */
+ public static URI toURI(String location) throws URISyntaxException {
+ return new URI(StringUtils.replace(location, " ", "%20"));
+ }
+
+ /**
+ * Set the {@link URLConnection#setUseCaches "useCaches"} flag on the
+ * given connection, preferring {@code false} but leaving the
+ * flag at {@code true} for JNLP based resources.
+ * @param con the URLConnection to set the flag on
+ */
+ public static void useCachesIfNecessary(URLConnection con) {
+ con.setUseCaches(con.getClass().getSimpleName().startsWith("JNLP"));
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/SerializationUtils.java b/spring-core/src/main/java/org/springframework/util/SerializationUtils.java
new file mode 100644
index 00000000..f85b1c72
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/SerializationUtils.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2002-2014 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.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * Static utilities for serialization and deserialization.
+ *
+ * @author Dave Syer
+ * @since 3.0.5
+ */
+public abstract class SerializationUtils {
+
+ /**
+ * Serialize the given object to a byte array.
+ * @param object the object to serialize
+ * @return an array of bytes representing the object in a portable fashion
+ */
+ public static byte[] serialize(Object object) {
+ if (object == null) {
+ return null;
+ }
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
+ try {
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(object);
+ oos.flush();
+ }
+ catch (IOException ex) {
+ throw new IllegalArgumentException("Failed to serialize object of type: " + object.getClass(), ex);
+ }
+ return baos.toByteArray();
+ }
+
+ /**
+ * Deserialize the byte array into an object.
+ * @param bytes a serialized object
+ * @return the result of deserializing the bytes
+ */
+ public static Object deserialize(byte[] bytes) {
+ if (bytes == null) {
+ return null;
+ }
+ try {
+ ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
+ return ois.readObject();
+ }
+ catch (IOException ex) {
+ throw new IllegalArgumentException("Failed to deserialize object", ex);
+ }
+ catch (ClassNotFoundException ex) {
+ throw new IllegalStateException("Failed to deserialize object type", ex);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/StopWatch.java b/spring-core/src/main/java/org/springframework/util/StopWatch.java
new file mode 100644
index 00000000..0c974991
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/StopWatch.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright 2002-2014 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.util;
+
+import java.text.NumberFormat;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Simple stop watch, allowing for timing of a number of tasks,
+ * exposing total running time and running time for each named task.
+ *
+ * <p>Conceals use of {@code System.currentTimeMillis()}, improving the
+ * readability of application code and reducing the likelihood of calculation errors.
+ *
+ * <p>Note that this object is not designed to be thread-safe and does not
+ * use synchronization.
+ *
+ * <p>This class is normally used to verify performance during proof-of-concepts
+ * and in development, rather than as part of production applications.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @since May 2, 2001
+ */
+public class StopWatch {
+
+ /**
+ * Identifier of this stop watch.
+ * Handy when we have output from multiple stop watches
+ * and need to distinguish between them in log or console output.
+ */
+ private final String id;
+
+ private boolean keepTaskList = true;
+
+ private final List<TaskInfo> taskList = new LinkedList<TaskInfo>();
+
+ /** Start time of the current task */
+ private long startTimeMillis;
+
+ /** Is the stop watch currently running? */
+ private boolean running;
+
+ /** Name of the current task */
+ private String currentTaskName;
+
+ private TaskInfo lastTaskInfo;
+
+ private int taskCount;
+
+ /** Total running time */
+ private long totalTimeMillis;
+
+
+ /**
+ * Construct a new stop watch. Does not start any task.
+ */
+ public StopWatch() {
+ this.id = "";
+ }
+
+ /**
+ * Construct a new stop watch with the given id.
+ * Does not start any task.
+ * @param id identifier for this stop watch.
+ * Handy when we have output from multiple stop watches
+ * and need to distinguish between them.
+ */
+ public StopWatch(String id) {
+ this.id = id;
+ }
+
+
+ /**
+ * Determine whether the TaskInfo array is built over time. Set this to
+ * "false" when using a StopWatch for millions of intervals, or the task
+ * info structure will consume excessive memory. Default is "true".
+ */
+ public void setKeepTaskList(boolean keepTaskList) {
+ this.keepTaskList = keepTaskList;
+ }
+
+
+ /**
+ * Start an unnamed task. The results are undefined if {@link #stop()}
+ * or timing methods are called without invoking this method.
+ * @see #stop()
+ */
+ public void start() throws IllegalStateException {
+ start("");
+ }
+
+ /**
+ * Start a named task. The results are undefined if {@link #stop()}
+ * or timing methods are called without invoking this method.
+ * @param taskName the name of the task to start
+ * @see #stop()
+ */
+ public void start(String taskName) throws IllegalStateException {
+ if (this.running) {
+ throw new IllegalStateException("Can't start StopWatch: it's already running");
+ }
+ this.startTimeMillis = System.currentTimeMillis();
+ this.running = true;
+ this.currentTaskName = taskName;
+ }
+
+ /**
+ * Stop the current task. The results are undefined if timing
+ * methods are called without invoking at least one pair
+ * {@link #start()} / {@link #stop()} methods.
+ * @see #start()
+ */
+ public void stop() throws IllegalStateException {
+ if (!this.running) {
+ throw new IllegalStateException("Can't stop StopWatch: it's not running");
+ }
+ long lastTime = System.currentTimeMillis() - this.startTimeMillis;
+ this.totalTimeMillis += lastTime;
+ this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
+ if (this.keepTaskList) {
+ this.taskList.add(lastTaskInfo);
+ }
+ ++this.taskCount;
+ this.running = false;
+ this.currentTaskName = null;
+ }
+
+ /**
+ * Return whether the stop watch is currently running.
+ */
+ public boolean isRunning() {
+ return this.running;
+ }
+
+
+ /**
+ * Return the time taken by the last task.
+ */
+ public long getLastTaskTimeMillis() throws IllegalStateException {
+ if (this.lastTaskInfo == null) {
+ throw new IllegalStateException("No tasks run: can't get last task interval");
+ }
+ return this.lastTaskInfo.getTimeMillis();
+ }
+
+ /**
+ * Return the name of the last task.
+ */
+ public String getLastTaskName() throws IllegalStateException {
+ if (this.lastTaskInfo == null) {
+ throw new IllegalStateException("No tasks run: can't get last task name");
+ }
+ return this.lastTaskInfo.getTaskName();
+ }
+
+ /**
+ * Return the last task as a TaskInfo object.
+ */
+ public TaskInfo getLastTaskInfo() throws IllegalStateException {
+ if (this.lastTaskInfo == null) {
+ throw new IllegalStateException("No tasks run: can't get last task info");
+ }
+ return this.lastTaskInfo;
+ }
+
+
+ /**
+ * Return the total time in milliseconds for all tasks.
+ */
+ public long getTotalTimeMillis() {
+ return this.totalTimeMillis;
+ }
+
+ /**
+ * Return the total time in seconds for all tasks.
+ */
+ public double getTotalTimeSeconds() {
+ return this.totalTimeMillis / 1000.0;
+ }
+
+ /**
+ * Return the number of tasks timed.
+ */
+ public int getTaskCount() {
+ return this.taskCount;
+ }
+
+ /**
+ * Return an array of the data for tasks performed.
+ */
+ public TaskInfo[] getTaskInfo() {
+ if (!this.keepTaskList) {
+ throw new UnsupportedOperationException("Task info is not being kept!");
+ }
+ return this.taskList.toArray(new TaskInfo[this.taskList.size()]);
+ }
+
+
+ /**
+ * Return a short description of the total running time.
+ */
+ public String shortSummary() {
+ return "StopWatch '" + this.id + "': running time (millis) = " + getTotalTimeMillis();
+ }
+
+ /**
+ * Return a string with a table describing all tasks performed.
+ * For custom reporting, call getTaskInfo() and use the task info directly.
+ */
+ public String prettyPrint() {
+ StringBuilder sb = new StringBuilder(shortSummary());
+ sb.append('\n');
+ if (!this.keepTaskList) {
+ sb.append("No task info kept");
+ }
+ else {
+ sb.append("-----------------------------------------\n");
+ sb.append("ms % Task name\n");
+ sb.append("-----------------------------------------\n");
+ NumberFormat nf = NumberFormat.getNumberInstance();
+ nf.setMinimumIntegerDigits(5);
+ nf.setGroupingUsed(false);
+ NumberFormat pf = NumberFormat.getPercentInstance();
+ pf.setMinimumIntegerDigits(3);
+ pf.setGroupingUsed(false);
+ for (TaskInfo task : getTaskInfo()) {
+ sb.append(nf.format(task.getTimeMillis())).append(" ");
+ sb.append(pf.format(task.getTimeSeconds() / getTotalTimeSeconds())).append(" ");
+ sb.append(task.getTaskName()).append("\n");
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Return an informative string describing all tasks performed
+ * For custom reporting, call {@code getTaskInfo()} and use the task info directly.
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(shortSummary());
+ if (this.keepTaskList) {
+ for (TaskInfo task : getTaskInfo()) {
+ sb.append("; [").append(task.getTaskName()).append("] took ").append(task.getTimeMillis());
+ long percent = Math.round((100.0 * task.getTimeSeconds()) / getTotalTimeSeconds());
+ sb.append(" = ").append(percent).append("%");
+ }
+ }
+ else {
+ sb.append("; no task info kept");
+ }
+ return sb.toString();
+ }
+
+
+ /**
+ * Inner class to hold data about one task executed within the stop watch.
+ */
+ public static final class TaskInfo {
+
+ private final String taskName;
+
+ private final long timeMillis;
+
+ TaskInfo(String taskName, long timeMillis) {
+ this.taskName = taskName;
+ this.timeMillis = timeMillis;
+ }
+
+ /**
+ * Return the name of this task.
+ */
+ public String getTaskName() {
+ return this.taskName;
+ }
+
+ /**
+ * Return the time in milliseconds this task took.
+ */
+ public long getTimeMillis() {
+ return this.timeMillis;
+ }
+
+ /**
+ * Return the time in seconds this task took.
+ */
+ public double getTimeSeconds() {
+ return this.timeMillis / 1000.0;
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/StreamUtils.java b/spring-core/src/main/java/org/springframework/util/StreamUtils.java
new file mode 100644
index 00000000..cc3107d8
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/StreamUtils.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2002-2013 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.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FilterInputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+
+
+/**
+ * Simple utility methods for dealing with streams. The copy methods of this class are
+ * similar to those defined in {@link FileCopyUtils} except that all affected streams are
+ * left open when done. All copy methods use a block size of 4096 bytes.
+ *
+ * <p>Mainly for use within the framework, but also useful for application code.
+ *
+ * @author Juergen Hoeller
+ * @author Phillip Webb
+ * @since 3.2.2
+ * @see FileCopyUtils
+ */
+public abstract class StreamUtils {
+
+ public static final int BUFFER_SIZE = 4096;
+
+
+ /**
+ * Copy the contents of the given InputStream into a new byte array.
+ * Leaves the stream open when done.
+ * @param in the stream to copy from
+ * @return the new byte array that has been copied to
+ * @throws IOException in case of I/O errors
+ */
+ public static byte[] copyToByteArray(InputStream in) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE);
+ copy(in, out);
+ return out.toByteArray();
+ }
+
+ /**
+ * Copy the contents of the given InputStream into a String.
+ * Leaves the stream open when done.
+ * @param in the InputStream to copy from
+ * @return the String that has been copied to
+ * @throws IOException in case of I/O errors
+ */
+ public static String copyToString(InputStream in, Charset charset) throws IOException {
+ Assert.notNull(in, "No InputStream specified");
+ StringBuilder out = new StringBuilder();
+ InputStreamReader reader = new InputStreamReader(in, charset);
+ char[] buffer = new char[BUFFER_SIZE];
+ int bytesRead = -1;
+ while ((bytesRead = reader.read(buffer)) != -1) {
+ out.append(buffer, 0, bytesRead);
+ }
+ return out.toString();
+ }
+
+ /**
+ * Copy the contents of the given byte array to the given OutputStream.
+ * Leaves the stream open when done.
+ * @param in the byte array to copy from
+ * @param out the OutputStream to copy to
+ * @throws IOException in case of I/O errors
+ */
+ public static void copy(byte[] in, OutputStream out) throws IOException {
+ Assert.notNull(in, "No input byte array specified");
+ Assert.notNull(out, "No OutputStream specified");
+ out.write(in);
+ }
+
+ /**
+ * Copy the contents of the given String to the given output OutputStream.
+ * Leaves the stream open when done.
+ * @param in the String to copy from
+ * @param charset the Charset
+ * @param out the OutputStream to copy to
+ * @throws IOException in case of I/O errors
+ */
+ public static void copy(String in, Charset charset, OutputStream out) throws IOException {
+ Assert.notNull(in, "No input String specified");
+ Assert.notNull(charset, "No charset specified");
+ Assert.notNull(out, "No OutputStream specified");
+ Writer writer = new OutputStreamWriter(out, charset);
+ writer.write(in);
+ writer.flush();
+ }
+
+ /**
+ * Copy the contents of the given InputStream to the given OutputStream.
+ * Leaves both streams open when done.
+ * @param in the InputStream to copy from
+ * @param out the OutputStream to copy to
+ * @return the number of bytes copied
+ * @throws IOException in case of I/O errors
+ */
+ public static int copy(InputStream in, OutputStream out) throws IOException {
+ Assert.notNull(in, "No InputStream specified");
+ Assert.notNull(out, "No OutputStream specified");
+ int byteCount = 0;
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int bytesRead = -1;
+ while ((bytesRead = in.read(buffer)) != -1) {
+ out.write(buffer, 0, bytesRead);
+ byteCount += bytesRead;
+ }
+ out.flush();
+ return byteCount;
+ }
+
+ /**
+ * Returns a variant of the given {@link InputStream} where calling
+ * {@link InputStream#close() close()} has no effect.
+ * @param in the InputStream to decorate
+ * @return a version of the InputStream that ignores calls to close
+ */
+ public static InputStream nonClosing(InputStream in) {
+ Assert.notNull(in, "No InputStream specified");
+ return new NonClosingInputStream(in);
+ }
+
+ /**
+ * Returns a variant of the given {@link OutputStream} where calling
+ * {@link OutputStream#close() close()} has no effect.
+ * @param out the OutputStream to decorate
+ * @return a version of the OutputStream that ignores calls to close
+ */
+ public static OutputStream nonClosing(OutputStream out) {
+ Assert.notNull(out, "No OutputStream specified");
+ return new NonClosingOutputStream(out);
+ }
+
+
+ private static class NonClosingInputStream extends FilterInputStream {
+
+ public NonClosingInputStream(InputStream in) {
+ super(in);
+ }
+
+ @Override
+ public void close() throws IOException {
+ }
+ }
+
+
+ private static class NonClosingOutputStream extends FilterOutputStream {
+
+ public NonClosingOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int let) throws IOException {
+ // It is critical that we override this method for performance
+ out.write(b, off, let);
+ }
+
+ @Override
+ public void close() throws IOException {
+ }
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/util/StringUtils.java b/spring-core/src/main/java/org/springframework/util/StringUtils.java
new file mode 100644
index 00000000..0ad561da
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/StringUtils.java
@@ -0,0 +1,1162 @@
+/*
+ * Copyright 2002-2014 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.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+
+/**
+ * Miscellaneous {@link String} utility methods.
+ *
+ * <p>Mainly for internal use within the framework; consider
+ * <a href="http://jakarta.apache.org/commons/lang/">Jakarta's Commons Lang</a>
+ * for a more comprehensive suite of String utilities.
+ *
+ * <p>This class delivers some simple functionality that should really
+ * be provided by the core Java {@code String} and {@link StringBuilder}
+ * classes, such as the ability to {@link #replace} all occurrences of a given
+ * substring in a target string. It also provides easy-to-use methods to convert
+ * between delimited strings, such as CSV strings, and collections and arrays.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Keith Donald
+ * @author Rob Harrop
+ * @author Rick Evans
+ * @author Arjen Poutsma
+ * @since 16 April 2001
+ */
+public abstract class StringUtils {
+
+ private static final String FOLDER_SEPARATOR = "/";
+
+ private static final String WINDOWS_FOLDER_SEPARATOR = "\\";
+
+ private static final String TOP_PATH = "..";
+
+ private static final String CURRENT_PATH = ".";
+
+ private static final char EXTENSION_SEPARATOR = '.';
+
+
+ //---------------------------------------------------------------------
+ // General convenience methods for working with Strings
+ //---------------------------------------------------------------------
+
+ /**
+ * Check whether the given String is empty.
+ * <p>This method accepts any Object as an argument, comparing it to
+ * {@code null} and the empty String. As a consequence, this method
+ * will never return {@code true} for a non-null non-String object.
+ * <p>The Object signature is useful for general attribute handling code
+ * that commonly deals with Strings but generally has to iterate over
+ * Objects since attributes may e.g. be primitive value objects as well.
+ * @param str the candidate String
+ * @since 3.2.1
+ */
+ public static boolean isEmpty(Object str) {
+ return (str == null || "".equals(str));
+ }
+
+ /**
+ * Check that the given CharSequence is neither {@code null} nor of length 0.
+ * Note: Will return {@code true} for a CharSequence that purely consists of whitespace.
+ * <p><pre class="code">
+ * StringUtils.hasLength(null) = false
+ * StringUtils.hasLength("") = false
+ * StringUtils.hasLength(" ") = true
+ * StringUtils.hasLength("Hello") = true
+ * </pre>
+ * @param str the CharSequence to check (may be {@code null})
+ * @return {@code true} if the CharSequence is not null and has length
+ * @see #hasText(String)
+ */
+ public static boolean hasLength(CharSequence str) {
+ return (str != null && str.length() > 0);
+ }
+
+ /**
+ * Check that the given String is neither {@code null} nor of length 0.
+ * Note: Will return {@code true} for a String that purely consists of whitespace.
+ * @param str the String to check (may be {@code null})
+ * @return {@code true} if the String is not null and has length
+ * @see #hasLength(CharSequence)
+ */
+ public static boolean hasLength(String str) {
+ return hasLength((CharSequence) str);
+ }
+
+ /**
+ * Check whether the given CharSequence has actual text.
+ * More specifically, returns {@code true} if the string not {@code null},
+ * its length is greater than 0, and it contains at least one non-whitespace character.
+ * <p><pre class="code">
+ * StringUtils.hasText(null) = false
+ * StringUtils.hasText("") = false
+ * StringUtils.hasText(" ") = false
+ * StringUtils.hasText("12345") = true
+ * StringUtils.hasText(" 12345 ") = true
+ * </pre>
+ * @param str the CharSequence to check (may be {@code null})
+ * @return {@code true} if the CharSequence is not {@code null},
+ * its length is greater than 0, and it does not contain whitespace only
+ * @see Character#isWhitespace
+ */
+ public static boolean hasText(CharSequence str) {
+ if (!hasLength(str)) {
+ return false;
+ }
+ int strLen = str.length();
+ for (int i = 0; i < strLen; i++) {
+ if (!Character.isWhitespace(str.charAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the given String has actual text.
+ * More specifically, returns {@code true} if the string not {@code null},
+ * its length is greater than 0, and it contains at least one non-whitespace character.
+ * @param str the String to check (may be {@code null})
+ * @return {@code true} if the String is not {@code null}, its length is
+ * greater than 0, and it does not contain whitespace only
+ * @see #hasText(CharSequence)
+ */
+ public static boolean hasText(String str) {
+ return hasText((CharSequence) str);
+ }
+
+ /**
+ * Check whether the given CharSequence contains any whitespace characters.
+ * @param str the CharSequence to check (may be {@code null})
+ * @return {@code true} if the CharSequence is not empty and
+ * contains at least 1 whitespace character
+ * @see Character#isWhitespace
+ */
+ public static boolean containsWhitespace(CharSequence str) {
+ if (!hasLength(str)) {
+ return false;
+ }
+ int strLen = str.length();
+ for (int i = 0; i < strLen; i++) {
+ if (Character.isWhitespace(str.charAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether the given String contains any whitespace characters.
+ * @param str the String to check (may be {@code null})
+ * @return {@code true} if the String is not empty and
+ * contains at least 1 whitespace character
+ * @see #containsWhitespace(CharSequence)
+ */
+ public static boolean containsWhitespace(String str) {
+ return containsWhitespace((CharSequence) str);
+ }
+
+ /**
+ * Trim leading and trailing whitespace from the given String.
+ * @param str the String to check
+ * @return the trimmed String
+ * @see java.lang.Character#isWhitespace
+ */
+ public static String trimWhitespace(String str) {
+ if (!hasLength(str)) {
+ return str;
+ }
+ StringBuilder sb = new StringBuilder(str);
+ while (sb.length() > 0 && Character.isWhitespace(sb.charAt(0))) {
+ sb.deleteCharAt(0);
+ }
+ while (sb.length() > 0 && Character.isWhitespace(sb.charAt(sb.length() - 1))) {
+ sb.deleteCharAt(sb.length() - 1);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Trim <i>all</i> whitespace from the given String:
+ * leading, trailing, and in between characters.
+ * @param str the String to check
+ * @return the trimmed String
+ * @see java.lang.Character#isWhitespace
+ */
+ public static String trimAllWhitespace(String str) {
+ if (!hasLength(str)) {
+ return str;
+ }
+ StringBuilder sb = new StringBuilder(str);
+ int index = 0;
+ while (sb.length() > index) {
+ if (Character.isWhitespace(sb.charAt(index))) {
+ sb.deleteCharAt(index);
+ }
+ else {
+ index++;
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Trim leading whitespace from the given String.
+ * @param str the String to check
+ * @return the trimmed String
+ * @see java.lang.Character#isWhitespace
+ */
+ public static String trimLeadingWhitespace(String str) {
+ if (!hasLength(str)) {
+ return str;
+ }
+ StringBuilder sb = new StringBuilder(str);
+ while (sb.length() > 0 && Character.isWhitespace(sb.charAt(0))) {
+ sb.deleteCharAt(0);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Trim trailing whitespace from the given String.
+ * @param str the String to check
+ * @return the trimmed String
+ * @see java.lang.Character#isWhitespace
+ */
+ public static String trimTrailingWhitespace(String str) {
+ if (!hasLength(str)) {
+ return str;
+ }
+ StringBuilder sb = new StringBuilder(str);
+ while (sb.length() > 0 && Character.isWhitespace(sb.charAt(sb.length() - 1))) {
+ sb.deleteCharAt(sb.length() - 1);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Trim all occurrences of the supplied leading character from the given String.
+ * @param str the String to check
+ * @param leadingCharacter the leading character to be trimmed
+ * @return the trimmed String
+ */
+ public static String trimLeadingCharacter(String str, char leadingCharacter) {
+ if (!hasLength(str)) {
+ return str;
+ }
+ StringBuilder sb = new StringBuilder(str);
+ while (sb.length() > 0 && sb.charAt(0) == leadingCharacter) {
+ sb.deleteCharAt(0);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Trim all occurrences of the supplied trailing character from the given String.
+ * @param str the String to check
+ * @param trailingCharacter the trailing character to be trimmed
+ * @return the trimmed String
+ */
+ public static String trimTrailingCharacter(String str, char trailingCharacter) {
+ if (!hasLength(str)) {
+ return str;
+ }
+ StringBuilder sb = new StringBuilder(str);
+ while (sb.length() > 0 && sb.charAt(sb.length() - 1) == trailingCharacter) {
+ sb.deleteCharAt(sb.length() - 1);
+ }
+ return sb.toString();
+ }
+
+
+ /**
+ * Test if the given String starts with the specified prefix,
+ * ignoring upper/lower case.
+ * @param str the String to check
+ * @param prefix the prefix to look for
+ * @see java.lang.String#startsWith
+ */
+ public static boolean startsWithIgnoreCase(String str, String prefix) {
+ if (str == null || prefix == null) {
+ return false;
+ }
+ if (str.startsWith(prefix)) {
+ return true;
+ }
+ if (str.length() < prefix.length()) {
+ return false;
+ }
+ String lcStr = str.substring(0, prefix.length()).toLowerCase();
+ String lcPrefix = prefix.toLowerCase();
+ return lcStr.equals(lcPrefix);
+ }
+
+ /**
+ * Test if the given String ends with the specified suffix,
+ * ignoring upper/lower case.
+ * @param str the String to check
+ * @param suffix the suffix to look for
+ * @see java.lang.String#endsWith
+ */
+ public static boolean endsWithIgnoreCase(String str, String suffix) {
+ if (str == null || suffix == null) {
+ return false;
+ }
+ if (str.endsWith(suffix)) {
+ return true;
+ }
+ if (str.length() < suffix.length()) {
+ return false;
+ }
+
+ String lcStr = str.substring(str.length() - suffix.length()).toLowerCase();
+ String lcSuffix = suffix.toLowerCase();
+ return lcStr.equals(lcSuffix);
+ }
+
+ /**
+ * Test whether the given string matches the given substring
+ * at the given index.
+ * @param str the original string (or StringBuilder)
+ * @param index the index in the original string to start matching against
+ * @param substring the substring to match at the given index
+ */
+ public static boolean substringMatch(CharSequence str, int index, CharSequence substring) {
+ for (int j = 0; j < substring.length(); j++) {
+ int i = index + j;
+ if (i >= str.length() || str.charAt(i) != substring.charAt(j)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Count the occurrences of the substring in string s.
+ * @param str string to search in. Return 0 if this is null.
+ * @param sub string to search for. Return 0 if this is null.
+ */
+ public static int countOccurrencesOf(String str, String sub) {
+ if (str == null || sub == null || str.length() == 0 || sub.length() == 0) {
+ return 0;
+ }
+ int count = 0;
+ int pos = 0;
+ int idx;
+ while ((idx = str.indexOf(sub, pos)) != -1) {
+ ++count;
+ pos = idx + sub.length();
+ }
+ return count;
+ }
+
+ /**
+ * Replace all occurrences of a substring within a string with
+ * another string.
+ * @param inString String to examine
+ * @param oldPattern String to replace
+ * @param newPattern String to insert
+ * @return a String with the replacements
+ */
+ public static String replace(String inString, String oldPattern, String newPattern) {
+ if (!hasLength(inString) || !hasLength(oldPattern) || newPattern == null) {
+ return inString;
+ }
+ StringBuilder sb = new StringBuilder();
+ int pos = 0; // our position in the old string
+ int index = inString.indexOf(oldPattern);
+ // the index of an occurrence we've found, or -1
+ int patLen = oldPattern.length();
+ while (index >= 0) {
+ sb.append(inString.substring(pos, index));
+ sb.append(newPattern);
+ pos = index + patLen;
+ index = inString.indexOf(oldPattern, pos);
+ }
+ sb.append(inString.substring(pos));
+ // remember to append any characters to the right of a match
+ return sb.toString();
+ }
+
+ /**
+ * Delete all occurrences of the given substring.
+ * @param inString the original String
+ * @param pattern the pattern to delete all occurrences of
+ * @return the resulting String
+ */
+ public static String delete(String inString, String pattern) {
+ return replace(inString, pattern, "");
+ }
+
+ /**
+ * Delete any character in a given String.
+ * @param inString the original String
+ * @param charsToDelete a set of characters to delete.
+ * E.g. "az\n" will delete 'a's, 'z's and new lines.
+ * @return the resulting String
+ */
+ public static String deleteAny(String inString, String charsToDelete) {
+ if (!hasLength(inString) || !hasLength(charsToDelete)) {
+ return inString;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < inString.length(); i++) {
+ char c = inString.charAt(i);
+ if (charsToDelete.indexOf(c) == -1) {
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+
+
+ //---------------------------------------------------------------------
+ // Convenience methods for working with formatted Strings
+ //---------------------------------------------------------------------
+
+ /**
+ * Quote the given String with single quotes.
+ * @param str the input String (e.g. "myString")
+ * @return the quoted String (e.g. "'myString'"),
+ * or {@code null} if the input was {@code null}
+ */
+ public static String quote(String str) {
+ return (str != null ? "'" + str + "'" : null);
+ }
+
+ /**
+ * Turn the given Object into a String with single quotes
+ * if it is a String; keeping the Object as-is else.
+ * @param obj the input Object (e.g. "myString")
+ * @return the quoted String (e.g. "'myString'"),
+ * or the input object as-is if not a String
+ */
+ public static Object quoteIfString(Object obj) {
+ return (obj instanceof String ? quote((String) obj) : obj);
+ }
+
+ /**
+ * Unqualify a string qualified by a '.' dot character. For example,
+ * "this.name.is.qualified", returns "qualified".
+ * @param qualifiedName the qualified name
+ */
+ public static String unqualify(String qualifiedName) {
+ return unqualify(qualifiedName, '.');
+ }
+
+ /**
+ * Unqualify a string qualified by a separator character. For example,
+ * "this:name:is:qualified" returns "qualified" if using a ':' separator.
+ * @param qualifiedName the qualified name
+ * @param separator the separator
+ */
+ public static String unqualify(String qualifiedName, char separator) {
+ return qualifiedName.substring(qualifiedName.lastIndexOf(separator) + 1);
+ }
+
+ /**
+ * Capitalize a {@code String}, changing the first letter to
+ * upper case as per {@link Character#toUpperCase(char)}.
+ * No other letters are changed.
+ * @param str the String to capitalize, may be {@code null}
+ * @return the capitalized String, {@code null} if null
+ */
+ public static String capitalize(String str) {
+ return changeFirstCharacterCase(str, true);
+ }
+
+ /**
+ * Uncapitalize a {@code String}, changing the first letter to
+ * lower case as per {@link Character#toLowerCase(char)}.
+ * No other letters are changed.
+ * @param str the String to uncapitalize, may be {@code null}
+ * @return the uncapitalized String, {@code null} if null
+ */
+ public static String uncapitalize(String str) {
+ return changeFirstCharacterCase(str, false);
+ }
+
+ private static String changeFirstCharacterCase(String str, boolean capitalize) {
+ if (str == null || str.length() == 0) {
+ return str;
+ }
+ StringBuilder sb = new StringBuilder(str.length());
+ if (capitalize) {
+ sb.append(Character.toUpperCase(str.charAt(0)));
+ }
+ else {
+ sb.append(Character.toLowerCase(str.charAt(0)));
+ }
+ sb.append(str.substring(1));
+ return sb.toString();
+ }
+
+ /**
+ * Extract the filename from the given path,
+ * e.g. "mypath/myfile.txt" -> "myfile.txt".
+ * @param path the file path (may be {@code null})
+ * @return the extracted filename, or {@code null} if none
+ */
+ public static String getFilename(String path) {
+ if (path == null) {
+ return null;
+ }
+ int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR);
+ return (separatorIndex != -1 ? path.substring(separatorIndex + 1) : path);
+ }
+
+ /**
+ * Extract the filename extension from the given path,
+ * e.g. "mypath/myfile.txt" -> "txt".
+ * @param path the file path (may be {@code null})
+ * @return the extracted filename extension, or {@code null} if none
+ */
+ public static String getFilenameExtension(String path) {
+ if (path == null) {
+ return null;
+ }
+ int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR);
+ if (extIndex == -1) {
+ return null;
+ }
+ int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR);
+ if (folderIndex > extIndex) {
+ return null;
+ }
+ return path.substring(extIndex + 1);
+ }
+
+ /**
+ * Strip the filename extension from the given path,
+ * e.g. "mypath/myfile.txt" -> "mypath/myfile".
+ * @param path the file path (may be {@code null})
+ * @return the path with stripped filename extension,
+ * or {@code null} if none
+ */
+ public static String stripFilenameExtension(String path) {
+ if (path == null) {
+ return null;
+ }
+ int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR);
+ if (extIndex == -1) {
+ return path;
+ }
+ int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR);
+ if (folderIndex > extIndex) {
+ return path;
+ }
+ return path.substring(0, extIndex);
+ }
+
+ /**
+ * Apply the given relative path to the given path,
+ * assuming standard Java folder separation (i.e. "/" separators).
+ * @param path the path to start from (usually a full file path)
+ * @param relativePath the relative path to apply
+ * (relative to the full file path above)
+ * @return the full file path that results from applying the relative path
+ */
+ public static String applyRelativePath(String path, String relativePath) {
+ int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR);
+ if (separatorIndex != -1) {
+ String newPath = path.substring(0, separatorIndex);
+ if (!relativePath.startsWith(FOLDER_SEPARATOR)) {
+ newPath += FOLDER_SEPARATOR;
+ }
+ return newPath + relativePath;
+ }
+ else {
+ return relativePath;
+ }
+ }
+
+ /**
+ * Normalize the path by suppressing sequences like "path/.." and
+ * inner simple dots.
+ * <p>The result is convenient for path comparison. For other uses,
+ * notice that Windows separators ("\") are replaced by simple slashes.
+ * @param path the original path
+ * @return the normalized path
+ */
+ public static String cleanPath(String path) {
+ if (path == null) {
+ return null;
+ }
+ String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR);
+
+ // Strip prefix from path to analyze, to not treat it as part of the
+ // first path element. This is necessary to correctly parse paths like
+ // "file:core/../core/io/Resource.class", where the ".." should just
+ // strip the first "core" directory while keeping the "file:" prefix.
+ int prefixIndex = pathToUse.indexOf(":");
+ String prefix = "";
+ if (prefixIndex != -1) {
+ prefix = pathToUse.substring(0, prefixIndex + 1);
+ if (prefix.contains("/")) {
+ prefix = "";
+ }
+ else {
+ pathToUse = pathToUse.substring(prefixIndex + 1);
+ }
+ }
+ if (pathToUse.startsWith(FOLDER_SEPARATOR)) {
+ prefix = prefix + FOLDER_SEPARATOR;
+ pathToUse = pathToUse.substring(1);
+ }
+
+ String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR);
+ List<String> pathElements = new LinkedList<String>();
+ int tops = 0;
+
+ for (int i = pathArray.length - 1; i >= 0; i--) {
+ String element = pathArray[i];
+ if (CURRENT_PATH.equals(element)) {
+ // Points to current directory - drop it.
+ }
+ else if (TOP_PATH.equals(element)) {
+ // Registering top path found.
+ tops++;
+ }
+ else {
+ if (tops > 0) {
+ // Merging path element with element corresponding to top path.
+ tops--;
+ }
+ else {
+ // Normal path element found.
+ pathElements.add(0, element);
+ }
+ }
+ }
+
+ // Remaining top paths need to be retained.
+ for (int i = 0; i < tops; i++) {
+ pathElements.add(0, TOP_PATH);
+ }
+
+ return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR);
+ }
+
+ /**
+ * Compare two paths after normalization of them.
+ * @param path1 first path for comparison
+ * @param path2 second path for comparison
+ * @return whether the two paths are equivalent after normalization
+ */
+ public static boolean pathEquals(String path1, String path2) {
+ return cleanPath(path1).equals(cleanPath(path2));
+ }
+
+ /**
+ * Parse the given {@code localeString} value into a {@link Locale}.
+ * <p>This is the inverse operation of {@link Locale#toString Locale's toString}.
+ * @param localeString the locale String, following {@code Locale's}
+ * {@code toString()} format ("en", "en_UK", etc);
+ * also accepts spaces as separators, as an alternative to underscores
+ * @return a corresponding {@code Locale} instance
+ * @throws IllegalArgumentException in case of an invalid locale specification
+ */
+ public static Locale parseLocaleString(String localeString) {
+ String[] parts = tokenizeToStringArray(localeString, "_ ", false, false);
+ String language = (parts.length > 0 ? parts[0] : "");
+ String country = (parts.length > 1 ? parts[1] : "");
+ validateLocalePart(language);
+ validateLocalePart(country);
+ String variant = "";
+ if (parts.length > 2) {
+ // There is definitely a variant, and it is everything after the country
+ // code sans the separator between the country code and the variant.
+ int endIndexOfCountryCode = localeString.indexOf(country, language.length()) + country.length();
+ // Strip off any leading '_' and whitespace, what's left is the variant.
+ variant = trimLeadingWhitespace(localeString.substring(endIndexOfCountryCode));
+ if (variant.startsWith("_")) {
+ variant = trimLeadingCharacter(variant, '_');
+ }
+ }
+ return (language.length() > 0 ? new Locale(language, country, variant) : null);
+ }
+
+ private static void validateLocalePart(String localePart) {
+ for (int i = 0; i < localePart.length(); i++) {
+ char ch = localePart.charAt(i);
+ if (ch != '_' && ch != ' ' && !Character.isLetterOrDigit(ch)) {
+ throw new IllegalArgumentException(
+ "Locale part \"" + localePart + "\" contains invalid characters");
+ }
+ }
+ }
+
+ /**
+ * Determine the RFC 3066 compliant language tag,
+ * as used for the HTTP "Accept-Language" header.
+ * @param locale the Locale to transform to a language tag
+ * @return the RFC 3066 compliant language tag as String
+ */
+ public static String toLanguageTag(Locale locale) {
+ return locale.getLanguage() + (hasText(locale.getCountry()) ? "-" + locale.getCountry() : "");
+ }
+
+
+ //---------------------------------------------------------------------
+ // Convenience methods for working with String arrays
+ //---------------------------------------------------------------------
+
+ /**
+ * Append the given String to the given String array, returning a new array
+ * consisting of the input array contents plus the given String.
+ * @param array the array to append to (can be {@code null})
+ * @param str the String to append
+ * @return the new array (never {@code null})
+ */
+ public static String[] addStringToArray(String[] array, String str) {
+ if (ObjectUtils.isEmpty(array)) {
+ return new String[] {str};
+ }
+ String[] newArr = new String[array.length + 1];
+ System.arraycopy(array, 0, newArr, 0, array.length);
+ newArr[array.length] = str;
+ return newArr;
+ }
+
+ /**
+ * Concatenate the given String arrays into one,
+ * with overlapping array elements included twice.
+ * <p>The order of elements in the original arrays is preserved.
+ * @param array1 the first array (can be {@code null})
+ * @param array2 the second array (can be {@code null})
+ * @return the new array ({@code null} if both given arrays were {@code null})
+ */
+ public static String[] concatenateStringArrays(String[] array1, String[] array2) {
+ if (ObjectUtils.isEmpty(array1)) {
+ return array2;
+ }
+ if (ObjectUtils.isEmpty(array2)) {
+ return array1;
+ }
+ String[] newArr = new String[array1.length + array2.length];
+ System.arraycopy(array1, 0, newArr, 0, array1.length);
+ System.arraycopy(array2, 0, newArr, array1.length, array2.length);
+ return newArr;
+ }
+
+ /**
+ * Merge the given String arrays into one, with overlapping
+ * array elements only included once.
+ * <p>The order of elements in the original arrays is preserved
+ * (with the exception of overlapping elements, which are only
+ * included on their first occurrence).
+ * @param array1 the first array (can be {@code null})
+ * @param array2 the second array (can be {@code null})
+ * @return the new array ({@code null} if both given arrays were {@code null})
+ */
+ public static String[] mergeStringArrays(String[] array1, String[] array2) {
+ if (ObjectUtils.isEmpty(array1)) {
+ return array2;
+ }
+ if (ObjectUtils.isEmpty(array2)) {
+ return array1;
+ }
+ List<String> result = new ArrayList<String>();
+ result.addAll(Arrays.asList(array1));
+ for (String str : array2) {
+ if (!result.contains(str)) {
+ result.add(str);
+ }
+ }
+ return toStringArray(result);
+ }
+
+ /**
+ * Turn given source String array into sorted array.
+ * @param array the source array
+ * @return the sorted array (never {@code null})
+ */
+ public static String[] sortStringArray(String[] array) {
+ if (ObjectUtils.isEmpty(array)) {
+ return new String[0];
+ }
+ Arrays.sort(array);
+ return array;
+ }
+
+ /**
+ * Copy the given Collection into a String array.
+ * The Collection must contain String elements only.
+ * @param collection the Collection to copy
+ * @return the String array ({@code null} if the passed-in
+ * Collection was {@code null})
+ */
+ public static String[] toStringArray(Collection<String> collection) {
+ if (collection == null) {
+ return null;
+ }
+ return collection.toArray(new String[collection.size()]);
+ }
+
+ /**
+ * Copy the given Enumeration into a String array.
+ * The Enumeration must contain String elements only.
+ * @param enumeration the Enumeration to copy
+ * @return the String array ({@code null} if the passed-in
+ * Enumeration was {@code null})
+ */
+ public static String[] toStringArray(Enumeration<String> enumeration) {
+ if (enumeration == null) {
+ return null;
+ }
+ List<String> list = Collections.list(enumeration);
+ return list.toArray(new String[list.size()]);
+ }
+
+ /**
+ * Trim the elements of the given String array,
+ * calling {@code String.trim()} on each of them.
+ * @param array the original String array
+ * @return the resulting array (of the same size) with trimmed elements
+ */
+ public static String[] trimArrayElements(String[] array) {
+ if (ObjectUtils.isEmpty(array)) {
+ return new String[0];
+ }
+ String[] result = new String[array.length];
+ for (int i = 0; i < array.length; i++) {
+ String element = array[i];
+ result[i] = (element != null ? element.trim() : null);
+ }
+ return result;
+ }
+
+ /**
+ * Remove duplicate Strings from the given array.
+ * Also sorts the array, as it uses a TreeSet.
+ * @param array the String array
+ * @return an array without duplicates, in natural sort order
+ */
+ public static String[] removeDuplicateStrings(String[] array) {
+ if (ObjectUtils.isEmpty(array)) {
+ return array;
+ }
+ Set<String> set = new TreeSet<String>();
+ for (String element : array) {
+ set.add(element);
+ }
+ return toStringArray(set);
+ }
+
+ /**
+ * Split a String at the first occurrence of the delimiter.
+ * Does not include the delimiter in the result.
+ * @param toSplit the string to split
+ * @param delimiter to split the string up with
+ * @return a two element array with index 0 being before the delimiter, and
+ * index 1 being after the delimiter (neither element includes the delimiter);
+ * or {@code null} if the delimiter wasn't found in the given input String
+ */
+ public static String[] split(String toSplit, String delimiter) {
+ if (!hasLength(toSplit) || !hasLength(delimiter)) {
+ return null;
+ }
+ int offset = toSplit.indexOf(delimiter);
+ if (offset < 0) {
+ return null;
+ }
+ String beforeDelimiter = toSplit.substring(0, offset);
+ String afterDelimiter = toSplit.substring(offset + delimiter.length());
+ return new String[] {beforeDelimiter, afterDelimiter};
+ }
+
+ /**
+ * Take an array Strings and split each element based on the given delimiter.
+ * A {@code Properties} instance is then generated, with the left of the
+ * delimiter providing the key, and the right of the delimiter providing the value.
+ * <p>Will trim both the key and value before adding them to the
+ * {@code Properties} instance.
+ * @param array the array to process
+ * @param delimiter to split each element using (typically the equals symbol)
+ * @return a {@code Properties} instance representing the array contents,
+ * or {@code null} if the array to process was null or empty
+ */
+ public static Properties splitArrayElementsIntoProperties(String[] array, String delimiter) {
+ return splitArrayElementsIntoProperties(array, delimiter, null);
+ }
+
+ /**
+ * Take an array Strings and split each element based on the given delimiter.
+ * A {@code Properties} instance is then generated, with the left of the
+ * delimiter providing the key, and the right of the delimiter providing the value.
+ * <p>Will trim both the key and value before adding them to the
+ * {@code Properties} instance.
+ * @param array the array to process
+ * @param delimiter to split each element using (typically the equals symbol)
+ * @param charsToDelete one or more characters to remove from each element
+ * prior to attempting the split operation (typically the quotation mark
+ * symbol), or {@code null} if no removal should occur
+ * @return a {@code Properties} instance representing the array contents,
+ * or {@code null} if the array to process was {@code null} or empty
+ */
+ public static Properties splitArrayElementsIntoProperties(
+ String[] array, String delimiter, String charsToDelete) {
+
+ if (ObjectUtils.isEmpty(array)) {
+ return null;
+ }
+ Properties result = new Properties();
+ for (String element : array) {
+ if (charsToDelete != null) {
+ element = deleteAny(element, charsToDelete);
+ }
+ String[] splittedElement = split(element, delimiter);
+ if (splittedElement == null) {
+ continue;
+ }
+ result.setProperty(splittedElement[0].trim(), splittedElement[1].trim());
+ }
+ return result;
+ }
+
+ /**
+ * Tokenize the given String into a String array via a StringTokenizer.
+ * Trims tokens and omits empty tokens.
+ * <p>The given delimiters string is supposed to consist of any number of
+ * delimiter characters. Each of those characters can be used to separate
+ * tokens. A delimiter is always a single character; for multi-character
+ * delimiters, consider using {@code delimitedListToStringArray}
+ * @param str the String to tokenize
+ * @param delimiters the delimiter characters, assembled as String
+ * (each of those characters is individually considered as delimiter).
+ * @return an array of the tokens
+ * @see java.util.StringTokenizer
+ * @see String#trim()
+ * @see #delimitedListToStringArray
+ */
+ public static String[] tokenizeToStringArray(String str, String delimiters) {
+ return tokenizeToStringArray(str, delimiters, true, true);
+ }
+
+ /**
+ * Tokenize the given String into a String array via a StringTokenizer.
+ * <p>The given delimiters string is supposed to consist of any number of
+ * delimiter characters. Each of those characters can be used to separate
+ * tokens. A delimiter is always a single character; for multi-character
+ * delimiters, consider using {@code delimitedListToStringArray}
+ * @param str the String to tokenize
+ * @param delimiters the delimiter characters, assembled as String
+ * (each of those characters is individually considered as delimiter)
+ * @param trimTokens trim the tokens via String's {@code trim}
+ * @param ignoreEmptyTokens omit empty tokens from the result array
+ * (only applies to tokens that are empty after trimming; StringTokenizer
+ * will not consider subsequent delimiters as token in the first place).
+ * @return an array of the tokens ({@code null} if the input String
+ * was {@code null})
+ * @see java.util.StringTokenizer
+ * @see String#trim()
+ * @see #delimitedListToStringArray
+ */
+ public static String[] tokenizeToStringArray(
+ String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {
+
+ if (str == null) {
+ return null;
+ }
+ StringTokenizer st = new StringTokenizer(str, delimiters);
+ List<String> tokens = new ArrayList<String>();
+ while (st.hasMoreTokens()) {
+ String token = st.nextToken();
+ if (trimTokens) {
+ token = token.trim();
+ }
+ if (!ignoreEmptyTokens || token.length() > 0) {
+ tokens.add(token);
+ }
+ }
+ return toStringArray(tokens);
+ }
+
+ /**
+ * Take a String which is a delimited list and convert it to a String array.
+ * <p>A single delimiter can consists of more than one character: It will still
+ * be considered as single delimiter string, rather than as bunch of potential
+ * delimiter characters - in contrast to {@code tokenizeToStringArray}.
+ * @param str the input String
+ * @param delimiter the delimiter between elements (this is a single delimiter,
+ * rather than a bunch individual delimiter characters)
+ * @return an array of the tokens in the list
+ * @see #tokenizeToStringArray
+ */
+ public static String[] delimitedListToStringArray(String str, String delimiter) {
+ return delimitedListToStringArray(str, delimiter, null);
+ }
+
+ /**
+ * Take a String which is a delimited list and convert it to a String array.
+ * <p>A single delimiter can consists of more than one character: It will still
+ * be considered as single delimiter string, rather than as bunch of potential
+ * delimiter characters - in contrast to {@code tokenizeToStringArray}.
+ * @param str the input String
+ * @param delimiter the delimiter between elements (this is a single delimiter,
+ * rather than a bunch individual delimiter characters)
+ * @param charsToDelete a set of characters to delete. Useful for deleting unwanted
+ * line breaks: e.g. "\r\n\f" will delete all new lines and line feeds in a String.
+ * @return an array of the tokens in the list
+ * @see #tokenizeToStringArray
+ */
+ public static String[] delimitedListToStringArray(String str, String delimiter, String charsToDelete) {
+ if (str == null) {
+ return new String[0];
+ }
+ if (delimiter == null) {
+ return new String[] {str};
+ }
+ List<String> result = new ArrayList<String>();
+ if ("".equals(delimiter)) {
+ for (int i = 0; i < str.length(); i++) {
+ result.add(deleteAny(str.substring(i, i + 1), charsToDelete));
+ }
+ }
+ else {
+ int pos = 0;
+ int delPos;
+ while ((delPos = str.indexOf(delimiter, pos)) != -1) {
+ result.add(deleteAny(str.substring(pos, delPos), charsToDelete));
+ pos = delPos + delimiter.length();
+ }
+ if (str.length() > 0 && pos <= str.length()) {
+ // Add rest of String, but not in case of empty input.
+ result.add(deleteAny(str.substring(pos), charsToDelete));
+ }
+ }
+ return toStringArray(result);
+ }
+
+ /**
+ * Convert a CSV list into an array of Strings.
+ * @param str the input String
+ * @return an array of Strings, or the empty array in case of empty input
+ */
+ public static String[] commaDelimitedListToStringArray(String str) {
+ return delimitedListToStringArray(str, ",");
+ }
+
+ /**
+ * Convenience method to convert a CSV string list to a set.
+ * Note that this will suppress duplicates.
+ * @param str the input String
+ * @return a Set of String entries in the list
+ */
+ public static Set<String> commaDelimitedListToSet(String str) {
+ Set<String> set = new TreeSet<String>();
+ String[] tokens = commaDelimitedListToStringArray(str);
+ for (String token : tokens) {
+ set.add(token);
+ }
+ return set;
+ }
+
+ /**
+ * Convenience method to return a Collection as a delimited (e.g. CSV)
+ * String. E.g. useful for {@code toString()} implementations.
+ * @param coll the Collection to display
+ * @param delim the delimiter to use (probably a ",")
+ * @param prefix the String to start each element with
+ * @param suffix the String to end each element with
+ * @return the delimited String
+ */
+ public static String collectionToDelimitedString(Collection<?> coll, String delim, String prefix, String suffix) {
+ if (CollectionUtils.isEmpty(coll)) {
+ return "";
+ }
+ StringBuilder sb = new StringBuilder();
+ Iterator<?> it = coll.iterator();
+ while (it.hasNext()) {
+ sb.append(prefix).append(it.next()).append(suffix);
+ if (it.hasNext()) {
+ sb.append(delim);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Convenience method to return a Collection as a delimited (e.g. CSV)
+ * String. E.g. useful for {@code toString()} implementations.
+ * @param coll the Collection to display
+ * @param delim the delimiter to use (probably a ",")
+ * @return the delimited String
+ */
+ public static String collectionToDelimitedString(Collection<?> coll, String delim) {
+ return collectionToDelimitedString(coll, delim, "", "");
+ }
+
+ /**
+ * Convenience method to return a Collection as a CSV String.
+ * E.g. useful for {@code toString()} implementations.
+ * @param coll the Collection to display
+ * @return the delimited String
+ */
+ public static String collectionToCommaDelimitedString(Collection<?> coll) {
+ return collectionToDelimitedString(coll, ",");
+ }
+
+ /**
+ * Convenience method to return a String array as a delimited (e.g. CSV)
+ * String. E.g. useful for {@code toString()} implementations.
+ * @param arr the array to display
+ * @param delim the delimiter to use (probably a ",")
+ * @return the delimited String
+ */
+ public static String arrayToDelimitedString(Object[] arr, String delim) {
+ if (ObjectUtils.isEmpty(arr)) {
+ return "";
+ }
+ if (arr.length == 1) {
+ return ObjectUtils.nullSafeToString(arr[0]);
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < arr.length; i++) {
+ if (i > 0) {
+ sb.append(delim);
+ }
+ sb.append(arr[i]);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Convenience method to return a String array as a CSV String.
+ * E.g. useful for {@code toString()} implementations.
+ * @param arr the array to display
+ * @return the delimited String
+ */
+ public static String arrayToCommaDelimitedString(Object[] arr) {
+ return arrayToDelimitedString(arr, ",");
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/StringValueResolver.java b/spring-core/src/main/java/org/springframework/util/StringValueResolver.java
new file mode 100644
index 00000000..acc77c1e
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/StringValueResolver.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2002-2007 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.util;
+
+/**
+ * Simple strategy interface for resolving a String value.
+ * Used by {@link org.springframework.beans.factory.config.ConfigurableBeanFactory}.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5
+ * @see org.springframework.beans.factory.config.ConfigurableBeanFactory#resolveAliases
+ * @see org.springframework.beans.factory.config.BeanDefinitionVisitor#BeanDefinitionVisitor(StringValueResolver)
+ * @see org.springframework.beans.factory.config.PropertyPlaceholderConfigurer
+ */
+public interface StringValueResolver {
+
+ /**
+ * Resolve the given String value, for example parsing placeholders.
+ * @param strVal the original String value
+ * @return the resolved String value
+ */
+ String resolveStringValue(String strVal);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java b/spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java
new file mode 100644
index 00000000..6c3acd7e
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2002-2013 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.util;
+
+/**
+ * Helper class for resolving placeholders in texts. Usually applied to file paths.
+ *
+ * <p>A text may contain {@code ${...}} placeholders, to be resolved as system properties:
+ * e.g. {@code ${user.dir}}. Default values can be supplied using the ":" separator
+ * between key and value.
+ *
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @author Dave Syer
+ * @since 1.2.5
+ * @see #PLACEHOLDER_PREFIX
+ * @see #PLACEHOLDER_SUFFIX
+ * @see System#getProperty(String)
+ */
+public abstract class SystemPropertyUtils {
+
+ /** Prefix for system property placeholders: "${" */
+ public static final String PLACEHOLDER_PREFIX = "${";
+
+ /** Suffix for system property placeholders: "}" */
+ public static final String PLACEHOLDER_SUFFIX = "}";
+
+ /** Value separator for system property placeholders: ":" */
+ public static final String VALUE_SEPARATOR = ":";
+
+
+ private static final PropertyPlaceholderHelper strictHelper =
+ new PropertyPlaceholderHelper(PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, VALUE_SEPARATOR, false);
+
+ private static final PropertyPlaceholderHelper nonStrictHelper =
+ new PropertyPlaceholderHelper(PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, VALUE_SEPARATOR, true);
+
+
+ /**
+ * Resolve {@code ${...}} placeholders in the given text, replacing them with
+ * corresponding system property values.
+ * @param text the String to resolve
+ * @return the resolved String
+ * @see #PLACEHOLDER_PREFIX
+ * @see #PLACEHOLDER_SUFFIX
+ * @throws IllegalArgumentException if there is an unresolvable placeholder
+ */
+ public static String resolvePlaceholders(String text) {
+ return resolvePlaceholders(text, false);
+ }
+
+ /**
+ * Resolve {@code ${...}} placeholders in the given text, replacing them with
+ * corresponding system property values. Unresolvable placeholders with no default
+ * value are ignored and passed through unchanged if the flag is set to {@code true}.
+ * @param text the String to resolve
+ * @param ignoreUnresolvablePlaceholders whether unresolved placeholders are to be ignored
+ * @return the resolved String
+ * @see #PLACEHOLDER_PREFIX
+ * @see #PLACEHOLDER_SUFFIX
+ * @throws IllegalArgumentException if there is an unresolvable placeholder
+ * and the "ignoreUnresolvablePlaceholders" flag is {@code false}
+ */
+ public static String resolvePlaceholders(String text, boolean ignoreUnresolvablePlaceholders) {
+ PropertyPlaceholderHelper helper = (ignoreUnresolvablePlaceholders ? nonStrictHelper : strictHelper);
+ return helper.replacePlaceholders(text, new SystemPropertyPlaceholderResolver(text));
+ }
+
+
+ /**
+ * PlaceholderResolver implementation that resolves against system properties
+ * and system environment variables.
+ */
+ private static class SystemPropertyPlaceholderResolver implements PropertyPlaceholderHelper.PlaceholderResolver {
+
+ private final String text;
+
+ public SystemPropertyPlaceholderResolver(String text) {
+ this.text = text;
+ }
+
+ public String resolvePlaceholder(String placeholderName) {
+ try {
+ String propVal = System.getProperty(placeholderName);
+ if (propVal == null) {
+ // Fall back to searching the system environment.
+ propVal = System.getenv(placeholderName);
+ }
+ return propVal;
+ }
+ catch (Throwable ex) {
+ System.err.println("Could not resolve placeholder '" + placeholderName + "' in [" +
+ this.text + "] as system property: " + ex);
+ return null;
+ }
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/TypeUtils.java b/spring-core/src/main/java/org/springframework/util/TypeUtils.java
new file mode 100644
index 00000000..3733f7cf
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/TypeUtils.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2002-2012 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.util;
+
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
+
+import org.springframework.util.ClassUtils;
+
+/**
+ * Utility to work with Java 5 generic type parameters.
+ * Mainly for internal use within the framework.
+ *
+ * @author Ramnivas Laddad
+ * @author Juergen Hoeller
+ * @author Chris Beams
+ * @since 2.0.7
+ */
+public abstract class TypeUtils {
+
+ /**
+ * Check if the right-hand side type may be assigned to the left-hand side
+ * type following the Java generics rules.
+ * @param lhsType the target type
+ * @param rhsType the value type that should be assigned to the target type
+ * @return true if rhs is assignable to lhs
+ */
+ public static boolean isAssignable(Type lhsType, Type rhsType) {
+ Assert.notNull(lhsType, "Left-hand side type must not be null");
+ Assert.notNull(rhsType, "Right-hand side type must not be null");
+
+ // all types are assignable to themselves and to class Object
+ if (lhsType.equals(rhsType) || lhsType.equals(Object.class)) {
+ return true;
+ }
+
+ if (lhsType instanceof Class<?>) {
+ Class<?> lhsClass = (Class<?>) lhsType;
+
+ // just comparing two classes
+ if (rhsType instanceof Class<?>) {
+ return ClassUtils.isAssignable(lhsClass, (Class<?>) rhsType);
+ }
+
+ if (rhsType instanceof ParameterizedType) {
+ Type rhsRaw = ((ParameterizedType) rhsType).getRawType();
+
+ // a parameterized type is always assignable to its raw class type
+ if (rhsRaw instanceof Class<?>) {
+ return ClassUtils.isAssignable(lhsClass, (Class<?>) rhsRaw);
+ }
+ }
+ else if (lhsClass.isArray() && rhsType instanceof GenericArrayType) {
+ Type rhsComponent = ((GenericArrayType) rhsType).getGenericComponentType();
+
+ return isAssignable(lhsClass.getComponentType(), rhsComponent);
+ }
+ }
+
+ // parameterized types are only assignable to other parameterized types and class types
+ if (lhsType instanceof ParameterizedType) {
+ if (rhsType instanceof Class<?>) {
+ Type lhsRaw = ((ParameterizedType) lhsType).getRawType();
+
+ if (lhsRaw instanceof Class<?>) {
+ return ClassUtils.isAssignable((Class<?>) lhsRaw, (Class<?>) rhsType);
+ }
+ }
+ else if (rhsType instanceof ParameterizedType) {
+ return isAssignable((ParameterizedType) lhsType, (ParameterizedType) rhsType);
+ }
+ }
+
+ if (lhsType instanceof GenericArrayType) {
+ Type lhsComponent = ((GenericArrayType) lhsType).getGenericComponentType();
+
+ if (rhsType instanceof Class<?>) {
+ Class<?> rhsClass = (Class<?>) rhsType;
+
+ if (rhsClass.isArray()) {
+ return isAssignable(lhsComponent, rhsClass.getComponentType());
+ }
+ }
+ else if (rhsType instanceof GenericArrayType) {
+ Type rhsComponent = ((GenericArrayType) rhsType).getGenericComponentType();
+
+ return isAssignable(lhsComponent, rhsComponent);
+ }
+ }
+
+ if (lhsType instanceof WildcardType) {
+ return isAssignable((WildcardType) lhsType, rhsType);
+ }
+
+ return false;
+ }
+
+ private static boolean isAssignable(ParameterizedType lhsType, ParameterizedType rhsType) {
+ if (lhsType.equals(rhsType)) {
+ return true;
+ }
+
+ Type[] lhsTypeArguments = lhsType.getActualTypeArguments();
+ Type[] rhsTypeArguments = rhsType.getActualTypeArguments();
+
+ if (lhsTypeArguments.length != rhsTypeArguments.length) {
+ return false;
+ }
+
+ for (int size = lhsTypeArguments.length, i = 0; i < size; ++i) {
+ Type lhsArg = lhsTypeArguments[i];
+ Type rhsArg = rhsTypeArguments[i];
+
+ if (!lhsArg.equals(rhsArg) &&
+ !(lhsArg instanceof WildcardType && isAssignable((WildcardType) lhsArg, rhsArg))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static boolean isAssignable(WildcardType lhsType, Type rhsType) {
+ Type[] lUpperBounds = lhsType.getUpperBounds();
+
+ // supply the implicit upper bound if none are specified
+ if (lUpperBounds.length == 0) {
+ lUpperBounds = new Type[] { Object.class };
+ }
+
+ Type[] lLowerBounds = lhsType.getLowerBounds();
+
+ // supply the implicit lower bound if none are specified
+ if (lLowerBounds.length == 0) {
+ lLowerBounds = new Type[] { null };
+ }
+
+ if (rhsType instanceof WildcardType) {
+ // both the upper and lower bounds of the right-hand side must be
+ // completely enclosed in the upper and lower bounds of the left-
+ // hand side.
+ WildcardType rhsWcType = (WildcardType) rhsType;
+ Type[] rUpperBounds = rhsWcType.getUpperBounds();
+
+ if (rUpperBounds.length == 0) {
+ rUpperBounds = new Type[] { Object.class };
+ }
+
+ Type[] rLowerBounds = rhsWcType.getLowerBounds();
+
+ if (rLowerBounds.length == 0) {
+ rLowerBounds = new Type[] { null };
+ }
+
+ for (Type lBound : lUpperBounds) {
+ for (Type rBound : rUpperBounds) {
+ if (!isAssignableBound(lBound, rBound)) {
+ return false;
+ }
+ }
+
+ for (Type rBound : rLowerBounds) {
+ if (!isAssignableBound(lBound, rBound)) {
+ return false;
+ }
+ }
+ }
+
+ for (Type lBound : lLowerBounds) {
+ for (Type rBound : rUpperBounds) {
+ if (!isAssignableBound(rBound, lBound)) {
+ return false;
+ }
+ }
+
+ for (Type rBound : rLowerBounds) {
+ if (!isAssignableBound(rBound, lBound)) {
+ return false;
+ }
+ }
+ }
+ }
+ else {
+ for (Type lBound : lUpperBounds) {
+ if (!isAssignableBound(lBound, rhsType)) {
+ return false;
+ }
+ }
+
+ for (Type lBound : lLowerBounds) {
+ if (!isAssignableBound(rhsType, lBound)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public static boolean isAssignableBound(Type lhsType, Type rhsType) {
+ if (rhsType == null) {
+ return true;
+ }
+
+ if (lhsType == null) {
+ return false;
+ }
+ return isAssignable(lhsType, rhsType);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/WeakReferenceMonitor.java b/spring-core/src/main/java/org/springframework/util/WeakReferenceMonitor.java
new file mode 100644
index 00000000..ad2e01c5
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/WeakReferenceMonitor.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2002-2010 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.util;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Track references to arbitrary objects using proxy and weak references. To
+ * monitor a handle, one should call {@link #monitor(Object, ReleaseListener)},
+ * with the given handle object usually being a holder that uses the target
+ * object underneath, and the release listener performing cleanup of the
+ * target object once the handle is not strongly referenced anymore.
+ *
+ * <p>When a given handle becomes weakly reachable, the specified listener
+ * will be called by a background thread. This thread will only be started
+ * lazily and will be stopped once no handles are registered for monitoring
+ * anymore, to be restarted if further handles are added.
+ *
+ * <p>Thanks to Tomasz Wysocki for the suggestion and the original
+ * implementation of this class!
+ *
+ * @author Colin Sampaleanu
+ * @author Juergen Hoeller
+ * @since 1.2
+ * @see #monitor
+ */
+public class WeakReferenceMonitor {
+
+ private static final Log logger = LogFactory.getLog(WeakReferenceMonitor.class);
+
+ // Queue receiving reachability events
+ private static final ReferenceQueue<Object> handleQueue = new ReferenceQueue<Object>();
+
+ // All tracked entries (WeakReference => ReleaseListener)
+ private static final Map<Reference, ReleaseListener> trackedEntries = new HashMap<Reference, ReleaseListener>();
+
+ // Thread polling handleQueue, lazy initialized
+ private static Thread monitoringThread = null;
+
+
+ /**
+ * Start to monitor given handle object for becoming weakly reachable.
+ * When the handle isn't used anymore, the given listener will be called.
+ * @param handle the object that will be monitored
+ * @param listener the listener that will be called upon release of the handle
+ */
+ public static void monitor(Object handle, ReleaseListener listener) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Monitoring handle [" + handle + "] with release listener [" + listener + "]");
+ }
+
+ // Make weak reference to this handle, so we can say when
+ // handle is not used any more by polling on handleQueue.
+ WeakReference<Object> weakRef = new WeakReference<Object>(handle, handleQueue);
+
+ // Add monitored entry to internal map of all monitored entries.
+ addEntry(weakRef, listener);
+ }
+
+ /**
+ * Add entry to internal map of tracked entries.
+ * Internal monitoring thread is started if not already running.
+ * @param ref reference to tracked handle
+ * @param entry the associated entry
+ */
+ private static void addEntry(Reference ref, ReleaseListener entry) {
+ synchronized (WeakReferenceMonitor.class) {
+ // Add entry, the key is given reference.
+ trackedEntries.put(ref, entry);
+
+ // Start monitoring thread lazily.
+ if (monitoringThread == null) {
+ monitoringThread = new Thread(new MonitoringProcess(), WeakReferenceMonitor.class.getName());
+ monitoringThread.setDaemon(true);
+ monitoringThread.start();
+ }
+ }
+ }
+
+ /**
+ * Remove entry from internal map of tracked entries.
+ * @param reference the reference that should be removed
+ * @return entry object associated with given reference
+ */
+ private static ReleaseListener removeEntry(Reference reference) {
+ synchronized (WeakReferenceMonitor.class) {
+ return trackedEntries.remove(reference);
+ }
+ }
+
+ /**
+ * Check whether to keep the monitoring thread alive,
+ * i.e. whether there are still entries being tracked.
+ */
+ private static boolean keepMonitoringThreadAlive() {
+ synchronized (WeakReferenceMonitor.class) {
+ if (!trackedEntries.isEmpty()) {
+ return true;
+ }
+ else {
+ logger.debug("No entries left to track - stopping reference monitor thread");
+ monitoringThread = null;
+ return false;
+ }
+ }
+ }
+
+
+ /**
+ * Thread implementation that performs the actual monitoring.
+ */
+ private static class MonitoringProcess implements Runnable {
+
+ public void run() {
+ logger.debug("Starting reference monitor thread");
+ // Check if there are any tracked entries left.
+ while (keepMonitoringThreadAlive()) {
+ try {
+ Reference reference = handleQueue.remove();
+ // Stop tracking this reference.
+ ReleaseListener entry = removeEntry(reference);
+ if (entry != null) {
+ // Invoke listener callback.
+ try {
+ entry.released();
+ }
+ catch (Throwable ex) {
+ logger.warn("Reference release listener threw exception", ex);
+ }
+ }
+ }
+ catch (InterruptedException ex) {
+ synchronized (WeakReferenceMonitor.class) {
+ monitoringThread = null;
+ }
+ logger.debug("Reference monitor thread interrupted", ex);
+ break;
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Listener that is notified when the handle is being released.
+ * To be implemented by users of this reference monitor.
+ */
+ public static interface ReleaseListener {
+
+ /**
+ * This callback method is invoked once the associated handle has been released,
+ * i.e. once there are no monitored strong references to the handle anymore.
+ */
+ void released();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/comparator/BooleanComparator.java b/spring-core/src/main/java/org/springframework/util/comparator/BooleanComparator.java
new file mode 100644
index 00000000..6fd0c229
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/comparator/BooleanComparator.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2002-2012 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.util.comparator;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+/**
+ * A Comparator for Boolean objects that can sort either true or false first.
+ *
+ * @author Keith Donald
+ * @since 1.2.2
+ */
+@SuppressWarnings("serial")
+public final class BooleanComparator implements Comparator<Boolean>, Serializable {
+
+ /**
+ * A shared default instance of this comparator, treating true lower
+ * than false.
+ */
+ public static final BooleanComparator TRUE_LOW = new BooleanComparator(true);
+
+ /**
+ * A shared default instance of this comparator, treating true higher
+ * than false.
+ */
+ public static final BooleanComparator TRUE_HIGH = new BooleanComparator(false);
+
+
+ private final boolean trueLow;
+
+
+ /**
+ * Create a BooleanComparator that sorts boolean values based on
+ * the provided flag.
+ * <p>Alternatively, you can use the default shared instances:
+ * {@code BooleanComparator.TRUE_LOW} and
+ * {@code BooleanComparator.TRUE_HIGH}.
+ * @param trueLow whether to treat true as lower or higher than false
+ * @see #TRUE_LOW
+ * @see #TRUE_HIGH
+ */
+ public BooleanComparator(boolean trueLow) {
+ this.trueLow = trueLow;
+ }
+
+
+ public int compare(Boolean v1, Boolean v2) {
+ return (v1 ^ v2) ? ((v1 ^ this.trueLow) ? 1 : -1) : 0;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof BooleanComparator)) {
+ return false;
+ }
+ return (this.trueLow == ((BooleanComparator) obj).trueLow);
+ }
+
+ @Override
+ public int hashCode() {
+ return (this.trueLow ? -1 : 1) * getClass().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "BooleanComparator: " + (this.trueLow ? "true low" : "true high");
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/comparator/ComparableComparator.java b/spring-core/src/main/java/org/springframework/util/comparator/ComparableComparator.java
new file mode 100644
index 00000000..40e4e7af
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/comparator/ComparableComparator.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2012 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.util.comparator;
+
+import java.util.Comparator;
+
+/**
+ * Comparator that adapts Comparables to the Comparator interface.
+ * Mainly for internal use in other Comparators, when supposed
+ * to work on Comparables.
+ *
+ * @author Keith Donald
+ * @since 1.2.2
+ * @see Comparable
+ */
+public class ComparableComparator<T extends Comparable<T>> implements Comparator<T> {
+
+ @SuppressWarnings("rawtypes")
+ public static final ComparableComparator INSTANCE = new ComparableComparator();
+
+ public int compare(T o1, T o2) {
+ return o1.compareTo(o2);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/comparator/CompoundComparator.java b/spring-core/src/main/java/org/springframework/util/comparator/CompoundComparator.java
new file mode 100644
index 00000000..ba56bd54
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/comparator/CompoundComparator.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2002-2012 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.util.comparator;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+import org.springframework.util.Assert;
+
+/**
+ * A comparator that chains a sequence of one or more more Comparators.
+ *
+ * <p>A compound comparator calls each Comparator in sequence until a single
+ * Comparator returns a non-zero result, or the comparators are exhausted and
+ * zero is returned.
+ *
+ * <p>This facilitates in-memory sorting similar to multi-column sorting in SQL.
+ * The order of any single Comparator in the list can also be reversed.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ */
+@SuppressWarnings("serial")
+public class CompoundComparator<T> implements Comparator<T>, Serializable {
+
+ private final List<InvertibleComparator<T>> comparators;
+
+
+ /**
+ * Construct a CompoundComparator with initially no Comparators. Clients
+ * must add at least one Comparator before calling the compare method or an
+ * IllegalStateException is thrown.
+ */
+ public CompoundComparator() {
+ this.comparators = new ArrayList<InvertibleComparator<T>>();
+ }
+
+ /**
+ * Construct a CompoundComparator from the Comparators in the provided array.
+ * <p>All Comparators will default to ascending sort order,
+ * unless they are InvertibleComparators.
+ * @param comparators the comparators to build into a compound comparator
+ * @see InvertibleComparator
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public CompoundComparator(Comparator... comparators) {
+ Assert.notNull(comparators, "Comparators must not be null");
+ this.comparators = new ArrayList<InvertibleComparator<T>>(comparators.length);
+ for (Comparator comparator : comparators) {
+ this.addComparator(comparator);
+ }
+ }
+
+
+ /**
+ * Add a Comparator to the end of the chain.
+ * <p>The Comparator will default to ascending sort order,
+ * unless it is a InvertibleComparator.
+ * @param comparator the Comparator to add to the end of the chain
+ * @see InvertibleComparator
+ */
+ public void addComparator(Comparator<T> comparator) {
+ if (comparator instanceof InvertibleComparator) {
+ this.comparators.add((InvertibleComparator<T>) comparator);
+ }
+ else {
+ this.comparators.add(new InvertibleComparator<T>(comparator));
+ }
+ }
+
+ /**
+ * Add a Comparator to the end of the chain using the provided sort order.
+ * @param comparator the Comparator to add to the end of the chain
+ * @param ascending the sort order: ascending (true) or descending (false)
+ */
+ public void addComparator(Comparator<T> comparator, boolean ascending) {
+ this.comparators.add(new InvertibleComparator<T>(comparator, ascending));
+ }
+
+ /**
+ * Replace the Comparator at the given index.
+ * <p>The Comparator will default to ascending sort order,
+ * unless it is a InvertibleComparator.
+ * @param index the index of the Comparator to replace
+ * @param comparator the Comparator to place at the given index
+ * @see InvertibleComparator
+ */
+ public void setComparator(int index, Comparator<T> comparator) {
+ if (comparator instanceof InvertibleComparator) {
+ this.comparators.set(index, (InvertibleComparator<T>) comparator);
+ }
+ else {
+ this.comparators.set(index, new InvertibleComparator<T>(comparator));
+ }
+ }
+
+ /**
+ * Replace the Comparator at the given index using the given sort order.
+ * @param index the index of the Comparator to replace
+ * @param comparator the Comparator to place at the given index
+ * @param ascending the sort order: ascending (true) or descending (false)
+ */
+ public void setComparator(int index, Comparator<T> comparator, boolean ascending) {
+ this.comparators.set(index, new InvertibleComparator<T>(comparator, ascending));
+ }
+
+ /**
+ * Invert the sort order of each sort definition contained by this compound
+ * comparator.
+ */
+ public void invertOrder() {
+ for (InvertibleComparator<T> comparator : this.comparators) {
+ comparator.invertOrder();
+ }
+ }
+
+ /**
+ * Invert the sort order of the sort definition at the specified index.
+ * @param index the index of the comparator to invert
+ */
+ public void invertOrder(int index) {
+ this.comparators.get(index).invertOrder();
+ }
+
+ /**
+ * Change the sort order at the given index to ascending.
+ * @param index the index of the comparator to change
+ */
+ public void setAscendingOrder(int index) {
+ this.comparators.get(index).setAscending(true);
+ }
+
+ /**
+ * Change the sort order at the given index to descending sort.
+ * @param index the index of the comparator to change
+ */
+ public void setDescendingOrder(int index) {
+ this.comparators.get(index).setAscending(false);
+ }
+
+ /**
+ * Returns the number of aggregated comparators.
+ */
+ public int getComparatorCount() {
+ return this.comparators.size();
+ }
+
+ public int compare(T o1, T o2) {
+ Assert.state(this.comparators.size() > 0,
+ "No sort definitions have been added to this CompoundComparator to compare");
+ for (InvertibleComparator<T> comparator : this.comparators) {
+ int result = comparator.compare(o1, o2);
+ if (result != 0) {
+ return result;
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof CompoundComparator)) {
+ return false;
+ }
+ CompoundComparator<T> other = (CompoundComparator<T>) obj;
+ return this.comparators.equals(other.comparators);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.comparators.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "CompoundComparator: " + this.comparators;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/comparator/InstanceComparator.java b/spring-core/src/main/java/org/springframework/util/comparator/InstanceComparator.java
new file mode 100644
index 00000000..f29310e2
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/comparator/InstanceComparator.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2002-2014 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.util.comparator;
+
+import java.util.Comparator;
+
+import org.springframework.util.Assert;
+
+/**
+ * Compares objects based on an arbitrary class order. Allows objects to be sorted based
+ * on the types of class that they inherit, for example: this comparator can be used to
+ * sort a list {@code Number}s such that {@code Long}s occur before {@code Integer}s.
+ *
+ * <p>Only the specified {@code instanceOrder} classes are considered during comparison.
+ * If two objects are both instances of the ordered type this comparator will return a
+ * {@code 0}. Consider combining with a {@link CompoundComparator} if additional sorting
+ * is required.
+ *
+ * @author Phillip Webb
+ * @since 3.2
+ * @param <T> the type of objects being compared
+ * @see CompoundComparator
+ */
+public class InstanceComparator<T> implements Comparator<T> {
+
+ private final Class<?>[] instanceOrder;
+
+
+ /**
+ * Create a new {@link InstanceComparator} instance.
+ * @param instanceOrder the ordered list of classes that should be used when comparing
+ * objects. Classes earlier in the list will be be given a higher priority.
+ */
+ public InstanceComparator(Class<?>... instanceOrder) {
+ Assert.notNull(instanceOrder, "'instanceOrder' must not be null");
+ this.instanceOrder = instanceOrder;
+ }
+
+
+ public int compare(T o1, T o2) {
+ int i1 = getOrder(o1);
+ int i2 = getOrder(o2);
+ return (i1 < i2 ? -1 : (i1 == i2 ? 0 : 1));
+ }
+
+ private int getOrder(T object) {
+ if (object != null) {
+ for (int i = 0; i < this.instanceOrder.length; i++) {
+ if (this.instanceOrder[i].isInstance(object)) {
+ return i;
+ }
+ }
+ }
+ return this.instanceOrder.length;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/comparator/InvertibleComparator.java b/spring-core/src/main/java/org/springframework/util/comparator/InvertibleComparator.java
new file mode 100644
index 00000000..f78e56ff
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/comparator/InvertibleComparator.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2002-2012 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.util.comparator;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+import org.springframework.util.Assert;
+
+/**
+ * A decorator for a comparator, with an "ascending" flag denoting
+ * whether comparison results should be treated in forward (standard
+ * ascending) order or flipped for reverse (descending) order.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ */
+@SuppressWarnings("serial")
+public class InvertibleComparator<T> implements Comparator<T>, Serializable {
+
+ private final Comparator<T> comparator;
+
+ private boolean ascending = true;
+
+
+ /**
+ * Create an InvertibleComparator that sorts ascending by default.
+ * For the actual comparison, the specified Comparator will be used.
+ * @param comparator the comparator to decorate
+ */
+ public InvertibleComparator(Comparator<T> comparator) {
+ Assert.notNull(comparator, "Comparator must not be null");
+ this.comparator = comparator;
+ }
+
+ /**
+ * Create an InvertibleComparator that sorts based on the provided order.
+ * For the actual comparison, the specified Comparator will be used.
+ * @param comparator the comparator to decorate
+ * @param ascending the sort order: ascending (true) or descending (false)
+ */
+ public InvertibleComparator(Comparator<T> comparator, boolean ascending) {
+ Assert.notNull(comparator, "Comparator must not be null");
+ this.comparator = comparator;
+ setAscending(ascending);
+ }
+
+
+ /**
+ * Specify the sort order: ascending (true) or descending (false).
+ */
+ public void setAscending(boolean ascending) {
+ this.ascending = ascending;
+ }
+
+ /**
+ * Return the sort order: ascending (true) or descending (false).
+ */
+ public boolean isAscending() {
+ return this.ascending;
+ }
+
+ /**
+ * Invert the sort order: ascending -> descending or
+ * descending -> ascending.
+ */
+ public void invertOrder() {
+ this.ascending = !this.ascending;
+ }
+
+
+ public int compare(T o1, T o2) {
+ int result = this.comparator.compare(o1, o2);
+ if (result != 0) {
+ // Invert the order if it is a reverse sort.
+ if (!this.ascending) {
+ if (Integer.MIN_VALUE == result) {
+ result = Integer.MAX_VALUE;
+ }
+ else {
+ result *= -1;
+ }
+ }
+ return result;
+ }
+ return 0;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof InvertibleComparator)) {
+ return false;
+ }
+ InvertibleComparator<T> other = (InvertibleComparator<T>) obj;
+ return (this.comparator.equals(other.comparator) && this.ascending == other.ascending);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.comparator.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "InvertibleComparator: [" + this.comparator + "]; ascending=" + this.ascending;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java b/spring-core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java
new file mode 100644
index 00000000..1171bdc1
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2002-2012 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.util.comparator;
+
+import java.util.Comparator;
+
+import org.springframework.util.Assert;
+
+/**
+ * A Comparator that will safely compare nulls to be lower or higher than
+ * other objects. Can decorate a given Comparator or work on Comparables.
+ *
+ * @author Keith Donald
+ * @author Juergen Hoeller
+ * @since 1.2.2
+ * @see Comparable
+ */
+public class NullSafeComparator<T> implements Comparator<T> {
+
+ /**
+ * A shared default instance of this comparator, treating nulls lower
+ * than non-null objects.
+ */
+ @SuppressWarnings("rawtypes")
+ public static final NullSafeComparator NULLS_LOW = new NullSafeComparator<Object>(true);
+
+ /**
+ * A shared default instance of this comparator, treating nulls higher
+ * than non-null objects.
+ */
+ @SuppressWarnings("rawtypes")
+ public static final NullSafeComparator NULLS_HIGH = new NullSafeComparator<Object>(false);
+
+ private final Comparator<T> nonNullComparator;
+
+ private final boolean nullsLow;
+
+
+ /**
+ * Create a NullSafeComparator that sorts {@code null} based on
+ * the provided flag, working on Comparables.
+ * <p>When comparing two non-null objects, their Comparable implementation
+ * will be used: this means that non-null elements (that this Comparator
+ * will be applied to) need to implement Comparable.
+ * <p>As a convenience, you can use the default shared instances:
+ * {@code NullSafeComparator.NULLS_LOW} and
+ * {@code NullSafeComparator.NULLS_HIGH}.
+ * @param nullsLow whether to treat nulls lower or higher than non-null objects
+ * @see Comparable
+ * @see #NULLS_LOW
+ * @see #NULLS_HIGH
+ */
+ @SuppressWarnings({ "unchecked"})
+ private NullSafeComparator(boolean nullsLow) {
+ this.nonNullComparator = new ComparableComparator();
+ this.nullsLow = nullsLow;
+ }
+
+ /**
+ * Create a NullSafeComparator that sorts {@code null} based on the
+ * provided flag, decorating the given Comparator.
+ * <p>When comparing two non-null objects, the specified Comparator will be used.
+ * The given underlying Comparator must be able to handle the elements that this
+ * Comparator will be applied to.
+ * @param comparator the comparator to use when comparing two non-null objects
+ * @param nullsLow whether to treat nulls lower or higher than non-null objects
+ */
+ public NullSafeComparator(Comparator<T> comparator, boolean nullsLow) {
+ Assert.notNull(comparator, "The non-null comparator is required");
+ this.nonNullComparator = comparator;
+ this.nullsLow = nullsLow;
+ }
+
+
+ public int compare(T o1, T o2) {
+ if (o1 == o2) {
+ return 0;
+ }
+ if (o1 == null) {
+ return (this.nullsLow ? -1 : 1);
+ }
+ if (o2 == null) {
+ return (this.nullsLow ? 1 : -1);
+ }
+ return this.nonNullComparator.compare(o1, o2);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof NullSafeComparator)) {
+ return false;
+ }
+ NullSafeComparator<T> other = (NullSafeComparator<T>) obj;
+ return (this.nonNullComparator.equals(other.nonNullComparator) && this.nullsLow == other.nullsLow);
+ }
+
+ @Override
+ public int hashCode() {
+ return (this.nullsLow ? -1 : 1) * this.nonNullComparator.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "NullSafeComparator: non-null comparator [" + this.nonNullComparator + "]; " +
+ (this.nullsLow ? "nulls low" : "nulls high");
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/comparator/package-info.java b/spring-core/src/main/java/org/springframework/util/comparator/package-info.java
new file mode 100644
index 00000000..f2213670
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/comparator/package-info.java
@@ -0,0 +1,9 @@
+
+/**
+ *
+ * Useful generic {@code java.util.Comparator} implementations,
+ * such as an invertible comparator and a compound comparator.
+ *
+ */
+package org.springframework.util.comparator;
+
diff --git a/spring-core/src/main/java/org/springframework/util/package-info.java b/spring-core/src/main/java/org/springframework/util/package-info.java
new file mode 100644
index 00000000..59adb5f8
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/package-info.java
@@ -0,0 +1,9 @@
+
+/**
+ *
+ * Miscellaneous utility classes, such as String manipulation utilities,
+ * a Log4J configurer, and a state holder for paged lists of objects.
+ *
+ */
+package org.springframework.util;
+
diff --git a/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxContentHandler.java b/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxContentHandler.java
new file mode 100644
index 00000000..46f4d98e
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxContentHandler.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2002-2012 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.util.xml;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLStreamException;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+/**
+ * Abstract base class for SAX {@code ContentHandler} implementations that use StAX as a basis. All methods
+ * delegate to internal template methods, capable of throwing a {@code XMLStreamException}. Additionally, an
+ * namespace context is used to keep track of declared namespaces.
+ *
+ * @author Arjen Poutsma
+ * @since 3.0
+ */
+abstract class AbstractStaxContentHandler implements ContentHandler {
+
+ private SimpleNamespaceContext namespaceContext = new SimpleNamespaceContext();
+
+ private boolean namespaceContextChanged = false;
+
+ public final void startDocument() throws SAXException {
+ namespaceContext.clear();
+ namespaceContextChanged = false;
+ try {
+ startDocumentInternal();
+ }
+ catch (XMLStreamException ex) {
+ throw new SAXException("Could not handle startDocument: " + ex.getMessage(), ex);
+ }
+ }
+
+ protected abstract void startDocumentInternal() throws XMLStreamException;
+
+ public final void endDocument() throws SAXException {
+ namespaceContext.clear();
+ namespaceContextChanged = false;
+ try {
+ endDocumentInternal();
+ }
+ catch (XMLStreamException ex) {
+ throw new SAXException("Could not handle startDocument: " + ex.getMessage(), ex);
+ }
+ }
+
+ protected abstract void endDocumentInternal() throws XMLStreamException;
+
+ /**
+ * Binds the given prefix to the given namespaces.
+ *
+ * @see SimpleNamespaceContext#bindNamespaceUri(String,String)
+ */
+ public final void startPrefixMapping(String prefix, String uri) {
+ namespaceContext.bindNamespaceUri(prefix, uri);
+ namespaceContextChanged = true;
+ }
+
+ /**
+ * Removes the binding for the given prefix.
+ *
+ * @see SimpleNamespaceContext#removeBinding(String)
+ */
+ public final void endPrefixMapping(String prefix) {
+ namespaceContext.removeBinding(prefix);
+ namespaceContextChanged = true;
+ }
+
+ public final void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
+ try {
+ startElementInternal(toQName(uri, qName), atts, namespaceContextChanged ? namespaceContext : null);
+ namespaceContextChanged = false;
+ }
+ catch (XMLStreamException ex) {
+ throw new SAXException("Could not handle startElement: " + ex.getMessage(), ex);
+ }
+ }
+
+ protected abstract void startElementInternal(QName name, Attributes atts, SimpleNamespaceContext namespaceContext)
+ throws XMLStreamException;
+
+ public final void endElement(String uri, String localName, String qName) throws SAXException {
+ try {
+ endElementInternal(toQName(uri, qName), namespaceContextChanged ? namespaceContext : null);
+ namespaceContextChanged = false;
+ }
+ catch (XMLStreamException ex) {
+ throw new SAXException("Could not handle endElement: " + ex.getMessage(), ex);
+ }
+ }
+
+ protected abstract void endElementInternal(QName name, SimpleNamespaceContext namespaceContext)
+ throws XMLStreamException;
+
+ public final void characters(char ch[], int start, int length) throws SAXException {
+ try {
+ charactersInternal(ch, start, length);
+ }
+ catch (XMLStreamException ex) {
+ throw new SAXException("Could not handle characters: " + ex.getMessage(), ex);
+ }
+ }
+
+ protected abstract void charactersInternal(char[] ch, int start, int length) throws XMLStreamException;
+
+ public final void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
+ try {
+ ignorableWhitespaceInternal(ch, start, length);
+ }
+ catch (XMLStreamException ex) {
+ throw new SAXException("Could not handle ignorableWhitespace:" + ex.getMessage(), ex);
+ }
+ }
+
+ protected abstract void ignorableWhitespaceInternal(char[] ch, int start, int length) throws XMLStreamException;
+
+ public final void processingInstruction(String target, String data) throws SAXException {
+ try {
+ processingInstructionInternal(target, data);
+ }
+ catch (XMLStreamException ex) {
+ throw new SAXException("Could not handle processingInstruction: " + ex.getMessage(), ex);
+ }
+ }
+
+ protected abstract void processingInstructionInternal(String target, String data) throws XMLStreamException;
+
+ public final void skippedEntity(String name) throws SAXException {
+ try {
+ skippedEntityInternal(name);
+ }
+ catch (XMLStreamException ex) {
+ throw new SAXException("Could not handle skippedEntity: " + ex.getMessage(), ex);
+ }
+ }
+
+ /**
+ * Convert a namespace URI and DOM or SAX qualified name to a {@code QName}. The qualified name can have the form
+ * {@code prefix:localname} or {@code localName}.
+ *
+ * @param namespaceUri the namespace URI
+ * @param qualifiedName the qualified name
+ * @return a QName
+ */
+ protected QName toQName(String namespaceUri, String qualifiedName) {
+ int idx = qualifiedName.indexOf(':');
+ if (idx == -1) {
+ return new QName(namespaceUri, qualifiedName);
+ }
+ else {
+ String prefix = qualifiedName.substring(0, idx);
+ String localPart = qualifiedName.substring(idx + 1);
+ return new QName(namespaceUri, localPart, prefix);
+ }
+ }
+
+ protected abstract void skippedEntityInternal(String name) throws XMLStreamException;
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxXMLReader.java b/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxXMLReader.java
new file mode 100644
index 00000000..feeefd6e
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxXMLReader.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2002-2012 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.util.xml;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.xml.namespace.QName;
+import javax.xml.stream.Location;
+import javax.xml.stream.XMLStreamException;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.SAXParseException;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * Abstract base class for SAX {@code XMLReader} implementations that use StAX as a basis.
+ *
+ * @author Arjen Poutsma
+ * @since 3.0
+ * @see #setContentHandler(org.xml.sax.ContentHandler)
+ * @see #setDTDHandler(org.xml.sax.DTDHandler)
+ * @see #setEntityResolver(org.xml.sax.EntityResolver)
+ * @see #setErrorHandler(org.xml.sax.ErrorHandler)
+ */
+abstract class AbstractStaxXMLReader extends AbstractXMLReader {
+
+ private static final String NAMESPACES_FEATURE_NAME = "http://xml.org/sax/features/namespaces";
+
+ private static final String NAMESPACE_PREFIXES_FEATURE_NAME = "http://xml.org/sax/features/namespace-prefixes";
+
+ private static final String IS_STANDALONE_FEATURE_NAME = "http://xml.org/sax/features/is-standalone";
+
+
+ private boolean namespacesFeature = true;
+
+ private boolean namespacePrefixesFeature = false;
+
+ private Boolean isStandalone;
+
+ private final Map<String, String> namespaces = new LinkedHashMap<String, String>();
+
+ @Override
+ public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
+ if (NAMESPACES_FEATURE_NAME.equals(name)) {
+ return this.namespacesFeature;
+ }
+ else if (NAMESPACE_PREFIXES_FEATURE_NAME.equals(name)) {
+ return this.namespacePrefixesFeature;
+ }
+ else if (IS_STANDALONE_FEATURE_NAME.equals(name)) {
+ if (this.isStandalone != null) {
+ return this.isStandalone;
+ }
+ else {
+ throw new SAXNotSupportedException("startDocument() callback not completed yet");
+ }
+ }
+ else {
+ return super.getFeature(name);
+ }
+ }
+
+ @Override
+ public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
+ if (NAMESPACES_FEATURE_NAME.equals(name)) {
+ this.namespacesFeature = value;
+ }
+ else if (NAMESPACE_PREFIXES_FEATURE_NAME.equals(name)) {
+ this.namespacePrefixesFeature = value;
+ }
+ else {
+ super.setFeature(name, value);
+ }
+ }
+
+ protected void setStandalone(boolean standalone) {
+ this.isStandalone = standalone;
+ }
+
+ /**
+ * Indicates whether the SAX feature {@code http://xml.org/sax/features/namespaces} is turned on.
+ */
+ protected boolean hasNamespacesFeature() {
+ return this.namespacesFeature;
+ }
+
+ /**
+ * Indicates whether the SAX feature {@code http://xml.org/sax/features/namespaces-prefixes} is turned on.
+ */
+ protected boolean hasNamespacePrefixesFeature() {
+ return this.namespacePrefixesFeature;
+ }
+
+ /**
+ * Convert a {@code QName} to a qualified name, as used by DOM and SAX.
+ * The returned string has a format of {@code prefix:localName} if the
+ * prefix is set, or just {@code localName} if not.
+ * @param qName the {@code QName}
+ * @return the qualified name
+ */
+ protected String toQualifiedName(QName qName) {
+ String prefix = qName.getPrefix();
+ if (!StringUtils.hasLength(prefix)) {
+ return qName.getLocalPart();
+ }
+ else {
+ return prefix + ":" + qName.getLocalPart();
+ }
+ }
+
+
+ /**
+ * Parse the StAX XML reader passed at construction-time.
+ * <p><b>NOTE:</b>: The given {@code InputSource} is not read, but ignored.
+ * @param ignored is ignored
+ * @throws SAXException a SAX exception, possibly wrapping a {@code XMLStreamException}
+ */
+ public final void parse(InputSource ignored) throws SAXException {
+ parse();
+ }
+
+ /**
+ * Parse the StAX XML reader passed at construction-time.
+ * <p><b>NOTE:</b>: The given system identifier is not read, but ignored.
+ * @param ignored is ignored
+ * @throws SAXException A SAX exception, possibly wrapping a {@code XMLStreamException}
+ */
+ public final void parse(String ignored) throws SAXException {
+ parse();
+ }
+
+ private void parse() throws SAXException {
+ try {
+ parseInternal();
+ }
+ catch (XMLStreamException ex) {
+ Locator locator = null;
+ if (ex.getLocation() != null) {
+ locator = new StaxLocator(ex.getLocation());
+ }
+ SAXParseException saxException = new SAXParseException(ex.getMessage(), locator, ex);
+ if (getErrorHandler() != null) {
+ getErrorHandler().fatalError(saxException);
+ }
+ else {
+ throw saxException;
+ }
+ }
+ }
+
+ /**
+ * Template-method that parses the StAX reader passed at construction-time.
+ */
+ protected abstract void parseInternal() throws SAXException, XMLStreamException;
+
+ /**
+ * Starts the prefix mapping for the given prefix.
+ * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String)
+ */
+ protected void startPrefixMapping(String prefix, String namespace) throws SAXException {
+ if (getContentHandler() != null) {
+ if (prefix == null) {
+ prefix = "";
+ }
+ if (!StringUtils.hasLength(namespace)) {
+ return;
+ }
+ if (!namespace.equals(namespaces.get(prefix))) {
+ getContentHandler().startPrefixMapping(prefix, namespace);
+ namespaces.put(prefix, namespace);
+ }
+ }
+ }
+
+ /**
+ * Ends the prefix mapping for the given prefix.
+ * @see org.xml.sax.ContentHandler#endPrefixMapping(String)
+ */
+ protected void endPrefixMapping(String prefix) throws SAXException {
+ if (getContentHandler() != null) {
+ if (namespaces.containsKey(prefix)) {
+ getContentHandler().endPrefixMapping(prefix);
+ namespaces.remove(prefix);
+ }
+ }
+ }
+
+ /**
+ * Implementation of the {@code Locator} interface that is based on a StAX {@code Location}.
+ * @see Locator
+ * @see Location
+ */
+ private static class StaxLocator implements Locator {
+
+ private Location location;
+
+ protected StaxLocator(Location location) {
+ this.location = location;
+ }
+
+ public String getPublicId() {
+ return location.getPublicId();
+ }
+
+ public String getSystemId() {
+ return location.getSystemId();
+ }
+
+ public int getLineNumber() {
+ return location.getLineNumber();
+ }
+
+ public int getColumnNumber() {
+ return location.getColumnNumber();
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLReader.java b/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLReader.java
new file mode 100644
index 00000000..300f9ff2
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLReader.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2002-2012 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.util.xml;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.DTDHandler;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * Abstract base class for SAX {@code XMLReader} implementations. Contains properties as defined in {@link
+ * XMLReader}, and does not recognize any features.
+ *
+ * @author Arjen Poutsma
+ * @see #setContentHandler(org.xml.sax.ContentHandler)
+ * @see #setDTDHandler(org.xml.sax.DTDHandler)
+ * @see #setEntityResolver(org.xml.sax.EntityResolver)
+ * @see #setErrorHandler(org.xml.sax.ErrorHandler)
+ * @since 3.0
+ */
+abstract class AbstractXMLReader implements XMLReader {
+
+ private DTDHandler dtdHandler;
+
+ private ContentHandler contentHandler;
+
+ private EntityResolver entityResolver;
+
+ private ErrorHandler errorHandler;
+
+ private LexicalHandler lexicalHandler;
+
+ public ContentHandler getContentHandler() {
+ return contentHandler;
+ }
+
+ public void setContentHandler(ContentHandler contentHandler) {
+ this.contentHandler = contentHandler;
+ }
+
+ public void setDTDHandler(DTDHandler dtdHandler) {
+ this.dtdHandler = dtdHandler;
+ }
+
+ public DTDHandler getDTDHandler() {
+ return dtdHandler;
+ }
+
+ public EntityResolver getEntityResolver() {
+ return entityResolver;
+ }
+
+ public void setEntityResolver(EntityResolver entityResolver) {
+ this.entityResolver = entityResolver;
+ }
+
+ public ErrorHandler getErrorHandler() {
+ return errorHandler;
+ }
+
+ public void setErrorHandler(ErrorHandler errorHandler) {
+ this.errorHandler = errorHandler;
+ }
+
+ protected LexicalHandler getLexicalHandler() {
+ return lexicalHandler;
+ }
+
+ /**
+ * Throws a {@code SAXNotRecognizedException} exception.
+ *
+ * @throws org.xml.sax.SAXNotRecognizedException
+ * always
+ */
+ public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
+ throw new SAXNotRecognizedException(name);
+ }
+
+ /**
+ * Throws a {@code SAXNotRecognizedException} exception.
+ *
+ * @throws SAXNotRecognizedException always
+ */
+ public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
+ throw new SAXNotRecognizedException(name);
+ }
+
+ /**
+ * Throws a {@code SAXNotRecognizedException} exception when the given property does not signify a lexical
+ * handler. The property name for a lexical handler is {@code http://xml.org/sax/properties/lexical-handler}.
+ */
+ public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
+ if ("http://xml.org/sax/properties/lexical-handler".equals(name)) {
+ return lexicalHandler;
+ }
+ else {
+ throw new SAXNotRecognizedException(name);
+ }
+ }
+
+ /**
+ * Throws a {@code SAXNotRecognizedException} exception when the given property does not signify a lexical
+ * handler. The property name for a lexical handler is {@code http://xml.org/sax/properties/lexical-handler}.
+ */
+ public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
+ if ("http://xml.org/sax/properties/lexical-handler".equals(name)) {
+ lexicalHandler = (LexicalHandler) value;
+ }
+ else {
+ throw new SAXNotRecognizedException(name);
+ }
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLStreamReader.java b/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLStreamReader.java
new file mode 100644
index 00000000..e8c8a643
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLStreamReader.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2002-2012 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.util.xml;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+import org.springframework.util.Assert;
+
+/**
+ * Abstract base class for {@code XMLStreamReader}s.
+ *
+ * @author Arjen Poutsma
+ * @since 3.0
+ */
+abstract class AbstractXMLStreamReader implements XMLStreamReader {
+
+ public String getElementText() throws XMLStreamException {
+ if (getEventType() != XMLStreamConstants.START_ELEMENT) {
+ throw new XMLStreamException("parser must be on START_ELEMENT to read next text", getLocation());
+ }
+ int eventType = next();
+ StringBuilder builder = new StringBuilder();
+ while (eventType != XMLStreamConstants.END_ELEMENT) {
+ if (eventType == XMLStreamConstants.CHARACTERS || eventType == XMLStreamConstants.CDATA ||
+ eventType == XMLStreamConstants.SPACE || eventType == XMLStreamConstants.ENTITY_REFERENCE) {
+ builder.append(getText());
+ }
+ else if (eventType == XMLStreamConstants.PROCESSING_INSTRUCTION ||
+ eventType == XMLStreamConstants.COMMENT) {
+ // skipping
+ }
+ else if (eventType == XMLStreamConstants.END_DOCUMENT) {
+ throw new XMLStreamException("unexpected end of document when reading element text content",
+ getLocation());
+ }
+ else if (eventType == XMLStreamConstants.START_ELEMENT) {
+ throw new XMLStreamException("element text content may not contain START_ELEMENT", getLocation());
+ }
+ else {
+ throw new XMLStreamException("Unexpected event type " + eventType, getLocation());
+ }
+ eventType = next();
+ }
+ return builder.toString();
+ }
+
+ public String getAttributeLocalName(int index) {
+ return getAttributeName(index).getLocalPart();
+ }
+
+ public String getAttributeNamespace(int index) {
+ return getAttributeName(index).getNamespaceURI();
+ }
+
+ public String getAttributePrefix(int index) {
+ return getAttributeName(index).getPrefix();
+ }
+
+ public String getNamespaceURI() {
+ int eventType = getEventType();
+ if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) {
+ return getName().getNamespaceURI();
+ }
+ else {
+ throw new IllegalStateException("parser must be on START_ELEMENT or END_ELEMENT state");
+ }
+ }
+
+ public String getNamespaceURI(String prefix) {
+ Assert.notNull(prefix, "No prefix given");
+ return getNamespaceContext().getNamespaceURI(prefix);
+ }
+
+ public boolean hasText() {
+ int eventType = getEventType();
+ return eventType == XMLStreamConstants.SPACE || eventType == XMLStreamConstants.CHARACTERS ||
+ eventType == XMLStreamConstants.COMMENT || eventType == XMLStreamConstants.CDATA ||
+ eventType == XMLStreamConstants.ENTITY_REFERENCE;
+ }
+
+ public String getPrefix() {
+ int eventType = getEventType();
+ if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) {
+ return getName().getPrefix();
+ }
+ else {
+ throw new IllegalStateException("parser must be on START_ELEMENT or END_ELEMENT state");
+ }
+ }
+
+ public boolean hasName() {
+ int eventType = getEventType();
+ return eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT;
+ }
+
+ public boolean isWhiteSpace() {
+ return getEventType() == XMLStreamConstants.SPACE;
+ }
+
+ public boolean isStartElement() {
+ return getEventType() == XMLStreamConstants.START_ELEMENT;
+ }
+
+ public boolean isEndElement() {
+ return getEventType() == XMLStreamConstants.END_ELEMENT;
+ }
+
+ public boolean isCharacters() {
+ return getEventType() == XMLStreamConstants.CHARACTERS;
+ }
+
+ public int nextTag() throws XMLStreamException {
+ int eventType = next();
+ while (eventType == XMLStreamConstants.CHARACTERS && isWhiteSpace() ||
+ eventType == XMLStreamConstants.CDATA && isWhiteSpace() || eventType == XMLStreamConstants.SPACE ||
+ eventType == XMLStreamConstants.PROCESSING_INSTRUCTION || eventType == XMLStreamConstants.COMMENT) {
+ eventType = next();
+ }
+ if (eventType != XMLStreamConstants.START_ELEMENT && eventType != XMLStreamConstants.END_ELEMENT) {
+ throw new XMLStreamException("expected start or end tag", getLocation());
+ }
+ return eventType;
+ }
+
+ public void require(int expectedType, String namespaceURI, String localName) throws XMLStreamException {
+ int eventType = getEventType();
+ if (eventType != expectedType) {
+ throw new XMLStreamException("Expected [" + expectedType + "] but read [" + eventType + "]");
+ }
+ }
+
+ public String getAttributeValue(String namespaceURI, String localName) {
+ for (int i = 0; i < getAttributeCount(); i++) {
+ QName name = getAttributeName(i);
+ if (name.getLocalPart().equals(localName) &&
+ (namespaceURI == null || name.getNamespaceURI().equals(namespaceURI))) {
+ return getAttributeValue(i);
+ }
+ }
+ return null;
+ }
+
+ public boolean hasNext() throws XMLStreamException {
+ return getEventType() != END_DOCUMENT;
+ }
+
+ public String getLocalName() {
+ return getName().getLocalPart();
+ }
+
+ public char[] getTextCharacters() {
+ return getText().toCharArray();
+ }
+
+ public int getTextCharacters(int sourceStart, char[] target, int targetStart, int length)
+ throws XMLStreamException {
+ char[] source = getTextCharacters();
+ length = Math.min(length, source.length);
+ System.arraycopy(source, sourceStart, target, targetStart, length);
+ return length;
+ }
+
+ public int getTextLength() {
+ return getText().length();
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/DomContentHandler.java b/spring-core/src/main/java/org/springframework/util/xml/DomContentHandler.java
new file mode 100644
index 00000000..534cb007
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/DomContentHandler.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2002-2012 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.util.xml;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.ProcessingInstruction;
+import org.w3c.dom.Text;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+import org.springframework.util.Assert;
+
+/**
+ * SAX {@code ContentHandler} that transforms callback calls to DOM {@code Node}s.
+ *
+ * @author Arjen Poutsma
+ * @see org.w3c.dom.Node
+ * @since 3.0
+ */
+class DomContentHandler implements ContentHandler {
+
+ private final Document document;
+
+ private final List<Element> elements = new ArrayList<Element>();
+
+ private final Node node;
+
+ /**
+ * Creates a new instance of the {@code DomContentHandler} with the given node.
+ *
+ * @param node the node to publish events to
+ */
+ DomContentHandler(Node node) {
+ Assert.notNull(node, "node must not be null");
+ this.node = node;
+ if (node instanceof Document) {
+ document = (Document) node;
+ }
+ else {
+ document = node.getOwnerDocument();
+ }
+ Assert.notNull(document, "document must not be null");
+ }
+
+ private Node getParent() {
+ if (!elements.isEmpty()) {
+ return elements.get(elements.size() - 1);
+ }
+ else {
+ return node;
+ }
+ }
+
+ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+ Node parent = getParent();
+ Element element = document.createElementNS(uri, qName);
+ for (int i = 0; i < attributes.getLength(); i++) {
+ String attrUri = attributes.getURI(i);
+ String attrQname = attributes.getQName(i);
+ String value = attributes.getValue(i);
+ if (!attrQname.startsWith("xmlns")) {
+ element.setAttributeNS(attrUri, attrQname, value);
+ }
+ }
+ element = (Element) parent.appendChild(element);
+ elements.add(element);
+ }
+
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ elements.remove(elements.size() - 1);
+ }
+
+ public void characters(char ch[], int start, int length) throws SAXException {
+ String data = new String(ch, start, length);
+ Node parent = getParent();
+ Node lastChild = parent.getLastChild();
+ if (lastChild != null && lastChild.getNodeType() == Node.TEXT_NODE) {
+ ((Text) lastChild).appendData(data);
+ }
+ else {
+ Text text = document.createTextNode(data);
+ parent.appendChild(text);
+ }
+ }
+
+ public void processingInstruction(String target, String data) throws SAXException {
+ Node parent = getParent();
+ ProcessingInstruction pi = document.createProcessingInstruction(target, data);
+ parent.appendChild(pi);
+ }
+
+ /*
+ * Unsupported
+ */
+
+ public void setDocumentLocator(Locator locator) {
+ }
+
+ public void startDocument() throws SAXException {
+ }
+
+ public void endDocument() throws SAXException {
+ }
+
+ public void startPrefixMapping(String prefix, String uri) throws SAXException {
+ }
+
+ public void endPrefixMapping(String prefix) throws SAXException {
+ }
+
+ public void ignorableWhitespace(char ch[], int start, int length) throws SAXException {
+ }
+
+ public void skippedEntity(String name) throws SAXException {
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/DomUtils.java b/spring-core/src/main/java/org/springframework/util/xml/DomUtils.java
new file mode 100644
index 00000000..61c6271d
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/DomUtils.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2002-2013 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.util.xml;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.w3c.dom.CharacterData;
+import org.w3c.dom.Comment;
+import org.w3c.dom.Element;
+import org.w3c.dom.EntityReference;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.ContentHandler;
+
+import org.springframework.util.Assert;
+
+/**
+ * Convenience methods for working with the DOM API,
+ * in particular for working with DOM Nodes and DOM Elements.
+ *
+ * @author Juergen Hoeller
+ * @author Rob Harrop
+ * @author Costin Leau
+ * @author Arjen Poutsma
+ * @author Luke Taylor
+ * @since 1.2
+ * @see org.w3c.dom.Node
+ * @see org.w3c.dom.Element
+ */
+public abstract class DomUtils {
+
+ /**
+ * Retrieves all child elements of the given DOM element that match any of the given element names.
+ * Only looks at the direct child level of the given element; do not go into further depth
+ * (in contrast to the DOM API's {@code getElementsByTagName} method).
+ * @param ele the DOM element to analyze
+ * @param childEleNames the child element names to look for
+ * @return a List of child {@code org.w3c.dom.Element} instances
+ * @see org.w3c.dom.Element
+ * @see org.w3c.dom.Element#getElementsByTagName
+ */
+ public static List<Element> getChildElementsByTagName(Element ele, String... childEleNames) {
+ Assert.notNull(ele, "Element must not be null");
+ Assert.notNull(childEleNames, "Element names collection must not be null");
+ List<String> childEleNameList = Arrays.asList(childEleNames);
+ NodeList nl = ele.getChildNodes();
+ List<Element> childEles = new ArrayList<Element>();
+ for (int i = 0; i < nl.getLength(); i++) {
+ Node node = nl.item(i);
+ if (node instanceof Element && nodeNameMatch(node, childEleNameList)) {
+ childEles.add((Element) node);
+ }
+ }
+ return childEles;
+ }
+
+ /**
+ * Retrieves all child elements of the given DOM element that match the given element name.
+ * Only look at the direct child level of the given element; do not go into further depth
+ * (in contrast to the DOM API's {@code getElementsByTagName} method).
+ * @param ele the DOM element to analyze
+ * @param childEleName the child element name to look for
+ * @return a List of child {@code org.w3c.dom.Element} instances
+ * @see org.w3c.dom.Element
+ * @see org.w3c.dom.Element#getElementsByTagName
+ */
+ public static List<Element> getChildElementsByTagName(Element ele, String childEleName) {
+ return getChildElementsByTagName(ele, new String[] {childEleName});
+ }
+
+ /**
+ * Utility method that returns the first child element identified by its name.
+ * @param ele the DOM element to analyze
+ * @param childEleName the child element name to look for
+ * @return the {@code org.w3c.dom.Element} instance, or {@code null} if none found
+ */
+ public static Element getChildElementByTagName(Element ele, String childEleName) {
+ Assert.notNull(ele, "Element must not be null");
+ Assert.notNull(childEleName, "Element name must not be null");
+ NodeList nl = ele.getChildNodes();
+ for (int i = 0; i < nl.getLength(); i++) {
+ Node node = nl.item(i);
+ if (node instanceof Element && nodeNameMatch(node, childEleName)) {
+ return (Element) node;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Utility method that returns the first child element value identified by its name.
+ * @param ele the DOM element to analyze
+ * @param childEleName the child element name to look for
+ * @return the extracted text value, or {@code null} if no child element found
+ */
+ public static String getChildElementValueByTagName(Element ele, String childEleName) {
+ Element child = getChildElementByTagName(ele, childEleName);
+ return (child != null ? getTextValue(child) : null);
+ }
+
+ /**
+ * Retrieves all child elements of the given DOM element
+ * @param ele the DOM element to analyze
+ * @return a List of child {@code org.w3c.dom.Element} instances
+ */
+ public static List<Element> getChildElements(Element ele) {
+ Assert.notNull(ele, "Element must not be null");
+ NodeList nl = ele.getChildNodes();
+ List<Element> childEles = new ArrayList<Element>();
+ for (int i = 0; i < nl.getLength(); i++) {
+ Node node = nl.item(i);
+ if (node instanceof Element) {
+ childEles.add((Element) node);
+ }
+ }
+ return childEles;
+ }
+
+ /**
+ * Extracts the text value from the given DOM element, ignoring XML comments.
+ * <p>Appends all CharacterData nodes and EntityReference nodes into a single
+ * String value, excluding Comment nodes. Only exposes actual user-specified
+ * text, no default values of any kind.
+ * @see CharacterData
+ * @see EntityReference
+ * @see Comment
+ */
+ public static String getTextValue(Element valueEle) {
+ Assert.notNull(valueEle, "Element must not be null");
+ StringBuilder sb = new StringBuilder();
+ NodeList nl = valueEle.getChildNodes();
+ for (int i = 0; i < nl.getLength(); i++) {
+ Node item = nl.item(i);
+ if ((item instanceof CharacterData && !(item instanceof Comment)) || item instanceof EntityReference) {
+ sb.append(item.getNodeValue());
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Namespace-aware equals comparison. Returns {@code true} if either
+ * {@link Node#getLocalName} or {@link Node#getNodeName} equals
+ * {@code desiredName}, otherwise returns {@code false}.
+ */
+ public static boolean nodeNameEquals(Node node, String desiredName) {
+ Assert.notNull(node, "Node must not be null");
+ Assert.notNull(desiredName, "Desired name must not be null");
+ return nodeNameMatch(node, desiredName);
+ }
+
+ /**
+ * Returns a SAX {@code ContentHandler} that transforms callback calls to DOM {@code Node}s.
+ * @param node the node to publish events to
+ * @return the content handler
+ */
+ public static ContentHandler createContentHandler(Node node) {
+ return new DomContentHandler(node);
+ }
+
+ /**
+ * Matches the given node's name and local name against the given desired name.
+ */
+ private static boolean nodeNameMatch(Node node, String desiredName) {
+ return (desiredName.equals(node.getNodeName()) || desiredName.equals(node.getLocalName()));
+ }
+
+ /**
+ * Matches the given node's name and local name against the given desired names.
+ */
+ private static boolean nodeNameMatch(Node node, Collection<?> desiredNames) {
+ return (desiredNames.contains(node.getNodeName()) || desiredNames.contains(node.getLocalName()));
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/SimpleNamespaceContext.java b/spring-core/src/main/java/org/springframework/util/xml/SimpleNamespaceContext.java
new file mode 100644
index 00000000..c0d966d6
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/SimpleNamespaceContext.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2002-2012 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.util.xml;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import javax.xml.XMLConstants;
+import javax.xml.namespace.NamespaceContext;
+
+import org.springframework.util.Assert;
+
+/**
+ * Simple {@code javax.xml.namespace.NamespaceContext} implementation. Follows the standard
+ * {@code NamespaceContext} contract, and is loadable via a {@code java.util.Map} or
+ * {@code java.util.Properties} object
+ *
+ * @author Arjen Poutsma
+ * @since 3.0
+ */
+public class SimpleNamespaceContext implements NamespaceContext {
+
+ private Map<String, String> prefixToNamespaceUri = new HashMap<String, String>();
+
+ private Map<String, List<String>> namespaceUriToPrefixes = new HashMap<String, List<String>>();
+
+ private String defaultNamespaceUri = "";
+
+ public String getNamespaceURI(String prefix) {
+ Assert.notNull(prefix, "prefix is null");
+ if (XMLConstants.XML_NS_PREFIX.equals(prefix)) {
+ return XMLConstants.XML_NS_URI;
+ }
+ else if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) {
+ return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
+ }
+ else if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) {
+ return defaultNamespaceUri;
+ }
+ else if (prefixToNamespaceUri.containsKey(prefix)) {
+ return prefixToNamespaceUri.get(prefix);
+ }
+ return "";
+ }
+
+ public String getPrefix(String namespaceUri) {
+ List prefixes = getPrefixesInternal(namespaceUri);
+ return prefixes.isEmpty() ? null : (String) prefixes.get(0);
+ }
+
+ public Iterator getPrefixes(String namespaceUri) {
+ return getPrefixesInternal(namespaceUri).iterator();
+ }
+
+ /**
+ * Sets the bindings for this namespace context. The supplied map must consist of string key value pairs.
+ *
+ * @param bindings the bindings
+ */
+ public void setBindings(Map<String, String> bindings) {
+ for (Map.Entry<String, String> entry : bindings.entrySet()) {
+ bindNamespaceUri(entry.getKey(), entry.getValue());
+ }
+ }
+
+ /**
+ * Binds the given namespace as default namespace.
+ *
+ * @param namespaceUri the namespace uri
+ */
+ public void bindDefaultNamespaceUri(String namespaceUri) {
+ bindNamespaceUri(XMLConstants.DEFAULT_NS_PREFIX, namespaceUri);
+ }
+
+ /**
+ * Binds the given prefix to the given namespace.
+ *
+ * @param prefix the namespace prefix
+ * @param namespaceUri the namespace uri
+ */
+ public void bindNamespaceUri(String prefix, String namespaceUri) {
+ Assert.notNull(prefix, "No prefix given");
+ Assert.notNull(namespaceUri, "No namespaceUri given");
+ if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) {
+ defaultNamespaceUri = namespaceUri;
+ }
+ else {
+ prefixToNamespaceUri.put(prefix, namespaceUri);
+ getPrefixesInternal(namespaceUri).add(prefix);
+ }
+ }
+
+ /** Removes all declared prefixes. */
+ public void clear() {
+ prefixToNamespaceUri.clear();
+ }
+
+ /**
+ * Returns all declared prefixes.
+ *
+ * @return the declared prefixes
+ */
+ public Iterator<String> getBoundPrefixes() {
+ return prefixToNamespaceUri.keySet().iterator();
+ }
+
+ private List<String> getPrefixesInternal(String namespaceUri) {
+ if (defaultNamespaceUri.equals(namespaceUri)) {
+ return Collections.singletonList(XMLConstants.DEFAULT_NS_PREFIX);
+ }
+ else if (XMLConstants.XML_NS_URI.equals(namespaceUri)) {
+ return Collections.singletonList(XMLConstants.XML_NS_PREFIX);
+ }
+ else if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(namespaceUri)) {
+ return Collections.singletonList(XMLConstants.XMLNS_ATTRIBUTE);
+ }
+ else {
+ List<String> list = namespaceUriToPrefixes.get(namespaceUri);
+ if (list == null) {
+ list = new ArrayList<String>();
+ namespaceUriToPrefixes.put(namespaceUri, list);
+ }
+ return list;
+ }
+ }
+
+ /**
+ * Removes the given prefix from this context.
+ *
+ * @param prefix the prefix to be removed
+ */
+ public void removeBinding(String prefix) {
+ if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) {
+ defaultNamespaceUri = "";
+ }
+ else {
+ String namespaceUri = prefixToNamespaceUri.remove(prefix);
+ List prefixes = getPrefixesInternal(namespaceUri);
+ prefixes.remove(prefix);
+ }
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/SimpleSaxErrorHandler.java b/spring-core/src/main/java/org/springframework/util/xml/SimpleSaxErrorHandler.java
new file mode 100644
index 00000000..ed06ed8c
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/SimpleSaxErrorHandler.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2002-2012 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.util.xml;
+
+import org.apache.commons.logging.Log;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+/**
+ * Simple {@code org.xml.sax.ErrorHandler} implementation:
+ * logs warnings using the given Commons Logging logger instance,
+ * and rethrows errors to discontinue the XML transformation.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ */
+public class SimpleSaxErrorHandler implements ErrorHandler {
+
+ private final Log logger;
+
+
+ /**
+ * Create a new SimpleSaxErrorHandler for the given
+ * Commons Logging logger instance.
+ */
+ public SimpleSaxErrorHandler(Log logger) {
+ this.logger = logger;
+ }
+
+
+ public void warning(SAXParseException ex) throws SAXException {
+ logger.warn("Ignored XML validation warning", ex);
+ }
+
+ public void error(SAXParseException ex) throws SAXException {
+ throw ex;
+ }
+
+ public void fatalError(SAXParseException ex) throws SAXException {
+ throw ex;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/SimpleTransformErrorListener.java b/spring-core/src/main/java/org/springframework/util/xml/SimpleTransformErrorListener.java
new file mode 100644
index 00000000..7fd5dffa
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/SimpleTransformErrorListener.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2002-2012 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.util.xml;
+
+import javax.xml.transform.ErrorListener;
+import javax.xml.transform.TransformerException;
+
+import org.apache.commons.logging.Log;
+
+/**
+ * Simple {@code javax.xml.transform.ErrorListener} implementation:
+ * logs warnings using the given Commons Logging logger instance,
+ * and rethrows errors to discontinue the XML transformation.
+ *
+ * @author Juergen Hoeller
+ * @since 1.2
+ */
+public class SimpleTransformErrorListener implements ErrorListener {
+
+ private final Log logger;
+
+
+ /**
+ * Create a new SimpleTransformErrorListener for the given
+ * Commons Logging logger instance.
+ */
+ public SimpleTransformErrorListener(Log logger) {
+ this.logger = logger;
+ }
+
+
+ public void warning(TransformerException ex) throws TransformerException {
+ logger.warn("XSLT transformation warning", ex);
+ }
+
+ public void error(TransformerException ex) throws TransformerException {
+ logger.error("XSLT transformation error", ex);
+ }
+
+ public void fatalError(TransformerException ex) throws TransformerException {
+ throw ex;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxEventContentHandler.java b/spring-core/src/main/java/org/springframework/util/xml/StaxEventContentHandler.java
new file mode 100644
index 00000000..686ff1a6
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/StaxEventContentHandler.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2002-2012 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.util.xml;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import javax.xml.XMLConstants;
+import javax.xml.namespace.QName;
+import javax.xml.stream.Location;
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.Namespace;
+import javax.xml.stream.events.XMLEvent;
+import javax.xml.stream.util.XMLEventConsumer;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * SAX {@code ContentHandler} that transforms callback calls to {@code XMLEvent}s
+ * and writes them to a {@code XMLEventConsumer}.
+ *
+ * @author Arjen Poutsma
+ * @since 3.0
+ * @see XMLEvent
+ * @see XMLEventConsumer
+ */
+class StaxEventContentHandler extends AbstractStaxContentHandler {
+
+ private final XMLEventFactory eventFactory;
+
+ private final XMLEventConsumer eventConsumer;
+
+
+ /**
+ * Construct a new instance of the {@code StaxEventContentHandler} that writes to the given
+ * {@code XMLEventConsumer}. A default {@code XMLEventFactory} will be created.
+ * @param consumer the consumer to write events to
+ */
+ StaxEventContentHandler(XMLEventConsumer consumer) {
+ this.eventFactory = XMLEventFactory.newInstance();
+ this.eventConsumer = consumer;
+ }
+
+ /**
+ * Construct a new instance of the {@code StaxEventContentHandler} that uses the given
+ * event factory to create events and writes to the given {@code XMLEventConsumer}.
+ * @param consumer the consumer to write events to
+ * @param factory the factory used to create events
+ */
+ StaxEventContentHandler(XMLEventConsumer consumer, XMLEventFactory factory) {
+ this.eventFactory = factory;
+ this.eventConsumer = consumer;
+ }
+
+ public void setDocumentLocator(Locator locator) {
+ if (locator != null) {
+ this.eventFactory.setLocation(new LocatorLocationAdapter(locator));
+ }
+ }
+
+ @Override
+ protected void startDocumentInternal() throws XMLStreamException {
+ consumeEvent(this.eventFactory.createStartDocument());
+ }
+
+ @Override
+ protected void endDocumentInternal() throws XMLStreamException {
+ consumeEvent(this.eventFactory.createEndDocument());
+ }
+
+ @Override
+ protected void startElementInternal(QName name, Attributes atts, SimpleNamespaceContext namespaceContext)
+ throws XMLStreamException {
+
+ List attributes = getAttributes(atts);
+ List namespaces = createNamespaces(namespaceContext);
+ consumeEvent(this.eventFactory.createStartElement(name, attributes.iterator(),
+ (namespaces != null ? namespaces.iterator() : null)));
+ }
+
+ @Override
+ protected void endElementInternal(QName name, SimpleNamespaceContext namespaceContext) throws XMLStreamException {
+ List namespaces = createNamespaces(namespaceContext);
+ consumeEvent(this.eventFactory.createEndElement(name, namespaces != null ? namespaces.iterator() : null));
+ }
+
+ @Override
+ protected void charactersInternal(char[] ch, int start, int length) throws XMLStreamException {
+ consumeEvent(this.eventFactory.createCharacters(new String(ch, start, length)));
+ }
+
+ @Override
+ protected void ignorableWhitespaceInternal(char[] ch, int start, int length) throws XMLStreamException {
+ consumeEvent(this.eventFactory.createIgnorableSpace(new String(ch, start, length)));
+ }
+
+ @Override
+ protected void processingInstructionInternal(String target, String data) throws XMLStreamException {
+ consumeEvent(this.eventFactory.createProcessingInstruction(target, data));
+ }
+
+ private void consumeEvent(XMLEvent event) throws XMLStreamException {
+ this.eventConsumer.add(event);
+ }
+
+ /**
+ * Create and return a list of {@code NameSpace} objects from the {@code NamespaceContext}.
+ */
+ private List<Namespace> createNamespaces(SimpleNamespaceContext namespaceContext) {
+ if (namespaceContext == null) {
+ return null;
+ }
+
+ List<Namespace> namespaces = new ArrayList<Namespace>();
+ String defaultNamespaceUri = namespaceContext.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX);
+ if (StringUtils.hasLength(defaultNamespaceUri)) {
+ namespaces.add(this.eventFactory.createNamespace(defaultNamespaceUri));
+ }
+ for (Iterator iterator = namespaceContext.getBoundPrefixes(); iterator.hasNext();) {
+ String prefix = (String) iterator.next();
+ String namespaceUri = namespaceContext.getNamespaceURI(prefix);
+ namespaces.add(this.eventFactory.createNamespace(prefix, namespaceUri));
+ }
+ return namespaces;
+ }
+
+ private List<Attribute> getAttributes(Attributes attributes) {
+ List<Attribute> list = new ArrayList<Attribute>();
+ for (int i = 0; i < attributes.getLength(); i++) {
+ QName name = toQName(attributes.getURI(i), attributes.getQName(i));
+ if (!("xmlns".equals(name.getLocalPart()) || "xmlns".equals(name.getPrefix()))) {
+ list.add(this.eventFactory.createAttribute(name, attributes.getValue(i)));
+ }
+ }
+ return list;
+ }
+
+ /* No operation */
+ @Override
+ protected void skippedEntityInternal(String name) throws XMLStreamException {
+ }
+
+
+ private static final class LocatorLocationAdapter implements Location {
+
+ private final Locator locator;
+
+ public LocatorLocationAdapter(Locator locator) {
+ this.locator = locator;
+ }
+
+ public int getLineNumber() {
+ return this.locator.getLineNumber();
+ }
+
+ public int getColumnNumber() {
+ return this.locator.getColumnNumber();
+ }
+
+ public int getCharacterOffset() {
+ return -1;
+ }
+
+ public String getPublicId() {
+ return this.locator.getPublicId();
+ }
+
+ public String getSystemId() {
+ return this.locator.getSystemId();
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxEventXMLReader.java b/spring-core/src/main/java/org/springframework/util/xml/StaxEventXMLReader.java
new file mode 100644
index 00000000..fd43354a
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/StaxEventXMLReader.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright 2002-2013 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.util.xml;
+
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.xml.namespace.QName;
+import javax.xml.stream.Location;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.Comment;
+import javax.xml.stream.events.DTD;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.EntityDeclaration;
+import javax.xml.stream.events.EntityReference;
+import javax.xml.stream.events.Namespace;
+import javax.xml.stream.events.NotationDeclaration;
+import javax.xml.stream.events.ProcessingInstruction;
+import javax.xml.stream.events.StartDocument;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.ext.Locator2;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * SAX {@code XMLReader} that reads from a StAX {@code XMLEventReader}. Consumes {@code XMLEvents} from
+ * an {@code XMLEventReader}, and calls the corresponding methods on the SAX callback interfaces.
+ *
+ * @author Arjen Poutsma
+ * @since 3.0
+ * @see XMLEventReader
+ * @see #setContentHandler(org.xml.sax.ContentHandler)
+ * @see #setDTDHandler(org.xml.sax.DTDHandler)
+ * @see #setEntityResolver(org.xml.sax.EntityResolver)
+ * @see #setErrorHandler(org.xml.sax.ErrorHandler)
+ */
+class StaxEventXMLReader extends AbstractStaxXMLReader {
+
+ private static final String DEFAULT_XML_VERSION = "1.0";
+
+ private final XMLEventReader reader;
+
+ private final Map<String, String> namespaces = new LinkedHashMap<String, String>();
+
+ private String xmlVersion = DEFAULT_XML_VERSION;
+
+ private String encoding;
+
+
+ /**
+ * Constructs a new instance of the {@code StaxEventXmlReader} that reads from the given
+ * {@code XMLEventReader}. The supplied event reader must be in {@code XMLStreamConstants.START_DOCUMENT} or
+ * {@code XMLStreamConstants.START_ELEMENT} state.
+ * @param reader the {@code XMLEventReader} to read from
+ * @throws IllegalStateException if the reader is not at the start of a document or element
+ */
+ StaxEventXMLReader(XMLEventReader reader) {
+ Assert.notNull(reader, "'reader' must not be null");
+ try {
+ XMLEvent event = reader.peek();
+ if (event != null && !(event.isStartDocument() || event.isStartElement())) {
+ throw new IllegalStateException("XMLEventReader not at start of document or element");
+ }
+ }
+ catch (XMLStreamException ex) {
+ throw new IllegalStateException("Could not read first element: " + ex.getMessage());
+ }
+ this.reader = reader;
+ }
+
+
+ @Override
+ protected void parseInternal() throws SAXException, XMLStreamException {
+ boolean documentStarted = false;
+ boolean documentEnded = false;
+ int elementDepth = 0;
+ while (this.reader.hasNext() && elementDepth >= 0) {
+ XMLEvent event = this.reader.nextEvent();
+ if (!event.isStartDocument() && !event.isEndDocument() && !documentStarted) {
+ handleStartDocument(event);
+ documentStarted = true;
+ }
+ switch (event.getEventType()) {
+ case XMLStreamConstants.START_DOCUMENT:
+ handleStartDocument(event);
+ documentStarted = true;
+ break;
+ case XMLStreamConstants.START_ELEMENT:
+ elementDepth++;
+ handleStartElement(event.asStartElement());
+ break;
+ case XMLStreamConstants.END_ELEMENT:
+ elementDepth--;
+ if (elementDepth >= 0) {
+ handleEndElement(event.asEndElement());
+ }
+ break;
+ case XMLStreamConstants.PROCESSING_INSTRUCTION:
+ handleProcessingInstruction((ProcessingInstruction) event);
+ break;
+ case XMLStreamConstants.CHARACTERS:
+ case XMLStreamConstants.SPACE:
+ case XMLStreamConstants.CDATA:
+ handleCharacters(event.asCharacters());
+ break;
+ case XMLStreamConstants.END_DOCUMENT:
+ handleEndDocument();
+ documentEnded = true;
+ break;
+ case XMLStreamConstants.NOTATION_DECLARATION:
+ handleNotationDeclaration((NotationDeclaration) event);
+ break;
+ case XMLStreamConstants.ENTITY_DECLARATION:
+ handleEntityDeclaration((EntityDeclaration) event);
+ break;
+ case XMLStreamConstants.COMMENT:
+ handleComment((Comment) event);
+ break;
+ case XMLStreamConstants.DTD:
+ handleDtd((DTD) event);
+ break;
+ case XMLStreamConstants.ENTITY_REFERENCE:
+ handleEntityReference((EntityReference) event);
+ break;
+ }
+ }
+ if (documentStarted && !documentEnded) {
+ handleEndDocument();
+ }
+
+ }
+
+ private void handleStartDocument(final XMLEvent event) throws SAXException {
+ if (event.isStartDocument()) {
+ StartDocument startDocument = (StartDocument) event;
+ String xmlVersion = startDocument.getVersion();
+ if (StringUtils.hasLength(xmlVersion)) {
+ this.xmlVersion = xmlVersion;
+ }
+ if (startDocument.encodingSet()) {
+ this.encoding = startDocument.getCharacterEncodingScheme();
+ }
+ }
+ if (getContentHandler() != null) {
+ final Location location = event.getLocation();
+ getContentHandler().setDocumentLocator(new Locator2() {
+ public int getColumnNumber() {
+ return (location != null ? location.getColumnNumber() : -1);
+ }
+ public int getLineNumber() {
+ return (location != null ? location.getLineNumber() : -1);
+ }
+ public String getPublicId() {
+ return (location != null ? location.getPublicId() : null);
+ }
+ public String getSystemId() {
+ return (location != null ? location.getSystemId() : null);
+ }
+ public String getXMLVersion() {
+ return xmlVersion;
+ }
+ public String getEncoding() {
+ return encoding;
+ }
+ });
+ getContentHandler().startDocument();
+ }
+ }
+
+ private void handleStartElement(StartElement startElement) throws SAXException {
+ if (getContentHandler() != null) {
+ QName qName = startElement.getName();
+ if (hasNamespacesFeature()) {
+ for (Iterator i = startElement.getNamespaces(); i.hasNext();) {
+ Namespace namespace = (Namespace) i.next();
+ startPrefixMapping(namespace.getPrefix(), namespace.getNamespaceURI());
+ }
+ for (Iterator i = startElement.getAttributes(); i.hasNext();){
+ Attribute attribute = (Attribute) i.next();
+ QName attributeName = attribute.getName();
+ startPrefixMapping(attributeName.getPrefix(), attributeName.getNamespaceURI());
+ }
+
+ getContentHandler().startElement(qName.getNamespaceURI(), qName.getLocalPart(), toQualifiedName(qName),
+ getAttributes(startElement));
+ }
+ else {
+ getContentHandler().startElement("", "", toQualifiedName(qName), getAttributes(startElement));
+ }
+ }
+ }
+
+ private void handleCharacters(Characters characters) throws SAXException {
+ char[] data = characters.getData().toCharArray();
+ if (getContentHandler() != null && characters.isIgnorableWhiteSpace()) {
+ getContentHandler().ignorableWhitespace(data, 0, data.length);
+ return;
+ }
+ if (characters.isCData() && getLexicalHandler() != null) {
+ getLexicalHandler().startCDATA();
+ }
+ if (getContentHandler() != null) {
+ getContentHandler().characters(data, 0, data.length);
+ }
+ if (characters.isCData() && getLexicalHandler() != null) {
+ getLexicalHandler().endCDATA();
+ }
+ }
+
+ private void handleEndElement(EndElement endElement) throws SAXException {
+ if (getContentHandler() != null) {
+ QName qName = endElement.getName();
+ if (hasNamespacesFeature()) {
+ getContentHandler().endElement(qName.getNamespaceURI(), qName.getLocalPart(), toQualifiedName(qName));
+ for (Iterator i = endElement.getNamespaces(); i.hasNext();) {
+ Namespace namespace = (Namespace) i.next();
+ endPrefixMapping(namespace.getPrefix());
+ }
+ }
+ else {
+ getContentHandler().endElement("", "", toQualifiedName(qName));
+ }
+
+ }
+ }
+
+ private void handleEndDocument() throws SAXException {
+ if (getContentHandler() != null) {
+ getContentHandler().endDocument();
+ }
+ }
+
+ private void handleNotationDeclaration(NotationDeclaration declaration) throws SAXException {
+ if (getDTDHandler() != null) {
+ getDTDHandler().notationDecl(declaration.getName(), declaration.getPublicId(), declaration.getSystemId());
+ }
+ }
+
+ private void handleEntityDeclaration(EntityDeclaration entityDeclaration) throws SAXException {
+ if (getDTDHandler() != null) {
+ getDTDHandler().unparsedEntityDecl(entityDeclaration.getName(), entityDeclaration.getPublicId(),
+ entityDeclaration.getSystemId(), entityDeclaration.getNotationName());
+ }
+ }
+
+ private void handleProcessingInstruction(ProcessingInstruction pi) throws SAXException {
+ if (getContentHandler() != null) {
+ getContentHandler().processingInstruction(pi.getTarget(), pi.getData());
+ }
+ }
+
+ private void handleComment(Comment comment) throws SAXException {
+ if (getLexicalHandler() != null) {
+ char[] ch = comment.getText().toCharArray();
+ getLexicalHandler().comment(ch, 0, ch.length);
+ }
+ }
+
+ private void handleDtd(DTD dtd) throws SAXException {
+ if (getLexicalHandler() != null) {
+ javax.xml.stream.Location location = dtd.getLocation();
+ getLexicalHandler().startDTD(null, location.getPublicId(), location.getSystemId());
+ }
+ if (getLexicalHandler() != null) {
+ getLexicalHandler().endDTD();
+ }
+
+ }
+
+ private void handleEntityReference(EntityReference reference) throws SAXException {
+ if (getLexicalHandler() != null) {
+ getLexicalHandler().startEntity(reference.getName());
+ }
+ if (getLexicalHandler() != null) {
+ getLexicalHandler().endEntity(reference.getName());
+ }
+
+ }
+
+ private Attributes getAttributes(StartElement event) {
+ AttributesImpl attributes = new AttributesImpl();
+ for (Iterator i = event.getAttributes(); i.hasNext();) {
+ Attribute attribute = (Attribute) i.next();
+ QName qName = attribute.getName();
+ String namespace = qName.getNamespaceURI();
+ if (namespace == null || !hasNamespacesFeature()) {
+ namespace = "";
+ }
+ String type = attribute.getDTDType();
+ if (type == null) {
+ type = "CDATA";
+ }
+ attributes.addAttribute(namespace, qName.getLocalPart(), toQualifiedName(qName), type, attribute.getValue());
+ }
+ if (hasNamespacePrefixesFeature()) {
+ for (Iterator i = event.getNamespaces(); i.hasNext();) {
+ Namespace namespace = (Namespace) i.next();
+ String prefix = namespace.getPrefix();
+ String namespaceUri = namespace.getNamespaceURI();
+ String qName;
+ if (StringUtils.hasLength(prefix)) {
+ qName = "xmlns:" + prefix;
+ }
+ else {
+ qName = "xmlns";
+ }
+ attributes.addAttribute("", "", qName, "CDATA", namespaceUri);
+ }
+ }
+
+ return attributes;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxResult.java b/spring-core/src/main/java/org/springframework/util/xml/StaxResult.java
new file mode 100644
index 00000000..801769cd
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/StaxResult.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2002-2013 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.util.xml;
+
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.sax.SAXResult;
+
+import org.xml.sax.ContentHandler;
+
+/**
+ * Implementation of the {@code Result} tagging interface for StAX writers. Can be constructed with
+ * an {@code XMLEventConsumer} or an {@code XMLStreamWriter}.
+ *
+ * <p>This class is necessary because there is no implementation of {@code Source} for StaxReaders
+ * in JAXP 1.3. There is a {@code StAXResult} in JAXP 1.4 (JDK 1.6), but this class is kept around
+ * for backwards compatibility reasons.
+ *
+ * <p>Even though {@code StaxResult} extends from {@code SAXResult}, calling the methods of
+ * {@code SAXResult} is <strong>not supported</strong>. In general, the only supported operation
+ * on this class is to use the {@code ContentHandler} obtained via {@link #getHandler()} to parse an
+ * input source using an {@code XMLReader}. Calling {@link #setHandler(org.xml.sax.ContentHandler)}
+ * will result in {@code UnsupportedOperationException}s.
+ *
+ * @author Arjen Poutsma
+ * @since 3.0
+ * @see XMLEventWriter
+ * @see XMLStreamWriter
+ * @see javax.xml.transform.Transformer
+ */
+class StaxResult extends SAXResult {
+
+ private XMLEventWriter eventWriter;
+
+ private XMLStreamWriter streamWriter;
+
+
+ /**
+ * Construct a new instance of the {@code StaxResult} with the specified {@code XMLStreamWriter}.
+ * @param streamWriter the {@code XMLStreamWriter} to write to
+ */
+ StaxResult(XMLStreamWriter streamWriter) {
+ super.setHandler(new StaxStreamContentHandler(streamWriter));
+ this.streamWriter = streamWriter;
+ }
+
+ /**
+ * Construct a new instance of the {@code StaxResult} with the specified {@code XMLEventWriter}.
+ * @param eventWriter the {@code XMLEventWriter} to write to
+ */
+ StaxResult(XMLEventWriter eventWriter) {
+ super.setHandler(new StaxEventContentHandler(eventWriter));
+ this.eventWriter = eventWriter;
+ }
+
+ /**
+ * Construct a new instance of the {@code StaxResult} with the specified {@code XMLEventWriter}
+ * and {@code XMLEventFactory}.
+ * @param eventWriter the {@code XMLEventWriter} to write to
+ * @param eventFactory the {@code XMLEventFactory} to use for creating events
+ */
+ StaxResult(XMLEventWriter eventWriter, XMLEventFactory eventFactory) {
+ super.setHandler(new StaxEventContentHandler(eventWriter, eventFactory));
+ this.eventWriter = eventWriter;
+ }
+
+
+ /**
+ * Return the {@code XMLEventWriter} used by this {@code StaxResult}. If this {@code StaxResult}
+ * was created with an {@code XMLStreamWriter}, the result will be {@code null}.
+ * @return the StAX event writer used by this result
+ * @see #StaxResult(javax.xml.stream.XMLEventWriter)
+ */
+ XMLEventWriter getXMLEventWriter() {
+ return this.eventWriter;
+ }
+
+ /**
+ * Return the {@code XMLStreamWriter} used by this {@code StaxResult}. If this {@code StaxResult}
+ * was created with an {@code XMLEventConsumer}, the result will be {@code null}.
+ * @return the StAX stream writer used by this result
+ * @see #StaxResult(javax.xml.stream.XMLStreamWriter)
+ */
+ XMLStreamWriter getXMLStreamWriter() {
+ return this.streamWriter;
+ }
+
+
+ /**
+ * Throws an {@code UnsupportedOperationException}.
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public void setHandler(ContentHandler handler) {
+ throw new UnsupportedOperationException("setHandler is not supported");
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxSource.java b/spring-core/src/main/java/org/springframework/util/xml/StaxSource.java
new file mode 100644
index 00000000..5706a431
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/StaxSource.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2002-2013 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.util.xml;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.transform.sax.SAXSource;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
+
+/**
+ * Implementation of the {@code Source} tagging interface for StAX readers. Can be constructed with
+ * an {@code XMLEventReader} or an {@code XMLStreamReader}.
+ *
+ * <p>This class is necessary because there is no implementation of {@code Source} for StAX Readers
+ * in JAXP 1.3. There is a {@code StAXSource} in JAXP 1.4 (JDK 1.6), but this class is kept around
+ * for backwards compatibility reasons.
+ *
+ * <p>Even though {@code StaxSource} extends from {@code SAXSource}, calling the methods of
+ * {@code SAXSource} is <strong>not supported</strong>. In general, the only supported operation
+ * on this class is to use the {@code XMLReader} obtained via {@link #getXMLReader()} to parse the
+ * input source obtained via {@link #getInputSource()}. Calling {@link #setXMLReader(XMLReader)}
+ * or {@link #setInputSource(InputSource)} will result in {@code UnsupportedOperationException}s.
+ *
+ * @author Arjen Poutsma
+ * @since 3.0
+ * @see XMLEventReader
+ * @see XMLStreamReader
+ * @see javax.xml.transform.Transformer
+ */
+class StaxSource extends SAXSource {
+
+ private XMLEventReader eventReader;
+
+ private XMLStreamReader streamReader;
+
+
+ /**
+ * Construct a new instance of the {@code StaxSource} with the specified {@code XMLStreamReader}.
+ * The supplied stream reader must be in {@code XMLStreamConstants.START_DOCUMENT} or
+ * {@code XMLStreamConstants.START_ELEMENT} state.
+ * @param streamReader the {@code XMLStreamReader} to read from
+ * @throws IllegalStateException if the reader is not at the start of a document or element
+ */
+ StaxSource(XMLStreamReader streamReader) {
+ super(new StaxStreamXMLReader(streamReader), new InputSource());
+ this.streamReader = streamReader;
+ }
+
+ /**
+ * Construct a new instance of the {@code StaxSource} with the specified {@code XMLEventReader}.
+ * The supplied event reader must be in {@code XMLStreamConstants.START_DOCUMENT} or
+ * {@code XMLStreamConstants.START_ELEMENT} state.
+ * @param eventReader the {@code XMLEventReader} to read from
+ * @throws IllegalStateException if the reader is not at the start of a document or element
+ */
+ StaxSource(XMLEventReader eventReader) {
+ super(new StaxEventXMLReader(eventReader), new InputSource());
+ this.eventReader = eventReader;
+ }
+
+
+ /**
+ * Return the {@code XMLEventReader} used by this {@code StaxSource}. If this {@code StaxSource}
+ * was created with an {@code XMLStreamReader}, the result will be {@code null}.
+ * @return the StAX event reader used by this source
+ * @see StaxSource#StaxSource(javax.xml.stream.XMLEventReader)
+ */
+ XMLEventReader getXMLEventReader() {
+ return this.eventReader;
+ }
+
+ /**
+ * Return the {@code XMLStreamReader} used by this {@code StaxSource}. If this {@code StaxSource}
+ * was created with an {@code XMLEventReader}, the result will be {@code null}.
+ * @return the StAX event reader used by this source
+ * @see StaxSource#StaxSource(javax.xml.stream.XMLEventReader)
+ */
+ XMLStreamReader getXMLStreamReader() {
+ return this.streamReader;
+ }
+
+
+ /**
+ * Throws an {@code UnsupportedOperationException}.
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public void setInputSource(InputSource inputSource) {
+ throw new UnsupportedOperationException("setInputSource is not supported");
+ }
+
+ /**
+ * Throws an {@code UnsupportedOperationException}.
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public void setXMLReader(XMLReader reader) {
+ throw new UnsupportedOperationException("setXMLReader is not supported");
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxStreamContentHandler.java b/spring-core/src/main/java/org/springframework/util/xml/StaxStreamContentHandler.java
new file mode 100644
index 00000000..2241e2f2
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/StaxStreamContentHandler.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2002-2012 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.util.xml;
+
+import java.util.Iterator;
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * SAX {@code ContentHandler} that writes to a {@code XMLStreamWriter}.
+ *
+ * @author Arjen Poutsma
+ * @see XMLStreamWriter
+ * @since 3.0
+ */
+class StaxStreamContentHandler extends AbstractStaxContentHandler {
+
+ private final XMLStreamWriter streamWriter;
+
+ /**
+ * Constructs a new instance of the {@code StaxStreamContentHandler} that writes to the given
+ * {@code XMLStreamWriter}.
+ *
+ * @param streamWriter the stream writer to write to
+ */
+ StaxStreamContentHandler(XMLStreamWriter streamWriter) {
+ Assert.notNull(streamWriter, "'streamWriter' must not be null");
+ this.streamWriter = streamWriter;
+ }
+
+ public void setDocumentLocator(Locator locator) {
+ }
+
+ @Override
+ protected void charactersInternal(char[] ch, int start, int length) throws XMLStreamException {
+ streamWriter.writeCharacters(ch, start, length);
+ }
+
+ @Override
+ protected void endDocumentInternal() throws XMLStreamException {
+ streamWriter.writeEndDocument();
+ }
+
+ @Override
+ protected void endElementInternal(QName name, SimpleNamespaceContext namespaceContext) throws XMLStreamException {
+ streamWriter.writeEndElement();
+ }
+
+ @Override
+ protected void ignorableWhitespaceInternal(char[] ch, int start, int length) throws XMLStreamException {
+ streamWriter.writeCharacters(ch, start, length);
+ }
+
+ @Override
+ protected void processingInstructionInternal(String target, String data) throws XMLStreamException {
+ streamWriter.writeProcessingInstruction(target, data);
+ }
+
+ @Override
+ protected void skippedEntityInternal(String name) {
+ }
+
+ @Override
+ protected void startDocumentInternal() throws XMLStreamException {
+ streamWriter.writeStartDocument();
+ }
+
+ @Override
+ protected void startElementInternal(QName name, Attributes attributes, SimpleNamespaceContext namespaceContext)
+ throws XMLStreamException {
+ streamWriter.writeStartElement(name.getPrefix(), name.getLocalPart(), name.getNamespaceURI());
+ if (namespaceContext != null) {
+ String defaultNamespaceUri = namespaceContext.getNamespaceURI("");
+ if (StringUtils.hasLength(defaultNamespaceUri)) {
+ streamWriter.writeNamespace("", defaultNamespaceUri);
+ streamWriter.setDefaultNamespace(defaultNamespaceUri);
+ }
+ for (Iterator<String> iterator = namespaceContext.getBoundPrefixes(); iterator.hasNext();) {
+ String prefix = iterator.next();
+ streamWriter.writeNamespace(prefix, namespaceContext.getNamespaceURI(prefix));
+ streamWriter.setPrefix(prefix, namespaceContext.getNamespaceURI(prefix));
+ }
+ }
+ for (int i = 0; i < attributes.getLength(); i++) {
+ QName attrName = toQName(attributes.getURI(i), attributes.getQName(i));
+ if (!("xmlns".equals(attrName.getLocalPart()) || "xmlns".equals(attrName.getPrefix()))) {
+ streamWriter.writeAttribute(attrName.getPrefix(), attrName.getNamespaceURI(), attrName.getLocalPart(),
+ attributes.getValue(i));
+ }
+ }
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxStreamXMLReader.java b/spring-core/src/main/java/org/springframework/util/xml/StaxStreamXMLReader.java
new file mode 100644
index 00000000..359514b5
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/StaxStreamXMLReader.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2002-2014 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.util.xml;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.Location;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.ext.Locator2;
+import org.xml.sax.helpers.AttributesImpl;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * SAX {@code XMLReader} that reads from a StAX {@code XMLStreamReader}. Reads from an
+ * {@code XMLStreamReader}, and calls the corresponding methods on the SAX callback interfaces.
+ *
+ * @author Arjen Poutsma
+ * @since 3.0
+ * @see XMLStreamReader
+ * @see #setContentHandler(org.xml.sax.ContentHandler)
+ * @see #setDTDHandler(org.xml.sax.DTDHandler)
+ * @see #setEntityResolver(org.xml.sax.EntityResolver)
+ * @see #setErrorHandler(org.xml.sax.ErrorHandler)
+ */
+class StaxStreamXMLReader extends AbstractStaxXMLReader {
+
+ private static final String DEFAULT_XML_VERSION = "1.0";
+
+ private final XMLStreamReader reader;
+
+ private String xmlVersion = DEFAULT_XML_VERSION;
+
+ private String encoding;
+
+
+ /**
+ * Construct a new instance of the {@code StaxStreamXmlReader} that reads from the given
+ * {@code XMLStreamReader}. The supplied stream reader must be in {@code XMLStreamConstants.START_DOCUMENT}
+ * or {@code XMLStreamConstants.START_ELEMENT} state.
+ * @param reader the {@code XMLEventReader} to read from
+ * @throws IllegalStateException if the reader is not at the start of a document or element
+ */
+ StaxStreamXMLReader(XMLStreamReader reader) {
+ Assert.notNull(reader, "'reader' must not be null");
+ int event = reader.getEventType();
+ if (!(event == XMLStreamConstants.START_DOCUMENT || event == XMLStreamConstants.START_ELEMENT)) {
+ throw new IllegalStateException("XMLEventReader not at start of document or element");
+ }
+ this.reader = reader;
+ }
+
+
+ @Override
+ protected void parseInternal() throws SAXException, XMLStreamException {
+ boolean documentStarted = false;
+ boolean documentEnded = false;
+ int elementDepth = 0;
+ int eventType = this.reader.getEventType();
+ while (true) {
+ if (eventType != XMLStreamConstants.START_DOCUMENT && eventType != XMLStreamConstants.END_DOCUMENT &&
+ !documentStarted) {
+ handleStartDocument();
+ documentStarted = true;
+ }
+ switch (eventType) {
+ case XMLStreamConstants.START_ELEMENT:
+ elementDepth++;
+ handleStartElement();
+ break;
+ case XMLStreamConstants.END_ELEMENT:
+ elementDepth--;
+ if (elementDepth >= 0) {
+ handleEndElement();
+ }
+ break;
+ case XMLStreamConstants.PROCESSING_INSTRUCTION:
+ handleProcessingInstruction();
+ break;
+ case XMLStreamConstants.CHARACTERS:
+ case XMLStreamConstants.SPACE:
+ case XMLStreamConstants.CDATA:
+ handleCharacters();
+ break;
+ case XMLStreamConstants.START_DOCUMENT:
+ handleStartDocument();
+ documentStarted = true;
+ break;
+ case XMLStreamConstants.END_DOCUMENT:
+ handleEndDocument();
+ documentEnded = true;
+ break;
+ case XMLStreamConstants.COMMENT:
+ handleComment();
+ break;
+ case XMLStreamConstants.DTD:
+ handleDtd();
+ break;
+ case XMLStreamConstants.ENTITY_REFERENCE:
+ handleEntityReference();
+ break;
+ }
+ if (this.reader.hasNext() && elementDepth >= 0) {
+ eventType = this.reader.next();
+ }
+ else {
+ break;
+ }
+ }
+ if (!documentEnded) {
+ handleEndDocument();
+ }
+ }
+
+ private void handleStartDocument() throws SAXException {
+ if (XMLStreamConstants.START_DOCUMENT == this.reader.getEventType()) {
+ String xmlVersion = this.reader.getVersion();
+ if (StringUtils.hasLength(xmlVersion)) {
+ this.xmlVersion = xmlVersion;
+ }
+ this.encoding = this.reader.getCharacterEncodingScheme();
+ }
+ if (getContentHandler() != null) {
+ final Location location = this.reader.getLocation();
+ getContentHandler().setDocumentLocator(new Locator2() {
+ public int getColumnNumber() {
+ return (location != null ? location.getColumnNumber() : -1);
+ }
+ public int getLineNumber() {
+ return (location != null ? location.getLineNumber() : -1);
+ }
+ public String getPublicId() {
+ return (location != null ? location.getPublicId() : null);
+ }
+ public String getSystemId() {
+ return (location != null ? location.getSystemId() : null);
+ }
+ public String getXMLVersion() {
+ return xmlVersion;
+ }
+ public String getEncoding() {
+ return encoding;
+ }
+ });
+ getContentHandler().startDocument();
+ if (this.reader.standaloneSet()) {
+ setStandalone(this.reader.isStandalone());
+ }
+ }
+ }
+
+ private void handleStartElement() throws SAXException {
+ if (getContentHandler() != null) {
+ QName qName = this.reader.getName();
+ if (hasNamespacesFeature()) {
+ for (int i = 0; i < this.reader.getNamespaceCount(); i++) {
+ startPrefixMapping(this.reader.getNamespacePrefix(i), this.reader.getNamespaceURI(i));
+ }
+ for (int i = 0; i < this.reader.getAttributeCount(); i++) {
+ String prefix = this.reader.getAttributePrefix(i);
+ String namespace = this.reader.getAttributeNamespace(i);
+ if (StringUtils.hasLength(namespace)) {
+ startPrefixMapping(prefix, namespace);
+ }
+ }
+ getContentHandler().startElement(qName.getNamespaceURI(), qName.getLocalPart(),
+ toQualifiedName(qName), getAttributes());
+ }
+ else {
+ getContentHandler().startElement("", "", toQualifiedName(qName), getAttributes());
+ }
+ }
+ }
+
+ private void handleEndElement() throws SAXException {
+ if (getContentHandler() != null) {
+ QName qName = this.reader.getName();
+ if (hasNamespacesFeature()) {
+ getContentHandler().endElement(qName.getNamespaceURI(), qName.getLocalPart(), toQualifiedName(qName));
+ for (int i = 0; i < this.reader.getNamespaceCount(); i++) {
+ String prefix = this.reader.getNamespacePrefix(i);
+ if (prefix == null) {
+ prefix = "";
+ }
+ endPrefixMapping(prefix);
+ }
+ }
+ else {
+ getContentHandler().endElement("", "", toQualifiedName(qName));
+ }
+ }
+ }
+
+ private void handleCharacters() throws SAXException {
+ if (XMLStreamConstants.CDATA == this.reader.getEventType() && getLexicalHandler() != null) {
+ getLexicalHandler().startCDATA();
+ }
+ if (getContentHandler() != null) {
+ getContentHandler().characters(this.reader.getTextCharacters(),
+ this.reader.getTextStart(), this.reader.getTextLength());
+ }
+ if (XMLStreamConstants.CDATA == this.reader.getEventType() && getLexicalHandler() != null) {
+ getLexicalHandler().endCDATA();
+ }
+ }
+
+ private void handleComment() throws SAXException {
+ if (getLexicalHandler() != null) {
+ getLexicalHandler().comment(this.reader.getTextCharacters(),
+ this.reader.getTextStart(), this.reader.getTextLength());
+ }
+ }
+
+ private void handleDtd() throws SAXException {
+ if (getLexicalHandler() != null) {
+ javax.xml.stream.Location location = this.reader.getLocation();
+ getLexicalHandler().startDTD(null, location.getPublicId(), location.getSystemId());
+ }
+ if (getLexicalHandler() != null) {
+ getLexicalHandler().endDTD();
+ }
+ }
+
+ private void handleEntityReference() throws SAXException {
+ if (getLexicalHandler() != null) {
+ getLexicalHandler().startEntity(this.reader.getLocalName());
+ }
+ if (getLexicalHandler() != null) {
+ getLexicalHandler().endEntity(this.reader.getLocalName());
+ }
+ }
+
+ private void handleEndDocument() throws SAXException {
+ if (getContentHandler() != null) {
+ getContentHandler().endDocument();
+ }
+ }
+
+ private void handleProcessingInstruction() throws SAXException {
+ if (getContentHandler() != null) {
+ getContentHandler().processingInstruction(this.reader.getPITarget(), this.reader.getPIData());
+ }
+ }
+
+ private Attributes getAttributes() {
+ AttributesImpl attributes = new AttributesImpl();
+ for (int i = 0; i < this.reader.getAttributeCount(); i++) {
+ String namespace = this.reader.getAttributeNamespace(i);
+ if (namespace == null || !hasNamespacesFeature()) {
+ namespace = "";
+ }
+ String type = this.reader.getAttributeType(i);
+ if (type == null) {
+ type = "CDATA";
+ }
+ attributes.addAttribute(namespace, this.reader.getAttributeLocalName(i),
+ toQualifiedName(this.reader.getAttributeName(i)), type, this.reader.getAttributeValue(i));
+ }
+ if (hasNamespacePrefixesFeature()) {
+ for (int i = 0; i < this.reader.getNamespaceCount(); i++) {
+ String prefix = this.reader.getNamespacePrefix(i);
+ String namespaceUri = this.reader.getNamespaceURI(i);
+ String qName;
+ if (StringUtils.hasLength(prefix)) {
+ qName = "xmlns:" + prefix;
+ }
+ else {
+ qName = "xmlns";
+ }
+ attributes.addAttribute("", "", qName, "CDATA", namespaceUri);
+ }
+ }
+
+ return attributes;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java b/spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java
new file mode 100644
index 00000000..41fd3f63
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright 2002-2013 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.util.xml;
+
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.stax.StAXResult;
+import javax.xml.transform.stax.StAXSource;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.XMLReader;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+
+/**
+ * Convenience methods for working with the StAX API.
+ *
+ * <p>In particular, methods for using StAX ({@code javax.xml.stream}) in combination with the TrAX API
+ * ({@code javax.xml.transform}), and converting StAX readers/writers into SAX readers/handlers and vice-versa.
+ *
+ * @author Arjen Poutsma
+ * @author Juergen Hoeller
+ * @since 3.0
+ */
+public abstract class StaxUtils {
+
+ // JAXP 1.4 is only available on JDK 1.6+
+ private static boolean jaxp14Available =
+ ClassUtils.isPresent("javax.xml.transform.stax.StAXSource", StaxUtils.class.getClassLoader());
+
+
+ // Stax Source
+
+ /**
+ * Create a custom, non-JAXP 1.4 StAX {@link Source} for the given {@link XMLStreamReader}.
+ * @param streamReader the StAX stream reader
+ * @return a source wrapping the {@code streamReader}
+ */
+ public static Source createCustomStaxSource(XMLStreamReader streamReader) {
+ return new StaxSource(streamReader);
+ }
+
+ /**
+ * Create a StAX {@link Source} for the given {@link XMLStreamReader}.
+ * <p>If JAXP 1.4 is available, this method returns a {@link StAXSource};
+ * otherwise it returns a custom StAX Source.
+ * @param streamReader the StAX stream reader
+ * @return a source wrapping the {@code streamReader}
+ * @see #createCustomStaxSource(XMLStreamReader)
+ */
+ public static Source createStaxSource(XMLStreamReader streamReader) {
+ if (jaxp14Available) {
+ return Jaxp14StaxHandler.createStaxSource(streamReader);
+ }
+ else {
+ return createCustomStaxSource(streamReader);
+ }
+ }
+
+ /**
+ * Create a custom, non-JAXP 1.4 StAX {@link Source} for the given {@link XMLEventReader}.
+ * @param eventReader the StAX event reader
+ * @return a source wrapping the {@code eventReader}
+ */
+ public static Source createCustomStaxSource(XMLEventReader eventReader) {
+ return new StaxSource(eventReader);
+ }
+
+ /**
+ * Create a StAX {@link Source} for the given {@link XMLEventReader}.
+ * <p>If JAXP 1.4 is available, this method returns a {@link StAXSource};
+ * otherwise it returns a custom StAX Source.
+ * @param eventReader the StAX event reader
+ * @return a source wrapping the {@code eventReader}
+ * @throws XMLStreamException in case of StAX errors
+ * @see #createCustomStaxSource(XMLEventReader)
+ */
+ public static Source createStaxSource(XMLEventReader eventReader) throws XMLStreamException {
+ if (jaxp14Available) {
+ return Jaxp14StaxHandler.createStaxSource(eventReader);
+ }
+ else {
+ return createCustomStaxSource(eventReader);
+ }
+ }
+
+ /**
+ * Indicate whether the given {@link Source} is a StAX Source.
+ * @return {@code true} if {@code source} is a custom StAX source or JAXP
+ * 1.4 {@link StAXSource}; {@code false} otherwise.
+ */
+ public static boolean isStaxSource(Source source) {
+ return ((source instanceof StaxSource) || (jaxp14Available && Jaxp14StaxHandler.isStaxSource(source)));
+ }
+
+ /**
+ * Indicate whether the given class is a StAX Source class.
+ * @return {@code true} if {@code source} is a custom StAX source or JAXP
+ * 1.4 {@link StAXSource} class; {@code false} otherwise.
+ */
+ public static boolean isStaxSourceClass(Class<? extends Source> clazz) {
+ return (StaxSource.class.equals(clazz) || (jaxp14Available && Jaxp14StaxHandler.isStaxSourceClass(clazz)));
+ }
+
+
+ // Stax Result
+
+ /**
+ * Create a custom, non-JAXP 1.4 StAX {@link Result} for the given {@link XMLStreamWriter}.
+ * @param streamWriter the StAX stream writer
+ * @return a source wrapping the {@code streamWriter}
+ */
+ public static Result createCustomStaxResult(XMLStreamWriter streamWriter) {
+ return new StaxResult(streamWriter);
+ }
+
+ /**
+ * Create a StAX {@link Result} for the given {@link XMLStreamWriter}.
+ * <p>If JAXP 1.4 is available, this method returns a {@link StAXResult};
+ * otherwise it returns a custom StAX Result.
+ * @param streamWriter the StAX stream writer
+ * @return a result wrapping the {@code streamWriter}
+ * @see #createCustomStaxResult(XMLStreamWriter)
+ */
+ public static Result createStaxResult(XMLStreamWriter streamWriter) {
+ if (jaxp14Available) {
+ return Jaxp14StaxHandler.createStaxResult(streamWriter);
+ }
+ else {
+ return createCustomStaxResult(streamWriter);
+ }
+ }
+
+ /**
+ * Create a custom, non-JAXP 1.4 StAX {@link Result} for the given {@link XMLEventWriter}.
+ * @param eventWriter the StAX event writer
+ * @return a source wrapping the {@code eventWriter}
+ */
+ public static Result createCustomStaxResult(XMLEventWriter eventWriter) {
+ return new StaxResult(eventWriter);
+ }
+
+ /**
+ * Create a StAX {@link Result} for the given {@link XMLEventWriter}.
+ * <p>If JAXP 1.4 is available, this method returns a {@link StAXResult}; otherwise it returns a
+ * custom StAX Result.
+ * @param eventWriter the StAX event writer
+ * @return a result wrapping {@code streamReader}
+ * @throws XMLStreamException in case of StAX errors
+ * @see #createCustomStaxResult(XMLEventWriter)
+ */
+ public static Result createStaxResult(XMLEventWriter eventWriter) throws XMLStreamException {
+ if (jaxp14Available) {
+ return Jaxp14StaxHandler.createStaxResult(eventWriter);
+ }
+ else {
+ return createCustomStaxResult(eventWriter);
+ }
+ }
+
+ /**
+ * Indicate whether the given {@link javax.xml.transform.Result} is a StAX Result.
+ * @return {@code true} if {@code result} is a custom Stax Result or JAXP 1.4
+ * {@link StAXResult}; {@code false} otherwise.
+ */
+ public static boolean isStaxResult(Result result) {
+ return (result instanceof StaxResult || (jaxp14Available && Jaxp14StaxHandler.isStaxResult(result)));
+ }
+
+ /**
+ * Return the {@link XMLStreamReader} for the given StAX Source.
+ * @param source a {@linkplain #createCustomStaxSource(XMLStreamReader) custom StAX Source} or
+ * JAXP 1.4 {@link StAXSource}
+ * @return the {@link XMLStreamReader}
+ * @throws IllegalArgumentException if {@code source} is neither a custom StAX Source
+ * nor JAXP 1.4 {@link StAXSource}
+ */
+ public static XMLStreamReader getXMLStreamReader(Source source) {
+ if (source instanceof StaxSource) {
+ return ((StaxSource) source).getXMLStreamReader();
+ }
+ else if (jaxp14Available) {
+ return Jaxp14StaxHandler.getXMLStreamReader(source);
+ }
+ else {
+ throw new IllegalArgumentException("Source '" + source + "' is neither StaxSource nor StAXSource");
+ }
+ }
+
+ /**
+ * Return the {@link XMLEventReader} for the given StAX Source.
+ * @param source a {@linkplain #createCustomStaxSource(XMLEventReader) custom StAX Source} or
+ * JAXP 1.4 {@link StAXSource}
+ * @return the {@link XMLEventReader}
+ * @throws IllegalArgumentException if {@code source} is neither a custom StAX Source
+ * nor a JAXP 1.4 {@link StAXSource}
+ */
+ public static XMLEventReader getXMLEventReader(Source source) {
+ if (source instanceof StaxSource) {
+ return ((StaxSource) source).getXMLEventReader();
+ }
+ else if (jaxp14Available) {
+ return Jaxp14StaxHandler.getXMLEventReader(source);
+ }
+ else {
+ throw new IllegalArgumentException("Source '" + source + "' is neither StaxSource nor StAXSource");
+ }
+ }
+
+ /**
+ * Return the {@link XMLStreamWriter} for the given StAX Result.
+ * @param result a {@linkplain #createCustomStaxResult(XMLStreamWriter) custom StAX Result} or
+ * JAXP 1.4 {@link StAXResult}
+ * @return the {@link XMLStreamReader}
+ * @throws IllegalArgumentException if {@code source} is neither a custom StAX Result
+ * nor a JAXP 1.4 {@link StAXResult}
+ */
+ public static XMLStreamWriter getXMLStreamWriter(Result result) {
+ if (result instanceof StaxResult) {
+ return ((StaxResult) result).getXMLStreamWriter();
+ }
+ else if (jaxp14Available) {
+ return Jaxp14StaxHandler.getXMLStreamWriter(result);
+ }
+ else {
+ throw new IllegalArgumentException("Result '" + result + "' is neither StaxResult nor StAXResult");
+ }
+ }
+
+ /**
+ * Return the {@link XMLEventWriter} for the given StAX Result.
+ * @param result a {@linkplain #createCustomStaxResult(XMLEventWriter) custom StAX Result} or
+ * JAXP 1.4 {@link StAXResult}
+ * @return the {@link XMLStreamReader}
+ * @throws IllegalArgumentException if {@code source} is neither a custom StAX Result
+ * nor a JAXP 1.4 {@link StAXResult}
+ */
+ public static XMLEventWriter getXMLEventWriter(Result result) {
+ if (result instanceof StaxResult) {
+ return ((StaxResult) result).getXMLEventWriter();
+ }
+ else if (jaxp14Available) {
+ return Jaxp14StaxHandler.getXMLEventWriter(result);
+ }
+ else {
+ throw new IllegalArgumentException("Result '" + result + "' is neither StaxResult nor StAXResult");
+ }
+ }
+
+ /**
+ * Create a SAX {@link ContentHandler} that writes to the given StAX {@link XMLStreamWriter}.
+ * @param streamWriter the StAX stream writer
+ * @return a content handler writing to the {@code streamWriter}
+ */
+ public static ContentHandler createContentHandler(XMLStreamWriter streamWriter) {
+ return new StaxStreamContentHandler(streamWriter);
+ }
+
+ /**
+ * Create a SAX {@link ContentHandler} that writes events to the given StAX {@link XMLEventWriter}.
+ * @param eventWriter the StAX event writer
+ * @return a content handler writing to the {@code eventWriter}
+ */
+ public static ContentHandler createContentHandler(XMLEventWriter eventWriter) {
+ return new StaxEventContentHandler(eventWriter);
+ }
+
+ /**
+ * Create a SAX {@link XMLReader} that reads from the given StAX {@link XMLStreamReader}.
+ * @param streamReader the StAX stream reader
+ * @return a XMLReader reading from the {@code streamWriter}
+ */
+ public static XMLReader createXMLReader(XMLStreamReader streamReader) {
+ return new StaxStreamXMLReader(streamReader);
+ }
+
+ /**
+ * Create a SAX {@link XMLReader} that reads from the given StAX {@link XMLEventReader}.
+ * @param eventReader the StAX event reader
+ * @return a XMLReader reading from the {@code eventWriter}
+ */
+ public static XMLReader createXMLReader(XMLEventReader eventReader) {
+ return new StaxEventXMLReader(eventReader);
+ }
+
+ /**
+ * Return a {@link XMLStreamReader} that reads from a {@link XMLEventReader}. Useful, because the StAX
+ * {@code XMLInputFactory} allows one to create a event reader from a stream reader, but not vice-versa.
+ * @return a stream reader that reads from an event reader
+ */
+ public static XMLStreamReader createEventStreamReader(XMLEventReader eventReader) throws XMLStreamException {
+ return new XMLEventStreamReader(eventReader);
+ }
+
+ /**
+ * Return a {@link XMLStreamWriter} that writes to a {@link XMLEventWriter}.
+ * @return a stream writer that writes to an event writer
+ * @since 3.2
+ */
+ public static XMLStreamWriter createEventStreamWriter(XMLEventWriter eventWriter) {
+ return new XMLEventStreamWriter(eventWriter, XMLEventFactory.newFactory());
+ }
+
+ /**
+ * Return a {@link XMLStreamWriter} that writes to a {@link XMLEventWriter}.
+ * @return a stream writer that writes to an event writer
+ * @since 3.0.5
+ */
+ public static XMLStreamWriter createEventStreamWriter(XMLEventWriter eventWriter, XMLEventFactory eventFactory) {
+ return new XMLEventStreamWriter(eventWriter, eventFactory);
+ }
+
+
+ /**
+ * Inner class to avoid a static JAXP 1.4 dependency.
+ */
+ private static class Jaxp14StaxHandler {
+
+ private static Source createStaxSource(XMLStreamReader streamReader) {
+ return new StAXSource(streamReader);
+ }
+
+ private static Source createStaxSource(XMLEventReader eventReader) throws XMLStreamException {
+ return new StAXSource(eventReader);
+ }
+
+ private static Result createStaxResult(XMLStreamWriter streamWriter) {
+ return new StAXResult(streamWriter);
+ }
+
+ private static Result createStaxResult(XMLEventWriter eventWriter) {
+ return new StAXResult(eventWriter);
+ }
+
+ private static boolean isStaxSource(Source source) {
+ return (source instanceof StAXSource);
+ }
+
+ private static boolean isStaxSourceClass(Class<? extends Source> clazz) {
+ return StAXSource.class.equals(clazz);
+ }
+
+ private static boolean isStaxResult(Result result) {
+ return (result instanceof StAXResult);
+ }
+
+ private static XMLStreamReader getXMLStreamReader(Source source) {
+ Assert.isInstanceOf(StAXSource.class, source);
+ return ((StAXSource) source).getXMLStreamReader();
+ }
+
+ private static XMLEventReader getXMLEventReader(Source source) {
+ Assert.isInstanceOf(StAXSource.class, source);
+ return ((StAXSource) source).getXMLEventReader();
+ }
+
+ private static XMLStreamWriter getXMLStreamWriter(Result result) {
+ Assert.isInstanceOf(StAXResult.class, result);
+ return ((StAXResult) result).getXMLStreamWriter();
+ }
+
+ private static XMLEventWriter getXMLEventWriter(Result result) {
+ Assert.isInstanceOf(StAXResult.class, result);
+ return ((StAXResult) result).getXMLEventWriter();
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/TransformerUtils.java b/spring-core/src/main/java/org/springframework/util/xml/TransformerUtils.java
new file mode 100644
index 00000000..4b48abd4
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/TransformerUtils.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2012 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.util.xml;
+
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+
+import org.springframework.util.Assert;
+
+/**
+ * Contains common behavior relating to {@link javax.xml.transform.Transformer Transformers}, and the
+ * {@code javax.xml.transform} package in general.
+ *
+ * @author Rick Evans
+ * @author Juergen Hoeller
+ * @since 2.5.5
+ */
+public abstract class TransformerUtils {
+
+ /**
+ * The indent amount of characters if {@link #enableIndenting(javax.xml.transform.Transformer) indenting is enabled}.
+ * <p>Defaults to "2".
+ */
+ public static final int DEFAULT_INDENT_AMOUNT = 2;
+
+ /**
+ * Enable indenting for the supplied {@link javax.xml.transform.Transformer}. <p>If the underlying XSLT engine is
+ * Xalan, then the special output key {@code indent-amount} will be also be set to a value of {@link
+ * #DEFAULT_INDENT_AMOUNT} characters.
+ *
+ * @param transformer the target transformer
+ * @see javax.xml.transform.Transformer#setOutputProperty(String, String)
+ * @see javax.xml.transform.OutputKeys#INDENT
+ */
+ public static void enableIndenting(Transformer transformer) {
+ enableIndenting(transformer, DEFAULT_INDENT_AMOUNT);
+ }
+
+ /**
+ * Enable indenting for the supplied {@link javax.xml.transform.Transformer}. <p>If the underlying XSLT engine is
+ * Xalan, then the special output key {@code indent-amount} will be also be set to a value of {@link
+ * #DEFAULT_INDENT_AMOUNT} characters.
+ *
+ * @param transformer the target transformer
+ * @param indentAmount the size of the indent (2 characters, 3 characters, etc.)
+ * @see javax.xml.transform.Transformer#setOutputProperty(String, String)
+ * @see javax.xml.transform.OutputKeys#INDENT
+ */
+ public static void enableIndenting(Transformer transformer, int indentAmount) {
+ Assert.notNull(transformer, "Transformer must not be null");
+ Assert.isTrue(indentAmount > -1, "The indent amount cannot be less than zero : got " + indentAmount);
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ try {
+ // Xalan-specific, but this is the most common XSLT engine in any case
+ transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", String.valueOf(indentAmount));
+ }
+ catch (IllegalArgumentException ignored) {
+ }
+ }
+
+ /**
+ * Disable indenting for the supplied {@link javax.xml.transform.Transformer}.
+ *
+ * @param transformer the target transformer
+ * @see javax.xml.transform.OutputKeys#INDENT
+ */
+ public static void disableIndenting(Transformer transformer) {
+ Assert.notNull(transformer, "Transformer must not be null");
+ transformer.setOutputProperty(OutputKeys.INDENT, "no");
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java b/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java
new file mode 100644
index 00000000..b8321a05
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2002-2013 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.util.xml;
+
+import java.util.Iterator;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+import javax.xml.stream.Location;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.Comment;
+import javax.xml.stream.events.Namespace;
+import javax.xml.stream.events.ProcessingInstruction;
+import javax.xml.stream.events.StartDocument;
+import javax.xml.stream.events.XMLEvent;
+
+/**
+ * Implementation of the {@link javax.xml.stream.XMLStreamReader} interface that wraps a
+ * {@link XMLEventReader}. Useful because the StAX {@link javax.xml.stream.XMLInputFactory}
+ * allows one to create a event reader from a stream reader, but not vice-versa.
+ *
+ * @author Arjen Poutsma
+ * @since 3.0
+ * @see StaxUtils#createEventStreamReader(javax.xml.stream.XMLEventReader)
+ */
+class XMLEventStreamReader extends AbstractXMLStreamReader {
+
+ private XMLEvent event;
+
+ private final XMLEventReader eventReader;
+
+
+ public XMLEventStreamReader(XMLEventReader eventReader) throws XMLStreamException {
+ this.eventReader = eventReader;
+ this.event = eventReader.nextEvent();
+ }
+
+
+ public QName getName() {
+ if (this.event.isStartElement()) {
+ return this.event.asStartElement().getName();
+ }
+ else if (this.event.isEndElement()) {
+ return this.event.asEndElement().getName();
+ }
+ else {
+ throw new IllegalStateException();
+ }
+ }
+
+ public Location getLocation() {
+ return this.event.getLocation();
+ }
+
+ public int getEventType() {
+ return this.event.getEventType();
+ }
+
+ public String getVersion() {
+ if (this.event.isStartDocument()) {
+ return ((StartDocument) this.event).getVersion();
+ }
+ else {
+ return null;
+ }
+ }
+
+ public Object getProperty(String name) throws IllegalArgumentException {
+ return this.eventReader.getProperty(name);
+ }
+
+ public boolean isStandalone() {
+ if (this.event.isStartDocument()) {
+ return ((StartDocument) event).isStandalone();
+ }
+ else {
+ throw new IllegalStateException();
+ }
+ }
+
+ public boolean standaloneSet() {
+ if (this.event.isStartDocument()) {
+ return ((StartDocument) this.event).standaloneSet();
+ }
+ else {
+ throw new IllegalStateException();
+ }
+ }
+
+ public String getEncoding() {
+ return null;
+ }
+
+ public String getCharacterEncodingScheme() {
+ return null;
+ }
+
+ public String getPITarget() {
+ if (this.event.isProcessingInstruction()) {
+ return ((ProcessingInstruction) this.event).getTarget();
+ }
+ else {
+ throw new IllegalStateException();
+ }
+ }
+
+ public String getPIData() {
+ if (this.event.isProcessingInstruction()) {
+ return ((ProcessingInstruction) this.event).getData();
+ }
+ else {
+ throw new IllegalStateException();
+ }
+ }
+
+ public int getTextStart() {
+ return 0;
+ }
+
+ public String getText() {
+ if (this.event.isCharacters()) {
+ return event.asCharacters().getData();
+ }
+ else if (this.event.getEventType() == XMLEvent.COMMENT) {
+ return ((Comment) this.event).getText();
+ }
+ else {
+ throw new IllegalStateException();
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ public int getAttributeCount() {
+ if (!this.event.isStartElement()) {
+ throw new IllegalStateException();
+ }
+ Iterator attributes = this.event.asStartElement().getAttributes();
+ return countIterator(attributes);
+ }
+
+ public boolean isAttributeSpecified(int index) {
+ return getAttribute(index).isSpecified();
+ }
+
+ public QName getAttributeName(int index) {
+ return getAttribute(index).getName();
+ }
+
+ public String getAttributeType(int index) {
+ return getAttribute(index).getDTDType();
+ }
+
+ public String getAttributeValue(int index) {
+ return getAttribute(index).getValue();
+ }
+
+ @SuppressWarnings("rawtypes")
+ private Attribute getAttribute(int index) {
+ if (!this.event.isStartElement()) {
+ throw new IllegalStateException();
+ }
+ int count = 0;
+ Iterator attributes = this.event.asStartElement().getAttributes();
+ while (attributes.hasNext()) {
+ Attribute attribute = (Attribute) attributes.next();
+ if (count == index) {
+ return attribute;
+ }
+ else {
+ count++;
+ }
+ }
+ throw new IllegalArgumentException();
+ }
+
+ public NamespaceContext getNamespaceContext() {
+ if (this.event.isStartElement()) {
+ return this.event.asStartElement().getNamespaceContext();
+ }
+ else {
+ throw new IllegalStateException();
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ public int getNamespaceCount() {
+ Iterator namespaces;
+ if (this.event.isStartElement()) {
+ namespaces = this.event.asStartElement().getNamespaces();
+ }
+ else if (this.event.isEndElement()) {
+ namespaces = this.event.asEndElement().getNamespaces();
+ }
+ else {
+ throw new IllegalStateException();
+ }
+ return countIterator(namespaces);
+ }
+
+ public String getNamespacePrefix(int index) {
+ return getNamespace(index).getPrefix();
+ }
+
+ public String getNamespaceURI(int index) {
+ return getNamespace(index).getNamespaceURI();
+ }
+
+ @SuppressWarnings("rawtypes")
+ private Namespace getNamespace(int index) {
+ Iterator namespaces;
+ if (this.event.isStartElement()) {
+ namespaces = this.event.asStartElement().getNamespaces();
+ }
+ else if (this.event.isEndElement()) {
+ namespaces = this.event.asEndElement().getNamespaces();
+ }
+ else {
+ throw new IllegalStateException();
+ }
+ int count = 0;
+ while (namespaces.hasNext()) {
+ Namespace namespace = (Namespace) namespaces.next();
+ if (count == index) {
+ return namespace;
+ }
+ else {
+ count++;
+ }
+ }
+ throw new IllegalArgumentException();
+ }
+
+ public int next() throws XMLStreamException {
+ this.event = this.eventReader.nextEvent();
+ return this.event.getEventType();
+ }
+
+ public void close() throws XMLStreamException {
+ this.eventReader.close();
+ }
+
+
+ @SuppressWarnings("rawtypes")
+ private static int countIterator(Iterator iterator) {
+ int count = 0;
+ while (iterator.hasNext()) {
+ iterator.next();
+ count++;
+ }
+ return count;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamWriter.java b/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamWriter.java
new file mode 100644
index 00000000..d65c4a36
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamWriter.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2002-2013 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.util.xml;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.Namespace;
+import javax.xml.stream.events.StartElement;
+
+import org.springframework.util.Assert;
+
+/**
+ * Implementation of the {@link javax.xml.stream.XMLStreamWriter} interface
+ * that wraps an {@link XMLEventWriter}.
+ *
+ * @author Arjen Poutsma
+ * @since 3.0.5
+ * @see StaxUtils#createEventStreamWriter(javax.xml.stream.XMLEventWriter, javax.xml.stream.XMLEventFactory)
+ */
+class XMLEventStreamWriter implements XMLStreamWriter {
+
+ private static final String DEFAULT_ENCODING = "UTF-8";
+
+ private final XMLEventWriter eventWriter;
+
+ private final XMLEventFactory eventFactory;
+
+ private final List<EndElement> endElements = new ArrayList<EndElement>();
+
+ private boolean emptyElement = false;
+
+
+ public XMLEventStreamWriter(XMLEventWriter eventWriter, XMLEventFactory eventFactory) {
+ Assert.notNull(eventWriter, "'eventWriter' must not be null");
+ Assert.notNull(eventFactory, "'eventFactory' must not be null");
+ this.eventWriter = eventWriter;
+ this.eventFactory = eventFactory;
+ }
+
+
+ public void setNamespaceContext(NamespaceContext context) throws XMLStreamException {
+ this.eventWriter.setNamespaceContext(context);
+ }
+
+ public NamespaceContext getNamespaceContext() {
+ return this.eventWriter.getNamespaceContext();
+ }
+
+ public void setPrefix(String prefix, String uri) throws XMLStreamException {
+ this.eventWriter.setPrefix(prefix, uri);
+ }
+
+ public String getPrefix(String uri) throws XMLStreamException {
+ return this.eventWriter.getPrefix(uri);
+ }
+
+ public void setDefaultNamespace(String uri) throws XMLStreamException {
+ this.eventWriter.setDefaultNamespace(uri);
+ }
+
+ public Object getProperty(String name) throws IllegalArgumentException {
+ throw new IllegalArgumentException();
+ }
+
+
+ public void writeStartDocument() throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ this.eventWriter.add(this.eventFactory.createStartDocument());
+ }
+
+ public void writeStartDocument(String version) throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ this.eventWriter.add(this.eventFactory.createStartDocument(DEFAULT_ENCODING, version));
+ }
+
+ public void writeStartDocument(String encoding, String version) throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ this.eventWriter.add(this.eventFactory.createStartDocument(encoding, version));
+ }
+
+ public void writeStartElement(String localName) throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ doWriteStartElement(this.eventFactory.createStartElement(new QName(localName), null, null));
+ }
+
+ public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ doWriteStartElement(this.eventFactory.createStartElement(new QName(namespaceURI, localName), null, null));
+ }
+
+ public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ doWriteStartElement(this.eventFactory.createStartElement(new QName(namespaceURI, localName, prefix), null, null));
+ }
+
+ private void doWriteStartElement(StartElement startElement) throws XMLStreamException {
+ this.eventWriter.add(startElement);
+ this.endElements.add(this.eventFactory.createEndElement(startElement.getName(), startElement.getNamespaces()));
+ }
+
+ public void writeEmptyElement(String localName) throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ writeStartElement(localName);
+ this.emptyElement = true;
+ }
+
+ public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ writeStartElement(namespaceURI, localName);
+ this.emptyElement = true;
+ }
+
+ public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ writeStartElement(prefix, localName, namespaceURI);
+ this.emptyElement = true;
+ }
+
+ private void closeEmptyElementIfNecessary() throws XMLStreamException {
+ if (this.emptyElement) {
+ this.emptyElement = false;
+ writeEndElement();
+ }
+ }
+
+ public void writeEndElement() throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ int last = this.endElements.size() - 1;
+ EndElement lastEndElement = this.endElements.get(last);
+ this.eventWriter.add(lastEndElement);
+ this.endElements.remove(last);
+ }
+
+ public void writeAttribute(String localName, String value) throws XMLStreamException {
+ this.eventWriter.add(this.eventFactory.createAttribute(localName, value));
+ }
+
+ public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException {
+ this.eventWriter.add(this.eventFactory.createAttribute(new QName(namespaceURI, localName), value));
+ }
+
+ public void writeAttribute(String prefix, String namespaceURI, String localName, String value)
+ throws XMLStreamException {
+
+ this.eventWriter.add(this.eventFactory.createAttribute(prefix, namespaceURI, localName, value));
+ }
+
+ public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException {
+ doWriteNamespace(this.eventFactory.createNamespace(prefix, namespaceURI));
+ }
+
+ public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException {
+ doWriteNamespace(this.eventFactory.createNamespace(namespaceURI));
+ }
+
+ @SuppressWarnings("rawtypes")
+ private void doWriteNamespace(Namespace namespace) throws XMLStreamException {
+ int last = this.endElements.size() - 1;
+ EndElement oldEndElement = this.endElements.get(last);
+ Iterator oldNamespaces = oldEndElement.getNamespaces();
+ List<Namespace> newNamespaces = new ArrayList<Namespace>();
+ while (oldNamespaces.hasNext()) {
+ Namespace oldNamespace = (Namespace) oldNamespaces.next();
+ newNamespaces.add(oldNamespace);
+ }
+ newNamespaces.add(namespace);
+ EndElement newEndElement = this.eventFactory.createEndElement(oldEndElement.getName(), newNamespaces.iterator());
+ this.eventWriter.add(namespace);
+ this.endElements.set(last, newEndElement);
+ }
+
+ public void writeCharacters(String text) throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ this.eventWriter.add(this.eventFactory.createCharacters(text));
+ }
+
+ public void writeCharacters(char[] text, int start, int len) throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ this.eventWriter.add(this.eventFactory.createCharacters(new String(text, start, len)));
+ }
+
+ public void writeCData(String data) throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ this.eventWriter.add(this.eventFactory.createCData(data));
+ }
+
+ public void writeComment(String data) throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ this.eventWriter.add(this.eventFactory.createComment(data));
+ }
+
+ public void writeProcessingInstruction(String target) throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ this.eventWriter.add(this.eventFactory.createProcessingInstruction(target, ""));
+ }
+
+ public void writeProcessingInstruction(String target, String data) throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ this.eventWriter.add(this.eventFactory.createProcessingInstruction(target, data));
+ }
+
+ public void writeDTD(String dtd) throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ this.eventWriter.add(this.eventFactory.createDTD(dtd));
+ }
+
+ public void writeEntityRef(String name) throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ this.eventWriter.add(this.eventFactory.createEntityReference(name, null));
+ }
+
+ public void writeEndDocument() throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ this.eventWriter.add(this.eventFactory.createEndDocument());
+ }
+
+ public void flush() throws XMLStreamException {
+ this.eventWriter.flush();
+ }
+
+ public void close() throws XMLStreamException {
+ closeEmptyElementIfNecessary();
+ this.eventWriter.close();
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java b/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java
new file mode 100644
index 00000000..d924fd15
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2002-2014 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.util.xml;
+
+import java.io.BufferedReader;
+import java.io.CharConversionException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * Detects whether an XML stream is using DTD- or XSD-based validation.
+ *
+ * @author Rob Harrop
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class XmlValidationModeDetector {
+
+ /**
+ * Indicates that the validation should be disabled.
+ */
+ public static final int VALIDATION_NONE = 0;
+
+ /**
+ * Indicates that the validation mode should be auto-guessed, since we cannot find
+ * a clear indication (probably choked on some special characters, or the like).
+ */
+ public static final int VALIDATION_AUTO = 1;
+
+ /**
+ * Indicates that DTD validation should be used (we found a "DOCTYPE" declaration).
+ */
+ public static final int VALIDATION_DTD = 2;
+
+ /**
+ * Indicates that XSD validation should be used (found no "DOCTYPE" declaration).
+ */
+ public static final int VALIDATION_XSD = 3;
+
+
+ /**
+ * The token in a XML document that declares the DTD to use for validation
+ * and thus that DTD validation is being used.
+ */
+ private static final String DOCTYPE = "DOCTYPE";
+
+ /**
+ * The token that indicates the start of an XML comment.
+ */
+ private static final String START_COMMENT = "<!--";
+
+ /**
+ * The token that indicates the end of an XML comment.
+ */
+ private static final String END_COMMENT = "-->";
+
+
+ /**
+ * Indicates whether or not the current parse position is inside an XML comment.
+ */
+ private boolean inComment;
+
+
+ /**
+ * Detect the validation mode for the XML document in the supplied {@link InputStream}.
+ * Note that the supplied {@link InputStream} is closed by this method before returning.
+ * @param inputStream the InputStream to parse
+ * @throws IOException in case of I/O failure
+ * @see #VALIDATION_DTD
+ * @see #VALIDATION_XSD
+ */
+ public int detectValidationMode(InputStream inputStream) throws IOException {
+ // Peek into the file to look for DOCTYPE.
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+ try {
+ boolean isDtdValidated = false;
+ String content;
+ while ((content = reader.readLine()) != null) {
+ content = consumeCommentTokens(content);
+ if (this.inComment || !StringUtils.hasText(content)) {
+ continue;
+ }
+ if (hasDoctype(content)) {
+ isDtdValidated = true;
+ break;
+ }
+ if (hasOpeningTag(content)) {
+ // End of meaningful data...
+ break;
+ }
+ }
+ return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
+ }
+ catch (CharConversionException ex) {
+ // Choked on some character encoding...
+ // Leave the decision up to the caller.
+ return VALIDATION_AUTO;
+ }
+ finally {
+ reader.close();
+ }
+ }
+
+
+ /**
+ * Does the content contain the the DTD DOCTYPE declaration?
+ */
+ private boolean hasDoctype(String content) {
+ return content.contains(DOCTYPE);
+ }
+
+ /**
+ * Does the supplied content contain an XML opening tag. If the parse state is currently
+ * in an XML comment then this method always returns false. It is expected that all comment
+ * tokens will have consumed for the supplied content before passing the remainder to this method.
+ */
+ private boolean hasOpeningTag(String content) {
+ if (this.inComment) {
+ return false;
+ }
+ int openTagIndex = content.indexOf('<');
+ return (openTagIndex > -1 && (content.length() > openTagIndex + 1) &&
+ Character.isLetter(content.charAt(openTagIndex + 1)));
+ }
+
+ /**
+ * Consumes all the leading comment data in the given String and returns the remaining content, which
+ * may be empty since the supplied content might be all comment data. For our purposes it is only important
+ * to strip leading comment content on a line since the first piece of non comment content will be either
+ * the DOCTYPE declaration or the root element of the document.
+ */
+ private String consumeCommentTokens(String line) {
+ if (!line.contains(START_COMMENT) && !line.contains(END_COMMENT)) {
+ return line;
+ }
+ while ((line = consume(line)) != null) {
+ if (!this.inComment && !line.trim().startsWith(START_COMMENT)) {
+ return line;
+ }
+ }
+ return line;
+ }
+
+ /**
+ * Consume the next comment token, update the "inComment" flag
+ * and return the remaining content.
+ */
+ private String consume(String line) {
+ int index = (this.inComment ? endComment(line) : startComment(line));
+ return (index == -1 ? null : line.substring(index));
+ }
+
+ /**
+ * Try to consume the {@link #START_COMMENT} token.
+ * @see #commentToken(String, String, boolean)
+ */
+ private int startComment(String line) {
+ return commentToken(line, START_COMMENT, true);
+ }
+
+ private int endComment(String line) {
+ return commentToken(line, END_COMMENT, false);
+ }
+
+ /**
+ * Try to consume the supplied token against the supplied content and update the
+ * in comment parse state to the supplied value. Returns the index into the content
+ * which is after the token or -1 if the token is not found.
+ */
+ private int commentToken(String line, String token, boolean inCommentIfPresent) {
+ int index = line.indexOf(token);
+ if (index > - 1) {
+ this.inComment = inCommentIfPresent;
+ }
+ return (index == -1 ? index : index + token.length());
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/xml/package-info.java b/spring-core/src/main/java/org/springframework/util/xml/package-info.java
new file mode 100644
index 00000000..86b0691b
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/package-info.java
@@ -0,0 +1,9 @@
+
+/**
+ *
+ * Miscellaneous utility classes for XML parsing and transformation,
+ * such as error handlers that log warnings via Commons Logging.
+ *
+ */
+package org.springframework.util.xml;
+
diff --git a/spring-core/src/main/java/overview.html b/spring-core/src/main/java/overview.html
new file mode 100644
index 00000000..2acb036f
--- /dev/null
+++ b/spring-core/src/main/java/overview.html
@@ -0,0 +1,7 @@
+<html>
+<body>
+<p>
+Spring's core utilities, used by many other Spring modules.
+</p>
+</body>
+</html> \ No newline at end of file