summaryrefslogtreecommitdiff
path: root/spring-core/src/main/java/org
diff options
context:
space:
mode:
authorEmmanuel Bourg <ebourg@apache.org>2015-07-15 23:21:27 +0200
committerEmmanuel Bourg <ebourg@apache.org>2015-07-15 23:21:27 +0200
commitda46d30e80e4c59a41cf52055d06faa1dbb7e383 (patch)
tree52b707fbbccd5b6100088913f32c1cbd00568790 /spring-core/src/main/java/org
parentc03c348db4e91c613982cbe6c99d0cf04ea14fe3 (diff)
Imported Upstream version 4.0.9
Diffstat (limited to 'spring-core/src/main/java/org')
-rw-r--r--spring-core/src/main/java/org/springframework/asm/AnnotationVisitor.java169
-rw-r--r--spring-core/src/main/java/org/springframework/asm/AnnotationWriter.java371
-rw-r--r--spring-core/src/main/java/org/springframework/asm/Attribute.java255
-rw-r--r--spring-core/src/main/java/org/springframework/asm/ByteVector.java339
-rw-r--r--spring-core/src/main/java/org/springframework/asm/ClassReader.java2511
-rw-r--r--spring-core/src/main/java/org/springframework/asm/ClassVisitor.java322
-rw-r--r--spring-core/src/main/java/org/springframework/asm/ClassWriter.java1776
-rw-r--r--spring-core/src/main/java/org/springframework/asm/Context.java145
-rw-r--r--spring-core/src/main/java/org/springframework/asm/Edge.java75
-rw-r--r--spring-core/src/main/java/org/springframework/asm/FieldVisitor.java152
-rw-r--r--spring-core/src/main/java/org/springframework/asm/FieldWriter.java329
-rw-r--r--spring-core/src/main/java/org/springframework/asm/Frame.java1462
-rw-r--r--spring-core/src/main/java/org/springframework/asm/Handle.java170
-rw-r--r--spring-core/src/main/java/org/springframework/asm/Handler.java121
-rw-r--r--spring-core/src/main/java/org/springframework/asm/Item.java314
-rw-r--r--spring-core/src/main/java/org/springframework/asm/Label.java565
-rw-r--r--spring-core/src/main/java/org/springframework/asm/MethodVisitor.java890
-rw-r--r--spring-core/src/main/java/org/springframework/asm/MethodWriter.java2913
-rw-r--r--spring-core/src/main/java/org/springframework/asm/Opcodes.java361
-rw-r--r--spring-core/src/main/java/org/springframework/asm/Type.java896
-rw-r--r--spring-core/src/main/java/org/springframework/asm/TypePath.java196
-rw-r--r--spring-core/src/main/java/org/springframework/asm/TypeReference.java452
-rw-r--r--spring-core/src/main/java/org/springframework/cglib/SpringCglibInfo.java8
-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.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/AttributeAccessorSupport.java5
-rw-r--r--spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java60
-rw-r--r--spring-core/src/main/java/org/springframework/core/CollectionFactory.java173
-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.java10
-rw-r--r--spring-core/src/main/java/org/springframework/core/ControlFlow.java4
-rw-r--r--spring-core/src/main/java/org/springframework/core/ControlFlowFactory.java9
-rw-r--r--spring-core/src/main/java/org/springframework/core/Conventions.java46
-rw-r--r--spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java45
-rw-r--r--spring-core/src/main/java/org/springframework/core/ExceptionDepthComparator.java5
-rw-r--r--spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java273
-rw-r--r--spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java374
-rw-r--r--spring-core/src/main/java/org/springframework/core/JdkVersion.java81
-rw-r--r--spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java2
-rw-r--r--spring-core/src/main/java/org/springframework/core/MethodParameter.java191
-rw-r--r--spring-core/src/main/java/org/springframework/core/NestedCheckedException.java2
-rw-r--r--spring-core/src/main/java/org/springframework/core/NestedIOException.java5
-rw-r--r--spring-core/src/main/java/org/springframework/core/NestedRuntimeException.java2
-rw-r--r--spring-core/src/main/java/org/springframework/core/OrderComparator.java20
-rw-r--r--spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java8
-rw-r--r--spring-core/src/main/java/org/springframework/core/ParameterizedTypeReference.java2
-rw-r--r--spring-core/src/main/java/org/springframework/core/PrioritizedParameterNameDiscoverer.java4
-rw-r--r--spring-core/src/main/java/org/springframework/core/ResolvableType.java1365
-rw-r--r--spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java408
-rw-r--r--spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java4
-rw-r--r--spring-core/src/main/java/org/springframework/core/SmartClassLoader.java2
-rw-r--r--spring-core/src/main/java/org/springframework/core/StandardReflectionParameterNameDiscoverer.java61
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java253
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java67
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java19
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java466
-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/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/TypeDescriptor.java740
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/converter/ConvertingComparator.java4
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ArrayToArrayConverter.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ArrayToCollectionConverter.java4
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ArrayToObjectConverter.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ArrayToStringConverter.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ByteBufferConverter.java115
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/CharacterToNumberFactory.java4
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/CollectionToArrayConverter.java5
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/CollectionToCollectionConverter.java4
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/CollectionToObjectConverter.java5
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/CollectionToStringConverter.java5
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java55
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java2
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/FallbackObjectToStringConverter.java16
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java75
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/IdToEntityConverter.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/MapToMapConverter.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/NumberToCharacterConverter.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/NumberToNumberConverterFactory.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ObjectToArrayConverter.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ObjectToCollectionConverter.java4
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java68
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ObjectToStringConverter.java1
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/PropertiesToStringConverter.java1
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToArrayConverter.java5
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToBooleanConverter.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToCharacterConverter.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToCollectionConverter.java4
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java2
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToLocaleConverter.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToNumberConverterFactory.java4
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToPropertiesConverter.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToUUIDConverter.java1
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ZoneIdToTimeZoneConverter.java44
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ZonedDateTimeToCalendarConverter.java45
-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.java27
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java15
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/CommandLinePropertySource.java2
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/EnumerablePropertySource.java1
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/JOptCommandLinePropertySource.java17
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/MutablePropertySources.java41
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/PropertiesPropertySource.java9
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java12
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/SimpleCommandLineArgsParser.java14
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/SimpleCommandLinePropertySource.java10
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/AbstractResource.java10
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/ByteArrayResource.java2
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java2
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/ClassRelativeResourceLoader.java10
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java12
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/DescriptiveResource.java2
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java4
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/FileSystemResourceLoader.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/InputStreamResource.java2
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/PathResource.java252
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/Resource.java1
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/UrlResource.java2
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/VfsResource.java17
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/VfsUtils.java101
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java4
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java5
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/ResourcePropertySource.java26
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/VfsPatternUtils.java5
-rw-r--r--spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java1
-rw-r--r--spring-core/src/main/java/org/springframework/core/serializer/DefaultSerializer.java1
-rw-r--r--spring-core/src/main/java/org/springframework/core/serializer/support/DeserializingConverter.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/serializer/support/SerializingConverter.java1
-rw-r--r--spring-core/src/main/java/org/springframework/core/style/DefaultToStringStyler.java5
-rw-r--r--spring-core/src/main/java/org/springframework/core/style/DefaultValueStyler.java23
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/AsyncListenableTaskExecutor.java52
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java23
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/SyncTaskExecutor.java1
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/TaskExecutor.java1
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/support/ConcurrentExecutorAdapter.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/support/ExecutorServiceAdapter.java6
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java36
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/AnnotatedTypeMetadata.java100
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java65
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java27
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java131
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/StandardClassMetadata.java24
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java57
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/AbstractRecursiveAnnotationVisitor.java95
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java244
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java101
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java164
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/ClassMetadataReadingVisitor.java66
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java60
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationArrayVisitor.java85
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationAttributesVisitor.java90
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReader.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/SimpleMetadataReaderFactory.java2
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/filter/AbstractClassTestingTypeFilter.java1
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java74
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/filter/AspectJTypeFilter.java1
-rw-r--r--spring-core/src/main/java/org/springframework/util/AlternativeJdkIdGenerator.java64
-rw-r--r--spring-core/src/main/java/org/springframework/util/AntPathMatcher.java369
-rw-r--r--spring-core/src/main/java/org/springframework/util/Assert.java8
-rw-r--r--spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java28
-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.java74
-rw-r--r--spring-core/src/main/java/org/springframework/util/CollectionUtils.java82
-rw-r--r--spring-core/src/main/java/org/springframework/util/CompositeIterator.java3
-rw-r--r--spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java16
-rw-r--r--spring-core/src/main/java/org/springframework/util/CustomizableThreadCreator.java19
-rw-r--r--spring-core/src/main/java/org/springframework/util/DefaultPropertiesPersister.java162
-rw-r--r--spring-core/src/main/java/org/springframework/util/IdGenerator.java (renamed from spring-core/src/main/java/org/springframework/asm/util/package-info.java)23
-rw-r--r--spring-core/src/main/java/org/springframework/util/InvalidMimeTypeException.java52
-rw-r--r--spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java19
-rw-r--r--spring-core/src/main/java/org/springframework/util/MimeType.java510
-rw-r--r--spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java328
-rw-r--r--spring-core/src/main/java/org/springframework/util/NumberUtils.java20
-rw-r--r--spring-core/src/main/java/org/springframework/util/PropertyPlaceholderHelper.java1
-rw-r--r--spring-core/src/main/java/org/springframework/util/ReflectionUtils.java6
-rw-r--r--spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java91
-rw-r--r--spring-core/src/main/java/org/springframework/util/ResourceUtils.java11
-rw-r--r--spring-core/src/main/java/org/springframework/util/SocketUtils.java304
-rw-r--r--spring-core/src/main/java/org/springframework/util/StreamUtils.java1
-rw-r--r--spring-core/src/main/java/org/springframework/util/StringUtils.java17
-rw-r--r--spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java1
-rw-r--r--spring-core/src/main/java/org/springframework/util/WeakReferenceMonitor.java11
-rw-r--r--spring-core/src/main/java/org/springframework/util/comparator/BooleanComparator.java1
-rw-r--r--spring-core/src/main/java/org/springframework/util/comparator/ComparableComparator.java1
-rw-r--r--spring-core/src/main/java/org/springframework/util/comparator/CompoundComparator.java33
-rw-r--r--spring-core/src/main/java/org/springframework/util/comparator/InstanceComparator.java1
-rw-r--r--spring-core/src/main/java/org/springframework/util/comparator/InvertibleComparator.java1
-rw-r--r--spring-core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java3
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/FutureAdapter.java125
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java40
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureAdapter.java67
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java (renamed from spring-core/src/main/java/org/springframework/asm/util/TraceClassVisitor.java)33
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.java99
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureTask.java84
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/package-info.java8
-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/AbstractStaxHandler.java273
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/AbstractStaxXMLReader.java6
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/AbstractXMLReader.java12
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/AbstractXMLStreamReader.java21
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/DomContentHandler.java11
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/SimpleNamespaceContext.java9
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/SimpleSaxErrorHandler.java3
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/SimpleTransformErrorListener.java3
-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/StaxEventHandler.java196
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/StaxEventXMLReader.java17
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/StaxResult.java42
-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/StaxStreamHandler.java139
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/StaxStreamXMLReader.java6
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java293
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java24
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamWriter.java32
226 files changed, 23150 insertions, 4935 deletions
diff --git a/spring-core/src/main/java/org/springframework/asm/AnnotationVisitor.java b/spring-core/src/main/java/org/springframework/asm/AnnotationVisitor.java
new file mode 100644
index 00000000..5f48e993
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/AnnotationVisitor.java
@@ -0,0 +1,169 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.springframework.asm;
+
+/**
+ * A visitor to visit a Java annotation. The methods of this class must be
+ * called in the following order: ( <tt>visit</tt> | <tt>visitEnum</tt> |
+ * <tt>visitAnnotation</tt> | <tt>visitArray</tt> )* <tt>visitEnd</tt>.
+ *
+ * @author Eric Bruneton
+ * @author Eugene Kuleshov
+ */
+public abstract class AnnotationVisitor {
+
+ /**
+ * The ASM API version implemented by this visitor. The value of this field
+ * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ */
+ protected final int api;
+
+ /**
+ * The annotation visitor to which this visitor must delegate method calls.
+ * May be null.
+ */
+ protected AnnotationVisitor av;
+
+ /**
+ * Constructs a new {@link AnnotationVisitor}.
+ *
+ * @param api
+ * the ASM API version implemented by this visitor. Must be one
+ * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ */
+ public AnnotationVisitor(final int api) {
+ this(api, null);
+ }
+
+ /**
+ * Constructs a new {@link AnnotationVisitor}.
+ *
+ * @param api
+ * the ASM API version implemented by this visitor. Must be one
+ * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ * @param av
+ * the annotation visitor to which this visitor must delegate
+ * method calls. May be null.
+ */
+ public AnnotationVisitor(final int api, final AnnotationVisitor av) {
+ if (api != Opcodes.ASM4 && api != Opcodes.ASM5) {
+ throw new IllegalArgumentException();
+ }
+ this.api = api;
+ this.av = av;
+ }
+
+ /**
+ * Visits a primitive value of the annotation.
+ *
+ * @param name
+ * the value name.
+ * @param value
+ * the actual value, whose type must be {@link Byte},
+ * {@link Boolean}, {@link Character}, {@link Short},
+ * {@link Integer} , {@link Long}, {@link Float}, {@link Double},
+ * {@link String} or {@link Type} or OBJECT or ARRAY sort. This
+ * value can also be an array of byte, boolean, short, char, int,
+ * long, float or double values (this is equivalent to using
+ * {@link #visitArray visitArray} and visiting each array element
+ * in turn, but is more convenient).
+ */
+ public void visit(String name, Object value) {
+ if (av != null) {
+ av.visit(name, value);
+ }
+ }
+
+ /**
+ * Visits an enumeration value of the annotation.
+ *
+ * @param name
+ * the value name.
+ * @param desc
+ * the class descriptor of the enumeration class.
+ * @param value
+ * the actual enumeration value.
+ */
+ public void visitEnum(String name, String desc, String value) {
+ if (av != null) {
+ av.visitEnum(name, desc, value);
+ }
+ }
+
+ /**
+ * Visits a nested annotation value of the annotation.
+ *
+ * @param name
+ * the value name.
+ * @param desc
+ * the class descriptor of the nested annotation class.
+ * @return a visitor to visit the actual nested annotation value, or
+ * <tt>null</tt> if this visitor is not interested in visiting this
+ * nested annotation. <i>The nested annotation value must be fully
+ * visited before calling other methods on this annotation
+ * visitor</i>.
+ */
+ public AnnotationVisitor visitAnnotation(String name, String desc) {
+ if (av != null) {
+ return av.visitAnnotation(name, desc);
+ }
+ return null;
+ }
+
+ /**
+ * Visits an array value of the annotation. Note that arrays of primitive
+ * types (such as byte, boolean, short, char, int, long, float or double)
+ * can be passed as value to {@link #visit visit}. This is what
+ * {@link ClassReader} does.
+ *
+ * @param name
+ * the value name.
+ * @return a visitor to visit the actual array value elements, or
+ * <tt>null</tt> if this visitor is not interested in visiting these
+ * values. The 'name' parameters passed to the methods of this
+ * visitor are ignored. <i>All the array values must be visited
+ * before calling other methods on this annotation visitor</i>.
+ */
+ public AnnotationVisitor visitArray(String name) {
+ if (av != null) {
+ return av.visitArray(name);
+ }
+ return null;
+ }
+
+ /**
+ * Visits the end of the annotation.
+ */
+ public void visitEnd() {
+ if (av != null) {
+ av.visitEnd();
+ }
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/AnnotationWriter.java b/spring-core/src/main/java/org/springframework/asm/AnnotationWriter.java
new file mode 100644
index 00000000..dcd88357
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/AnnotationWriter.java
@@ -0,0 +1,371 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.springframework.asm;
+
+/**
+ * An {@link AnnotationVisitor} that generates annotations in bytecode form.
+ *
+ * @author Eric Bruneton
+ * @author Eugene Kuleshov
+ */
+final class AnnotationWriter extends AnnotationVisitor {
+
+ /**
+ * The class writer to which this annotation must be added.
+ */
+ private final ClassWriter cw;
+
+ /**
+ * The number of values in this annotation.
+ */
+ private int size;
+
+ /**
+ * <tt>true<tt> if values are named, <tt>false</tt> otherwise. Annotation
+ * writers used for annotation default and annotation arrays use unnamed
+ * values.
+ */
+ private final boolean named;
+
+ /**
+ * The annotation values in bytecode form. This byte vector only contains
+ * the values themselves, i.e. the number of values must be stored as a
+ * unsigned short just before these bytes.
+ */
+ private final ByteVector bv;
+
+ /**
+ * The byte vector to be used to store the number of values of this
+ * annotation. See {@link #bv}.
+ */
+ private final ByteVector parent;
+
+ /**
+ * Where the number of values of this annotation must be stored in
+ * {@link #parent}.
+ */
+ private final int offset;
+
+ /**
+ * Next annotation writer. This field is used to store annotation lists.
+ */
+ AnnotationWriter next;
+
+ /**
+ * Previous annotation writer. This field is used to store annotation lists.
+ */
+ AnnotationWriter prev;
+
+ // ------------------------------------------------------------------------
+ // Constructor
+ // ------------------------------------------------------------------------
+
+ /**
+ * Constructs a new {@link AnnotationWriter}.
+ *
+ * @param cw
+ * the class writer to which this annotation must be added.
+ * @param named
+ * <tt>true<tt> if values are named, <tt>false</tt> otherwise.
+ * @param bv
+ * where the annotation values must be stored.
+ * @param parent
+ * where the number of annotation values must be stored.
+ * @param offset
+ * where in <tt>parent</tt> the number of annotation values must
+ * be stored.
+ */
+ AnnotationWriter(final ClassWriter cw, final boolean named,
+ final ByteVector bv, final ByteVector parent, final int offset) {
+ super(Opcodes.ASM5);
+ this.cw = cw;
+ this.named = named;
+ this.bv = bv;
+ this.parent = parent;
+ this.offset = offset;
+ }
+
+ // ------------------------------------------------------------------------
+ // Implementation of the AnnotationVisitor abstract class
+ // ------------------------------------------------------------------------
+
+ @Override
+ public void visit(final String name, final Object value) {
+ ++size;
+ if (named) {
+ bv.putShort(cw.newUTF8(name));
+ }
+ if (value instanceof String) {
+ bv.put12('s', cw.newUTF8((String) value));
+ } else if (value instanceof Byte) {
+ bv.put12('B', cw.newInteger(((Byte) value).byteValue()).index);
+ } else if (value instanceof Boolean) {
+ int v = ((Boolean) value).booleanValue() ? 1 : 0;
+ bv.put12('Z', cw.newInteger(v).index);
+ } else if (value instanceof Character) {
+ bv.put12('C', cw.newInteger(((Character) value).charValue()).index);
+ } else if (value instanceof Short) {
+ bv.put12('S', cw.newInteger(((Short) value).shortValue()).index);
+ } else if (value instanceof Type) {
+ bv.put12('c', cw.newUTF8(((Type) value).getDescriptor()));
+ } else if (value instanceof byte[]) {
+ byte[] v = (byte[]) value;
+ bv.put12('[', v.length);
+ for (int i = 0; i < v.length; i++) {
+ bv.put12('B', cw.newInteger(v[i]).index);
+ }
+ } else if (value instanceof boolean[]) {
+ boolean[] v = (boolean[]) value;
+ bv.put12('[', v.length);
+ for (int i = 0; i < v.length; i++) {
+ bv.put12('Z', cw.newInteger(v[i] ? 1 : 0).index);
+ }
+ } else if (value instanceof short[]) {
+ short[] v = (short[]) value;
+ bv.put12('[', v.length);
+ for (int i = 0; i < v.length; i++) {
+ bv.put12('S', cw.newInteger(v[i]).index);
+ }
+ } else if (value instanceof char[]) {
+ char[] v = (char[]) value;
+ bv.put12('[', v.length);
+ for (int i = 0; i < v.length; i++) {
+ bv.put12('C', cw.newInteger(v[i]).index);
+ }
+ } else if (value instanceof int[]) {
+ int[] v = (int[]) value;
+ bv.put12('[', v.length);
+ for (int i = 0; i < v.length; i++) {
+ bv.put12('I', cw.newInteger(v[i]).index);
+ }
+ } else if (value instanceof long[]) {
+ long[] v = (long[]) value;
+ bv.put12('[', v.length);
+ for (int i = 0; i < v.length; i++) {
+ bv.put12('J', cw.newLong(v[i]).index);
+ }
+ } else if (value instanceof float[]) {
+ float[] v = (float[]) value;
+ bv.put12('[', v.length);
+ for (int i = 0; i < v.length; i++) {
+ bv.put12('F', cw.newFloat(v[i]).index);
+ }
+ } else if (value instanceof double[]) {
+ double[] v = (double[]) value;
+ bv.put12('[', v.length);
+ for (int i = 0; i < v.length; i++) {
+ bv.put12('D', cw.newDouble(v[i]).index);
+ }
+ } else {
+ Item i = cw.newConstItem(value);
+ bv.put12(".s.IFJDCS".charAt(i.type), i.index);
+ }
+ }
+
+ @Override
+ public void visitEnum(final String name, final String desc,
+ final String value) {
+ ++size;
+ if (named) {
+ bv.putShort(cw.newUTF8(name));
+ }
+ bv.put12('e', cw.newUTF8(desc)).putShort(cw.newUTF8(value));
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(final String name,
+ final String desc) {
+ ++size;
+ if (named) {
+ bv.putShort(cw.newUTF8(name));
+ }
+ // write tag and type, and reserve space for values count
+ bv.put12('@', cw.newUTF8(desc)).putShort(0);
+ return new AnnotationWriter(cw, true, bv, bv, bv.length - 2);
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(final String name) {
+ ++size;
+ if (named) {
+ bv.putShort(cw.newUTF8(name));
+ }
+ // write tag, and reserve space for array size
+ bv.put12('[', 0);
+ return new AnnotationWriter(cw, false, bv, bv, bv.length - 2);
+ }
+
+ @Override
+ public void visitEnd() {
+ if (parent != null) {
+ byte[] data = parent.data;
+ data[offset] = (byte) (size >>> 8);
+ data[offset + 1] = (byte) size;
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Utility methods
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the size of this annotation writer list.
+ *
+ * @return the size of this annotation writer list.
+ */
+ int getSize() {
+ int size = 0;
+ AnnotationWriter aw = this;
+ while (aw != null) {
+ size += aw.bv.length;
+ aw = aw.next;
+ }
+ return size;
+ }
+
+ /**
+ * Puts the annotations of this annotation writer list into the given byte
+ * vector.
+ *
+ * @param out
+ * where the annotations must be put.
+ */
+ void put(final ByteVector out) {
+ int n = 0;
+ int size = 2;
+ AnnotationWriter aw = this;
+ AnnotationWriter last = null;
+ while (aw != null) {
+ ++n;
+ size += aw.bv.length;
+ aw.visitEnd(); // in case user forgot to call visitEnd
+ aw.prev = last;
+ last = aw;
+ aw = aw.next;
+ }
+ out.putInt(size);
+ out.putShort(n);
+ aw = last;
+ while (aw != null) {
+ out.putByteArray(aw.bv.data, 0, aw.bv.length);
+ aw = aw.prev;
+ }
+ }
+
+ /**
+ * Puts the given annotation lists into the given byte vector.
+ *
+ * @param panns
+ * an array of annotation writer lists.
+ * @param off
+ * index of the first annotation to be written.
+ * @param out
+ * where the annotations must be put.
+ */
+ static void put(final AnnotationWriter[] panns, final int off,
+ final ByteVector out) {
+ int size = 1 + 2 * (panns.length - off);
+ for (int i = off; i < panns.length; ++i) {
+ size += panns[i] == null ? 0 : panns[i].getSize();
+ }
+ out.putInt(size).putByte(panns.length - off);
+ for (int i = off; i < panns.length; ++i) {
+ AnnotationWriter aw = panns[i];
+ AnnotationWriter last = null;
+ int n = 0;
+ while (aw != null) {
+ ++n;
+ aw.visitEnd(); // in case user forgot to call visitEnd
+ aw.prev = last;
+ last = aw;
+ aw = aw.next;
+ }
+ out.putShort(n);
+ aw = last;
+ while (aw != null) {
+ out.putByteArray(aw.bv.data, 0, aw.bv.length);
+ aw = aw.prev;
+ }
+ }
+ }
+
+ /**
+ * Puts the given type reference and type path into the given bytevector.
+ * LOCAL_VARIABLE and RESOURCE_VARIABLE target types are not supported.
+ *
+ * @param typeRef
+ * a reference to the annotated type. See {@link TypeReference}.
+ * @param typePath
+ * the path to the annotated type argument, wildcard bound, array
+ * element type, or static inner type within 'typeRef'. May be
+ * <tt>null</tt> if the annotation targets 'typeRef' as a whole.
+ * @param out
+ * where the type reference and type path must be put.
+ */
+ static void putTarget(int typeRef, TypePath typePath, ByteVector out) {
+ switch (typeRef >>> 24) {
+ case 0x00: // CLASS_TYPE_PARAMETER
+ case 0x01: // METHOD_TYPE_PARAMETER
+ case 0x16: // METHOD_FORMAL_PARAMETER
+ out.putShort(typeRef >>> 16);
+ break;
+ case 0x13: // FIELD
+ case 0x14: // METHOD_RETURN
+ case 0x15: // METHOD_RECEIVER
+ out.putByte(typeRef >>> 24);
+ break;
+ case 0x47: // CAST
+ case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
+ case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT
+ case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
+ case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT
+ out.putInt(typeRef);
+ break;
+ // case 0x10: // CLASS_EXTENDS
+ // case 0x11: // CLASS_TYPE_PARAMETER_BOUND
+ // case 0x12: // METHOD_TYPE_PARAMETER_BOUND
+ // case 0x17: // THROWS
+ // case 0x42: // EXCEPTION_PARAMETER
+ // case 0x43: // INSTANCEOF
+ // case 0x44: // NEW
+ // case 0x45: // CONSTRUCTOR_REFERENCE
+ // case 0x46: // METHOD_REFERENCE
+ default:
+ out.put12(typeRef >>> 24, (typeRef & 0xFFFF00) >> 8);
+ break;
+ }
+ if (typePath == null) {
+ out.putByte(0);
+ } else {
+ int length = typePath.b[typePath.offset] * 2 + 1;
+ out.putByteArray(typePath.b, typePath.offset, length);
+ }
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/Attribute.java b/spring-core/src/main/java/org/springframework/asm/Attribute.java
new file mode 100644
index 00000000..598df115
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/Attribute.java
@@ -0,0 +1,255 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.springframework.asm;
+
+/**
+ * A non standard class, field, method or code attribute.
+ *
+ * @author Eric Bruneton
+ * @author Eugene Kuleshov
+ */
+public class Attribute {
+
+ /**
+ * The type of this attribute.
+ */
+ public final String type;
+
+ /**
+ * The raw value of this attribute, used only for unknown attributes.
+ */
+ byte[] value;
+
+ /**
+ * The next attribute in this attribute list. May be <tt>null</tt>.
+ */
+ Attribute next;
+
+ /**
+ * Constructs a new empty attribute.
+ *
+ * @param type
+ * the type of the attribute.
+ */
+ protected Attribute(final String type) {
+ this.type = type;
+ }
+
+ /**
+ * Returns <tt>true</tt> if this type of attribute is unknown. The default
+ * implementation of this method always returns <tt>true</tt>.
+ *
+ * @return <tt>true</tt> if this type of attribute is unknown.
+ */
+ public boolean isUnknown() {
+ return true;
+ }
+
+ /**
+ * Returns <tt>true</tt> if this type of attribute is a code attribute.
+ *
+ * @return <tt>true</tt> if this type of attribute is a code attribute.
+ */
+ public boolean isCodeAttribute() {
+ return false;
+ }
+
+ /**
+ * Returns the labels corresponding to this attribute.
+ *
+ * @return the labels corresponding to this attribute, or <tt>null</tt> if
+ * this attribute is not a code attribute that contains labels.
+ */
+ protected Label[] getLabels() {
+ return null;
+ }
+
+ /**
+ * Reads a {@link #type type} attribute. This method must return a
+ * <i>new</i> {@link Attribute} object, of type {@link #type type},
+ * corresponding to the <tt>len</tt> bytes starting at the given offset, in
+ * the given class reader.
+ *
+ * @param cr
+ * the class that contains the attribute to be read.
+ * @param off
+ * index of the first byte of the attribute's content in
+ * {@link ClassReader#b cr.b}. The 6 attribute header bytes,
+ * containing the type and the length of the attribute, are not
+ * taken into account here.
+ * @param len
+ * the length of the attribute's content.
+ * @param buf
+ * buffer to be used to call {@link ClassReader#readUTF8
+ * readUTF8}, {@link ClassReader#readClass(int,char[]) readClass}
+ * or {@link ClassReader#readConst readConst}.
+ * @param codeOff
+ * index of the first byte of code's attribute content in
+ * {@link ClassReader#b cr.b}, or -1 if the attribute to be read
+ * is not a code attribute. The 6 attribute header bytes,
+ * containing the type and the length of the attribute, are not
+ * taken into account here.
+ * @param labels
+ * the labels of the method's code, or <tt>null</tt> if the
+ * attribute to be read is not a code attribute.
+ * @return a <i>new</i> {@link Attribute} object corresponding to the given
+ * bytes.
+ */
+ protected Attribute read(final ClassReader cr, final int off,
+ final int len, final char[] buf, final int codeOff,
+ final Label[] labels) {
+ Attribute attr = new Attribute(type);
+ attr.value = new byte[len];
+ System.arraycopy(cr.b, off, attr.value, 0, len);
+ return attr;
+ }
+
+ /**
+ * Returns the byte array form of this attribute.
+ *
+ * @param cw
+ * the class to which this attribute must be added. This
+ * parameter can be used to add to the constant pool of this
+ * class the items that corresponds to this attribute.
+ * @param code
+ * the bytecode of the method corresponding to this code
+ * attribute, or <tt>null</tt> if this attribute is not a code
+ * attributes.
+ * @param len
+ * the length of the bytecode of the method corresponding to this
+ * code attribute, or <tt>null</tt> if this attribute is not a
+ * code attribute.
+ * @param maxStack
+ * the maximum stack size of the method corresponding to this
+ * code attribute, or -1 if this attribute is not a code
+ * attribute.
+ * @param maxLocals
+ * the maximum number of local variables of the method
+ * corresponding to this code attribute, or -1 if this attribute
+ * is not a code attribute.
+ * @return the byte array form of this attribute.
+ */
+ protected ByteVector write(final ClassWriter cw, final byte[] code,
+ final int len, final int maxStack, final int maxLocals) {
+ ByteVector v = new ByteVector();
+ v.data = value;
+ v.length = value.length;
+ return v;
+ }
+
+ /**
+ * Returns the length of the attribute list that begins with this attribute.
+ *
+ * @return the length of the attribute list that begins with this attribute.
+ */
+ final int getCount() {
+ int count = 0;
+ Attribute attr = this;
+ while (attr != null) {
+ count += 1;
+ attr = attr.next;
+ }
+ return count;
+ }
+
+ /**
+ * Returns the size of all the attributes in this attribute list.
+ *
+ * @param cw
+ * the class writer to be used to convert the attributes into
+ * byte arrays, with the {@link #write write} method.
+ * @param code
+ * the bytecode of the method corresponding to these code
+ * attributes, or <tt>null</tt> if these attributes are not code
+ * attributes.
+ * @param len
+ * the length of the bytecode of the method corresponding to
+ * these code attributes, or <tt>null</tt> if these attributes
+ * are not code attributes.
+ * @param maxStack
+ * the maximum stack size of the method corresponding to these
+ * code attributes, or -1 if these attributes are not code
+ * attributes.
+ * @param maxLocals
+ * the maximum number of local variables of the method
+ * corresponding to these code attributes, or -1 if these
+ * attributes are not code attributes.
+ * @return the size of all the attributes in this attribute list. This size
+ * includes the size of the attribute headers.
+ */
+ final int getSize(final ClassWriter cw, final byte[] code, final int len,
+ final int maxStack, final int maxLocals) {
+ Attribute attr = this;
+ int size = 0;
+ while (attr != null) {
+ cw.newUTF8(attr.type);
+ size += attr.write(cw, code, len, maxStack, maxLocals).length + 6;
+ attr = attr.next;
+ }
+ return size;
+ }
+
+ /**
+ * Writes all the attributes of this attribute list in the given byte
+ * vector.
+ *
+ * @param cw
+ * the class writer to be used to convert the attributes into
+ * byte arrays, with the {@link #write write} method.
+ * @param code
+ * the bytecode of the method corresponding to these code
+ * attributes, or <tt>null</tt> if these attributes are not code
+ * attributes.
+ * @param len
+ * the length of the bytecode of the method corresponding to
+ * these code attributes, or <tt>null</tt> if these attributes
+ * are not code attributes.
+ * @param maxStack
+ * the maximum stack size of the method corresponding to these
+ * code attributes, or -1 if these attributes are not code
+ * attributes.
+ * @param maxLocals
+ * the maximum number of local variables of the method
+ * corresponding to these code attributes, or -1 if these
+ * attributes are not code attributes.
+ * @param out
+ * where the attributes must be written.
+ */
+ final void put(final ClassWriter cw, final byte[] code, final int len,
+ final int maxStack, final int maxLocals, final ByteVector out) {
+ Attribute attr = this;
+ while (attr != null) {
+ ByteVector b = attr.write(cw, code, len, maxStack, maxLocals);
+ out.putShort(cw.newUTF8(attr.type)).putInt(b.length);
+ out.putByteArray(b.data, 0, b.length);
+ attr = attr.next;
+ }
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/ByteVector.java b/spring-core/src/main/java/org/springframework/asm/ByteVector.java
new file mode 100644
index 00000000..4992f1c8
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/ByteVector.java
@@ -0,0 +1,339 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.springframework.asm;
+
+/**
+ * A dynamically extensible vector of bytes. This class is roughly equivalent to
+ * a DataOutputStream on top of a ByteArrayOutputStream, but is more efficient.
+ *
+ * @author Eric Bruneton
+ */
+public class ByteVector {
+
+ /**
+ * The content of this vector.
+ */
+ byte[] data;
+
+ /**
+ * Actual number of bytes in this vector.
+ */
+ int length;
+
+ /**
+ * Constructs a new {@link ByteVector ByteVector} with a default initial
+ * size.
+ */
+ public ByteVector() {
+ data = new byte[64];
+ }
+
+ /**
+ * Constructs a new {@link ByteVector ByteVector} with the given initial
+ * size.
+ *
+ * @param initialSize
+ * the initial size of the byte vector to be constructed.
+ */
+ public ByteVector(final int initialSize) {
+ data = new byte[initialSize];
+ }
+
+ /**
+ * Puts a byte into this byte vector. The byte vector is automatically
+ * enlarged if necessary.
+ *
+ * @param b
+ * a byte.
+ * @return this byte vector.
+ */
+ public ByteVector putByte(final int b) {
+ int length = this.length;
+ if (length + 1 > data.length) {
+ enlarge(1);
+ }
+ data[length++] = (byte) b;
+ this.length = length;
+ return this;
+ }
+
+ /**
+ * Puts two bytes into this byte vector. The byte vector is automatically
+ * enlarged if necessary.
+ *
+ * @param b1
+ * a byte.
+ * @param b2
+ * another byte.
+ * @return this byte vector.
+ */
+ ByteVector put11(final int b1, final int b2) {
+ int length = this.length;
+ if (length + 2 > data.length) {
+ enlarge(2);
+ }
+ byte[] data = this.data;
+ data[length++] = (byte) b1;
+ data[length++] = (byte) b2;
+ this.length = length;
+ return this;
+ }
+
+ /**
+ * Puts a short into this byte vector. The byte vector is automatically
+ * enlarged if necessary.
+ *
+ * @param s
+ * a short.
+ * @return this byte vector.
+ */
+ public ByteVector putShort(final int s) {
+ int length = this.length;
+ if (length + 2 > data.length) {
+ enlarge(2);
+ }
+ byte[] data = this.data;
+ data[length++] = (byte) (s >>> 8);
+ data[length++] = (byte) s;
+ this.length = length;
+ return this;
+ }
+
+ /**
+ * Puts a byte and a short into this byte vector. The byte vector is
+ * automatically enlarged if necessary.
+ *
+ * @param b
+ * a byte.
+ * @param s
+ * a short.
+ * @return this byte vector.
+ */
+ ByteVector put12(final int b, final int s) {
+ int length = this.length;
+ if (length + 3 > data.length) {
+ enlarge(3);
+ }
+ byte[] data = this.data;
+ data[length++] = (byte) b;
+ data[length++] = (byte) (s >>> 8);
+ data[length++] = (byte) s;
+ this.length = length;
+ return this;
+ }
+
+ /**
+ * Puts an int into this byte vector. The byte vector is automatically
+ * enlarged if necessary.
+ *
+ * @param i
+ * an int.
+ * @return this byte vector.
+ */
+ public ByteVector putInt(final int i) {
+ int length = this.length;
+ if (length + 4 > data.length) {
+ enlarge(4);
+ }
+ byte[] data = this.data;
+ data[length++] = (byte) (i >>> 24);
+ data[length++] = (byte) (i >>> 16);
+ data[length++] = (byte) (i >>> 8);
+ data[length++] = (byte) i;
+ this.length = length;
+ return this;
+ }
+
+ /**
+ * Puts a long into this byte vector. The byte vector is automatically
+ * enlarged if necessary.
+ *
+ * @param l
+ * a long.
+ * @return this byte vector.
+ */
+ public ByteVector putLong(final long l) {
+ int length = this.length;
+ if (length + 8 > data.length) {
+ enlarge(8);
+ }
+ byte[] data = this.data;
+ int i = (int) (l >>> 32);
+ data[length++] = (byte) (i >>> 24);
+ data[length++] = (byte) (i >>> 16);
+ data[length++] = (byte) (i >>> 8);
+ data[length++] = (byte) i;
+ i = (int) l;
+ data[length++] = (byte) (i >>> 24);
+ data[length++] = (byte) (i >>> 16);
+ data[length++] = (byte) (i >>> 8);
+ data[length++] = (byte) i;
+ this.length = length;
+ return this;
+ }
+
+ /**
+ * Puts an UTF8 string into this byte vector. The byte vector is
+ * automatically enlarged if necessary.
+ *
+ * @param s
+ * a String whose UTF8 encoded length must be less than 65536.
+ * @return this byte vector.
+ */
+ public ByteVector putUTF8(final String s) {
+ int charLength = s.length();
+ if (charLength > 65535) {
+ throw new IllegalArgumentException();
+ }
+ int len = length;
+ if (len + 2 + charLength > data.length) {
+ enlarge(2 + charLength);
+ }
+ byte[] data = this.data;
+ // optimistic algorithm: instead of computing the byte length and then
+ // serializing the string (which requires two loops), we assume the byte
+ // length is equal to char length (which is the most frequent case), and
+ // we start serializing the string right away. During the serialization,
+ // if we find that this assumption is wrong, we continue with the
+ // general method.
+ data[len++] = (byte) (charLength >>> 8);
+ data[len++] = (byte) charLength;
+ for (int i = 0; i < charLength; ++i) {
+ char c = s.charAt(i);
+ if (c >= '\001' && c <= '\177') {
+ data[len++] = (byte) c;
+ } else {
+ length = len;
+ return encodeUTF8(s, i, 65535);
+ }
+ }
+ length = len;
+ return this;
+ }
+
+ /**
+ * Puts an UTF8 string into this byte vector. The byte vector is
+ * automatically enlarged if necessary. The string length is encoded in two
+ * bytes before the encoded characters, if there is space for that (i.e. if
+ * this.length - i - 2 >= 0).
+ *
+ * @param s
+ * the String to encode.
+ * @param i
+ * the index of the first character to encode. The previous
+ * characters are supposed to have already been encoded, using
+ * only one byte per character.
+ * @param maxByteLength
+ * the maximum byte length of the encoded string, including the
+ * already encoded characters.
+ * @return this byte vector.
+ */
+ ByteVector encodeUTF8(final String s, int i, int maxByteLength) {
+ int charLength = s.length();
+ int byteLength = i;
+ char c;
+ for (int j = i; j < charLength; ++j) {
+ c = s.charAt(j);
+ if (c >= '\001' && c <= '\177') {
+ byteLength++;
+ } else if (c > '\u07FF') {
+ byteLength += 3;
+ } else {
+ byteLength += 2;
+ }
+ }
+ if (byteLength > maxByteLength) {
+ throw new IllegalArgumentException();
+ }
+ int start = length - i - 2;
+ if (start >= 0) {
+ data[start] = (byte) (byteLength >>> 8);
+ data[start + 1] = (byte) byteLength;
+ }
+ if (length + byteLength - i > data.length) {
+ enlarge(byteLength - i);
+ }
+ int len = length;
+ for (int j = i; j < charLength; ++j) {
+ c = s.charAt(j);
+ if (c >= '\001' && c <= '\177') {
+ data[len++] = (byte) c;
+ } else if (c > '\u07FF') {
+ data[len++] = (byte) (0xE0 | c >> 12 & 0xF);
+ data[len++] = (byte) (0x80 | c >> 6 & 0x3F);
+ data[len++] = (byte) (0x80 | c & 0x3F);
+ } else {
+ data[len++] = (byte) (0xC0 | c >> 6 & 0x1F);
+ data[len++] = (byte) (0x80 | c & 0x3F);
+ }
+ }
+ length = len;
+ return this;
+ }
+
+ /**
+ * Puts an array of bytes into this byte vector. The byte vector is
+ * automatically enlarged if necessary.
+ *
+ * @param b
+ * an array of bytes. May be <tt>null</tt> to put <tt>len</tt>
+ * null bytes into this byte vector.
+ * @param off
+ * index of the fist byte of b that must be copied.
+ * @param len
+ * number of bytes of b that must be copied.
+ * @return this byte vector.
+ */
+ public ByteVector putByteArray(final byte[] b, final int off, final int len) {
+ if (length + len > data.length) {
+ enlarge(len);
+ }
+ if (b != null) {
+ System.arraycopy(b, off, data, length, len);
+ }
+ length += len;
+ return this;
+ }
+
+ /**
+ * Enlarge this byte vector so that it can receive n more bytes.
+ *
+ * @param size
+ * number of additional bytes that this byte vector should be
+ * able to receive.
+ */
+ private void enlarge(final int size) {
+ int length1 = 2 * data.length;
+ int length2 = length + size;
+ byte[] newData = new byte[length1 > length2 ? length1 : length2];
+ System.arraycopy(data, 0, newData, 0, length);
+ data = newData;
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/ClassReader.java b/spring-core/src/main/java/org/springframework/asm/ClassReader.java
new file mode 100644
index 00000000..978977fa
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/ClassReader.java
@@ -0,0 +1,2511 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.springframework.asm;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A Java class parser to make a {@link ClassVisitor} visit an existing class.
+ * This class parses a byte array conforming to the Java class file format and
+ * calls the appropriate visit methods of a given class visitor for each field,
+ * method and bytecode instruction encountered.
+ *
+ * @author Eric Bruneton
+ * @author Eugene Kuleshov
+ */
+public class ClassReader {
+
+ /**
+ * True to enable signatures support.
+ */
+ static final boolean SIGNATURES = true;
+
+ /**
+ * True to enable annotations support.
+ */
+ static final boolean ANNOTATIONS = true;
+
+ /**
+ * True to enable stack map frames support.
+ */
+ static final boolean FRAMES = true;
+
+ /**
+ * True to enable bytecode writing support.
+ */
+ static final boolean WRITER = true;
+
+ /**
+ * True to enable JSR_W and GOTO_W support.
+ */
+ static final boolean RESIZE = true;
+
+ /**
+ * Flag to skip method code. If this class is set <code>CODE</code>
+ * attribute won't be visited. This can be used, for example, to retrieve
+ * annotations for methods and method parameters.
+ */
+ public static final int SKIP_CODE = 1;
+
+ /**
+ * Flag to skip the debug information in the class. If this flag is set the
+ * debug information of the class is not visited, i.e. the
+ * {@link MethodVisitor#visitLocalVariable visitLocalVariable} and
+ * {@link MethodVisitor#visitLineNumber visitLineNumber} methods will not be
+ * called.
+ */
+ public static final int SKIP_DEBUG = 2;
+
+ /**
+ * Flag to skip the stack map frames in the class. If this flag is set the
+ * stack map frames of the class is not visited, i.e. the
+ * {@link MethodVisitor#visitFrame visitFrame} method will not be called.
+ * This flag is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is
+ * used: it avoids visiting frames that will be ignored and recomputed from
+ * scratch in the class writer.
+ */
+ public static final int SKIP_FRAMES = 4;
+
+ /**
+ * Flag to expand the stack map frames. By default stack map frames are
+ * visited in their original format (i.e. "expanded" for classes whose
+ * version is less than V1_6, and "compressed" for the other classes). If
+ * this flag is set, stack map frames are always visited in expanded format
+ * (this option adds a decompression/recompression step in ClassReader and
+ * ClassWriter which degrades performances quite a lot).
+ */
+ public static final int EXPAND_FRAMES = 8;
+
+ /**
+ * The class to be parsed. <i>The content of this array must not be
+ * modified. This field is intended for {@link Attribute} sub classes, and
+ * is normally not needed by class generators or adapters.</i>
+ */
+ public final byte[] b;
+
+ /**
+ * The start index of each constant pool item in {@link #b b}, plus one. The
+ * one byte offset skips the constant pool item tag that indicates its type.
+ */
+ private final int[] items;
+
+ /**
+ * The String objects corresponding to the CONSTANT_Utf8 items. This cache
+ * avoids multiple parsing of a given CONSTANT_Utf8 constant pool item,
+ * which GREATLY improves performances (by a factor 2 to 3). This caching
+ * strategy could be extended to all constant pool items, but its benefit
+ * would not be so great for these items (because they are much less
+ * expensive to parse than CONSTANT_Utf8 items).
+ */
+ private final String[] strings;
+
+ /**
+ * Maximum length of the strings contained in the constant pool of the
+ * class.
+ */
+ private final int maxStringLength;
+
+ /**
+ * Start index of the class header information (access, name...) in
+ * {@link #b b}.
+ */
+ public final int header;
+
+ // ------------------------------------------------------------------------
+ // Constructors
+ // ------------------------------------------------------------------------
+
+ /**
+ * Constructs a new {@link ClassReader} object.
+ *
+ * @param b
+ * the bytecode of the class to be read.
+ */
+ public ClassReader(final byte[] b) {
+ this(b, 0, b.length);
+ }
+
+ /**
+ * Constructs a new {@link ClassReader} object.
+ *
+ * @param b
+ * the bytecode of the class to be read.
+ * @param off
+ * the start offset of the class data.
+ * @param len
+ * the length of the class data.
+ */
+ public ClassReader(final byte[] b, final int off, final int len) {
+ this.b = b;
+ // checks the class version
+ /* SPRING PATCH: REMOVED FOR FORWARD COMPATIBILITY WITH JDK 9
+ if (readShort(off + 6) > Opcodes.V1_8) {
+ throw new IllegalArgumentException();
+ }
+ */
+ // parses the constant pool
+ items = new int[readUnsignedShort(off + 8)];
+ int n = items.length;
+ strings = new String[n];
+ int max = 0;
+ int index = off + 10;
+ for (int i = 1; i < n; ++i) {
+ items[i] = index + 1;
+ int size;
+ switch (b[index]) {
+ case ClassWriter.FIELD:
+ case ClassWriter.METH:
+ case ClassWriter.IMETH:
+ case ClassWriter.INT:
+ case ClassWriter.FLOAT:
+ case ClassWriter.NAME_TYPE:
+ case ClassWriter.INDY:
+ size = 5;
+ break;
+ case ClassWriter.LONG:
+ case ClassWriter.DOUBLE:
+ size = 9;
+ ++i;
+ break;
+ case ClassWriter.UTF8:
+ size = 3 + readUnsignedShort(index + 1);
+ if (size > max) {
+ max = size;
+ }
+ break;
+ case ClassWriter.HANDLE:
+ size = 4;
+ break;
+ // case ClassWriter.CLASS:
+ // case ClassWriter.STR:
+ // case ClassWriter.MTYPE
+ default:
+ size = 3;
+ break;
+ }
+ index += size;
+ }
+ maxStringLength = max;
+ // the class header information starts just after the constant pool
+ header = index;
+ }
+
+ /**
+ * Returns the class's access flags (see {@link Opcodes}). This value may
+ * not reflect Deprecated and Synthetic flags when bytecode is before 1.5
+ * and those flags are represented by attributes.
+ *
+ * @return the class access flags
+ *
+ * @see ClassVisitor#visit(int, int, String, String, String, String[])
+ */
+ public int getAccess() {
+ return readUnsignedShort(header);
+ }
+
+ /**
+ * Returns the internal name of the class (see
+ * {@link Type#getInternalName() getInternalName}).
+ *
+ * @return the internal class name
+ *
+ * @see ClassVisitor#visit(int, int, String, String, String, String[])
+ */
+ public String getClassName() {
+ return readClass(header + 2, new char[maxStringLength]);
+ }
+
+ /**
+ * Returns the internal of name of the super class (see
+ * {@link Type#getInternalName() getInternalName}). For interfaces, the
+ * super class is {@link Object}.
+ *
+ * @return the internal name of super class, or <tt>null</tt> for
+ * {@link Object} class.
+ *
+ * @see ClassVisitor#visit(int, int, String, String, String, String[])
+ */
+ public String getSuperName() {
+ return readClass(header + 4, new char[maxStringLength]);
+ }
+
+ /**
+ * Returns the internal names of the class's interfaces (see
+ * {@link Type#getInternalName() getInternalName}).
+ *
+ * @return the array of internal names for all implemented interfaces or
+ * <tt>null</tt>.
+ *
+ * @see ClassVisitor#visit(int, int, String, String, String, String[])
+ */
+ public String[] getInterfaces() {
+ int index = header + 6;
+ int n = readUnsignedShort(index);
+ String[] interfaces = new String[n];
+ if (n > 0) {
+ char[] buf = new char[maxStringLength];
+ for (int i = 0; i < n; ++i) {
+ index += 2;
+ interfaces[i] = readClass(index, buf);
+ }
+ }
+ return interfaces;
+ }
+
+ /**
+ * Copies the constant pool data into the given {@link ClassWriter}. Should
+ * be called before the {@link #accept(ClassVisitor,int)} method.
+ *
+ * @param classWriter
+ * the {@link ClassWriter} to copy constant pool into.
+ */
+ void copyPool(final ClassWriter classWriter) {
+ char[] buf = new char[maxStringLength];
+ int ll = items.length;
+ Item[] items2 = new Item[ll];
+ for (int i = 1; i < ll; i++) {
+ int index = items[i];
+ int tag = b[index - 1];
+ Item item = new Item(i);
+ int nameType;
+ switch (tag) {
+ case ClassWriter.FIELD:
+ case ClassWriter.METH:
+ case ClassWriter.IMETH:
+ nameType = items[readUnsignedShort(index + 2)];
+ item.set(tag, readClass(index, buf), readUTF8(nameType, buf),
+ readUTF8(nameType + 2, buf));
+ break;
+ case ClassWriter.INT:
+ item.set(readInt(index));
+ break;
+ case ClassWriter.FLOAT:
+ item.set(Float.intBitsToFloat(readInt(index)));
+ break;
+ case ClassWriter.NAME_TYPE:
+ item.set(tag, readUTF8(index, buf), readUTF8(index + 2, buf),
+ null);
+ break;
+ case ClassWriter.LONG:
+ item.set(readLong(index));
+ ++i;
+ break;
+ case ClassWriter.DOUBLE:
+ item.set(Double.longBitsToDouble(readLong(index)));
+ ++i;
+ break;
+ case ClassWriter.UTF8: {
+ String s = strings[i];
+ if (s == null) {
+ index = items[i];
+ s = strings[i] = readUTF(index + 2,
+ readUnsignedShort(index), buf);
+ }
+ item.set(tag, s, null, null);
+ break;
+ }
+ case ClassWriter.HANDLE: {
+ int fieldOrMethodRef = items[readUnsignedShort(index + 1)];
+ nameType = items[readUnsignedShort(fieldOrMethodRef + 2)];
+ item.set(ClassWriter.HANDLE_BASE + readByte(index),
+ readClass(fieldOrMethodRef, buf),
+ readUTF8(nameType, buf), readUTF8(nameType + 2, buf));
+ break;
+ }
+ case ClassWriter.INDY:
+ if (classWriter.bootstrapMethods == null) {
+ copyBootstrapMethods(classWriter, items2, buf);
+ }
+ nameType = items[readUnsignedShort(index + 2)];
+ item.set(readUTF8(nameType, buf), readUTF8(nameType + 2, buf),
+ readUnsignedShort(index));
+ break;
+ // case ClassWriter.STR:
+ // case ClassWriter.CLASS:
+ // case ClassWriter.MTYPE
+ default:
+ item.set(tag, readUTF8(index, buf), null, null);
+ break;
+ }
+
+ int index2 = item.hashCode % items2.length;
+ item.next = items2[index2];
+ items2[index2] = item;
+ }
+
+ int off = items[1] - 1;
+ classWriter.pool.putByteArray(b, off, header - off);
+ classWriter.items = items2;
+ classWriter.threshold = (int) (0.75d * ll);
+ classWriter.index = ll;
+ }
+
+ /**
+ * Copies the bootstrap method data into the given {@link ClassWriter}.
+ * Should be called before the {@link #accept(ClassVisitor,int)} method.
+ *
+ * @param classWriter
+ * the {@link ClassWriter} to copy bootstrap methods into.
+ */
+ private void copyBootstrapMethods(final ClassWriter classWriter,
+ final Item[] items, final char[] c) {
+ // finds the "BootstrapMethods" attribute
+ int u = getAttributes();
+ boolean found = false;
+ for (int i = readUnsignedShort(u); i > 0; --i) {
+ String attrName = readUTF8(u + 2, c);
+ if ("BootstrapMethods".equals(attrName)) {
+ found = true;
+ break;
+ }
+ u += 6 + readInt(u + 4);
+ }
+ if (!found) {
+ return;
+ }
+ // copies the bootstrap methods in the class writer
+ int boostrapMethodCount = readUnsignedShort(u + 8);
+ for (int j = 0, v = u + 10; j < boostrapMethodCount; j++) {
+ int position = v - u - 10;
+ int hashCode = readConst(readUnsignedShort(v), c).hashCode();
+ for (int k = readUnsignedShort(v + 2); k > 0; --k) {
+ hashCode ^= readConst(readUnsignedShort(v + 4), c).hashCode();
+ v += 2;
+ }
+ v += 4;
+ Item item = new Item(j);
+ item.set(position, hashCode & 0x7FFFFFFF);
+ int index = item.hashCode % items.length;
+ item.next = items[index];
+ items[index] = item;
+ }
+ int attrSize = readInt(u + 4);
+ ByteVector bootstrapMethods = new ByteVector(attrSize + 62);
+ bootstrapMethods.putByteArray(b, u + 10, attrSize - 2);
+ classWriter.bootstrapMethodsCount = boostrapMethodCount;
+ classWriter.bootstrapMethods = bootstrapMethods;
+ }
+
+ /**
+ * Constructs a new {@link ClassReader} object.
+ *
+ * @param is
+ * an input stream from which to read the class.
+ * @throws IOException
+ * if a problem occurs during reading.
+ */
+ public ClassReader(final InputStream is) throws IOException {
+ this(readClass(is, false));
+ }
+
+ /**
+ * Constructs a new {@link ClassReader} object.
+ *
+ * @param name
+ * the binary qualified name of the class to be read.
+ * @throws IOException
+ * if an exception occurs during reading.
+ */
+ public ClassReader(final String name) throws IOException {
+ this(readClass(
+ ClassLoader.getSystemResourceAsStream(name.replace('.', '/')
+ + ".class"), true));
+ }
+
+ /**
+ * Reads the bytecode of a class.
+ *
+ * @param is
+ * an input stream from which to read the class.
+ * @param close
+ * true to close the input stream after reading.
+ * @return the bytecode read from the given input stream.
+ * @throws IOException
+ * if a problem occurs during reading.
+ */
+ private static byte[] readClass(final InputStream is, boolean close)
+ throws IOException {
+ if (is == null) {
+ throw new IOException("Class not found");
+ }
+ try {
+ byte[] b = new byte[is.available()];
+ int len = 0;
+ while (true) {
+ int n = is.read(b, len, b.length - len);
+ if (n == -1) {
+ if (len < b.length) {
+ byte[] c = new byte[len];
+ System.arraycopy(b, 0, c, 0, len);
+ b = c;
+ }
+ return b;
+ }
+ len += n;
+ if (len == b.length) {
+ int last = is.read();
+ if (last < 0) {
+ return b;
+ }
+ byte[] c = new byte[b.length + 1000];
+ System.arraycopy(b, 0, c, 0, len);
+ c[len++] = (byte) last;
+ b = c;
+ }
+ }
+ } finally {
+ if (close) {
+ is.close();
+ }
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Public methods
+ // ------------------------------------------------------------------------
+
+ /**
+ * Makes the given visitor visit the Java class of this {@link ClassReader}
+ * . This class is the one specified in the constructor (see
+ * {@link #ClassReader(byte[]) ClassReader}).
+ *
+ * @param classVisitor
+ * the visitor that must visit this class.
+ * @param flags
+ * option flags that can be used to modify the default behavior
+ * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}
+ * , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}.
+ */
+ public void accept(final ClassVisitor classVisitor, final int flags) {
+ accept(classVisitor, new Attribute[0], flags);
+ }
+
+ /**
+ * Makes the given visitor visit the Java class of this {@link ClassReader}.
+ * This class is the one specified in the constructor (see
+ * {@link #ClassReader(byte[]) ClassReader}).
+ *
+ * @param classVisitor
+ * the visitor that must visit this class.
+ * @param attrs
+ * prototypes of the attributes that must be parsed during the
+ * visit of the class. Any attribute whose type is not equal to
+ * the type of one the prototypes will not be parsed: its byte
+ * array value will be passed unchanged to the ClassWriter.
+ * <i>This may corrupt it if this value contains references to
+ * the constant pool, or has syntactic or semantic links with a
+ * class element that has been transformed by a class adapter
+ * between the reader and the writer</i>.
+ * @param flags
+ * option flags that can be used to modify the default behavior
+ * of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}
+ * , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}.
+ */
+ public void accept(final ClassVisitor classVisitor,
+ final Attribute[] attrs, final int flags) {
+ int u = header; // current offset in the class file
+ char[] c = new char[maxStringLength]; // buffer used to read strings
+
+ Context context = new Context();
+ context.attrs = attrs;
+ context.flags = flags;
+ context.buffer = c;
+
+ // reads the class declaration
+ int access = readUnsignedShort(u);
+ String name = readClass(u + 2, c);
+ String superClass = readClass(u + 4, c);
+ String[] interfaces = new String[readUnsignedShort(u + 6)];
+ u += 8;
+ for (int i = 0; i < interfaces.length; ++i) {
+ interfaces[i] = readClass(u, c);
+ u += 2;
+ }
+
+ // reads the class attributes
+ String signature = null;
+ String sourceFile = null;
+ String sourceDebug = null;
+ String enclosingOwner = null;
+ String enclosingName = null;
+ String enclosingDesc = null;
+ int anns = 0;
+ int ianns = 0;
+ int tanns = 0;
+ int itanns = 0;
+ int innerClasses = 0;
+ Attribute attributes = null;
+
+ u = getAttributes();
+ for (int i = readUnsignedShort(u); i > 0; --i) {
+ String attrName = readUTF8(u + 2, c);
+ // tests are sorted in decreasing frequency order
+ // (based on frequencies observed on typical classes)
+ if ("SourceFile".equals(attrName)) {
+ sourceFile = readUTF8(u + 8, c);
+ } else if ("InnerClasses".equals(attrName)) {
+ innerClasses = u + 8;
+ } else if ("EnclosingMethod".equals(attrName)) {
+ enclosingOwner = readClass(u + 8, c);
+ int item = readUnsignedShort(u + 10);
+ if (item != 0) {
+ enclosingName = readUTF8(items[item], c);
+ enclosingDesc = readUTF8(items[item] + 2, c);
+ }
+ } else if (SIGNATURES && "Signature".equals(attrName)) {
+ signature = readUTF8(u + 8, c);
+ } else if (ANNOTATIONS
+ && "RuntimeVisibleAnnotations".equals(attrName)) {
+ anns = u + 8;
+ } else if (ANNOTATIONS
+ && "RuntimeVisibleTypeAnnotations".equals(attrName)) {
+ tanns = u + 8;
+ } else if ("Deprecated".equals(attrName)) {
+ access |= Opcodes.ACC_DEPRECATED;
+ } else if ("Synthetic".equals(attrName)) {
+ access |= Opcodes.ACC_SYNTHETIC
+ | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
+ } else if ("SourceDebugExtension".equals(attrName)) {
+ int len = readInt(u + 4);
+ sourceDebug = readUTF(u + 8, len, new char[len]);
+ } else if (ANNOTATIONS
+ && "RuntimeInvisibleAnnotations".equals(attrName)) {
+ ianns = u + 8;
+ } else if (ANNOTATIONS
+ && "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
+ itanns = u + 8;
+ } else if ("BootstrapMethods".equals(attrName)) {
+ int[] bootstrapMethods = new int[readUnsignedShort(u + 8)];
+ for (int j = 0, v = u + 10; j < bootstrapMethods.length; j++) {
+ bootstrapMethods[j] = v;
+ v += 2 + readUnsignedShort(v + 2) << 1;
+ }
+ context.bootstrapMethods = bootstrapMethods;
+ } else {
+ Attribute attr = readAttribute(attrs, attrName, u + 8,
+ readInt(u + 4), c, -1, null);
+ if (attr != null) {
+ attr.next = attributes;
+ attributes = attr;
+ }
+ }
+ u += 6 + readInt(u + 4);
+ }
+
+ // visits the class declaration
+ classVisitor.visit(readInt(items[1] - 7), access, name, signature,
+ superClass, interfaces);
+
+ // visits the source and debug info
+ if ((flags & SKIP_DEBUG) == 0
+ && (sourceFile != null || sourceDebug != null)) {
+ classVisitor.visitSource(sourceFile, sourceDebug);
+ }
+
+ // visits the outer class
+ if (enclosingOwner != null) {
+ classVisitor.visitOuterClass(enclosingOwner, enclosingName,
+ enclosingDesc);
+ }
+
+ // visits the class annotations and type annotations
+ if (ANNOTATIONS && anns != 0) {
+ for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
+ v = readAnnotationValues(v + 2, c, true,
+ classVisitor.visitAnnotation(readUTF8(v, c), true));
+ }
+ }
+ if (ANNOTATIONS && ianns != 0) {
+ for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
+ v = readAnnotationValues(v + 2, c, true,
+ classVisitor.visitAnnotation(readUTF8(v, c), false));
+ }
+ }
+ if (ANNOTATIONS && tanns != 0) {
+ for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
+ v = readAnnotationTarget(context, v);
+ v = readAnnotationValues(v + 2, c, true,
+ classVisitor.visitTypeAnnotation(context.typeRef,
+ context.typePath, readUTF8(v, c), true));
+ }
+ }
+ if (ANNOTATIONS && itanns != 0) {
+ for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
+ v = readAnnotationTarget(context, v);
+ v = readAnnotationValues(v + 2, c, true,
+ classVisitor.visitTypeAnnotation(context.typeRef,
+ context.typePath, readUTF8(v, c), false));
+ }
+ }
+
+ // visits the attributes
+ while (attributes != null) {
+ Attribute attr = attributes.next;
+ attributes.next = null;
+ classVisitor.visitAttribute(attributes);
+ attributes = attr;
+ }
+
+ // visits the inner classes
+ if (innerClasses != 0) {
+ int v = innerClasses + 2;
+ for (int i = readUnsignedShort(innerClasses); i > 0; --i) {
+ classVisitor.visitInnerClass(readClass(v, c),
+ readClass(v + 2, c), readUTF8(v + 4, c),
+ readUnsignedShort(v + 6));
+ v += 8;
+ }
+ }
+
+ // visits the fields and methods
+ u = header + 10 + 2 * interfaces.length;
+ for (int i = readUnsignedShort(u - 2); i > 0; --i) {
+ u = readField(classVisitor, context, u);
+ }
+ u += 2;
+ for (int i = readUnsignedShort(u - 2); i > 0; --i) {
+ u = readMethod(classVisitor, context, u);
+ }
+
+ // visits the end of the class
+ classVisitor.visitEnd();
+ }
+
+ /**
+ * Reads a field and makes the given visitor visit it.
+ *
+ * @param classVisitor
+ * the visitor that must visit the field.
+ * @param context
+ * information about the class being parsed.
+ * @param u
+ * the start offset of the field in the class file.
+ * @return the offset of the first byte following the field in the class.
+ */
+ private int readField(final ClassVisitor classVisitor,
+ final Context context, int u) {
+ // reads the field declaration
+ char[] c = context.buffer;
+ int access = readUnsignedShort(u);
+ String name = readUTF8(u + 2, c);
+ String desc = readUTF8(u + 4, c);
+ u += 6;
+
+ // reads the field attributes
+ String signature = null;
+ int anns = 0;
+ int ianns = 0;
+ int tanns = 0;
+ int itanns = 0;
+ Object value = null;
+ Attribute attributes = null;
+
+ for (int i = readUnsignedShort(u); i > 0; --i) {
+ String attrName = readUTF8(u + 2, c);
+ // tests are sorted in decreasing frequency order
+ // (based on frequencies observed on typical classes)
+ if ("ConstantValue".equals(attrName)) {
+ int item = readUnsignedShort(u + 8);
+ value = item == 0 ? null : readConst(item, c);
+ } else if (SIGNATURES && "Signature".equals(attrName)) {
+ signature = readUTF8(u + 8, c);
+ } else if ("Deprecated".equals(attrName)) {
+ access |= Opcodes.ACC_DEPRECATED;
+ } else if ("Synthetic".equals(attrName)) {
+ access |= Opcodes.ACC_SYNTHETIC
+ | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
+ } else if (ANNOTATIONS
+ && "RuntimeVisibleAnnotations".equals(attrName)) {
+ anns = u + 8;
+ } else if (ANNOTATIONS
+ && "RuntimeVisibleTypeAnnotations".equals(attrName)) {
+ tanns = u + 8;
+ } else if (ANNOTATIONS
+ && "RuntimeInvisibleAnnotations".equals(attrName)) {
+ ianns = u + 8;
+ } else if (ANNOTATIONS
+ && "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
+ itanns = u + 8;
+ } else {
+ Attribute attr = readAttribute(context.attrs, attrName, u + 8,
+ readInt(u + 4), c, -1, null);
+ if (attr != null) {
+ attr.next = attributes;
+ attributes = attr;
+ }
+ }
+ u += 6 + readInt(u + 4);
+ }
+ u += 2;
+
+ // visits the field declaration
+ FieldVisitor fv = classVisitor.visitField(access, name, desc,
+ signature, value);
+ if (fv == null) {
+ return u;
+ }
+
+ // visits the field annotations and type annotations
+ if (ANNOTATIONS && anns != 0) {
+ for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
+ v = readAnnotationValues(v + 2, c, true,
+ fv.visitAnnotation(readUTF8(v, c), true));
+ }
+ }
+ if (ANNOTATIONS && ianns != 0) {
+ for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
+ v = readAnnotationValues(v + 2, c, true,
+ fv.visitAnnotation(readUTF8(v, c), false));
+ }
+ }
+ if (ANNOTATIONS && tanns != 0) {
+ for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
+ v = readAnnotationTarget(context, v);
+ v = readAnnotationValues(v + 2, c, true,
+ fv.visitTypeAnnotation(context.typeRef,
+ context.typePath, readUTF8(v, c), true));
+ }
+ }
+ if (ANNOTATIONS && itanns != 0) {
+ for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
+ v = readAnnotationTarget(context, v);
+ v = readAnnotationValues(v + 2, c, true,
+ fv.visitTypeAnnotation(context.typeRef,
+ context.typePath, readUTF8(v, c), false));
+ }
+ }
+
+ // visits the field attributes
+ while (attributes != null) {
+ Attribute attr = attributes.next;
+ attributes.next = null;
+ fv.visitAttribute(attributes);
+ attributes = attr;
+ }
+
+ // visits the end of the field
+ fv.visitEnd();
+
+ return u;
+ }
+
+ /**
+ * Reads a method and makes the given visitor visit it.
+ *
+ * @param classVisitor
+ * the visitor that must visit the method.
+ * @param context
+ * information about the class being parsed.
+ * @param u
+ * the start offset of the method in the class file.
+ * @return the offset of the first byte following the method in the class.
+ */
+ private int readMethod(final ClassVisitor classVisitor,
+ final Context context, int u) {
+ // reads the method declaration
+ char[] c = context.buffer;
+ context.access = readUnsignedShort(u);
+ context.name = readUTF8(u + 2, c);
+ context.desc = readUTF8(u + 4, c);
+ u += 6;
+
+ // reads the method attributes
+ int code = 0;
+ int exception = 0;
+ String[] exceptions = null;
+ String signature = null;
+ int methodParameters = 0;
+ int anns = 0;
+ int ianns = 0;
+ int tanns = 0;
+ int itanns = 0;
+ int dann = 0;
+ int mpanns = 0;
+ int impanns = 0;
+ int firstAttribute = u;
+ Attribute attributes = null;
+
+ for (int i = readUnsignedShort(u); i > 0; --i) {
+ String attrName = readUTF8(u + 2, c);
+ // tests are sorted in decreasing frequency order
+ // (based on frequencies observed on typical classes)
+ if ("Code".equals(attrName)) {
+ if ((context.flags & SKIP_CODE) == 0) {
+ code = u + 8;
+ }
+ } else if ("Exceptions".equals(attrName)) {
+ exceptions = new String[readUnsignedShort(u + 8)];
+ exception = u + 10;
+ for (int j = 0; j < exceptions.length; ++j) {
+ exceptions[j] = readClass(exception, c);
+ exception += 2;
+ }
+ } else if (SIGNATURES && "Signature".equals(attrName)) {
+ signature = readUTF8(u + 8, c);
+ } else if ("Deprecated".equals(attrName)) {
+ context.access |= Opcodes.ACC_DEPRECATED;
+ } else if (ANNOTATIONS
+ && "RuntimeVisibleAnnotations".equals(attrName)) {
+ anns = u + 8;
+ } else if (ANNOTATIONS
+ && "RuntimeVisibleTypeAnnotations".equals(attrName)) {
+ tanns = u + 8;
+ } else if (ANNOTATIONS && "AnnotationDefault".equals(attrName)) {
+ dann = u + 8;
+ } else if ("Synthetic".equals(attrName)) {
+ context.access |= Opcodes.ACC_SYNTHETIC
+ | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
+ } else if (ANNOTATIONS
+ && "RuntimeInvisibleAnnotations".equals(attrName)) {
+ ianns = u + 8;
+ } else if (ANNOTATIONS
+ && "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
+ itanns = u + 8;
+ } else if (ANNOTATIONS
+ && "RuntimeVisibleParameterAnnotations".equals(attrName)) {
+ mpanns = u + 8;
+ } else if (ANNOTATIONS
+ && "RuntimeInvisibleParameterAnnotations".equals(attrName)) {
+ impanns = u + 8;
+ } else if ("MethodParameters".equals(attrName)) {
+ methodParameters = u + 8;
+ } else {
+ Attribute attr = readAttribute(context.attrs, attrName, u + 8,
+ readInt(u + 4), c, -1, null);
+ if (attr != null) {
+ attr.next = attributes;
+ attributes = attr;
+ }
+ }
+ u += 6 + readInt(u + 4);
+ }
+ u += 2;
+
+ // visits the method declaration
+ MethodVisitor mv = classVisitor.visitMethod(context.access,
+ context.name, context.desc, signature, exceptions);
+ if (mv == null) {
+ return u;
+ }
+
+ /*
+ * if the returned MethodVisitor is in fact a MethodWriter, it means
+ * there is no method adapter between the reader and the writer. If, in
+ * addition, the writer's constant pool was copied from this reader
+ * (mw.cw.cr == this), and the signature and exceptions of the method
+ * have not been changed, then it is possible to skip all visit events
+ * and just copy the original code of the method to the writer (the
+ * access, name and descriptor can have been changed, this is not
+ * important since they are not copied as is from the reader).
+ */
+ if (WRITER && mv instanceof MethodWriter) {
+ MethodWriter mw = (MethodWriter) mv;
+ if (mw.cw.cr == this && signature == mw.signature) {
+ boolean sameExceptions = false;
+ if (exceptions == null) {
+ sameExceptions = mw.exceptionCount == 0;
+ } else if (exceptions.length == mw.exceptionCount) {
+ sameExceptions = true;
+ for (int j = exceptions.length - 1; j >= 0; --j) {
+ exception -= 2;
+ if (mw.exceptions[j] != readUnsignedShort(exception)) {
+ sameExceptions = false;
+ break;
+ }
+ }
+ }
+ if (sameExceptions) {
+ /*
+ * we do not copy directly the code into MethodWriter to
+ * save a byte array copy operation. The real copy will be
+ * done in ClassWriter.toByteArray().
+ */
+ mw.classReaderOffset = firstAttribute;
+ mw.classReaderLength = u - firstAttribute;
+ return u;
+ }
+ }
+ }
+
+ // visit the method parameters
+ if (methodParameters != 0) {
+ for (int i = b[methodParameters] & 0xFF, v = methodParameters + 1; i > 0; --i, v = v + 4) {
+ mv.visitParameter(readUTF8(v, c), readUnsignedShort(v + 2));
+ }
+ }
+
+ // visits the method annotations
+ if (ANNOTATIONS && dann != 0) {
+ AnnotationVisitor dv = mv.visitAnnotationDefault();
+ readAnnotationValue(dann, c, null, dv);
+ if (dv != null) {
+ dv.visitEnd();
+ }
+ }
+ if (ANNOTATIONS && anns != 0) {
+ for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; --i) {
+ v = readAnnotationValues(v + 2, c, true,
+ mv.visitAnnotation(readUTF8(v, c), true));
+ }
+ }
+ if (ANNOTATIONS && ianns != 0) {
+ for (int i = readUnsignedShort(ianns), v = ianns + 2; i > 0; --i) {
+ v = readAnnotationValues(v + 2, c, true,
+ mv.visitAnnotation(readUTF8(v, c), false));
+ }
+ }
+ if (ANNOTATIONS && tanns != 0) {
+ for (int i = readUnsignedShort(tanns), v = tanns + 2; i > 0; --i) {
+ v = readAnnotationTarget(context, v);
+ v = readAnnotationValues(v + 2, c, true,
+ mv.visitTypeAnnotation(context.typeRef,
+ context.typePath, readUTF8(v, c), true));
+ }
+ }
+ if (ANNOTATIONS && itanns != 0) {
+ for (int i = readUnsignedShort(itanns), v = itanns + 2; i > 0; --i) {
+ v = readAnnotationTarget(context, v);
+ v = readAnnotationValues(v + 2, c, true,
+ mv.visitTypeAnnotation(context.typeRef,
+ context.typePath, readUTF8(v, c), false));
+ }
+ }
+ if (ANNOTATIONS && mpanns != 0) {
+ readParameterAnnotations(mv, context, mpanns, true);
+ }
+ if (ANNOTATIONS && impanns != 0) {
+ readParameterAnnotations(mv, context, impanns, false);
+ }
+
+ // visits the method attributes
+ while (attributes != null) {
+ Attribute attr = attributes.next;
+ attributes.next = null;
+ mv.visitAttribute(attributes);
+ attributes = attr;
+ }
+
+ // visits the method code
+ if (code != 0) {
+ mv.visitCode();
+ readCode(mv, context, code);
+ }
+
+ // visits the end of the method
+ mv.visitEnd();
+
+ return u;
+ }
+
+ /**
+ * Reads the bytecode of a method and makes the given visitor visit it.
+ *
+ * @param mv
+ * the visitor that must visit the method's code.
+ * @param context
+ * information about the class being parsed.
+ * @param u
+ * the start offset of the code attribute in the class file.
+ */
+ private void readCode(final MethodVisitor mv, final Context context, int u) {
+ // reads the header
+ byte[] b = this.b;
+ char[] c = context.buffer;
+ int maxStack = readUnsignedShort(u);
+ int maxLocals = readUnsignedShort(u + 2);
+ int codeLength = readInt(u + 4);
+ u += 8;
+
+ // reads the bytecode to find the labels
+ int codeStart = u;
+ int codeEnd = u + codeLength;
+ Label[] labels = context.labels = new Label[codeLength + 2];
+ readLabel(codeLength + 1, labels);
+ while (u < codeEnd) {
+ int offset = u - codeStart;
+ int opcode = b[u] & 0xFF;
+ switch (ClassWriter.TYPE[opcode]) {
+ case ClassWriter.NOARG_INSN:
+ case ClassWriter.IMPLVAR_INSN:
+ u += 1;
+ break;
+ case ClassWriter.LABEL_INSN:
+ readLabel(offset + readShort(u + 1), labels);
+ u += 3;
+ break;
+ case ClassWriter.LABELW_INSN:
+ readLabel(offset + readInt(u + 1), labels);
+ u += 5;
+ break;
+ case ClassWriter.WIDE_INSN:
+ opcode = b[u + 1] & 0xFF;
+ if (opcode == Opcodes.IINC) {
+ u += 6;
+ } else {
+ u += 4;
+ }
+ break;
+ case ClassWriter.TABL_INSN:
+ // skips 0 to 3 padding bytes
+ u = u + 4 - (offset & 3);
+ // reads instruction
+ readLabel(offset + readInt(u), labels);
+ for (int i = readInt(u + 8) - readInt(u + 4) + 1; i > 0; --i) {
+ readLabel(offset + readInt(u + 12), labels);
+ u += 4;
+ }
+ u += 12;
+ break;
+ case ClassWriter.LOOK_INSN:
+ // skips 0 to 3 padding bytes
+ u = u + 4 - (offset & 3);
+ // reads instruction
+ readLabel(offset + readInt(u), labels);
+ for (int i = readInt(u + 4); i > 0; --i) {
+ readLabel(offset + readInt(u + 12), labels);
+ u += 8;
+ }
+ u += 8;
+ break;
+ case ClassWriter.VAR_INSN:
+ case ClassWriter.SBYTE_INSN:
+ case ClassWriter.LDC_INSN:
+ u += 2;
+ break;
+ case ClassWriter.SHORT_INSN:
+ case ClassWriter.LDCW_INSN:
+ case ClassWriter.FIELDORMETH_INSN:
+ case ClassWriter.TYPE_INSN:
+ case ClassWriter.IINC_INSN:
+ u += 3;
+ break;
+ case ClassWriter.ITFMETH_INSN:
+ case ClassWriter.INDYMETH_INSN:
+ u += 5;
+ break;
+ // case MANA_INSN:
+ default:
+ u += 4;
+ break;
+ }
+ }
+
+ // reads the try catch entries to find the labels, and also visits them
+ for (int i = readUnsignedShort(u); i > 0; --i) {
+ Label start = readLabel(readUnsignedShort(u + 2), labels);
+ Label end = readLabel(readUnsignedShort(u + 4), labels);
+ Label handler = readLabel(readUnsignedShort(u + 6), labels);
+ String type = readUTF8(items[readUnsignedShort(u + 8)], c);
+ mv.visitTryCatchBlock(start, end, handler, type);
+ u += 8;
+ }
+ u += 2;
+
+ // reads the code attributes
+ int[] tanns = null; // start index of each visible type annotation
+ int[] itanns = null; // start index of each invisible type annotation
+ int tann = 0; // current index in tanns array
+ int itann = 0; // current index in itanns array
+ int ntoff = -1; // next visible type annotation code offset
+ int nitoff = -1; // next invisible type annotation code offset
+ int varTable = 0;
+ int varTypeTable = 0;
+ boolean zip = true;
+ boolean unzip = (context.flags & EXPAND_FRAMES) != 0;
+ int stackMap = 0;
+ int stackMapSize = 0;
+ int frameCount = 0;
+ Context frame = null;
+ Attribute attributes = null;
+
+ for (int i = readUnsignedShort(u); i > 0; --i) {
+ String attrName = readUTF8(u + 2, c);
+ if ("LocalVariableTable".equals(attrName)) {
+ if ((context.flags & SKIP_DEBUG) == 0) {
+ varTable = u + 8;
+ for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) {
+ int label = readUnsignedShort(v + 10);
+ if (labels[label] == null) {
+ readLabel(label, labels).status |= Label.DEBUG;
+ }
+ label += readUnsignedShort(v + 12);
+ if (labels[label] == null) {
+ readLabel(label, labels).status |= Label.DEBUG;
+ }
+ v += 10;
+ }
+ }
+ } else if ("LocalVariableTypeTable".equals(attrName)) {
+ varTypeTable = u + 8;
+ } else if ("LineNumberTable".equals(attrName)) {
+ if ((context.flags & SKIP_DEBUG) == 0) {
+ for (int j = readUnsignedShort(u + 8), v = u; j > 0; --j) {
+ int label = readUnsignedShort(v + 10);
+ if (labels[label] == null) {
+ readLabel(label, labels).status |= Label.DEBUG;
+ }
+ Label l = labels[label];
+ while (l.line > 0) {
+ if (l.next == null) {
+ l.next = new Label();
+ }
+ l = l.next;
+ }
+ l.line = readUnsignedShort(v + 12);
+ v += 4;
+ }
+ }
+ } else if (ANNOTATIONS
+ && "RuntimeVisibleTypeAnnotations".equals(attrName)) {
+ tanns = readTypeAnnotations(mv, context, u + 8, true);
+ ntoff = tanns.length == 0 || readByte(tanns[0]) < 0x43 ? -1
+ : readUnsignedShort(tanns[0] + 1);
+ } else if (ANNOTATIONS
+ && "RuntimeInvisibleTypeAnnotations".equals(attrName)) {
+ itanns = readTypeAnnotations(mv, context, u + 8, false);
+ nitoff = itanns.length == 0 || readByte(itanns[0]) < 0x43 ? -1
+ : readUnsignedShort(itanns[0] + 1);
+ } else if (FRAMES && "StackMapTable".equals(attrName)) {
+ if ((context.flags & SKIP_FRAMES) == 0) {
+ stackMap = u + 10;
+ stackMapSize = readInt(u + 4);
+ frameCount = readUnsignedShort(u + 8);
+ }
+ /*
+ * here we do not extract the labels corresponding to the
+ * attribute content. This would require a full parsing of the
+ * attribute, which would need to be repeated in the second
+ * phase (see below). Instead the content of the attribute is
+ * read one frame at a time (i.e. after a frame has been
+ * visited, the next frame is read), and the labels it contains
+ * are also extracted one frame at a time. Thanks to the
+ * ordering of frames, having only a "one frame lookahead" is
+ * not a problem, i.e. it is not possible to see an offset
+ * smaller than the offset of the current insn and for which no
+ * Label exist.
+ */
+ /*
+ * This is not true for UNINITIALIZED type offsets. We solve
+ * this by parsing the stack map table without a full decoding
+ * (see below).
+ */
+ } else if (FRAMES && "StackMap".equals(attrName)) {
+ if ((context.flags & SKIP_FRAMES) == 0) {
+ zip = false;
+ stackMap = u + 10;
+ stackMapSize = readInt(u + 4);
+ frameCount = readUnsignedShort(u + 8);
+ }
+ /*
+ * IMPORTANT! here we assume that the frames are ordered, as in
+ * the StackMapTable attribute, although this is not guaranteed
+ * by the attribute format.
+ */
+ } else {
+ for (int j = 0; j < context.attrs.length; ++j) {
+ if (context.attrs[j].type.equals(attrName)) {
+ Attribute attr = context.attrs[j].read(this, u + 8,
+ readInt(u + 4), c, codeStart - 8, labels);
+ if (attr != null) {
+ attr.next = attributes;
+ attributes = attr;
+ }
+ }
+ }
+ }
+ u += 6 + readInt(u + 4);
+ }
+ u += 2;
+
+ // generates the first (implicit) stack map frame
+ if (FRAMES && stackMap != 0) {
+ /*
+ * for the first explicit frame the offset is not offset_delta + 1
+ * but only offset_delta; setting the implicit frame offset to -1
+ * allow the use of the "offset_delta + 1" rule in all cases
+ */
+ frame = context;
+ frame.offset = -1;
+ frame.mode = 0;
+ frame.localCount = 0;
+ frame.localDiff = 0;
+ frame.stackCount = 0;
+ frame.local = new Object[maxLocals];
+ frame.stack = new Object[maxStack];
+ if (unzip) {
+ getImplicitFrame(context);
+ }
+ /*
+ * Finds labels for UNINITIALIZED frame types. Instead of decoding
+ * each element of the stack map table, we look for 3 consecutive
+ * bytes that "look like" an UNINITIALIZED type (tag 8, offset
+ * within code bounds, NEW instruction at this offset). We may find
+ * false positives (i.e. not real UNINITIALIZED types), but this
+ * should be rare, and the only consequence will be the creation of
+ * an unneeded label. This is better than creating a label for each
+ * NEW instruction, and faster than fully decoding the whole stack
+ * map table.
+ */
+ for (int i = stackMap; i < stackMap + stackMapSize - 2; ++i) {
+ if (b[i] == 8) { // UNINITIALIZED FRAME TYPE
+ int v = readUnsignedShort(i + 1);
+ if (v >= 0 && v < codeLength) {
+ if ((b[codeStart + v] & 0xFF) == Opcodes.NEW) {
+ readLabel(v, labels);
+ }
+ }
+ }
+ }
+ }
+
+ // visits the instructions
+ u = codeStart;
+ while (u < codeEnd) {
+ int offset = u - codeStart;
+
+ // visits the label and line number for this offset, if any
+ Label l = labels[offset];
+ if (l != null) {
+ Label next = l.next;
+ l.next = null;
+ mv.visitLabel(l);
+ if ((context.flags & SKIP_DEBUG) == 0 && l.line > 0) {
+ mv.visitLineNumber(l.line, l);
+ while (next != null) {
+ mv.visitLineNumber(next.line, l);
+ next = next.next;
+ }
+ }
+ }
+
+ // visits the frame for this offset, if any
+ while (FRAMES && frame != null
+ && (frame.offset == offset || frame.offset == -1)) {
+ // if there is a frame for this offset, makes the visitor visit
+ // it, and reads the next frame if there is one.
+ if (frame.offset != -1) {
+ if (!zip || unzip) {
+ mv.visitFrame(Opcodes.F_NEW, frame.localCount,
+ frame.local, frame.stackCount, frame.stack);
+ } else {
+ mv.visitFrame(frame.mode, frame.localDiff, frame.local,
+ frame.stackCount, frame.stack);
+ }
+ }
+ if (frameCount > 0) {
+ stackMap = readFrame(stackMap, zip, unzip, frame);
+ --frameCount;
+ } else {
+ frame = null;
+ }
+ }
+
+ // visits the instruction at this offset
+ int opcode = b[u] & 0xFF;
+ switch (ClassWriter.TYPE[opcode]) {
+ case ClassWriter.NOARG_INSN:
+ mv.visitInsn(opcode);
+ u += 1;
+ break;
+ case ClassWriter.IMPLVAR_INSN:
+ if (opcode > Opcodes.ISTORE) {
+ opcode -= 59; // ISTORE_0
+ mv.visitVarInsn(Opcodes.ISTORE + (opcode >> 2),
+ opcode & 0x3);
+ } else {
+ opcode -= 26; // ILOAD_0
+ mv.visitVarInsn(Opcodes.ILOAD + (opcode >> 2), opcode & 0x3);
+ }
+ u += 1;
+ break;
+ case ClassWriter.LABEL_INSN:
+ mv.visitJumpInsn(opcode, labels[offset + readShort(u + 1)]);
+ u += 3;
+ break;
+ case ClassWriter.LABELW_INSN:
+ mv.visitJumpInsn(opcode - 33, labels[offset + readInt(u + 1)]);
+ u += 5;
+ break;
+ case ClassWriter.WIDE_INSN:
+ opcode = b[u + 1] & 0xFF;
+ if (opcode == Opcodes.IINC) {
+ mv.visitIincInsn(readUnsignedShort(u + 2), readShort(u + 4));
+ u += 6;
+ } else {
+ mv.visitVarInsn(opcode, readUnsignedShort(u + 2));
+ u += 4;
+ }
+ break;
+ case ClassWriter.TABL_INSN: {
+ // skips 0 to 3 padding bytes
+ u = u + 4 - (offset & 3);
+ // reads instruction
+ int label = offset + readInt(u);
+ int min = readInt(u + 4);
+ int max = readInt(u + 8);
+ Label[] table = new Label[max - min + 1];
+ u += 12;
+ for (int i = 0; i < table.length; ++i) {
+ table[i] = labels[offset + readInt(u)];
+ u += 4;
+ }
+ mv.visitTableSwitchInsn(min, max, labels[label], table);
+ break;
+ }
+ case ClassWriter.LOOK_INSN: {
+ // skips 0 to 3 padding bytes
+ u = u + 4 - (offset & 3);
+ // reads instruction
+ int label = offset + readInt(u);
+ int len = readInt(u + 4);
+ int[] keys = new int[len];
+ Label[] values = new Label[len];
+ u += 8;
+ for (int i = 0; i < len; ++i) {
+ keys[i] = readInt(u);
+ values[i] = labels[offset + readInt(u + 4)];
+ u += 8;
+ }
+ mv.visitLookupSwitchInsn(labels[label], keys, values);
+ break;
+ }
+ case ClassWriter.VAR_INSN:
+ mv.visitVarInsn(opcode, b[u + 1] & 0xFF);
+ u += 2;
+ break;
+ case ClassWriter.SBYTE_INSN:
+ mv.visitIntInsn(opcode, b[u + 1]);
+ u += 2;
+ break;
+ case ClassWriter.SHORT_INSN:
+ mv.visitIntInsn(opcode, readShort(u + 1));
+ u += 3;
+ break;
+ case ClassWriter.LDC_INSN:
+ mv.visitLdcInsn(readConst(b[u + 1] & 0xFF, c));
+ u += 2;
+ break;
+ case ClassWriter.LDCW_INSN:
+ mv.visitLdcInsn(readConst(readUnsignedShort(u + 1), c));
+ u += 3;
+ break;
+ case ClassWriter.FIELDORMETH_INSN:
+ case ClassWriter.ITFMETH_INSN: {
+ int cpIndex = items[readUnsignedShort(u + 1)];
+ boolean itf = b[cpIndex - 1] == ClassWriter.IMETH;
+ String iowner = readClass(cpIndex, c);
+ cpIndex = items[readUnsignedShort(cpIndex + 2)];
+ String iname = readUTF8(cpIndex, c);
+ String idesc = readUTF8(cpIndex + 2, c);
+ if (opcode < Opcodes.INVOKEVIRTUAL) {
+ mv.visitFieldInsn(opcode, iowner, iname, idesc);
+ } else {
+ mv.visitMethodInsn(opcode, iowner, iname, idesc, itf);
+ }
+ if (opcode == Opcodes.INVOKEINTERFACE) {
+ u += 5;
+ } else {
+ u += 3;
+ }
+ break;
+ }
+ case ClassWriter.INDYMETH_INSN: {
+ int cpIndex = items[readUnsignedShort(u + 1)];
+ int bsmIndex = context.bootstrapMethods[readUnsignedShort(cpIndex)];
+ Handle bsm = (Handle) readConst(readUnsignedShort(bsmIndex), c);
+ int bsmArgCount = readUnsignedShort(bsmIndex + 2);
+ Object[] bsmArgs = new Object[bsmArgCount];
+ bsmIndex += 4;
+ for (int i = 0; i < bsmArgCount; i++) {
+ bsmArgs[i] = readConst(readUnsignedShort(bsmIndex), c);
+ bsmIndex += 2;
+ }
+ cpIndex = items[readUnsignedShort(cpIndex + 2)];
+ String iname = readUTF8(cpIndex, c);
+ String idesc = readUTF8(cpIndex + 2, c);
+ mv.visitInvokeDynamicInsn(iname, idesc, bsm, bsmArgs);
+ u += 5;
+ break;
+ }
+ case ClassWriter.TYPE_INSN:
+ mv.visitTypeInsn(opcode, readClass(u + 1, c));
+ u += 3;
+ break;
+ case ClassWriter.IINC_INSN:
+ mv.visitIincInsn(b[u + 1] & 0xFF, b[u + 2]);
+ u += 3;
+ break;
+ // case MANA_INSN:
+ default:
+ mv.visitMultiANewArrayInsn(readClass(u + 1, c), b[u + 3] & 0xFF);
+ u += 4;
+ break;
+ }
+
+ // visit the instruction annotations, if any
+ while (tanns != null && tann < tanns.length && ntoff <= offset) {
+ if (ntoff == offset) {
+ int v = readAnnotationTarget(context, tanns[tann]);
+ readAnnotationValues(v + 2, c, true,
+ mv.visitInsnAnnotation(context.typeRef,
+ context.typePath, readUTF8(v, c), true));
+ }
+ ntoff = ++tann >= tanns.length || readByte(tanns[tann]) < 0x43 ? -1
+ : readUnsignedShort(tanns[tann] + 1);
+ }
+ while (itanns != null && itann < itanns.length && nitoff <= offset) {
+ if (nitoff == offset) {
+ int v = readAnnotationTarget(context, itanns[itann]);
+ readAnnotationValues(v + 2, c, true,
+ mv.visitInsnAnnotation(context.typeRef,
+ context.typePath, readUTF8(v, c), false));
+ }
+ nitoff = ++itann >= itanns.length
+ || readByte(itanns[itann]) < 0x43 ? -1
+ : readUnsignedShort(itanns[itann] + 1);
+ }
+ }
+ if (labels[codeLength] != null) {
+ mv.visitLabel(labels[codeLength]);
+ }
+
+ // visits the local variable tables
+ if ((context.flags & SKIP_DEBUG) == 0 && varTable != 0) {
+ int[] typeTable = null;
+ if (varTypeTable != 0) {
+ u = varTypeTable + 2;
+ typeTable = new int[readUnsignedShort(varTypeTable) * 3];
+ for (int i = typeTable.length; i > 0;) {
+ typeTable[--i] = u + 6; // signature
+ typeTable[--i] = readUnsignedShort(u + 8); // index
+ typeTable[--i] = readUnsignedShort(u); // start
+ u += 10;
+ }
+ }
+ u = varTable + 2;
+ for (int i = readUnsignedShort(varTable); i > 0; --i) {
+ int start = readUnsignedShort(u);
+ int length = readUnsignedShort(u + 2);
+ int index = readUnsignedShort(u + 8);
+ String vsignature = null;
+ if (typeTable != null) {
+ for (int j = 0; j < typeTable.length; j += 3) {
+ if (typeTable[j] == start && typeTable[j + 1] == index) {
+ vsignature = readUTF8(typeTable[j + 2], c);
+ break;
+ }
+ }
+ }
+ mv.visitLocalVariable(readUTF8(u + 4, c), readUTF8(u + 6, c),
+ vsignature, labels[start], labels[start + length],
+ index);
+ u += 10;
+ }
+ }
+
+ // visits the local variables type annotations
+ if (tanns != null) {
+ for (int i = 0; i < tanns.length; ++i) {
+ if ((readByte(tanns[i]) >> 1) == (0x40 >> 1)) {
+ int v = readAnnotationTarget(context, tanns[i]);
+ v = readAnnotationValues(v + 2, c, true,
+ mv.visitLocalVariableAnnotation(context.typeRef,
+ context.typePath, context.start,
+ context.end, context.index, readUTF8(v, c),
+ true));
+ }
+ }
+ }
+ if (itanns != null) {
+ for (int i = 0; i < itanns.length; ++i) {
+ if ((readByte(itanns[i]) >> 1) == (0x40 >> 1)) {
+ int v = readAnnotationTarget(context, itanns[i]);
+ v = readAnnotationValues(v + 2, c, true,
+ mv.visitLocalVariableAnnotation(context.typeRef,
+ context.typePath, context.start,
+ context.end, context.index, readUTF8(v, c),
+ false));
+ }
+ }
+ }
+
+ // visits the code attributes
+ while (attributes != null) {
+ Attribute attr = attributes.next;
+ attributes.next = null;
+ mv.visitAttribute(attributes);
+ attributes = attr;
+ }
+
+ // visits the max stack and max locals values
+ mv.visitMaxs(maxStack, maxLocals);
+ }
+
+ /**
+ * Parses a type annotation table to find the labels, and to visit the try
+ * catch block annotations.
+ *
+ * @param u
+ * the start offset of a type annotation table.
+ * @param mv
+ * the method visitor to be used to visit the try catch block
+ * annotations.
+ * @param context
+ * information about the class being parsed.
+ * @param visible
+ * if the type annotation table to parse contains runtime visible
+ * annotations.
+ * @return the start offset of each type annotation in the parsed table.
+ */
+ private int[] readTypeAnnotations(final MethodVisitor mv,
+ final Context context, int u, boolean visible) {
+ char[] c = context.buffer;
+ int[] offsets = new int[readUnsignedShort(u)];
+ u += 2;
+ for (int i = 0; i < offsets.length; ++i) {
+ offsets[i] = u;
+ int target = readInt(u);
+ switch (target >>> 24) {
+ case 0x00: // CLASS_TYPE_PARAMETER
+ case 0x01: // METHOD_TYPE_PARAMETER
+ case 0x16: // METHOD_FORMAL_PARAMETER
+ u += 2;
+ break;
+ case 0x13: // FIELD
+ case 0x14: // METHOD_RETURN
+ case 0x15: // METHOD_RECEIVER
+ u += 1;
+ break;
+ case 0x40: // LOCAL_VARIABLE
+ case 0x41: // RESOURCE_VARIABLE
+ for (int j = readUnsignedShort(u + 1); j > 0; --j) {
+ int start = readUnsignedShort(u + 3);
+ int length = readUnsignedShort(u + 5);
+ readLabel(start, context.labels);
+ readLabel(start + length, context.labels);
+ u += 6;
+ }
+ u += 3;
+ break;
+ case 0x47: // CAST
+ case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
+ case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT
+ case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
+ case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT
+ u += 4;
+ break;
+ // case 0x10: // CLASS_EXTENDS
+ // case 0x11: // CLASS_TYPE_PARAMETER_BOUND
+ // case 0x12: // METHOD_TYPE_PARAMETER_BOUND
+ // case 0x17: // THROWS
+ // case 0x42: // EXCEPTION_PARAMETER
+ // case 0x43: // INSTANCEOF
+ // case 0x44: // NEW
+ // case 0x45: // CONSTRUCTOR_REFERENCE
+ // case 0x46: // METHOD_REFERENCE
+ default:
+ u += 3;
+ break;
+ }
+ int pathLength = readByte(u);
+ if ((target >>> 24) == 0x42) {
+ TypePath path = pathLength == 0 ? null : new TypePath(b, u);
+ u += 1 + 2 * pathLength;
+ u = readAnnotationValues(u + 2, c, true,
+ mv.visitTryCatchAnnotation(target, path,
+ readUTF8(u, c), visible));
+ } else {
+ u = readAnnotationValues(u + 3 + 2 * pathLength, c, true, null);
+ }
+ }
+ return offsets;
+ }
+
+ /**
+ * Parses the header of a type annotation to extract its target_type and
+ * target_path (the result is stored in the given context), and returns the
+ * start offset of the rest of the type_annotation structure (i.e. the
+ * offset to the type_index field, which is followed by
+ * num_element_value_pairs and then the name,value pairs).
+ *
+ * @param context
+ * information about the class being parsed. This is where the
+ * extracted target_type and target_path must be stored.
+ * @param u
+ * the start offset of a type_annotation structure.
+ * @return the start offset of the rest of the type_annotation structure.
+ */
+ private int readAnnotationTarget(final Context context, int u) {
+ int target = readInt(u);
+ switch (target >>> 24) {
+ case 0x00: // CLASS_TYPE_PARAMETER
+ case 0x01: // METHOD_TYPE_PARAMETER
+ case 0x16: // METHOD_FORMAL_PARAMETER
+ target &= 0xFFFF0000;
+ u += 2;
+ break;
+ case 0x13: // FIELD
+ case 0x14: // METHOD_RETURN
+ case 0x15: // METHOD_RECEIVER
+ target &= 0xFF000000;
+ u += 1;
+ break;
+ case 0x40: // LOCAL_VARIABLE
+ case 0x41: { // RESOURCE_VARIABLE
+ target &= 0xFF000000;
+ int n = readUnsignedShort(u + 1);
+ context.start = new Label[n];
+ context.end = new Label[n];
+ context.index = new int[n];
+ u += 3;
+ for (int i = 0; i < n; ++i) {
+ int start = readUnsignedShort(u);
+ int length = readUnsignedShort(u + 2);
+ context.start[i] = readLabel(start, context.labels);
+ context.end[i] = readLabel(start + length, context.labels);
+ context.index[i] = readUnsignedShort(u + 4);
+ u += 6;
+ }
+ break;
+ }
+ case 0x47: // CAST
+ case 0x48: // CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
+ case 0x49: // METHOD_INVOCATION_TYPE_ARGUMENT
+ case 0x4A: // CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
+ case 0x4B: // METHOD_REFERENCE_TYPE_ARGUMENT
+ target &= 0xFF0000FF;
+ u += 4;
+ break;
+ // case 0x10: // CLASS_EXTENDS
+ // case 0x11: // CLASS_TYPE_PARAMETER_BOUND
+ // case 0x12: // METHOD_TYPE_PARAMETER_BOUND
+ // case 0x17: // THROWS
+ // case 0x42: // EXCEPTION_PARAMETER
+ // case 0x43: // INSTANCEOF
+ // case 0x44: // NEW
+ // case 0x45: // CONSTRUCTOR_REFERENCE
+ // case 0x46: // METHOD_REFERENCE
+ default:
+ target &= (target >>> 24) < 0x43 ? 0xFFFFFF00 : 0xFF000000;
+ u += 3;
+ break;
+ }
+ int pathLength = readByte(u);
+ context.typeRef = target;
+ context.typePath = pathLength == 0 ? null : new TypePath(b, u);
+ return u + 1 + 2 * pathLength;
+ }
+
+ /**
+ * Reads parameter annotations and makes the given visitor visit them.
+ *
+ * @param mv
+ * the visitor that must visit the annotations.
+ * @param context
+ * information about the class being parsed.
+ * @param v
+ * start offset in {@link #b b} of the annotations to be read.
+ * @param visible
+ * <tt>true</tt> if the annotations to be read are visible at
+ * runtime.
+ */
+ private void readParameterAnnotations(final MethodVisitor mv,
+ final Context context, int v, final boolean visible) {
+ int i;
+ int n = b[v++] & 0xFF;
+ // workaround for a bug in javac (javac compiler generates a parameter
+ // annotation array whose size is equal to the number of parameters in
+ // the Java source file, while it should generate an array whose size is
+ // equal to the number of parameters in the method descriptor - which
+ // includes the synthetic parameters added by the compiler). This work-
+ // around supposes that the synthetic parameters are the first ones.
+ int synthetics = Type.getArgumentTypes(context.desc).length - n;
+ AnnotationVisitor av;
+ for (i = 0; i < synthetics; ++i) {
+ // virtual annotation to detect synthetic parameters in MethodWriter
+ av = mv.visitParameterAnnotation(i, "Ljava/lang/Synthetic;", false);
+ if (av != null) {
+ av.visitEnd();
+ }
+ }
+ char[] c = context.buffer;
+ for (; i < n + synthetics; ++i) {
+ int j = readUnsignedShort(v);
+ v += 2;
+ for (; j > 0; --j) {
+ av = mv.visitParameterAnnotation(i, readUTF8(v, c), visible);
+ v = readAnnotationValues(v + 2, c, true, av);
+ }
+ }
+ }
+
+ /**
+ * Reads the values of an annotation and makes the given visitor visit them.
+ *
+ * @param v
+ * the start offset in {@link #b b} of the values to be read
+ * (including the unsigned short that gives the number of
+ * values).
+ * @param buf
+ * buffer to be used to call {@link #readUTF8 readUTF8},
+ * {@link #readClass(int,char[]) readClass} or {@link #readConst
+ * readConst}.
+ * @param named
+ * if the annotation values are named or not.
+ * @param av
+ * the visitor that must visit the values.
+ * @return the end offset of the annotation values.
+ */
+ private int readAnnotationValues(int v, final char[] buf,
+ final boolean named, final AnnotationVisitor av) {
+ int i = readUnsignedShort(v);
+ v += 2;
+ if (named) {
+ for (; i > 0; --i) {
+ v = readAnnotationValue(v + 2, buf, readUTF8(v, buf), av);
+ }
+ } else {
+ for (; i > 0; --i) {
+ v = readAnnotationValue(v, buf, null, av);
+ }
+ }
+ if (av != null) {
+ av.visitEnd();
+ }
+ return v;
+ }
+
+ /**
+ * Reads a value of an annotation and makes the given visitor visit it.
+ *
+ * @param v
+ * the start offset in {@link #b b} of the value to be read
+ * (<i>not including the value name constant pool index</i>).
+ * @param buf
+ * buffer to be used to call {@link #readUTF8 readUTF8},
+ * {@link #readClass(int,char[]) readClass} or {@link #readConst
+ * readConst}.
+ * @param name
+ * the name of the value to be read.
+ * @param av
+ * the visitor that must visit the value.
+ * @return the end offset of the annotation value.
+ */
+ private int readAnnotationValue(int v, final char[] buf, final String name,
+ final AnnotationVisitor av) {
+ int i;
+ if (av == null) {
+ switch (b[v] & 0xFF) {
+ case 'e': // enum_const_value
+ return v + 5;
+ case '@': // annotation_value
+ return readAnnotationValues(v + 3, buf, true, null);
+ case '[': // array_value
+ return readAnnotationValues(v + 1, buf, false, null);
+ default:
+ return v + 3;
+ }
+ }
+ switch (b[v++] & 0xFF) {
+ case 'I': // pointer to CONSTANT_Integer
+ case 'J': // pointer to CONSTANT_Long
+ case 'F': // pointer to CONSTANT_Float
+ case 'D': // pointer to CONSTANT_Double
+ av.visit(name, readConst(readUnsignedShort(v), buf));
+ v += 2;
+ break;
+ case 'B': // pointer to CONSTANT_Byte
+ av.visit(name,
+ new Byte((byte) readInt(items[readUnsignedShort(v)])));
+ v += 2;
+ break;
+ case 'Z': // pointer to CONSTANT_Boolean
+ av.visit(name,
+ readInt(items[readUnsignedShort(v)]) == 0 ? Boolean.FALSE
+ : Boolean.TRUE);
+ v += 2;
+ break;
+ case 'S': // pointer to CONSTANT_Short
+ av.visit(name, new Short(
+ (short) readInt(items[readUnsignedShort(v)])));
+ v += 2;
+ break;
+ case 'C': // pointer to CONSTANT_Char
+ av.visit(name, new Character(
+ (char) readInt(items[readUnsignedShort(v)])));
+ v += 2;
+ break;
+ case 's': // pointer to CONSTANT_Utf8
+ av.visit(name, readUTF8(v, buf));
+ v += 2;
+ break;
+ case 'e': // enum_const_value
+ av.visitEnum(name, readUTF8(v, buf), readUTF8(v + 2, buf));
+ v += 4;
+ break;
+ case 'c': // class_info
+ av.visit(name, Type.getType(readUTF8(v, buf)));
+ v += 2;
+ break;
+ case '@': // annotation_value
+ v = readAnnotationValues(v + 2, buf, true,
+ av.visitAnnotation(name, readUTF8(v, buf)));
+ break;
+ case '[': // array_value
+ int size = readUnsignedShort(v);
+ v += 2;
+ if (size == 0) {
+ return readAnnotationValues(v - 2, buf, false,
+ av.visitArray(name));
+ }
+ switch (this.b[v++] & 0xFF) {
+ case 'B':
+ byte[] bv = new byte[size];
+ for (i = 0; i < size; i++) {
+ bv[i] = (byte) readInt(items[readUnsignedShort(v)]);
+ v += 3;
+ }
+ av.visit(name, bv);
+ --v;
+ break;
+ case 'Z':
+ boolean[] zv = new boolean[size];
+ for (i = 0; i < size; i++) {
+ zv[i] = readInt(items[readUnsignedShort(v)]) != 0;
+ v += 3;
+ }
+ av.visit(name, zv);
+ --v;
+ break;
+ case 'S':
+ short[] sv = new short[size];
+ for (i = 0; i < size; i++) {
+ sv[i] = (short) readInt(items[readUnsignedShort(v)]);
+ v += 3;
+ }
+ av.visit(name, sv);
+ --v;
+ break;
+ case 'C':
+ char[] cv = new char[size];
+ for (i = 0; i < size; i++) {
+ cv[i] = (char) readInt(items[readUnsignedShort(v)]);
+ v += 3;
+ }
+ av.visit(name, cv);
+ --v;
+ break;
+ case 'I':
+ int[] iv = new int[size];
+ for (i = 0; i < size; i++) {
+ iv[i] = readInt(items[readUnsignedShort(v)]);
+ v += 3;
+ }
+ av.visit(name, iv);
+ --v;
+ break;
+ case 'J':
+ long[] lv = new long[size];
+ for (i = 0; i < size; i++) {
+ lv[i] = readLong(items[readUnsignedShort(v)]);
+ v += 3;
+ }
+ av.visit(name, lv);
+ --v;
+ break;
+ case 'F':
+ float[] fv = new float[size];
+ for (i = 0; i < size; i++) {
+ fv[i] = Float
+ .intBitsToFloat(readInt(items[readUnsignedShort(v)]));
+ v += 3;
+ }
+ av.visit(name, fv);
+ --v;
+ break;
+ case 'D':
+ double[] dv = new double[size];
+ for (i = 0; i < size; i++) {
+ dv[i] = Double
+ .longBitsToDouble(readLong(items[readUnsignedShort(v)]));
+ v += 3;
+ }
+ av.visit(name, dv);
+ --v;
+ break;
+ default:
+ v = readAnnotationValues(v - 3, buf, false, av.visitArray(name));
+ }
+ }
+ return v;
+ }
+
+ /**
+ * Computes the implicit frame of the method currently being parsed (as
+ * defined in the given {@link Context}) and stores it in the given context.
+ *
+ * @param frame
+ * information about the class being parsed.
+ */
+ private void getImplicitFrame(final Context frame) {
+ String desc = frame.desc;
+ Object[] locals = frame.local;
+ int local = 0;
+ if ((frame.access & Opcodes.ACC_STATIC) == 0) {
+ if ("<init>".equals(frame.name)) {
+ locals[local++] = Opcodes.UNINITIALIZED_THIS;
+ } else {
+ locals[local++] = readClass(header + 2, frame.buffer);
+ }
+ }
+ int i = 1;
+ loop: while (true) {
+ int j = i;
+ switch (desc.charAt(i++)) {
+ case 'Z':
+ case 'C':
+ case 'B':
+ case 'S':
+ case 'I':
+ locals[local++] = Opcodes.INTEGER;
+ break;
+ case 'F':
+ locals[local++] = Opcodes.FLOAT;
+ break;
+ case 'J':
+ locals[local++] = Opcodes.LONG;
+ break;
+ case 'D':
+ locals[local++] = Opcodes.DOUBLE;
+ break;
+ case '[':
+ while (desc.charAt(i) == '[') {
+ ++i;
+ }
+ if (desc.charAt(i) == 'L') {
+ ++i;
+ while (desc.charAt(i) != ';') {
+ ++i;
+ }
+ }
+ locals[local++] = desc.substring(j, ++i);
+ break;
+ case 'L':
+ while (desc.charAt(i) != ';') {
+ ++i;
+ }
+ locals[local++] = desc.substring(j + 1, i++);
+ break;
+ default:
+ break loop;
+ }
+ }
+ frame.localCount = local;
+ }
+
+ /**
+ * Reads a stack map frame and stores the result in the given
+ * {@link Context} object.
+ *
+ * @param stackMap
+ * the start offset of a stack map frame in the class file.
+ * @param zip
+ * if the stack map frame at stackMap is compressed or not.
+ * @param unzip
+ * if the stack map frame must be uncompressed.
+ * @param frame
+ * where the parsed stack map frame must be stored.
+ * @return the offset of the first byte following the parsed frame.
+ */
+ private int readFrame(int stackMap, boolean zip, boolean unzip,
+ Context frame) {
+ char[] c = frame.buffer;
+ Label[] labels = frame.labels;
+ int tag;
+ int delta;
+ if (zip) {
+ tag = b[stackMap++] & 0xFF;
+ } else {
+ tag = MethodWriter.FULL_FRAME;
+ frame.offset = -1;
+ }
+ frame.localDiff = 0;
+ if (tag < MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME) {
+ delta = tag;
+ frame.mode = Opcodes.F_SAME;
+ frame.stackCount = 0;
+ } else if (tag < MethodWriter.RESERVED) {
+ delta = tag - MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME;
+ stackMap = readFrameType(frame.stack, 0, stackMap, c, labels);
+ frame.mode = Opcodes.F_SAME1;
+ frame.stackCount = 1;
+ } else {
+ delta = readUnsignedShort(stackMap);
+ stackMap += 2;
+ if (tag == MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
+ stackMap = readFrameType(frame.stack, 0, stackMap, c, labels);
+ frame.mode = Opcodes.F_SAME1;
+ frame.stackCount = 1;
+ } else if (tag >= MethodWriter.CHOP_FRAME
+ && tag < MethodWriter.SAME_FRAME_EXTENDED) {
+ frame.mode = Opcodes.F_CHOP;
+ frame.localDiff = MethodWriter.SAME_FRAME_EXTENDED - tag;
+ frame.localCount -= frame.localDiff;
+ frame.stackCount = 0;
+ } else if (tag == MethodWriter.SAME_FRAME_EXTENDED) {
+ frame.mode = Opcodes.F_SAME;
+ frame.stackCount = 0;
+ } else if (tag < MethodWriter.FULL_FRAME) {
+ int local = unzip ? frame.localCount : 0;
+ for (int i = tag - MethodWriter.SAME_FRAME_EXTENDED; i > 0; i--) {
+ stackMap = readFrameType(frame.local, local++, stackMap, c,
+ labels);
+ }
+ frame.mode = Opcodes.F_APPEND;
+ frame.localDiff = tag - MethodWriter.SAME_FRAME_EXTENDED;
+ frame.localCount += frame.localDiff;
+ frame.stackCount = 0;
+ } else { // if (tag == FULL_FRAME) {
+ frame.mode = Opcodes.F_FULL;
+ int n = readUnsignedShort(stackMap);
+ stackMap += 2;
+ frame.localDiff = n;
+ frame.localCount = n;
+ for (int local = 0; n > 0; n--) {
+ stackMap = readFrameType(frame.local, local++, stackMap, c,
+ labels);
+ }
+ n = readUnsignedShort(stackMap);
+ stackMap += 2;
+ frame.stackCount = n;
+ for (int stack = 0; n > 0; n--) {
+ stackMap = readFrameType(frame.stack, stack++, stackMap, c,
+ labels);
+ }
+ }
+ }
+ frame.offset += delta + 1;
+ readLabel(frame.offset, labels);
+ return stackMap;
+ }
+
+ /**
+ * Reads a stack map frame type and stores it at the given index in the
+ * given array.
+ *
+ * @param frame
+ * the array where the parsed type must be stored.
+ * @param index
+ * the index in 'frame' where the parsed type must be stored.
+ * @param v
+ * the start offset of the stack map frame type to read.
+ * @param buf
+ * a buffer to read strings.
+ * @param labels
+ * the labels of the method currently being parsed, indexed by
+ * their offset. If the parsed type is an Uninitialized type, a
+ * new label for the corresponding NEW instruction is stored in
+ * this array if it does not already exist.
+ * @return the offset of the first byte after the parsed type.
+ */
+ private int readFrameType(final Object[] frame, final int index, int v,
+ final char[] buf, final Label[] labels) {
+ int type = b[v++] & 0xFF;
+ switch (type) {
+ case 0:
+ frame[index] = Opcodes.TOP;
+ break;
+ case 1:
+ frame[index] = Opcodes.INTEGER;
+ break;
+ case 2:
+ frame[index] = Opcodes.FLOAT;
+ break;
+ case 3:
+ frame[index] = Opcodes.DOUBLE;
+ break;
+ case 4:
+ frame[index] = Opcodes.LONG;
+ break;
+ case 5:
+ frame[index] = Opcodes.NULL;
+ break;
+ case 6:
+ frame[index] = Opcodes.UNINITIALIZED_THIS;
+ break;
+ case 7: // Object
+ frame[index] = readClass(v, buf);
+ v += 2;
+ break;
+ default: // Uninitialized
+ frame[index] = readLabel(readUnsignedShort(v), labels);
+ v += 2;
+ }
+ return v;
+ }
+
+ /**
+ * Returns the label corresponding to the given offset. The default
+ * implementation of this method creates a label for the given offset if it
+ * has not been already created.
+ *
+ * @param offset
+ * a bytecode offset in a method.
+ * @param labels
+ * the already created labels, indexed by their offset. If a
+ * label already exists for offset this method must not create a
+ * new one. Otherwise it must store the new label in this array.
+ * @return a non null Label, which must be equal to labels[offset].
+ */
+ protected Label readLabel(int offset, Label[] labels) {
+ if (labels[offset] == null) {
+ labels[offset] = new Label();
+ }
+ return labels[offset];
+ }
+
+ /**
+ * Returns the start index of the attribute_info structure of this class.
+ *
+ * @return the start index of the attribute_info structure of this class.
+ */
+ private int getAttributes() {
+ // skips the header
+ int u = header + 8 + readUnsignedShort(header + 6) * 2;
+ // skips fields and methods
+ for (int i = readUnsignedShort(u); i > 0; --i) {
+ for (int j = readUnsignedShort(u + 8); j > 0; --j) {
+ u += 6 + readInt(u + 12);
+ }
+ u += 8;
+ }
+ u += 2;
+ for (int i = readUnsignedShort(u); i > 0; --i) {
+ for (int j = readUnsignedShort(u + 8); j > 0; --j) {
+ u += 6 + readInt(u + 12);
+ }
+ u += 8;
+ }
+ // the attribute_info structure starts just after the methods
+ return u + 2;
+ }
+
+ /**
+ * Reads an attribute in {@link #b b}.
+ *
+ * @param attrs
+ * prototypes of the attributes that must be parsed during the
+ * visit of the class. Any attribute whose type is not equal to
+ * the type of one the prototypes is ignored (i.e. an empty
+ * {@link Attribute} instance is returned).
+ * @param type
+ * the type of the attribute.
+ * @param off
+ * index of the first byte of the attribute's content in
+ * {@link #b b}. The 6 attribute header bytes, containing the
+ * type and the length of the attribute, are not taken into
+ * account here (they have already been read).
+ * @param len
+ * the length of the attribute's content.
+ * @param buf
+ * buffer to be used to call {@link #readUTF8 readUTF8},
+ * {@link #readClass(int,char[]) readClass} or {@link #readConst
+ * readConst}.
+ * @param codeOff
+ * index of the first byte of code's attribute content in
+ * {@link #b b}, or -1 if the attribute to be read is not a code
+ * attribute. The 6 attribute header bytes, containing the type
+ * and the length of the attribute, are not taken into account
+ * here.
+ * @param labels
+ * the labels of the method's code, or <tt>null</tt> if the
+ * attribute to be read is not a code attribute.
+ * @return the attribute that has been read, or <tt>null</tt> to skip this
+ * attribute.
+ */
+ private Attribute readAttribute(final Attribute[] attrs, final String type,
+ final int off, final int len, final char[] buf, final int codeOff,
+ final Label[] labels) {
+ for (int i = 0; i < attrs.length; ++i) {
+ if (attrs[i].type.equals(type)) {
+ return attrs[i].read(this, off, len, buf, codeOff, labels);
+ }
+ }
+ return new Attribute(type).read(this, off, len, null, -1, null);
+ }
+
+ // ------------------------------------------------------------------------
+ // Utility methods: low level parsing
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the number of constant pool items in {@link #b b}.
+ *
+ * @return the number of constant pool items in {@link #b b}.
+ */
+ public int getItemCount() {
+ return items.length;
+ }
+
+ /**
+ * Returns the start index of the constant pool item in {@link #b b}, plus
+ * one. <i>This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.</i>
+ *
+ * @param item
+ * the index a constant pool item.
+ * @return the start index of the constant pool item in {@link #b b}, plus
+ * one.
+ */
+ public int getItem(final int item) {
+ return items[item];
+ }
+
+ /**
+ * Returns the maximum length of the strings contained in the constant pool
+ * of the class.
+ *
+ * @return the maximum length of the strings contained in the constant pool
+ * of the class.
+ */
+ public int getMaxStringLength() {
+ return maxStringLength;
+ }
+
+ /**
+ * Reads a byte value in {@link #b b}. <i>This method is intended for
+ * {@link Attribute} sub classes, and is normally not needed by class
+ * generators or adapters.</i>
+ *
+ * @param index
+ * the start index of the value to be read in {@link #b b}.
+ * @return the read value.
+ */
+ public int readByte(final int index) {
+ return b[index] & 0xFF;
+ }
+
+ /**
+ * Reads an unsigned short value in {@link #b b}. <i>This method is intended
+ * for {@link Attribute} sub classes, and is normally not needed by class
+ * generators or adapters.</i>
+ *
+ * @param index
+ * the start index of the value to be read in {@link #b b}.
+ * @return the read value.
+ */
+ public int readUnsignedShort(final int index) {
+ byte[] b = this.b;
+ return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
+ }
+
+ /**
+ * Reads a signed short value in {@link #b b}. <i>This method is intended
+ * for {@link Attribute} sub classes, and is normally not needed by class
+ * generators or adapters.</i>
+ *
+ * @param index
+ * the start index of the value to be read in {@link #b b}.
+ * @return the read value.
+ */
+ public short readShort(final int index) {
+ byte[] b = this.b;
+ return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF));
+ }
+
+ /**
+ * Reads a signed int value in {@link #b b}. <i>This method is intended for
+ * {@link Attribute} sub classes, and is normally not needed by class
+ * generators or adapters.</i>
+ *
+ * @param index
+ * the start index of the value to be read in {@link #b b}.
+ * @return the read value.
+ */
+ public int readInt(final int index) {
+ byte[] b = this.b;
+ return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16)
+ | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF);
+ }
+
+ /**
+ * Reads a signed long value in {@link #b b}. <i>This method is intended for
+ * {@link Attribute} sub classes, and is normally not needed by class
+ * generators or adapters.</i>
+ *
+ * @param index
+ * the start index of the value to be read in {@link #b b}.
+ * @return the read value.
+ */
+ public long readLong(final int index) {
+ long l1 = readInt(index);
+ long l0 = readInt(index + 4) & 0xFFFFFFFFL;
+ return (l1 << 32) | l0;
+ }
+
+ /**
+ * Reads an UTF8 string constant pool item in {@link #b b}. <i>This method
+ * is intended for {@link Attribute} sub classes, and is normally not needed
+ * by class generators or adapters.</i>
+ *
+ * @param index
+ * the start index of an unsigned short value in {@link #b b},
+ * whose value is the index of an UTF8 constant pool item.
+ * @param buf
+ * buffer to be used to read the item. This buffer must be
+ * sufficiently large. It is not automatically resized.
+ * @return the String corresponding to the specified UTF8 item.
+ */
+ public String readUTF8(int index, final char[] buf) {
+ int item = readUnsignedShort(index);
+ if (index == 0 || item == 0) {
+ return null;
+ }
+ String s = strings[item];
+ if (s != null) {
+ return s;
+ }
+ index = items[item];
+ return strings[item] = readUTF(index + 2, readUnsignedShort(index), buf);
+ }
+
+ /**
+ * Reads UTF8 string in {@link #b b}.
+ *
+ * @param index
+ * start offset of the UTF8 string to be read.
+ * @param utfLen
+ * length of the UTF8 string to be read.
+ * @param buf
+ * buffer to be used to read the string. This buffer must be
+ * sufficiently large. It is not automatically resized.
+ * @return the String corresponding to the specified UTF8 string.
+ */
+ private String readUTF(int index, final int utfLen, final char[] buf) {
+ int endIndex = index + utfLen;
+ byte[] b = this.b;
+ int strLen = 0;
+ int c;
+ int st = 0;
+ char cc = 0;
+ while (index < endIndex) {
+ c = b[index++];
+ switch (st) {
+ case 0:
+ c = c & 0xFF;
+ if (c < 0x80) { // 0xxxxxxx
+ buf[strLen++] = (char) c;
+ } else if (c < 0xE0 && c > 0xBF) { // 110x xxxx 10xx xxxx
+ cc = (char) (c & 0x1F);
+ st = 1;
+ } else { // 1110 xxxx 10xx xxxx 10xx xxxx
+ cc = (char) (c & 0x0F);
+ st = 2;
+ }
+ break;
+
+ case 1: // byte 2 of 2-byte char or byte 3 of 3-byte char
+ buf[strLen++] = (char) ((cc << 6) | (c & 0x3F));
+ st = 0;
+ break;
+
+ case 2: // byte 2 of 3-byte char
+ cc = (char) ((cc << 6) | (c & 0x3F));
+ st = 1;
+ break;
+ }
+ }
+ return new String(buf, 0, strLen);
+ }
+
+ /**
+ * Reads a class constant pool item in {@link #b b}. <i>This method is
+ * intended for {@link Attribute} sub classes, and is normally not needed by
+ * class generators or adapters.</i>
+ *
+ * @param index
+ * the start index of an unsigned short value in {@link #b b},
+ * whose value is the index of a class constant pool item.
+ * @param buf
+ * buffer to be used to read the item. This buffer must be
+ * sufficiently large. It is not automatically resized.
+ * @return the String corresponding to the specified class item.
+ */
+ public String readClass(final int index, final char[] buf) {
+ // computes the start index of the CONSTANT_Class item in b
+ // and reads the CONSTANT_Utf8 item designated by
+ // the first two bytes of this CONSTANT_Class item
+ return readUTF8(items[readUnsignedShort(index)], buf);
+ }
+
+ /**
+ * Reads a numeric or string constant pool item in {@link #b b}. <i>This
+ * method is intended for {@link Attribute} sub classes, and is normally not
+ * needed by class generators or adapters.</i>
+ *
+ * @param item
+ * the index of a constant pool item.
+ * @param buf
+ * buffer to be used to read the item. This buffer must be
+ * sufficiently large. It is not automatically resized.
+ * @return the {@link Integer}, {@link Float}, {@link Long}, {@link Double},
+ * {@link String}, {@link Type} or {@link Handle} corresponding to
+ * the given constant pool item.
+ */
+ public Object readConst(final int item, final char[] buf) {
+ int index = items[item];
+ switch (b[index - 1]) {
+ case ClassWriter.INT:
+ return new Integer(readInt(index));
+ case ClassWriter.FLOAT:
+ return new Float(Float.intBitsToFloat(readInt(index)));
+ case ClassWriter.LONG:
+ return new Long(readLong(index));
+ case ClassWriter.DOUBLE:
+ return new Double(Double.longBitsToDouble(readLong(index)));
+ case ClassWriter.CLASS:
+ return Type.getObjectType(readUTF8(index, buf));
+ case ClassWriter.STR:
+ return readUTF8(index, buf);
+ case ClassWriter.MTYPE:
+ return Type.getMethodType(readUTF8(index, buf));
+ default: // case ClassWriter.HANDLE_BASE + [1..9]:
+ int tag = readByte(index);
+ int[] items = this.items;
+ int cpIndex = items[readUnsignedShort(index + 1)];
+ String owner = readClass(cpIndex, buf);
+ cpIndex = items[readUnsignedShort(cpIndex + 2)];
+ String name = readUTF8(cpIndex, buf);
+ String desc = readUTF8(cpIndex + 2, buf);
+ return new Handle(tag, owner, name, desc);
+ }
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/ClassVisitor.java b/spring-core/src/main/java/org/springframework/asm/ClassVisitor.java
new file mode 100644
index 00000000..ba58188a
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/ClassVisitor.java
@@ -0,0 +1,322 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.springframework.asm;
+
+/**
+ * A visitor to visit a Java class. The methods of this class must be called in
+ * the following order: <tt>visit</tt> [ <tt>visitSource</tt> ] [
+ * <tt>visitOuterClass</tt> ] ( <tt>visitAnnotation</tt> |
+ * <tt>visitTypeAnnotation</tt> | <tt>visitAttribute</tt> )* (
+ * <tt>visitInnerClass</tt> | <tt>visitField</tt> | <tt>visitMethod</tt> )*
+ * <tt>visitEnd</tt>.
+ *
+ * @author Eric Bruneton
+ */
+public abstract class ClassVisitor {
+
+ /**
+ * The ASM API version implemented by this visitor. The value of this field
+ * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ */
+ protected final int api;
+
+ /**
+ * The class visitor to which this visitor must delegate method calls. May
+ * be null.
+ */
+ protected ClassVisitor cv;
+
+ /**
+ * Constructs a new {@link ClassVisitor}.
+ *
+ * @param api
+ * the ASM API version implemented by this visitor. Must be one
+ * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ */
+ public ClassVisitor(final int api) {
+ this(api, null);
+ }
+
+ /**
+ * Constructs a new {@link ClassVisitor}.
+ *
+ * @param api
+ * the ASM API version implemented by this visitor. Must be one
+ * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ * @param cv
+ * the class visitor to which this visitor must delegate method
+ * calls. May be null.
+ */
+ public ClassVisitor(final int api, final ClassVisitor cv) {
+ if (api != Opcodes.ASM4 && api != Opcodes.ASM5) {
+ throw new IllegalArgumentException();
+ }
+ this.api = api;
+ this.cv = cv;
+ }
+
+ /**
+ * Visits the header of the class.
+ *
+ * @param version
+ * the class version.
+ * @param access
+ * the class's access flags (see {@link Opcodes}). This parameter
+ * also indicates if the class is deprecated.
+ * @param name
+ * the internal name of the class (see
+ * {@link Type#getInternalName() getInternalName}).
+ * @param signature
+ * the signature of this class. May be <tt>null</tt> if the class
+ * is not a generic one, and does not extend or implement generic
+ * classes or interfaces.
+ * @param superName
+ * the internal of name of the super class (see
+ * {@link Type#getInternalName() getInternalName}). For
+ * interfaces, the super class is {@link Object}. May be
+ * <tt>null</tt>, but only for the {@link Object} class.
+ * @param interfaces
+ * the internal names of the class's interfaces (see
+ * {@link Type#getInternalName() getInternalName}). May be
+ * <tt>null</tt>.
+ */
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ if (cv != null) {
+ cv.visit(version, access, name, signature, superName, interfaces);
+ }
+ }
+
+ /**
+ * Visits the source of the class.
+ *
+ * @param source
+ * the name of the source file from which the class was compiled.
+ * May be <tt>null</tt>.
+ * @param debug
+ * additional debug information to compute the correspondance
+ * between source and compiled elements of the class. May be
+ * <tt>null</tt>.
+ */
+ public void visitSource(String source, String debug) {
+ if (cv != null) {
+ cv.visitSource(source, debug);
+ }
+ }
+
+ /**
+ * Visits the enclosing class of the class. This method must be called only
+ * if the class has an enclosing class.
+ *
+ * @param owner
+ * internal name of the enclosing class of the class.
+ * @param name
+ * the name of the method that contains the class, or
+ * <tt>null</tt> if the class is not enclosed in a method of its
+ * enclosing class.
+ * @param desc
+ * the descriptor of the method that contains the class, or
+ * <tt>null</tt> if the class is not enclosed in a method of its
+ * enclosing class.
+ */
+ public void visitOuterClass(String owner, String name, String desc) {
+ if (cv != null) {
+ cv.visitOuterClass(owner, name, desc);
+ }
+ }
+
+ /**
+ * Visits an annotation of the class.
+ *
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * <tt>true</tt> if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or <tt>null</tt> if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (cv != null) {
+ return cv.visitAnnotation(desc, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits an annotation on a type in the class signature.
+ *
+ * @param typeRef
+ * a reference to the annotated type. The sort of this type
+ * reference must be {@link TypeReference#CLASS_TYPE_PARAMETER
+ * CLASS_TYPE_PARAMETER},
+ * {@link TypeReference#CLASS_TYPE_PARAMETER_BOUND
+ * CLASS_TYPE_PARAMETER_BOUND} or
+ * {@link TypeReference#CLASS_EXTENDS CLASS_EXTENDS}. See
+ * {@link TypeReference}.
+ * @param typePath
+ * the path to the annotated type argument, wildcard bound, array
+ * element type, or static inner type within 'typeRef'. May be
+ * <tt>null</tt> if the annotation targets 'typeRef' as a whole.
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * <tt>true</tt> if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or <tt>null</tt> if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitTypeAnnotation(int typeRef,
+ TypePath typePath, String desc, boolean visible) {
+ /* SPRING PATCH: REMOVED FOR COMPATIBILITY WITH CGLIB 3.1
+ if (api < Opcodes.ASM5) {
+ throw new RuntimeException();
+ }
+ */
+ if (cv != null) {
+ return cv.visitTypeAnnotation(typeRef, typePath, desc, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a non standard attribute of the class.
+ *
+ * @param attr
+ * an attribute.
+ */
+ public void visitAttribute(Attribute attr) {
+ if (cv != null) {
+ cv.visitAttribute(attr);
+ }
+ }
+
+ /**
+ * Visits information about an inner class. This inner class is not
+ * necessarily a member of the class being visited.
+ *
+ * @param name
+ * the internal name of an inner class (see
+ * {@link Type#getInternalName() getInternalName}).
+ * @param outerName
+ * the internal name of the class to which the inner class
+ * belongs (see {@link Type#getInternalName() getInternalName}).
+ * May be <tt>null</tt> for not member classes.
+ * @param innerName
+ * the (simple) name of the inner class inside its enclosing
+ * class. May be <tt>null</tt> for anonymous inner classes.
+ * @param access
+ * the access flags of the inner class as originally declared in
+ * the enclosing class.
+ */
+ public void visitInnerClass(String name, String outerName,
+ String innerName, int access) {
+ if (cv != null) {
+ cv.visitInnerClass(name, outerName, innerName, access);
+ }
+ }
+
+ /**
+ * Visits a field of the class.
+ *
+ * @param access
+ * the field's access flags (see {@link Opcodes}). This parameter
+ * also indicates if the field is synthetic and/or deprecated.
+ * @param name
+ * the field's name.
+ * @param desc
+ * the field's descriptor (see {@link Type Type}).
+ * @param signature
+ * the field's signature. May be <tt>null</tt> if the field's
+ * type does not use generic types.
+ * @param value
+ * the field's initial value. This parameter, which may be
+ * <tt>null</tt> if the field does not have an initial value,
+ * must be an {@link Integer}, a {@link Float}, a {@link Long}, a
+ * {@link Double} or a {@link String} (for <tt>int</tt>,
+ * <tt>float</tt>, <tt>long</tt> or <tt>String</tt> fields
+ * respectively). <i>This parameter is only used for static
+ * fields</i>. Its value is ignored for non static fields, which
+ * must be initialized through bytecode instructions in
+ * constructors or methods.
+ * @return a visitor to visit field annotations and attributes, or
+ * <tt>null</tt> if this class visitor is not interested in visiting
+ * these annotations and attributes.
+ */
+ public FieldVisitor visitField(int access, String name, String desc,
+ String signature, Object value) {
+ if (cv != null) {
+ return cv.visitField(access, name, desc, signature, value);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a method of the class. This method <i>must</i> return a new
+ * {@link MethodVisitor} instance (or <tt>null</tt>) each time it is called,
+ * i.e., it should not return a previously returned visitor.
+ *
+ * @param access
+ * the method's access flags (see {@link Opcodes}). This
+ * parameter also indicates if the method is synthetic and/or
+ * deprecated.
+ * @param name
+ * the method's name.
+ * @param desc
+ * the method's descriptor (see {@link Type Type}).
+ * @param signature
+ * the method's signature. May be <tt>null</tt> if the method
+ * parameters, return type and exceptions do not use generic
+ * types.
+ * @param exceptions
+ * the internal names of the method's exception classes (see
+ * {@link Type#getInternalName() getInternalName}). May be
+ * <tt>null</tt>.
+ * @return an object to visit the byte code of the method, or <tt>null</tt>
+ * if this class visitor is not interested in visiting the code of
+ * this method.
+ */
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ if (cv != null) {
+ return cv.visitMethod(access, name, desc, signature, exceptions);
+ }
+ return null;
+ }
+
+ /**
+ * Visits the end of the class. This method, which is the last one to be
+ * called, is used to inform the visitor that all the fields and methods of
+ * the class have been visited.
+ */
+ public void visitEnd() {
+ if (cv != null) {
+ cv.visitEnd();
+ }
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/ClassWriter.java b/spring-core/src/main/java/org/springframework/asm/ClassWriter.java
new file mode 100644
index 00000000..a3d3a2de
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/ClassWriter.java
@@ -0,0 +1,1776 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.springframework.asm;
+
+/**
+ * A {@link ClassVisitor} that generates classes in bytecode form. More
+ * precisely this visitor generates a byte array conforming to the Java class
+ * file format. It can be used alone, to generate a Java class "from scratch",
+ * or with one or more {@link ClassReader ClassReader} and adapter class visitor
+ * to generate a modified class from one or more existing Java classes.
+ *
+ * @author Eric Bruneton
+ */
+public class ClassWriter extends ClassVisitor {
+
+ /**
+ * Flag to automatically compute the maximum stack size and the maximum
+ * number of local variables of methods. If this flag is set, then the
+ * arguments of the {@link MethodVisitor#visitMaxs visitMaxs} method of the
+ * {@link MethodVisitor} returned by the {@link #visitMethod visitMethod}
+ * method will be ignored, and computed automatically from the signature and
+ * the bytecode of each method.
+ *
+ * @see #ClassWriter(int)
+ */
+ public static final int COMPUTE_MAXS = 1;
+
+ /**
+ * Flag to automatically compute the stack map frames of methods from
+ * scratch. If this flag is set, then the calls to the
+ * {@link MethodVisitor#visitFrame} method are ignored, and the stack map
+ * frames are recomputed from the methods bytecode. The arguments of the
+ * {@link MethodVisitor#visitMaxs visitMaxs} method are also ignored and
+ * recomputed from the bytecode. In other words, computeFrames implies
+ * computeMaxs.
+ *
+ * @see #ClassWriter(int)
+ */
+ public static final int COMPUTE_FRAMES = 2;
+
+ /**
+ * Pseudo access flag to distinguish between the synthetic attribute and the
+ * synthetic access flag.
+ */
+ static final int ACC_SYNTHETIC_ATTRIBUTE = 0x40000;
+
+ /**
+ * Factor to convert from ACC_SYNTHETIC_ATTRIBUTE to Opcode.ACC_SYNTHETIC.
+ */
+ static final int TO_ACC_SYNTHETIC = ACC_SYNTHETIC_ATTRIBUTE
+ / Opcodes.ACC_SYNTHETIC;
+
+ /**
+ * The type of instructions without any argument.
+ */
+ static final int NOARG_INSN = 0;
+
+ /**
+ * The type of instructions with an signed byte argument.
+ */
+ static final int SBYTE_INSN = 1;
+
+ /**
+ * The type of instructions with an signed short argument.
+ */
+ static final int SHORT_INSN = 2;
+
+ /**
+ * The type of instructions with a local variable index argument.
+ */
+ static final int VAR_INSN = 3;
+
+ /**
+ * The type of instructions with an implicit local variable index argument.
+ */
+ static final int IMPLVAR_INSN = 4;
+
+ /**
+ * The type of instructions with a type descriptor argument.
+ */
+ static final int TYPE_INSN = 5;
+
+ /**
+ * The type of field and method invocations instructions.
+ */
+ static final int FIELDORMETH_INSN = 6;
+
+ /**
+ * The type of the INVOKEINTERFACE/INVOKEDYNAMIC instruction.
+ */
+ static final int ITFMETH_INSN = 7;
+
+ /**
+ * The type of the INVOKEDYNAMIC instruction.
+ */
+ static final int INDYMETH_INSN = 8;
+
+ /**
+ * The type of instructions with a 2 bytes bytecode offset label.
+ */
+ static final int LABEL_INSN = 9;
+
+ /**
+ * The type of instructions with a 4 bytes bytecode offset label.
+ */
+ static final int LABELW_INSN = 10;
+
+ /**
+ * The type of the LDC instruction.
+ */
+ static final int LDC_INSN = 11;
+
+ /**
+ * The type of the LDC_W and LDC2_W instructions.
+ */
+ static final int LDCW_INSN = 12;
+
+ /**
+ * The type of the IINC instruction.
+ */
+ static final int IINC_INSN = 13;
+
+ /**
+ * The type of the TABLESWITCH instruction.
+ */
+ static final int TABL_INSN = 14;
+
+ /**
+ * The type of the LOOKUPSWITCH instruction.
+ */
+ static final int LOOK_INSN = 15;
+
+ /**
+ * The type of the MULTIANEWARRAY instruction.
+ */
+ static final int MANA_INSN = 16;
+
+ /**
+ * The type of the WIDE instruction.
+ */
+ static final int WIDE_INSN = 17;
+
+ /**
+ * The instruction types of all JVM opcodes.
+ */
+ static final byte[] TYPE;
+
+ /**
+ * The type of CONSTANT_Class constant pool items.
+ */
+ static final int CLASS = 7;
+
+ /**
+ * The type of CONSTANT_Fieldref constant pool items.
+ */
+ static final int FIELD = 9;
+
+ /**
+ * The type of CONSTANT_Methodref constant pool items.
+ */
+ static final int METH = 10;
+
+ /**
+ * The type of CONSTANT_InterfaceMethodref constant pool items.
+ */
+ static final int IMETH = 11;
+
+ /**
+ * The type of CONSTANT_String constant pool items.
+ */
+ static final int STR = 8;
+
+ /**
+ * The type of CONSTANT_Integer constant pool items.
+ */
+ static final int INT = 3;
+
+ /**
+ * The type of CONSTANT_Float constant pool items.
+ */
+ static final int FLOAT = 4;
+
+ /**
+ * The type of CONSTANT_Long constant pool items.
+ */
+ static final int LONG = 5;
+
+ /**
+ * The type of CONSTANT_Double constant pool items.
+ */
+ static final int DOUBLE = 6;
+
+ /**
+ * The type of CONSTANT_NameAndType constant pool items.
+ */
+ static final int NAME_TYPE = 12;
+
+ /**
+ * The type of CONSTANT_Utf8 constant pool items.
+ */
+ static final int UTF8 = 1;
+
+ /**
+ * The type of CONSTANT_MethodType constant pool items.
+ */
+ static final int MTYPE = 16;
+
+ /**
+ * The type of CONSTANT_MethodHandle constant pool items.
+ */
+ static final int HANDLE = 15;
+
+ /**
+ * The type of CONSTANT_InvokeDynamic constant pool items.
+ */
+ static final int INDY = 18;
+
+ /**
+ * The base value for all CONSTANT_MethodHandle constant pool items.
+ * Internally, ASM store the 9 variations of CONSTANT_MethodHandle into 9
+ * different items.
+ */
+ static final int HANDLE_BASE = 20;
+
+ /**
+ * Normal type Item stored in the ClassWriter {@link ClassWriter#typeTable},
+ * instead of the constant pool, in order to avoid clashes with normal
+ * constant pool items in the ClassWriter constant pool's hash table.
+ */
+ static final int TYPE_NORMAL = 30;
+
+ /**
+ * Uninitialized type Item stored in the ClassWriter
+ * {@link ClassWriter#typeTable}, instead of the constant pool, in order to
+ * avoid clashes with normal constant pool items in the ClassWriter constant
+ * pool's hash table.
+ */
+ static final int TYPE_UNINIT = 31;
+
+ /**
+ * Merged type Item stored in the ClassWriter {@link ClassWriter#typeTable},
+ * instead of the constant pool, in order to avoid clashes with normal
+ * constant pool items in the ClassWriter constant pool's hash table.
+ */
+ static final int TYPE_MERGED = 32;
+
+ /**
+ * The type of BootstrapMethods items. These items are stored in a special
+ * class attribute named BootstrapMethods and not in the constant pool.
+ */
+ static final int BSM = 33;
+
+ /**
+ * The class reader from which this class writer was constructed, if any.
+ */
+ ClassReader cr;
+
+ /**
+ * Minor and major version numbers of the class to be generated.
+ */
+ int version;
+
+ /**
+ * Index of the next item to be added in the constant pool.
+ */
+ int index;
+
+ /**
+ * The constant pool of this class.
+ */
+ final ByteVector pool;
+
+ /**
+ * The constant pool's hash table data.
+ */
+ Item[] items;
+
+ /**
+ * The threshold of the constant pool's hash table.
+ */
+ int threshold;
+
+ /**
+ * A reusable key used to look for items in the {@link #items} hash table.
+ */
+ final Item key;
+
+ /**
+ * A reusable key used to look for items in the {@link #items} hash table.
+ */
+ final Item key2;
+
+ /**
+ * A reusable key used to look for items in the {@link #items} hash table.
+ */
+ final Item key3;
+
+ /**
+ * A reusable key used to look for items in the {@link #items} hash table.
+ */
+ final Item key4;
+
+ /**
+ * A type table used to temporarily store internal names that will not
+ * necessarily be stored in the constant pool. This type table is used by
+ * the control flow and data flow analysis algorithm used to compute stack
+ * map frames from scratch. This array associates to each index <tt>i</tt>
+ * the Item whose index is <tt>i</tt>. All Item objects stored in this array
+ * are also stored in the {@link #items} hash table. These two arrays allow
+ * to retrieve an Item from its index or, conversely, to get the index of an
+ * Item from its value. Each Item stores an internal name in its
+ * {@link Item#strVal1} field.
+ */
+ Item[] typeTable;
+
+ /**
+ * Number of elements in the {@link #typeTable} array.
+ */
+ private short typeCount;
+
+ /**
+ * The access flags of this class.
+ */
+ private int access;
+
+ /**
+ * The constant pool item that contains the internal name of this class.
+ */
+ private int name;
+
+ /**
+ * The internal name of this class.
+ */
+ String thisName;
+
+ /**
+ * The constant pool item that contains the signature of this class.
+ */
+ private int signature;
+
+ /**
+ * The constant pool item that contains the internal name of the super class
+ * of this class.
+ */
+ private int superName;
+
+ /**
+ * Number of interfaces implemented or extended by this class or interface.
+ */
+ private int interfaceCount;
+
+ /**
+ * The interfaces implemented or extended by this class or interface. More
+ * precisely, this array contains the indexes of the constant pool items
+ * that contain the internal names of these interfaces.
+ */
+ private int[] interfaces;
+
+ /**
+ * The index of the constant pool item that contains the name of the source
+ * file from which this class was compiled.
+ */
+ private int sourceFile;
+
+ /**
+ * The SourceDebug attribute of this class.
+ */
+ private ByteVector sourceDebug;
+
+ /**
+ * The constant pool item that contains the name of the enclosing class of
+ * this class.
+ */
+ private int enclosingMethodOwner;
+
+ /**
+ * The constant pool item that contains the name and descriptor of the
+ * enclosing method of this class.
+ */
+ private int enclosingMethod;
+
+ /**
+ * The runtime visible annotations of this class.
+ */
+ private AnnotationWriter anns;
+
+ /**
+ * The runtime invisible annotations of this class.
+ */
+ private AnnotationWriter ianns;
+
+ /**
+ * The runtime visible type annotations of this class.
+ */
+ private AnnotationWriter tanns;
+
+ /**
+ * The runtime invisible type annotations of this class.
+ */
+ private AnnotationWriter itanns;
+
+ /**
+ * The non standard attributes of this class.
+ */
+ private Attribute attrs;
+
+ /**
+ * The number of entries in the InnerClasses attribute.
+ */
+ private int innerClassesCount;
+
+ /**
+ * The InnerClasses attribute.
+ */
+ private ByteVector innerClasses;
+
+ /**
+ * The number of entries in the BootstrapMethods attribute.
+ */
+ int bootstrapMethodsCount;
+
+ /**
+ * The BootstrapMethods attribute.
+ */
+ ByteVector bootstrapMethods;
+
+ /**
+ * The fields of this class. These fields are stored in a linked list of
+ * {@link FieldWriter} objects, linked to each other by their
+ * {@link FieldWriter#fv} field. This field stores the first element of this
+ * list.
+ */
+ FieldWriter firstField;
+
+ /**
+ * The fields of this class. These fields are stored in a linked list of
+ * {@link FieldWriter} objects, linked to each other by their
+ * {@link FieldWriter#fv} field. This field stores the last element of this
+ * list.
+ */
+ FieldWriter lastField;
+
+ /**
+ * The methods of this class. These methods are stored in a linked list of
+ * {@link MethodWriter} objects, linked to each other by their
+ * {@link MethodWriter#mv} field. This field stores the first element of
+ * this list.
+ */
+ MethodWriter firstMethod;
+
+ /**
+ * The methods of this class. These methods are stored in a linked list of
+ * {@link MethodWriter} objects, linked to each other by their
+ * {@link MethodWriter#mv} field. This field stores the last element of this
+ * list.
+ */
+ MethodWriter lastMethod;
+
+ /**
+ * <tt>true</tt> if the maximum stack size and number of local variables
+ * must be automatically computed.
+ */
+ private boolean computeMaxs;
+
+ /**
+ * <tt>true</tt> if the stack map frames must be recomputed from scratch.
+ */
+ private boolean computeFrames;
+
+ /**
+ * <tt>true</tt> if the stack map tables of this class are invalid. The
+ * {@link MethodWriter#resizeInstructions} method cannot transform existing
+ * stack map tables, and so produces potentially invalid classes when it is
+ * executed. In this case the class is reread and rewritten with the
+ * {@link #COMPUTE_FRAMES} option (the resizeInstructions method can resize
+ * stack map tables when this option is used).
+ */
+ boolean invalidFrames;
+
+ // ------------------------------------------------------------------------
+ // Static initializer
+ // ------------------------------------------------------------------------
+
+ /**
+ * Computes the instruction types of JVM opcodes.
+ */
+ static {
+ int i;
+ byte[] b = new byte[220];
+ String s = "AAAAAAAAAAAAAAAABCLMMDDDDDEEEEEEEEEEEEEEEEEEEEAAAAAAAADD"
+ + "DDDEEEEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ + "AAAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAAAAAAJJJJJJJJJJJJJJJJDOPAA"
+ + "AAAAGGGGGGGHIFBFAAFFAARQJJKKJJJJJJJJJJJJJJJJJJ";
+ for (i = 0; i < b.length; ++i) {
+ b[i] = (byte) (s.charAt(i) - 'A');
+ }
+ TYPE = b;
+
+ // code to generate the above string
+ //
+ // // SBYTE_INSN instructions
+ // b[Constants.NEWARRAY] = SBYTE_INSN;
+ // b[Constants.BIPUSH] = SBYTE_INSN;
+ //
+ // // SHORT_INSN instructions
+ // b[Constants.SIPUSH] = SHORT_INSN;
+ //
+ // // (IMPL)VAR_INSN instructions
+ // b[Constants.RET] = VAR_INSN;
+ // for (i = Constants.ILOAD; i <= Constants.ALOAD; ++i) {
+ // b[i] = VAR_INSN;
+ // }
+ // for (i = Constants.ISTORE; i <= Constants.ASTORE; ++i) {
+ // b[i] = VAR_INSN;
+ // }
+ // for (i = 26; i <= 45; ++i) { // ILOAD_0 to ALOAD_3
+ // b[i] = IMPLVAR_INSN;
+ // }
+ // for (i = 59; i <= 78; ++i) { // ISTORE_0 to ASTORE_3
+ // b[i] = IMPLVAR_INSN;
+ // }
+ //
+ // // TYPE_INSN instructions
+ // b[Constants.NEW] = TYPE_INSN;
+ // b[Constants.ANEWARRAY] = TYPE_INSN;
+ // b[Constants.CHECKCAST] = TYPE_INSN;
+ // b[Constants.INSTANCEOF] = TYPE_INSN;
+ //
+ // // (Set)FIELDORMETH_INSN instructions
+ // for (i = Constants.GETSTATIC; i <= Constants.INVOKESTATIC; ++i) {
+ // b[i] = FIELDORMETH_INSN;
+ // }
+ // b[Constants.INVOKEINTERFACE] = ITFMETH_INSN;
+ // b[Constants.INVOKEDYNAMIC] = INDYMETH_INSN;
+ //
+ // // LABEL(W)_INSN instructions
+ // for (i = Constants.IFEQ; i <= Constants.JSR; ++i) {
+ // b[i] = LABEL_INSN;
+ // }
+ // b[Constants.IFNULL] = LABEL_INSN;
+ // b[Constants.IFNONNULL] = LABEL_INSN;
+ // b[200] = LABELW_INSN; // GOTO_W
+ // b[201] = LABELW_INSN; // JSR_W
+ // // temporary opcodes used internally by ASM - see Label and
+ // MethodWriter
+ // for (i = 202; i < 220; ++i) {
+ // b[i] = LABEL_INSN;
+ // }
+ //
+ // // LDC(_W) instructions
+ // b[Constants.LDC] = LDC_INSN;
+ // b[19] = LDCW_INSN; // LDC_W
+ // b[20] = LDCW_INSN; // LDC2_W
+ //
+ // // special instructions
+ // b[Constants.IINC] = IINC_INSN;
+ // b[Constants.TABLESWITCH] = TABL_INSN;
+ // b[Constants.LOOKUPSWITCH] = LOOK_INSN;
+ // b[Constants.MULTIANEWARRAY] = MANA_INSN;
+ // b[196] = WIDE_INSN; // WIDE
+ //
+ // for (i = 0; i < b.length; ++i) {
+ // System.err.print((char)('A' + b[i]));
+ // }
+ // System.err.println();
+ }
+
+ // ------------------------------------------------------------------------
+ // Constructor
+ // ------------------------------------------------------------------------
+
+ /**
+ * Constructs a new {@link ClassWriter} object.
+ *
+ * @param flags
+ * option flags that can be used to modify the default behavior
+ * of this class. See {@link #COMPUTE_MAXS},
+ * {@link #COMPUTE_FRAMES}.
+ */
+ public ClassWriter(final int flags) {
+ super(Opcodes.ASM5);
+ index = 1;
+ pool = new ByteVector();
+ items = new Item[256];
+ threshold = (int) (0.75d * items.length);
+ key = new Item();
+ key2 = new Item();
+ key3 = new Item();
+ key4 = new Item();
+ this.computeMaxs = (flags & COMPUTE_MAXS) != 0;
+ this.computeFrames = (flags & COMPUTE_FRAMES) != 0;
+ }
+
+ /**
+ * Constructs a new {@link ClassWriter} object and enables optimizations for
+ * "mostly add" bytecode transformations. These optimizations are the
+ * following:
+ *
+ * <ul>
+ * <li>The constant pool from the original class is copied as is in the new
+ * class, which saves time. New constant pool entries will be added at the
+ * end if necessary, but unused constant pool entries <i>won't be
+ * removed</i>.</li>
+ * <li>Methods that are not transformed are copied as is in the new class,
+ * directly from the original class bytecode (i.e. without emitting visit
+ * events for all the method instructions), which saves a <i>lot</i> of
+ * time. Untransformed methods are detected by the fact that the
+ * {@link ClassReader} receives {@link MethodVisitor} objects that come from
+ * a {@link ClassWriter} (and not from any other {@link ClassVisitor}
+ * instance).</li>
+ * </ul>
+ *
+ * @param classReader
+ * the {@link ClassReader} used to read the original class. It
+ * will be used to copy the entire constant pool from the
+ * original class and also to copy other fragments of original
+ * bytecode where applicable.
+ * @param flags
+ * option flags that can be used to modify the default behavior
+ * of this class. <i>These option flags do not affect methods
+ * that are copied as is in the new class. This means that the
+ * maximum stack size nor the stack frames will be computed for
+ * these methods</i>. See {@link #COMPUTE_MAXS},
+ * {@link #COMPUTE_FRAMES}.
+ */
+ public ClassWriter(final ClassReader classReader, final int flags) {
+ this(flags);
+ classReader.copyPool(this);
+ this.cr = classReader;
+ }
+
+ // ------------------------------------------------------------------------
+ // Implementation of the ClassVisitor abstract class
+ // ------------------------------------------------------------------------
+
+ @Override
+ public final void visit(final int version, final int access,
+ final String name, final String signature, final String superName,
+ final String[] interfaces) {
+ this.version = version;
+ this.access = access;
+ this.name = newClass(name);
+ thisName = name;
+ if (ClassReader.SIGNATURES && signature != null) {
+ this.signature = newUTF8(signature);
+ }
+ this.superName = superName == null ? 0 : newClass(superName);
+ if (interfaces != null && interfaces.length > 0) {
+ interfaceCount = interfaces.length;
+ this.interfaces = new int[interfaceCount];
+ for (int i = 0; i < interfaceCount; ++i) {
+ this.interfaces[i] = newClass(interfaces[i]);
+ }
+ }
+ }
+
+ @Override
+ public final void visitSource(final String file, final String debug) {
+ if (file != null) {
+ sourceFile = newUTF8(file);
+ }
+ if (debug != null) {
+ sourceDebug = new ByteVector().encodeUTF8(debug, 0,
+ Integer.MAX_VALUE);
+ }
+ }
+
+ @Override
+ public final void visitOuterClass(final String owner, final String name,
+ final String desc) {
+ enclosingMethodOwner = newClass(owner);
+ if (name != null && desc != null) {
+ enclosingMethod = newNameType(name, desc);
+ }
+ }
+
+ @Override
+ public final AnnotationVisitor visitAnnotation(final String desc,
+ final boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ // write type, and reserve space for values count
+ bv.putShort(newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(this, true, bv, bv, 2);
+ if (visible) {
+ aw.next = anns;
+ anns = aw;
+ } else {
+ aw.next = ianns;
+ ianns = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public final AnnotationVisitor visitTypeAnnotation(int typeRef,
+ TypePath typePath, final String desc, final boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ // write target_type and target_info
+ AnnotationWriter.putTarget(typeRef, typePath, bv);
+ // write type, and reserve space for values count
+ bv.putShort(newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(this, true, bv, bv,
+ bv.length - 2);
+ if (visible) {
+ aw.next = tanns;
+ tanns = aw;
+ } else {
+ aw.next = itanns;
+ itanns = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public final void visitAttribute(final Attribute attr) {
+ attr.next = attrs;
+ attrs = attr;
+ }
+
+ @Override
+ public final void visitInnerClass(final String name,
+ final String outerName, final String innerName, final int access) {
+ if (innerClasses == null) {
+ innerClasses = new ByteVector();
+ }
+ // Sec. 4.7.6 of the JVMS states "Every CONSTANT_Class_info entry in the
+ // constant_pool table which represents a class or interface C that is
+ // not a package member must have exactly one corresponding entry in the
+ // classes array". To avoid duplicates we keep track in the intVal field
+ // of the Item of each CONSTANT_Class_info entry C whether an inner
+ // class entry has already been added for C (this field is unused for
+ // class entries, and changing its value does not change the hashcode
+ // and equality tests). If so we store the index of this inner class
+ // entry (plus one) in intVal. This hack allows duplicate detection in
+ // O(1) time.
+ Item nameItem = newClassItem(name);
+ if (nameItem.intVal == 0) {
+ ++innerClassesCount;
+ innerClasses.putShort(nameItem.index);
+ innerClasses.putShort(outerName == null ? 0 : newClass(outerName));
+ innerClasses.putShort(innerName == null ? 0 : newUTF8(innerName));
+ innerClasses.putShort(access);
+ nameItem.intVal = innerClassesCount;
+ } else {
+ // Compare the inner classes entry nameItem.intVal - 1 with the
+ // arguments of this method and throw an exception if there is a
+ // difference?
+ }
+ }
+
+ @Override
+ public final FieldVisitor visitField(final int access, final String name,
+ final String desc, final String signature, final Object value) {
+ return new FieldWriter(this, access, name, desc, signature, value);
+ }
+
+ @Override
+ public final MethodVisitor visitMethod(final int access, final String name,
+ final String desc, final String signature, final String[] exceptions) {
+ return new MethodWriter(this, access, name, desc, signature,
+ exceptions, computeMaxs, computeFrames);
+ }
+
+ @Override
+ public final void visitEnd() {
+ }
+
+ // ------------------------------------------------------------------------
+ // Other public methods
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the bytecode of the class that was build with this class writer.
+ *
+ * @return the bytecode of the class that was build with this class writer.
+ */
+ public byte[] toByteArray() {
+ if (index > 0xFFFF) {
+ throw new RuntimeException("Class file too large!");
+ }
+ // computes the real size of the bytecode of this class
+ int size = 24 + 2 * interfaceCount;
+ int nbFields = 0;
+ FieldWriter fb = firstField;
+ while (fb != null) {
+ ++nbFields;
+ size += fb.getSize();
+ fb = (FieldWriter) fb.fv;
+ }
+ int nbMethods = 0;
+ MethodWriter mb = firstMethod;
+ while (mb != null) {
+ ++nbMethods;
+ size += mb.getSize();
+ mb = (MethodWriter) mb.mv;
+ }
+ int attributeCount = 0;
+ if (bootstrapMethods != null) {
+ // we put it as first attribute in order to improve a bit
+ // ClassReader.copyBootstrapMethods
+ ++attributeCount;
+ size += 8 + bootstrapMethods.length;
+ newUTF8("BootstrapMethods");
+ }
+ if (ClassReader.SIGNATURES && signature != 0) {
+ ++attributeCount;
+ size += 8;
+ newUTF8("Signature");
+ }
+ if (sourceFile != 0) {
+ ++attributeCount;
+ size += 8;
+ newUTF8("SourceFile");
+ }
+ if (sourceDebug != null) {
+ ++attributeCount;
+ size += sourceDebug.length + 6;
+ newUTF8("SourceDebugExtension");
+ }
+ if (enclosingMethodOwner != 0) {
+ ++attributeCount;
+ size += 10;
+ newUTF8("EnclosingMethod");
+ }
+ if ((access & Opcodes.ACC_DEPRECATED) != 0) {
+ ++attributeCount;
+ size += 6;
+ newUTF8("Deprecated");
+ }
+ if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
+ if ((version & 0xFFFF) < Opcodes.V1_5
+ || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) {
+ ++attributeCount;
+ size += 6;
+ newUTF8("Synthetic");
+ }
+ }
+ if (innerClasses != null) {
+ ++attributeCount;
+ size += 8 + innerClasses.length;
+ newUTF8("InnerClasses");
+ }
+ if (ClassReader.ANNOTATIONS && anns != null) {
+ ++attributeCount;
+ size += 8 + anns.getSize();
+ newUTF8("RuntimeVisibleAnnotations");
+ }
+ if (ClassReader.ANNOTATIONS && ianns != null) {
+ ++attributeCount;
+ size += 8 + ianns.getSize();
+ newUTF8("RuntimeInvisibleAnnotations");
+ }
+ if (ClassReader.ANNOTATIONS && tanns != null) {
+ ++attributeCount;
+ size += 8 + tanns.getSize();
+ newUTF8("RuntimeVisibleTypeAnnotations");
+ }
+ if (ClassReader.ANNOTATIONS && itanns != null) {
+ ++attributeCount;
+ size += 8 + itanns.getSize();
+ newUTF8("RuntimeInvisibleTypeAnnotations");
+ }
+ if (attrs != null) {
+ attributeCount += attrs.getCount();
+ size += attrs.getSize(this, null, 0, -1, -1);
+ }
+ size += pool.length;
+ // allocates a byte vector of this size, in order to avoid unnecessary
+ // arraycopy operations in the ByteVector.enlarge() method
+ ByteVector out = new ByteVector(size);
+ out.putInt(0xCAFEBABE).putInt(version);
+ out.putShort(index).putByteArray(pool.data, 0, pool.length);
+ int mask = Opcodes.ACC_DEPRECATED | ACC_SYNTHETIC_ATTRIBUTE
+ | ((access & ACC_SYNTHETIC_ATTRIBUTE) / TO_ACC_SYNTHETIC);
+ out.putShort(access & ~mask).putShort(name).putShort(superName);
+ out.putShort(interfaceCount);
+ for (int i = 0; i < interfaceCount; ++i) {
+ out.putShort(interfaces[i]);
+ }
+ out.putShort(nbFields);
+ fb = firstField;
+ while (fb != null) {
+ fb.put(out);
+ fb = (FieldWriter) fb.fv;
+ }
+ out.putShort(nbMethods);
+ mb = firstMethod;
+ while (mb != null) {
+ mb.put(out);
+ mb = (MethodWriter) mb.mv;
+ }
+ out.putShort(attributeCount);
+ if (bootstrapMethods != null) {
+ out.putShort(newUTF8("BootstrapMethods"));
+ out.putInt(bootstrapMethods.length + 2).putShort(
+ bootstrapMethodsCount);
+ out.putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length);
+ }
+ if (ClassReader.SIGNATURES && signature != 0) {
+ out.putShort(newUTF8("Signature")).putInt(2).putShort(signature);
+ }
+ if (sourceFile != 0) {
+ out.putShort(newUTF8("SourceFile")).putInt(2).putShort(sourceFile);
+ }
+ if (sourceDebug != null) {
+ int len = sourceDebug.length;
+ out.putShort(newUTF8("SourceDebugExtension")).putInt(len);
+ out.putByteArray(sourceDebug.data, 0, len);
+ }
+ if (enclosingMethodOwner != 0) {
+ out.putShort(newUTF8("EnclosingMethod")).putInt(4);
+ out.putShort(enclosingMethodOwner).putShort(enclosingMethod);
+ }
+ if ((access & Opcodes.ACC_DEPRECATED) != 0) {
+ out.putShort(newUTF8("Deprecated")).putInt(0);
+ }
+ if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
+ if ((version & 0xFFFF) < Opcodes.V1_5
+ || (access & ACC_SYNTHETIC_ATTRIBUTE) != 0) {
+ out.putShort(newUTF8("Synthetic")).putInt(0);
+ }
+ }
+ if (innerClasses != null) {
+ out.putShort(newUTF8("InnerClasses"));
+ out.putInt(innerClasses.length + 2).putShort(innerClassesCount);
+ out.putByteArray(innerClasses.data, 0, innerClasses.length);
+ }
+ if (ClassReader.ANNOTATIONS && anns != null) {
+ out.putShort(newUTF8("RuntimeVisibleAnnotations"));
+ anns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && ianns != null) {
+ out.putShort(newUTF8("RuntimeInvisibleAnnotations"));
+ ianns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && tanns != null) {
+ out.putShort(newUTF8("RuntimeVisibleTypeAnnotations"));
+ tanns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && itanns != null) {
+ out.putShort(newUTF8("RuntimeInvisibleTypeAnnotations"));
+ itanns.put(out);
+ }
+ if (attrs != null) {
+ attrs.put(this, null, 0, -1, -1, out);
+ }
+ if (invalidFrames) {
+ anns = null;
+ ianns = null;
+ attrs = null;
+ innerClassesCount = 0;
+ innerClasses = null;
+ bootstrapMethodsCount = 0;
+ bootstrapMethods = null;
+ firstField = null;
+ lastField = null;
+ firstMethod = null;
+ lastMethod = null;
+ computeMaxs = false;
+ computeFrames = true;
+ invalidFrames = false;
+ new ClassReader(out.data).accept(this, ClassReader.SKIP_FRAMES);
+ return toByteArray();
+ }
+ return out.data;
+ }
+
+ // ------------------------------------------------------------------------
+ // Utility methods: constant pool management
+ // ------------------------------------------------------------------------
+
+ /**
+ * Adds a number or string constant to the constant pool of the class being
+ * build. Does nothing if the constant pool already contains a similar item.
+ *
+ * @param cst
+ * the value of the constant to be added to the constant pool.
+ * This parameter must be an {@link Integer}, a {@link Float}, a
+ * {@link Long}, a {@link Double}, a {@link String} or a
+ * {@link Type}.
+ * @return a new or already existing constant item with the given value.
+ */
+ Item newConstItem(final Object cst) {
+ if (cst instanceof Integer) {
+ int val = ((Integer) cst).intValue();
+ return newInteger(val);
+ } else if (cst instanceof Byte) {
+ int val = ((Byte) cst).intValue();
+ return newInteger(val);
+ } else if (cst instanceof Character) {
+ int val = ((Character) cst).charValue();
+ return newInteger(val);
+ } else if (cst instanceof Short) {
+ int val = ((Short) cst).intValue();
+ return newInteger(val);
+ } else if (cst instanceof Boolean) {
+ int val = ((Boolean) cst).booleanValue() ? 1 : 0;
+ return newInteger(val);
+ } else if (cst instanceof Float) {
+ float val = ((Float) cst).floatValue();
+ return newFloat(val);
+ } else if (cst instanceof Long) {
+ long val = ((Long) cst).longValue();
+ return newLong(val);
+ } else if (cst instanceof Double) {
+ double val = ((Double) cst).doubleValue();
+ return newDouble(val);
+ } else if (cst instanceof String) {
+ return newString((String) cst);
+ } else if (cst instanceof Type) {
+ Type t = (Type) cst;
+ int s = t.getSort();
+ if (s == Type.OBJECT) {
+ return newClassItem(t.getInternalName());
+ } else if (s == Type.METHOD) {
+ return newMethodTypeItem(t.getDescriptor());
+ } else { // s == primitive type or array
+ return newClassItem(t.getDescriptor());
+ }
+ } else if (cst instanceof Handle) {
+ Handle h = (Handle) cst;
+ return newHandleItem(h.tag, h.owner, h.name, h.desc);
+ } else {
+ throw new IllegalArgumentException("value " + cst);
+ }
+ }
+
+ /**
+ * Adds a number or string constant to the constant pool of the class being
+ * build. Does nothing if the constant pool already contains a similar item.
+ * <i>This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.</i>
+ *
+ * @param cst
+ * the value of the constant to be added to the constant pool.
+ * This parameter must be an {@link Integer}, a {@link Float}, a
+ * {@link Long}, a {@link Double} or a {@link String}.
+ * @return the index of a new or already existing constant item with the
+ * given value.
+ */
+ public int newConst(final Object cst) {
+ return newConstItem(cst).index;
+ }
+
+ /**
+ * Adds an UTF8 string to the constant pool of the class being build. Does
+ * nothing if the constant pool already contains a similar item. <i>This
+ * method is intended for {@link Attribute} sub classes, and is normally not
+ * needed by class generators or adapters.</i>
+ *
+ * @param value
+ * the String value.
+ * @return the index of a new or already existing UTF8 item.
+ */
+ public int newUTF8(final String value) {
+ key.set(UTF8, value, null, null);
+ Item result = get(key);
+ if (result == null) {
+ pool.putByte(UTF8).putUTF8(value);
+ result = new Item(index++, key);
+ put(result);
+ }
+ return result.index;
+ }
+
+ /**
+ * Adds a class reference to the constant pool of the class being build.
+ * Does nothing if the constant pool already contains a similar item.
+ * <i>This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.</i>
+ *
+ * @param value
+ * the internal name of the class.
+ * @return a new or already existing class reference item.
+ */
+ Item newClassItem(final String value) {
+ key2.set(CLASS, value, null, null);
+ Item result = get(key2);
+ if (result == null) {
+ pool.put12(CLASS, newUTF8(value));
+ result = new Item(index++, key2);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a class reference to the constant pool of the class being build.
+ * Does nothing if the constant pool already contains a similar item.
+ * <i>This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.</i>
+ *
+ * @param value
+ * the internal name of the class.
+ * @return the index of a new or already existing class reference item.
+ */
+ public int newClass(final String value) {
+ return newClassItem(value).index;
+ }
+
+ /**
+ * Adds a method type reference to the constant pool of the class being
+ * build. Does nothing if the constant pool already contains a similar item.
+ * <i>This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.</i>
+ *
+ * @param methodDesc
+ * method descriptor of the method type.
+ * @return a new or already existing method type reference item.
+ */
+ Item newMethodTypeItem(final String methodDesc) {
+ key2.set(MTYPE, methodDesc, null, null);
+ Item result = get(key2);
+ if (result == null) {
+ pool.put12(MTYPE, newUTF8(methodDesc));
+ result = new Item(index++, key2);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a method type reference to the constant pool of the class being
+ * build. Does nothing if the constant pool already contains a similar item.
+ * <i>This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.</i>
+ *
+ * @param methodDesc
+ * method descriptor of the method type.
+ * @return the index of a new or already existing method type reference
+ * item.
+ */
+ public int newMethodType(final String methodDesc) {
+ return newMethodTypeItem(methodDesc).index;
+ }
+
+ /**
+ * Adds a handle to the constant pool of the class being build. Does nothing
+ * if the constant pool already contains a similar item. <i>This method is
+ * intended for {@link Attribute} sub classes, and is normally not needed by
+ * class generators or adapters.</i>
+ *
+ * @param tag
+ * the kind of this handle. Must be {@link Opcodes#H_GETFIELD},
+ * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD},
+ * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL},
+ * {@link Opcodes#H_INVOKESTATIC},
+ * {@link Opcodes#H_INVOKESPECIAL},
+ * {@link Opcodes#H_NEWINVOKESPECIAL} or
+ * {@link Opcodes#H_INVOKEINTERFACE}.
+ * @param owner
+ * the internal name of the field or method owner class.
+ * @param name
+ * the name of the field or method.
+ * @param desc
+ * the descriptor of the field or method.
+ * @return a new or an already existing method type reference item.
+ */
+ Item newHandleItem(final int tag, final String owner, final String name,
+ final String desc) {
+ key4.set(HANDLE_BASE + tag, owner, name, desc);
+ Item result = get(key4);
+ if (result == null) {
+ if (tag <= Opcodes.H_PUTSTATIC) {
+ put112(HANDLE, tag, newField(owner, name, desc));
+ } else {
+ put112(HANDLE,
+ tag,
+ newMethod(owner, name, desc,
+ tag == Opcodes.H_INVOKEINTERFACE));
+ }
+ result = new Item(index++, key4);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a handle to the constant pool of the class being build. Does nothing
+ * if the constant pool already contains a similar item. <i>This method is
+ * intended for {@link Attribute} sub classes, and is normally not needed by
+ * class generators or adapters.</i>
+ *
+ * @param tag
+ * the kind of this handle. Must be {@link Opcodes#H_GETFIELD},
+ * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD},
+ * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL},
+ * {@link Opcodes#H_INVOKESTATIC},
+ * {@link Opcodes#H_INVOKESPECIAL},
+ * {@link Opcodes#H_NEWINVOKESPECIAL} or
+ * {@link Opcodes#H_INVOKEINTERFACE}.
+ * @param owner
+ * the internal name of the field or method owner class.
+ * @param name
+ * the name of the field or method.
+ * @param desc
+ * the descriptor of the field or method.
+ * @return the index of a new or already existing method type reference
+ * item.
+ */
+ public int newHandle(final int tag, final String owner, final String name,
+ final String desc) {
+ return newHandleItem(tag, owner, name, desc).index;
+ }
+
+ /**
+ * Adds an invokedynamic reference to the constant pool of the class being
+ * build. Does nothing if the constant pool already contains a similar item.
+ * <i>This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.</i>
+ *
+ * @param name
+ * name of the invoked method.
+ * @param desc
+ * descriptor of the invoke method.
+ * @param bsm
+ * the bootstrap method.
+ * @param bsmArgs
+ * the bootstrap method constant arguments.
+ *
+ * @return a new or an already existing invokedynamic type reference item.
+ */
+ Item newInvokeDynamicItem(final String name, final String desc,
+ final Handle bsm, final Object... bsmArgs) {
+ // cache for performance
+ ByteVector bootstrapMethods = this.bootstrapMethods;
+ if (bootstrapMethods == null) {
+ bootstrapMethods = this.bootstrapMethods = new ByteVector();
+ }
+
+ int position = bootstrapMethods.length; // record current position
+
+ int hashCode = bsm.hashCode();
+ bootstrapMethods.putShort(newHandle(bsm.tag, bsm.owner, bsm.name,
+ bsm.desc));
+
+ int argsLength = bsmArgs.length;
+ bootstrapMethods.putShort(argsLength);
+
+ for (int i = 0; i < argsLength; i++) {
+ Object bsmArg = bsmArgs[i];
+ hashCode ^= bsmArg.hashCode();
+ bootstrapMethods.putShort(newConst(bsmArg));
+ }
+
+ byte[] data = bootstrapMethods.data;
+ int length = (1 + 1 + argsLength) << 1; // (bsm + argCount + arguments)
+ hashCode &= 0x7FFFFFFF;
+ Item result = items[hashCode % items.length];
+ loop: while (result != null) {
+ if (result.type != BSM || result.hashCode != hashCode) {
+ result = result.next;
+ continue;
+ }
+
+ // because the data encode the size of the argument
+ // we don't need to test if these size are equals
+ int resultPosition = result.intVal;
+ for (int p = 0; p < length; p++) {
+ if (data[position + p] != data[resultPosition + p]) {
+ result = result.next;
+ continue loop;
+ }
+ }
+ break;
+ }
+
+ int bootstrapMethodIndex;
+ if (result != null) {
+ bootstrapMethodIndex = result.index;
+ bootstrapMethods.length = position; // revert to old position
+ } else {
+ bootstrapMethodIndex = bootstrapMethodsCount++;
+ result = new Item(bootstrapMethodIndex);
+ result.set(position, hashCode);
+ put(result);
+ }
+
+ // now, create the InvokeDynamic constant
+ key3.set(name, desc, bootstrapMethodIndex);
+ result = get(key3);
+ if (result == null) {
+ put122(INDY, bootstrapMethodIndex, newNameType(name, desc));
+ result = new Item(index++, key3);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds an invokedynamic reference to the constant pool of the class being
+ * build. Does nothing if the constant pool already contains a similar item.
+ * <i>This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.</i>
+ *
+ * @param name
+ * name of the invoked method.
+ * @param desc
+ * descriptor of the invoke method.
+ * @param bsm
+ * the bootstrap method.
+ * @param bsmArgs
+ * the bootstrap method constant arguments.
+ *
+ * @return the index of a new or already existing invokedynamic reference
+ * item.
+ */
+ public int newInvokeDynamic(final String name, final String desc,
+ final Handle bsm, final Object... bsmArgs) {
+ return newInvokeDynamicItem(name, desc, bsm, bsmArgs).index;
+ }
+
+ /**
+ * Adds a field reference to the constant pool of the class being build.
+ * Does nothing if the constant pool already contains a similar item.
+ *
+ * @param owner
+ * the internal name of the field's owner class.
+ * @param name
+ * the field's name.
+ * @param desc
+ * the field's descriptor.
+ * @return a new or already existing field reference item.
+ */
+ Item newFieldItem(final String owner, final String name, final String desc) {
+ key3.set(FIELD, owner, name, desc);
+ Item result = get(key3);
+ if (result == null) {
+ put122(FIELD, newClass(owner), newNameType(name, desc));
+ result = new Item(index++, key3);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a field reference to the constant pool of the class being build.
+ * Does nothing if the constant pool already contains a similar item.
+ * <i>This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.</i>
+ *
+ * @param owner
+ * the internal name of the field's owner class.
+ * @param name
+ * the field's name.
+ * @param desc
+ * the field's descriptor.
+ * @return the index of a new or already existing field reference item.
+ */
+ public int newField(final String owner, final String name, final String desc) {
+ return newFieldItem(owner, name, desc).index;
+ }
+
+ /**
+ * Adds a method reference to the constant pool of the class being build.
+ * Does nothing if the constant pool already contains a similar item.
+ *
+ * @param owner
+ * the internal name of the method's owner class.
+ * @param name
+ * the method's name.
+ * @param desc
+ * the method's descriptor.
+ * @param itf
+ * <tt>true</tt> if <tt>owner</tt> is an interface.
+ * @return a new or already existing method reference item.
+ */
+ Item newMethodItem(final String owner, final String name,
+ final String desc, final boolean itf) {
+ int type = itf ? IMETH : METH;
+ key3.set(type, owner, name, desc);
+ Item result = get(key3);
+ if (result == null) {
+ put122(type, newClass(owner), newNameType(name, desc));
+ result = new Item(index++, key3);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a method reference to the constant pool of the class being build.
+ * Does nothing if the constant pool already contains a similar item.
+ * <i>This method is intended for {@link Attribute} sub classes, and is
+ * normally not needed by class generators or adapters.</i>
+ *
+ * @param owner
+ * the internal name of the method's owner class.
+ * @param name
+ * the method's name.
+ * @param desc
+ * the method's descriptor.
+ * @param itf
+ * <tt>true</tt> if <tt>owner</tt> is an interface.
+ * @return the index of a new or already existing method reference item.
+ */
+ public int newMethod(final String owner, final String name,
+ final String desc, final boolean itf) {
+ return newMethodItem(owner, name, desc, itf).index;
+ }
+
+ /**
+ * Adds an integer to the constant pool of the class being build. Does
+ * nothing if the constant pool already contains a similar item.
+ *
+ * @param value
+ * the int value.
+ * @return a new or already existing int item.
+ */
+ Item newInteger(final int value) {
+ key.set(value);
+ Item result = get(key);
+ if (result == null) {
+ pool.putByte(INT).putInt(value);
+ result = new Item(index++, key);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a float to the constant pool of the class being build. Does nothing
+ * if the constant pool already contains a similar item.
+ *
+ * @param value
+ * the float value.
+ * @return a new or already existing float item.
+ */
+ Item newFloat(final float value) {
+ key.set(value);
+ Item result = get(key);
+ if (result == null) {
+ pool.putByte(FLOAT).putInt(key.intVal);
+ result = new Item(index++, key);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a long to the constant pool of the class being build. Does nothing
+ * if the constant pool already contains a similar item.
+ *
+ * @param value
+ * the long value.
+ * @return a new or already existing long item.
+ */
+ Item newLong(final long value) {
+ key.set(value);
+ Item result = get(key);
+ if (result == null) {
+ pool.putByte(LONG).putLong(value);
+ result = new Item(index, key);
+ index += 2;
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a double to the constant pool of the class being build. Does nothing
+ * if the constant pool already contains a similar item.
+ *
+ * @param value
+ * the double value.
+ * @return a new or already existing double item.
+ */
+ Item newDouble(final double value) {
+ key.set(value);
+ Item result = get(key);
+ if (result == null) {
+ pool.putByte(DOUBLE).putLong(key.longVal);
+ result = new Item(index, key);
+ index += 2;
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a string to the constant pool of the class being build. Does nothing
+ * if the constant pool already contains a similar item.
+ *
+ * @param value
+ * the String value.
+ * @return a new or already existing string item.
+ */
+ private Item newString(final String value) {
+ key2.set(STR, value, null, null);
+ Item result = get(key2);
+ if (result == null) {
+ pool.put12(STR, newUTF8(value));
+ result = new Item(index++, key2);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds a name and type to the constant pool of the class being build. Does
+ * nothing if the constant pool already contains a similar item. <i>This
+ * method is intended for {@link Attribute} sub classes, and is normally not
+ * needed by class generators or adapters.</i>
+ *
+ * @param name
+ * a name.
+ * @param desc
+ * a type descriptor.
+ * @return the index of a new or already existing name and type item.
+ */
+ public int newNameType(final String name, final String desc) {
+ return newNameTypeItem(name, desc).index;
+ }
+
+ /**
+ * Adds a name and type to the constant pool of the class being build. Does
+ * nothing if the constant pool already contains a similar item.
+ *
+ * @param name
+ * a name.
+ * @param desc
+ * a type descriptor.
+ * @return a new or already existing name and type item.
+ */
+ Item newNameTypeItem(final String name, final String desc) {
+ key2.set(NAME_TYPE, name, desc, null);
+ Item result = get(key2);
+ if (result == null) {
+ put122(NAME_TYPE, newUTF8(name), newUTF8(desc));
+ result = new Item(index++, key2);
+ put(result);
+ }
+ return result;
+ }
+
+ /**
+ * Adds the given internal name to {@link #typeTable} and returns its index.
+ * Does nothing if the type table already contains this internal name.
+ *
+ * @param type
+ * the internal name to be added to the type table.
+ * @return the index of this internal name in the type table.
+ */
+ int addType(final String type) {
+ key.set(TYPE_NORMAL, type, null, null);
+ Item result = get(key);
+ if (result == null) {
+ result = addType(key);
+ }
+ return result.index;
+ }
+
+ /**
+ * Adds the given "uninitialized" type to {@link #typeTable} and returns its
+ * index. This method is used for UNINITIALIZED types, made of an internal
+ * name and a bytecode offset.
+ *
+ * @param type
+ * the internal name to be added to the type table.
+ * @param offset
+ * the bytecode offset of the NEW instruction that created this
+ * UNINITIALIZED type value.
+ * @return the index of this internal name in the type table.
+ */
+ int addUninitializedType(final String type, final int offset) {
+ key.type = TYPE_UNINIT;
+ key.intVal = offset;
+ key.strVal1 = type;
+ key.hashCode = 0x7FFFFFFF & (TYPE_UNINIT + type.hashCode() + offset);
+ Item result = get(key);
+ if (result == null) {
+ result = addType(key);
+ }
+ return result.index;
+ }
+
+ /**
+ * Adds the given Item to {@link #typeTable}.
+ *
+ * @param item
+ * the value to be added to the type table.
+ * @return the added Item, which a new Item instance with the same value as
+ * the given Item.
+ */
+ private Item addType(final Item item) {
+ ++typeCount;
+ Item result = new Item(typeCount, key);
+ put(result);
+ if (typeTable == null) {
+ typeTable = new Item[16];
+ }
+ if (typeCount == typeTable.length) {
+ Item[] newTable = new Item[2 * typeTable.length];
+ System.arraycopy(typeTable, 0, newTable, 0, typeTable.length);
+ typeTable = newTable;
+ }
+ typeTable[typeCount] = result;
+ return result;
+ }
+
+ /**
+ * Returns the index of the common super type of the two given types. This
+ * method calls {@link #getCommonSuperClass} and caches the result in the
+ * {@link #items} hash table to speedup future calls with the same
+ * parameters.
+ *
+ * @param type1
+ * index of an internal name in {@link #typeTable}.
+ * @param type2
+ * index of an internal name in {@link #typeTable}.
+ * @return the index of the common super type of the two given types.
+ */
+ int getMergedType(final int type1, final int type2) {
+ key2.type = TYPE_MERGED;
+ key2.longVal = type1 | (((long) type2) << 32);
+ key2.hashCode = 0x7FFFFFFF & (TYPE_MERGED + type1 + type2);
+ Item result = get(key2);
+ if (result == null) {
+ String t = typeTable[type1].strVal1;
+ String u = typeTable[type2].strVal1;
+ key2.intVal = addType(getCommonSuperClass(t, u));
+ result = new Item((short) 0, key2);
+ put(result);
+ }
+ return result.intVal;
+ }
+
+ /**
+ * Returns the common super type of the two given types. The default
+ * implementation of this method <i>loads</i> the two given classes and uses
+ * the java.lang.Class methods to find the common super class. It can be
+ * overridden to compute this common super type in other ways, in particular
+ * without actually loading any class, or to take into account the class
+ * that is currently being generated by this ClassWriter, which can of
+ * course not be loaded since it is under construction.
+ *
+ * @param type1
+ * the internal name of a class.
+ * @param type2
+ * the internal name of another class.
+ * @return the internal name of the common super class of the two given
+ * classes.
+ */
+ protected String getCommonSuperClass(final String type1, final String type2) {
+ Class<?> c, d;
+ ClassLoader classLoader = getClass().getClassLoader();
+ try {
+ c = Class.forName(type1.replace('/', '.'), false, classLoader);
+ d = Class.forName(type2.replace('/', '.'), false, classLoader);
+ } catch (Exception e) {
+ throw new RuntimeException(e.toString());
+ }
+ if (c.isAssignableFrom(d)) {
+ return type1;
+ }
+ if (d.isAssignableFrom(c)) {
+ return type2;
+ }
+ if (c.isInterface() || d.isInterface()) {
+ return "java/lang/Object";
+ } else {
+ do {
+ c = c.getSuperclass();
+ } while (!c.isAssignableFrom(d));
+ return c.getName().replace('.', '/');
+ }
+ }
+
+ /**
+ * Returns the constant pool's hash table item which is equal to the given
+ * item.
+ *
+ * @param key
+ * a constant pool item.
+ * @return the constant pool's hash table item which is equal to the given
+ * item, or <tt>null</tt> if there is no such item.
+ */
+ private Item get(final Item key) {
+ Item i = items[key.hashCode % items.length];
+ while (i != null && (i.type != key.type || !key.isEqualTo(i))) {
+ i = i.next;
+ }
+ return i;
+ }
+
+ /**
+ * Puts the given item in the constant pool's hash table. The hash table
+ * <i>must</i> not already contains this item.
+ *
+ * @param i
+ * the item to be added to the constant pool's hash table.
+ */
+ private void put(final Item i) {
+ if (index + typeCount > threshold) {
+ int ll = items.length;
+ int nl = ll * 2 + 1;
+ Item[] newItems = new Item[nl];
+ for (int l = ll - 1; l >= 0; --l) {
+ Item j = items[l];
+ while (j != null) {
+ int index = j.hashCode % newItems.length;
+ Item k = j.next;
+ j.next = newItems[index];
+ newItems[index] = j;
+ j = k;
+ }
+ }
+ items = newItems;
+ threshold = (int) (nl * 0.75);
+ }
+ int index = i.hashCode % items.length;
+ i.next = items[index];
+ items[index] = i;
+ }
+
+ /**
+ * Puts one byte and two shorts into the constant pool.
+ *
+ * @param b
+ * a byte.
+ * @param s1
+ * a short.
+ * @param s2
+ * another short.
+ */
+ private void put122(final int b, final int s1, final int s2) {
+ pool.put12(b, s1).putShort(s2);
+ }
+
+ /**
+ * Puts two bytes and one short into the constant pool.
+ *
+ * @param b1
+ * a byte.
+ * @param b2
+ * another byte.
+ * @param s
+ * a short.
+ */
+ private void put112(final int b1, final int b2, final int s) {
+ pool.put11(b1, b2).putShort(s);
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/Context.java b/spring-core/src/main/java/org/springframework/asm/Context.java
new file mode 100644
index 00000000..cab4e23f
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/Context.java
@@ -0,0 +1,145 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.springframework.asm;
+
+/**
+ * Information about a class being parsed in a {@link ClassReader}.
+ *
+ * @author Eric Bruneton
+ */
+class Context {
+
+ /**
+ * Prototypes of the attributes that must be parsed for this class.
+ */
+ Attribute[] attrs;
+
+ /**
+ * The {@link ClassReader} option flags for the parsing of this class.
+ */
+ int flags;
+
+ /**
+ * The buffer used to read strings.
+ */
+ char[] buffer;
+
+ /**
+ * The start index of each bootstrap method.
+ */
+ int[] bootstrapMethods;
+
+ /**
+ * The access flags of the method currently being parsed.
+ */
+ int access;
+
+ /**
+ * The name of the method currently being parsed.
+ */
+ String name;
+
+ /**
+ * The descriptor of the method currently being parsed.
+ */
+ String desc;
+
+ /**
+ * The label objects, indexed by bytecode offset, of the method currently
+ * being parsed (only bytecode offsets for which a label is needed have a
+ * non null associated Label object).
+ */
+ Label[] labels;
+
+ /**
+ * The target of the type annotation currently being parsed.
+ */
+ int typeRef;
+
+ /**
+ * The path of the type annotation currently being parsed.
+ */
+ TypePath typePath;
+
+ /**
+ * The offset of the latest stack map frame that has been parsed.
+ */
+ int offset;
+
+ /**
+ * The labels corresponding to the start of the local variable ranges in the
+ * local variable type annotation currently being parsed.
+ */
+ Label[] start;
+
+ /**
+ * The labels corresponding to the end of the local variable ranges in the
+ * local variable type annotation currently being parsed.
+ */
+ Label[] end;
+
+ /**
+ * The local variable indices for each local variable range in the local
+ * variable type annotation currently being parsed.
+ */
+ int[] index;
+
+ /**
+ * The encoding of the latest stack map frame that has been parsed.
+ */
+ int mode;
+
+ /**
+ * The number of locals in the latest stack map frame that has been parsed.
+ */
+ int localCount;
+
+ /**
+ * The number locals in the latest stack map frame that has been parsed,
+ * minus the number of locals in the previous frame.
+ */
+ int localDiff;
+
+ /**
+ * The local values of the latest stack map frame that has been parsed.
+ */
+ Object[] local;
+
+ /**
+ * The stack size of the latest stack map frame that has been parsed.
+ */
+ int stackCount;
+
+ /**
+ * The stack values of the latest stack map frame that has been parsed.
+ */
+ Object[] stack;
+} \ No newline at end of file
diff --git a/spring-core/src/main/java/org/springframework/asm/Edge.java b/spring-core/src/main/java/org/springframework/asm/Edge.java
new file mode 100644
index 00000000..196adc12
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/Edge.java
@@ -0,0 +1,75 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.springframework.asm;
+
+/**
+ * An edge in the control flow graph of a method body. See {@link Label Label}.
+ *
+ * @author Eric Bruneton
+ */
+class Edge {
+
+ /**
+ * Denotes a normal control flow graph edge.
+ */
+ static final int NORMAL = 0;
+
+ /**
+ * Denotes a control flow graph edge corresponding to an exception handler.
+ * More precisely any {@link Edge} whose {@link #info} is strictly positive
+ * corresponds to an exception handler. The actual value of {@link #info} is
+ * the index, in the {@link ClassWriter} type table, of the exception that
+ * is catched.
+ */
+ static final int EXCEPTION = 0x7FFFFFFF;
+
+ /**
+ * Information about this control flow graph edge. If
+ * {@link ClassWriter#COMPUTE_MAXS} is used this field is the (relative)
+ * stack size in the basic block from which this edge originates. This size
+ * is equal to the stack size at the "jump" instruction to which this edge
+ * corresponds, relatively to the stack size at the beginning of the
+ * originating basic block. If {@link ClassWriter#COMPUTE_FRAMES} is used,
+ * this field is the kind of this control flow graph edge (i.e. NORMAL or
+ * EXCEPTION).
+ */
+ int info;
+
+ /**
+ * The successor block of the basic block from which this edge originates.
+ */
+ Label successor;
+
+ /**
+ * The next edge in the list of successors of the originating basic block.
+ * See {@link Label#successors successors}.
+ */
+ Edge next;
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/FieldVisitor.java b/spring-core/src/main/java/org/springframework/asm/FieldVisitor.java
new file mode 100644
index 00000000..2822f3b2
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/FieldVisitor.java
@@ -0,0 +1,152 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.springframework.asm;
+
+/**
+ * A visitor to visit a Java field. The methods of this class must be called in
+ * the following order: ( <tt>visitAnnotation</tt> |
+ * <tt>visitTypeAnnotation</tt> | <tt>visitAttribute</tt> )* <tt>visitEnd</tt>.
+ *
+ * @author Eric Bruneton
+ */
+public abstract class FieldVisitor {
+
+ /**
+ * The ASM API version implemented by this visitor. The value of this field
+ * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ */
+ protected final int api;
+
+ /**
+ * The field visitor to which this visitor must delegate method calls. May
+ * be null.
+ */
+ protected FieldVisitor fv;
+
+ /**
+ * Constructs a new {@link FieldVisitor}.
+ *
+ * @param api
+ * the ASM API version implemented by this visitor. Must be one
+ * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ */
+ public FieldVisitor(final int api) {
+ this(api, null);
+ }
+
+ /**
+ * Constructs a new {@link FieldVisitor}.
+ *
+ * @param api
+ * the ASM API version implemented by this visitor. Must be one
+ * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ * @param fv
+ * the field visitor to which this visitor must delegate method
+ * calls. May be null.
+ */
+ public FieldVisitor(final int api, final FieldVisitor fv) {
+ if (api != Opcodes.ASM4 && api != Opcodes.ASM5) {
+ throw new IllegalArgumentException();
+ }
+ this.api = api;
+ this.fv = fv;
+ }
+
+ /**
+ * Visits an annotation of the field.
+ *
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * <tt>true</tt> if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or <tt>null</tt> if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (fv != null) {
+ return fv.visitAnnotation(desc, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits an annotation on the type of the field.
+ *
+ * @param typeRef
+ * a reference to the annotated type. The sort of this type
+ * reference must be {@link TypeReference#FIELD FIELD}. See
+ * {@link TypeReference}.
+ * @param typePath
+ * the path to the annotated type argument, wildcard bound, array
+ * element type, or static inner type within 'typeRef'. May be
+ * <tt>null</tt> if the annotation targets 'typeRef' as a whole.
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * <tt>true</tt> if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or <tt>null</tt> if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitTypeAnnotation(int typeRef,
+ TypePath typePath, String desc, boolean visible) {
+ /* SPRING PATCH: REMOVED FOR COMPATIBILITY WITH CGLIB 3.1
+ if (api < Opcodes.ASM5) {
+ throw new RuntimeException();
+ }
+ */
+ if (fv != null) {
+ return fv.visitTypeAnnotation(typeRef, typePath, desc, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a non standard attribute of the field.
+ *
+ * @param attr
+ * an attribute.
+ */
+ public void visitAttribute(Attribute attr) {
+ if (fv != null) {
+ fv.visitAttribute(attr);
+ }
+ }
+
+ /**
+ * Visits the end of the field. This method, which is the last one to be
+ * called, is used to inform the visitor that all the annotations and
+ * attributes of the field have been visited.
+ */
+ public void visitEnd() {
+ if (fv != null) {
+ fv.visitEnd();
+ }
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/FieldWriter.java b/spring-core/src/main/java/org/springframework/asm/FieldWriter.java
new file mode 100644
index 00000000..f5a135e5
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/FieldWriter.java
@@ -0,0 +1,329 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.springframework.asm;
+
+/**
+ * An {@link FieldVisitor} that generates Java fields in bytecode form.
+ *
+ * @author Eric Bruneton
+ */
+final class FieldWriter extends FieldVisitor {
+
+ /**
+ * The class writer to which this field must be added.
+ */
+ private final ClassWriter cw;
+
+ /**
+ * Access flags of this field.
+ */
+ private final int access;
+
+ /**
+ * The index of the constant pool item that contains the name of this
+ * method.
+ */
+ private final int name;
+
+ /**
+ * The index of the constant pool item that contains the descriptor of this
+ * field.
+ */
+ private final int desc;
+
+ /**
+ * The index of the constant pool item that contains the signature of this
+ * field.
+ */
+ private int signature;
+
+ /**
+ * The index of the constant pool item that contains the constant value of
+ * this field.
+ */
+ private int value;
+
+ /**
+ * The runtime visible annotations of this field. May be <tt>null</tt>.
+ */
+ private AnnotationWriter anns;
+
+ /**
+ * The runtime invisible annotations of this field. May be <tt>null</tt>.
+ */
+ private AnnotationWriter ianns;
+
+ /**
+ * The runtime visible type annotations of this field. May be <tt>null</tt>.
+ */
+ private AnnotationWriter tanns;
+
+ /**
+ * The runtime invisible type annotations of this field. May be
+ * <tt>null</tt>.
+ */
+ private AnnotationWriter itanns;
+
+ /**
+ * The non standard attributes of this field. May be <tt>null</tt>.
+ */
+ private Attribute attrs;
+
+ // ------------------------------------------------------------------------
+ // Constructor
+ // ------------------------------------------------------------------------
+
+ /**
+ * Constructs a new {@link FieldWriter}.
+ *
+ * @param cw
+ * the class writer to which this field must be added.
+ * @param access
+ * the field's access flags (see {@link Opcodes}).
+ * @param name
+ * the field's name.
+ * @param desc
+ * the field's descriptor (see {@link Type}).
+ * @param signature
+ * the field's signature. May be <tt>null</tt>.
+ * @param value
+ * the field's constant value. May be <tt>null</tt>.
+ */
+ FieldWriter(final ClassWriter cw, final int access, final String name,
+ final String desc, final String signature, final Object value) {
+ super(Opcodes.ASM5);
+ if (cw.firstField == null) {
+ cw.firstField = this;
+ } else {
+ cw.lastField.fv = this;
+ }
+ cw.lastField = this;
+ this.cw = cw;
+ this.access = access;
+ this.name = cw.newUTF8(name);
+ this.desc = cw.newUTF8(desc);
+ if (ClassReader.SIGNATURES && signature != null) {
+ this.signature = cw.newUTF8(signature);
+ }
+ if (value != null) {
+ this.value = cw.newConstItem(value).index;
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Implementation of the FieldVisitor abstract class
+ // ------------------------------------------------------------------------
+
+ @Override
+ public AnnotationVisitor visitAnnotation(final String desc,
+ final boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ // write type, and reserve space for values count
+ bv.putShort(cw.newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2);
+ if (visible) {
+ aw.next = anns;
+ anns = aw;
+ } else {
+ aw.next = ianns;
+ ianns = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(final int typeRef,
+ final TypePath typePath, final String desc, final boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ // write target_type and target_info
+ AnnotationWriter.putTarget(typeRef, typePath, bv);
+ // write type, and reserve space for values count
+ bv.putShort(cw.newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv,
+ bv.length - 2);
+ if (visible) {
+ aw.next = tanns;
+ tanns = aw;
+ } else {
+ aw.next = itanns;
+ itanns = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public void visitAttribute(final Attribute attr) {
+ attr.next = attrs;
+ attrs = attr;
+ }
+
+ @Override
+ public void visitEnd() {
+ }
+
+ // ------------------------------------------------------------------------
+ // Utility methods
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the size of this field.
+ *
+ * @return the size of this field.
+ */
+ int getSize() {
+ int size = 8;
+ if (value != 0) {
+ cw.newUTF8("ConstantValue");
+ size += 8;
+ }
+ if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
+ if ((cw.version & 0xFFFF) < Opcodes.V1_5
+ || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
+ cw.newUTF8("Synthetic");
+ size += 6;
+ }
+ }
+ if ((access & Opcodes.ACC_DEPRECATED) != 0) {
+ cw.newUTF8("Deprecated");
+ size += 6;
+ }
+ if (ClassReader.SIGNATURES && signature != 0) {
+ cw.newUTF8("Signature");
+ size += 8;
+ }
+ if (ClassReader.ANNOTATIONS && anns != null) {
+ cw.newUTF8("RuntimeVisibleAnnotations");
+ size += 8 + anns.getSize();
+ }
+ if (ClassReader.ANNOTATIONS && ianns != null) {
+ cw.newUTF8("RuntimeInvisibleAnnotations");
+ size += 8 + ianns.getSize();
+ }
+ if (ClassReader.ANNOTATIONS && tanns != null) {
+ cw.newUTF8("RuntimeVisibleTypeAnnotations");
+ size += 8 + tanns.getSize();
+ }
+ if (ClassReader.ANNOTATIONS && itanns != null) {
+ cw.newUTF8("RuntimeInvisibleTypeAnnotations");
+ size += 8 + itanns.getSize();
+ }
+ if (attrs != null) {
+ size += attrs.getSize(cw, null, 0, -1, -1);
+ }
+ return size;
+ }
+
+ /**
+ * Puts the content of this field into the given byte vector.
+ *
+ * @param out
+ * where the content of this field must be put.
+ */
+ void put(final ByteVector out) {
+ final int FACTOR = ClassWriter.TO_ACC_SYNTHETIC;
+ int mask = Opcodes.ACC_DEPRECATED | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE
+ | ((access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / FACTOR);
+ out.putShort(access & ~mask).putShort(name).putShort(desc);
+ int attributeCount = 0;
+ if (value != 0) {
+ ++attributeCount;
+ }
+ if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
+ if ((cw.version & 0xFFFF) < Opcodes.V1_5
+ || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
+ ++attributeCount;
+ }
+ }
+ if ((access & Opcodes.ACC_DEPRECATED) != 0) {
+ ++attributeCount;
+ }
+ if (ClassReader.SIGNATURES && signature != 0) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && anns != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && ianns != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && tanns != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && itanns != null) {
+ ++attributeCount;
+ }
+ if (attrs != null) {
+ attributeCount += attrs.getCount();
+ }
+ out.putShort(attributeCount);
+ if (value != 0) {
+ out.putShort(cw.newUTF8("ConstantValue"));
+ out.putInt(2).putShort(value);
+ }
+ if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
+ if ((cw.version & 0xFFFF) < Opcodes.V1_5
+ || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
+ out.putShort(cw.newUTF8("Synthetic")).putInt(0);
+ }
+ }
+ if ((access & Opcodes.ACC_DEPRECATED) != 0) {
+ out.putShort(cw.newUTF8("Deprecated")).putInt(0);
+ }
+ if (ClassReader.SIGNATURES && signature != 0) {
+ out.putShort(cw.newUTF8("Signature"));
+ out.putInt(2).putShort(signature);
+ }
+ if (ClassReader.ANNOTATIONS && anns != null) {
+ out.putShort(cw.newUTF8("RuntimeVisibleAnnotations"));
+ anns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && ianns != null) {
+ out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations"));
+ ianns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && tanns != null) {
+ out.putShort(cw.newUTF8("RuntimeVisibleTypeAnnotations"));
+ tanns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && itanns != null) {
+ out.putShort(cw.newUTF8("RuntimeInvisibleTypeAnnotations"));
+ itanns.put(out);
+ }
+ if (attrs != null) {
+ attrs.put(cw, null, 0, -1, -1, out);
+ }
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/Frame.java b/spring-core/src/main/java/org/springframework/asm/Frame.java
new file mode 100644
index 00000000..29e71c5a
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/Frame.java
@@ -0,0 +1,1462 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.springframework.asm;
+
+/**
+ * Information about the input and output stack map frames of a basic block.
+ *
+ * @author Eric Bruneton
+ */
+final class Frame {
+
+ /*
+ * Frames are computed in a two steps process: during the visit of each
+ * instruction, the state of the frame at the end of current basic block is
+ * updated by simulating the action of the instruction on the previous state
+ * of this so called "output frame". In visitMaxs, a fix point algorithm is
+ * used to compute the "input frame" of each basic block, i.e. the stack map
+ * frame at the beginning of the basic block, starting from the input frame
+ * of the first basic block (which is computed from the method descriptor),
+ * and by using the previously computed output frames to compute the input
+ * state of the other blocks.
+ *
+ * All output and input frames are stored as arrays of integers. Reference
+ * and array types are represented by an index into a type table (which is
+ * not the same as the constant pool of the class, in order to avoid adding
+ * unnecessary constants in the pool - not all computed frames will end up
+ * being stored in the stack map table). This allows very fast type
+ * comparisons.
+ *
+ * Output stack map frames are computed relatively to the input frame of the
+ * basic block, which is not yet known when output frames are computed. It
+ * is therefore necessary to be able to represent abstract types such as
+ * "the type at position x in the input frame locals" or "the type at
+ * position x from the top of the input frame stack" or even "the type at
+ * position x in the input frame, with y more (or less) array dimensions".
+ * This explains the rather complicated type format used in output frames.
+ *
+ * This format is the following: DIM KIND VALUE (4, 4 and 24 bits). DIM is a
+ * signed number of array dimensions (from -8 to 7). KIND is either BASE,
+ * LOCAL or STACK. BASE is used for types that are not relative to the input
+ * frame. LOCAL is used for types that are relative to the input local
+ * variable types. STACK is used for types that are relative to the input
+ * stack types. VALUE depends on KIND. For LOCAL types, it is an index in
+ * the input local variable types. For STACK types, it is a position
+ * relatively to the top of input frame stack. For BASE types, it is either
+ * one of the constants defined below, or for OBJECT and UNINITIALIZED
+ * types, a tag and an index in the type table.
+ *
+ * Output frames can contain types of any kind and with a positive or
+ * negative dimension (and even unassigned types, represented by 0 - which
+ * does not correspond to any valid type value). Input frames can only
+ * contain BASE types of positive or null dimension. In all cases the type
+ * table contains only internal type names (array type descriptors are
+ * forbidden - dimensions must be represented through the DIM field).
+ *
+ * The LONG and DOUBLE types are always represented by using two slots (LONG
+ * + TOP or DOUBLE + TOP), for local variable types as well as in the
+ * operand stack. This is necessary to be able to simulate DUPx_y
+ * instructions, whose effect would be dependent on the actual type values
+ * if types were always represented by a single slot in the stack (and this
+ * is not possible, since actual type values are not always known - cf LOCAL
+ * and STACK type kinds).
+ */
+
+ /**
+ * Mask to get the dimension of a frame type. This dimension is a signed
+ * integer between -8 and 7.
+ */
+ static final int DIM = 0xF0000000;
+
+ /**
+ * Constant to be added to a type to get a type with one more dimension.
+ */
+ static final int ARRAY_OF = 0x10000000;
+
+ /**
+ * Constant to be added to a type to get a type with one less dimension.
+ */
+ static final int ELEMENT_OF = 0xF0000000;
+
+ /**
+ * Mask to get the kind of a frame type.
+ *
+ * @see #BASE
+ * @see #LOCAL
+ * @see #STACK
+ */
+ static final int KIND = 0xF000000;
+
+ /**
+ * Flag used for LOCAL and STACK types. Indicates that if this type happens
+ * to be a long or double type (during the computations of input frames),
+ * then it must be set to TOP because the second word of this value has been
+ * reused to store other data in the basic block. Hence the first word no
+ * longer stores a valid long or double value.
+ */
+ static final int TOP_IF_LONG_OR_DOUBLE = 0x800000;
+
+ /**
+ * Mask to get the value of a frame type.
+ */
+ static final int VALUE = 0x7FFFFF;
+
+ /**
+ * Mask to get the kind of base types.
+ */
+ static final int BASE_KIND = 0xFF00000;
+
+ /**
+ * Mask to get the value of base types.
+ */
+ static final int BASE_VALUE = 0xFFFFF;
+
+ /**
+ * Kind of the types that are not relative to an input stack map frame.
+ */
+ static final int BASE = 0x1000000;
+
+ /**
+ * Base kind of the base reference types. The BASE_VALUE of such types is an
+ * index into the type table.
+ */
+ static final int OBJECT = BASE | 0x700000;
+
+ /**
+ * Base kind of the uninitialized base types. The BASE_VALUE of such types
+ * in an index into the type table (the Item at that index contains both an
+ * instruction offset and an internal class name).
+ */
+ static final int UNINITIALIZED = BASE | 0x800000;
+
+ /**
+ * Kind of the types that are relative to the local variable types of an
+ * input stack map frame. The value of such types is a local variable index.
+ */
+ private static final int LOCAL = 0x2000000;
+
+ /**
+ * Kind of the the types that are relative to the stack of an input stack
+ * map frame. The value of such types is a position relatively to the top of
+ * this stack.
+ */
+ private static final int STACK = 0x3000000;
+
+ /**
+ * The TOP type. This is a BASE type.
+ */
+ static final int TOP = BASE | 0;
+
+ /**
+ * The BOOLEAN type. This is a BASE type mainly used for array types.
+ */
+ static final int BOOLEAN = BASE | 9;
+
+ /**
+ * The BYTE type. This is a BASE type mainly used for array types.
+ */
+ static final int BYTE = BASE | 10;
+
+ /**
+ * The CHAR type. This is a BASE type mainly used for array types.
+ */
+ static final int CHAR = BASE | 11;
+
+ /**
+ * The SHORT type. This is a BASE type mainly used for array types.
+ */
+ static final int SHORT = BASE | 12;
+
+ /**
+ * The INTEGER type. This is a BASE type.
+ */
+ static final int INTEGER = BASE | 1;
+
+ /**
+ * The FLOAT type. This is a BASE type.
+ */
+ static final int FLOAT = BASE | 2;
+
+ /**
+ * The DOUBLE type. This is a BASE type.
+ */
+ static final int DOUBLE = BASE | 3;
+
+ /**
+ * The LONG type. This is a BASE type.
+ */
+ static final int LONG = BASE | 4;
+
+ /**
+ * The NULL type. This is a BASE type.
+ */
+ static final int NULL = BASE | 5;
+
+ /**
+ * The UNINITIALIZED_THIS type. This is a BASE type.
+ */
+ static final int UNINITIALIZED_THIS = BASE | 6;
+
+ /**
+ * The stack size variation corresponding to each JVM instruction. This
+ * stack variation is equal to the size of the values produced by an
+ * instruction, minus the size of the values consumed by this instruction.
+ */
+ static final int[] SIZE;
+
+ /**
+ * Computes the stack size variation corresponding to each JVM instruction.
+ */
+ static {
+ int i;
+ int[] b = new int[202];
+ String s = "EFFFFFFFFGGFFFGGFFFEEFGFGFEEEEEEEEEEEEEEEEEEEEDEDEDDDDD"
+ + "CDCDEEEEEEEEEEEEEEEEEEEEBABABBBBDCFFFGGGEDCDCDCDCDCDCDCDCD"
+ + "CDCEEEEDDDDDDDCDCDCEFEFDDEEFFDEDEEEBDDBBDDDDDDCCCCCCCCEFED"
+ + "DDCDCDEEEEEEEEEEFEEEEEEDDEEDDEE";
+ for (i = 0; i < b.length; ++i) {
+ b[i] = s.charAt(i) - 'E';
+ }
+ SIZE = b;
+
+ // code to generate the above string
+ //
+ // int NA = 0; // not applicable (unused opcode or variable size opcode)
+ //
+ // b = new int[] {
+ // 0, //NOP, // visitInsn
+ // 1, //ACONST_NULL, // -
+ // 1, //ICONST_M1, // -
+ // 1, //ICONST_0, // -
+ // 1, //ICONST_1, // -
+ // 1, //ICONST_2, // -
+ // 1, //ICONST_3, // -
+ // 1, //ICONST_4, // -
+ // 1, //ICONST_5, // -
+ // 2, //LCONST_0, // -
+ // 2, //LCONST_1, // -
+ // 1, //FCONST_0, // -
+ // 1, //FCONST_1, // -
+ // 1, //FCONST_2, // -
+ // 2, //DCONST_0, // -
+ // 2, //DCONST_1, // -
+ // 1, //BIPUSH, // visitIntInsn
+ // 1, //SIPUSH, // -
+ // 1, //LDC, // visitLdcInsn
+ // NA, //LDC_W, // -
+ // NA, //LDC2_W, // -
+ // 1, //ILOAD, // visitVarInsn
+ // 2, //LLOAD, // -
+ // 1, //FLOAD, // -
+ // 2, //DLOAD, // -
+ // 1, //ALOAD, // -
+ // NA, //ILOAD_0, // -
+ // NA, //ILOAD_1, // -
+ // NA, //ILOAD_2, // -
+ // NA, //ILOAD_3, // -
+ // NA, //LLOAD_0, // -
+ // NA, //LLOAD_1, // -
+ // NA, //LLOAD_2, // -
+ // NA, //LLOAD_3, // -
+ // NA, //FLOAD_0, // -
+ // NA, //FLOAD_1, // -
+ // NA, //FLOAD_2, // -
+ // NA, //FLOAD_3, // -
+ // NA, //DLOAD_0, // -
+ // NA, //DLOAD_1, // -
+ // NA, //DLOAD_2, // -
+ // NA, //DLOAD_3, // -
+ // NA, //ALOAD_0, // -
+ // NA, //ALOAD_1, // -
+ // NA, //ALOAD_2, // -
+ // NA, //ALOAD_3, // -
+ // -1, //IALOAD, // visitInsn
+ // 0, //LALOAD, // -
+ // -1, //FALOAD, // -
+ // 0, //DALOAD, // -
+ // -1, //AALOAD, // -
+ // -1, //BALOAD, // -
+ // -1, //CALOAD, // -
+ // -1, //SALOAD, // -
+ // -1, //ISTORE, // visitVarInsn
+ // -2, //LSTORE, // -
+ // -1, //FSTORE, // -
+ // -2, //DSTORE, // -
+ // -1, //ASTORE, // -
+ // NA, //ISTORE_0, // -
+ // NA, //ISTORE_1, // -
+ // NA, //ISTORE_2, // -
+ // NA, //ISTORE_3, // -
+ // NA, //LSTORE_0, // -
+ // NA, //LSTORE_1, // -
+ // NA, //LSTORE_2, // -
+ // NA, //LSTORE_3, // -
+ // NA, //FSTORE_0, // -
+ // NA, //FSTORE_1, // -
+ // NA, //FSTORE_2, // -
+ // NA, //FSTORE_3, // -
+ // NA, //DSTORE_0, // -
+ // NA, //DSTORE_1, // -
+ // NA, //DSTORE_2, // -
+ // NA, //DSTORE_3, // -
+ // NA, //ASTORE_0, // -
+ // NA, //ASTORE_1, // -
+ // NA, //ASTORE_2, // -
+ // NA, //ASTORE_3, // -
+ // -3, //IASTORE, // visitInsn
+ // -4, //LASTORE, // -
+ // -3, //FASTORE, // -
+ // -4, //DASTORE, // -
+ // -3, //AASTORE, // -
+ // -3, //BASTORE, // -
+ // -3, //CASTORE, // -
+ // -3, //SASTORE, // -
+ // -1, //POP, // -
+ // -2, //POP2, // -
+ // 1, //DUP, // -
+ // 1, //DUP_X1, // -
+ // 1, //DUP_X2, // -
+ // 2, //DUP2, // -
+ // 2, //DUP2_X1, // -
+ // 2, //DUP2_X2, // -
+ // 0, //SWAP, // -
+ // -1, //IADD, // -
+ // -2, //LADD, // -
+ // -1, //FADD, // -
+ // -2, //DADD, // -
+ // -1, //ISUB, // -
+ // -2, //LSUB, // -
+ // -1, //FSUB, // -
+ // -2, //DSUB, // -
+ // -1, //IMUL, // -
+ // -2, //LMUL, // -
+ // -1, //FMUL, // -
+ // -2, //DMUL, // -
+ // -1, //IDIV, // -
+ // -2, //LDIV, // -
+ // -1, //FDIV, // -
+ // -2, //DDIV, // -
+ // -1, //IREM, // -
+ // -2, //LREM, // -
+ // -1, //FREM, // -
+ // -2, //DREM, // -
+ // 0, //INEG, // -
+ // 0, //LNEG, // -
+ // 0, //FNEG, // -
+ // 0, //DNEG, // -
+ // -1, //ISHL, // -
+ // -1, //LSHL, // -
+ // -1, //ISHR, // -
+ // -1, //LSHR, // -
+ // -1, //IUSHR, // -
+ // -1, //LUSHR, // -
+ // -1, //IAND, // -
+ // -2, //LAND, // -
+ // -1, //IOR, // -
+ // -2, //LOR, // -
+ // -1, //IXOR, // -
+ // -2, //LXOR, // -
+ // 0, //IINC, // visitIincInsn
+ // 1, //I2L, // visitInsn
+ // 0, //I2F, // -
+ // 1, //I2D, // -
+ // -1, //L2I, // -
+ // -1, //L2F, // -
+ // 0, //L2D, // -
+ // 0, //F2I, // -
+ // 1, //F2L, // -
+ // 1, //F2D, // -
+ // -1, //D2I, // -
+ // 0, //D2L, // -
+ // -1, //D2F, // -
+ // 0, //I2B, // -
+ // 0, //I2C, // -
+ // 0, //I2S, // -
+ // -3, //LCMP, // -
+ // -1, //FCMPL, // -
+ // -1, //FCMPG, // -
+ // -3, //DCMPL, // -
+ // -3, //DCMPG, // -
+ // -1, //IFEQ, // visitJumpInsn
+ // -1, //IFNE, // -
+ // -1, //IFLT, // -
+ // -1, //IFGE, // -
+ // -1, //IFGT, // -
+ // -1, //IFLE, // -
+ // -2, //IF_ICMPEQ, // -
+ // -2, //IF_ICMPNE, // -
+ // -2, //IF_ICMPLT, // -
+ // -2, //IF_ICMPGE, // -
+ // -2, //IF_ICMPGT, // -
+ // -2, //IF_ICMPLE, // -
+ // -2, //IF_ACMPEQ, // -
+ // -2, //IF_ACMPNE, // -
+ // 0, //GOTO, // -
+ // 1, //JSR, // -
+ // 0, //RET, // visitVarInsn
+ // -1, //TABLESWITCH, // visiTableSwitchInsn
+ // -1, //LOOKUPSWITCH, // visitLookupSwitch
+ // -1, //IRETURN, // visitInsn
+ // -2, //LRETURN, // -
+ // -1, //FRETURN, // -
+ // -2, //DRETURN, // -
+ // -1, //ARETURN, // -
+ // 0, //RETURN, // -
+ // NA, //GETSTATIC, // visitFieldInsn
+ // NA, //PUTSTATIC, // -
+ // NA, //GETFIELD, // -
+ // NA, //PUTFIELD, // -
+ // NA, //INVOKEVIRTUAL, // visitMethodInsn
+ // NA, //INVOKESPECIAL, // -
+ // NA, //INVOKESTATIC, // -
+ // NA, //INVOKEINTERFACE, // -
+ // NA, //INVOKEDYNAMIC, // visitInvokeDynamicInsn
+ // 1, //NEW, // visitTypeInsn
+ // 0, //NEWARRAY, // visitIntInsn
+ // 0, //ANEWARRAY, // visitTypeInsn
+ // 0, //ARRAYLENGTH, // visitInsn
+ // NA, //ATHROW, // -
+ // 0, //CHECKCAST, // visitTypeInsn
+ // 0, //INSTANCEOF, // -
+ // -1, //MONITORENTER, // visitInsn
+ // -1, //MONITOREXIT, // -
+ // NA, //WIDE, // NOT VISITED
+ // NA, //MULTIANEWARRAY, // visitMultiANewArrayInsn
+ // -1, //IFNULL, // visitJumpInsn
+ // -1, //IFNONNULL, // -
+ // NA, //GOTO_W, // -
+ // NA, //JSR_W, // -
+ // };
+ // for (i = 0; i < b.length; ++i) {
+ // System.err.print((char)('E' + b[i]));
+ // }
+ // System.err.println();
+ }
+
+ /**
+ * The label (i.e. basic block) to which these input and output stack map
+ * frames correspond.
+ */
+ Label owner;
+
+ /**
+ * The input stack map frame locals.
+ */
+ int[] inputLocals;
+
+ /**
+ * The input stack map frame stack.
+ */
+ int[] inputStack;
+
+ /**
+ * The output stack map frame locals.
+ */
+ private int[] outputLocals;
+
+ /**
+ * The output stack map frame stack.
+ */
+ private int[] outputStack;
+
+ /**
+ * Relative size of the output stack. The exact semantics of this field
+ * depends on the algorithm that is used.
+ *
+ * When only the maximum stack size is computed, this field is the size of
+ * the output stack relatively to the top of the input stack.
+ *
+ * When the stack map frames are completely computed, this field is the
+ * actual number of types in {@link #outputStack}.
+ */
+ private int outputStackTop;
+
+ /**
+ * Number of types that are initialized in the basic block.
+ *
+ * @see #initializations
+ */
+ private int initializationCount;
+
+ /**
+ * The types that are initialized in the basic block. A constructor
+ * invocation on an UNINITIALIZED or UNINITIALIZED_THIS type must replace
+ * <i>every occurence</i> of this type in the local variables and in the
+ * operand stack. This cannot be done during the first phase of the
+ * algorithm since, during this phase, the local variables and the operand
+ * stack are not completely computed. It is therefore necessary to store the
+ * types on which constructors are invoked in the basic block, in order to
+ * do this replacement during the second phase of the algorithm, where the
+ * frames are fully computed. Note that this array can contain types that
+ * are relative to input locals or to the input stack (see below for the
+ * description of the algorithm).
+ */
+ private int[] initializations;
+
+ /**
+ * Returns the output frame local variable type at the given index.
+ *
+ * @param local
+ * the index of the local that must be returned.
+ * @return the output frame local variable type at the given index.
+ */
+ private int get(final int local) {
+ if (outputLocals == null || local >= outputLocals.length) {
+ // this local has never been assigned in this basic block,
+ // so it is still equal to its value in the input frame
+ return LOCAL | local;
+ } else {
+ int type = outputLocals[local];
+ if (type == 0) {
+ // this local has never been assigned in this basic block,
+ // so it is still equal to its value in the input frame
+ type = outputLocals[local] = LOCAL | local;
+ }
+ return type;
+ }
+ }
+
+ /**
+ * Sets the output frame local variable type at the given index.
+ *
+ * @param local
+ * the index of the local that must be set.
+ * @param type
+ * the value of the local that must be set.
+ */
+ private void set(final int local, final int type) {
+ // creates and/or resizes the output local variables array if necessary
+ if (outputLocals == null) {
+ outputLocals = new int[10];
+ }
+ int n = outputLocals.length;
+ if (local >= n) {
+ int[] t = new int[Math.max(local + 1, 2 * n)];
+ System.arraycopy(outputLocals, 0, t, 0, n);
+ outputLocals = t;
+ }
+ // sets the local variable
+ outputLocals[local] = type;
+ }
+
+ /**
+ * Pushes a new type onto the output frame stack.
+ *
+ * @param type
+ * the type that must be pushed.
+ */
+ private void push(final int type) {
+ // creates and/or resizes the output stack array if necessary
+ if (outputStack == null) {
+ outputStack = new int[10];
+ }
+ int n = outputStack.length;
+ if (outputStackTop >= n) {
+ int[] t = new int[Math.max(outputStackTop + 1, 2 * n)];
+ System.arraycopy(outputStack, 0, t, 0, n);
+ outputStack = t;
+ }
+ // pushes the type on the output stack
+ outputStack[outputStackTop++] = type;
+ // updates the maximun height reached by the output stack, if needed
+ int top = owner.inputStackTop + outputStackTop;
+ if (top > owner.outputStackMax) {
+ owner.outputStackMax = top;
+ }
+ }
+
+ /**
+ * Pushes a new type onto the output frame stack.
+ *
+ * @param cw
+ * the ClassWriter to which this label belongs.
+ * @param desc
+ * the descriptor of the type to be pushed. Can also be a method
+ * descriptor (in this case this method pushes its return type
+ * onto the output frame stack).
+ */
+ private void push(final ClassWriter cw, final String desc) {
+ int type = type(cw, desc);
+ if (type != 0) {
+ push(type);
+ if (type == LONG || type == DOUBLE) {
+ push(TOP);
+ }
+ }
+ }
+
+ /**
+ * Returns the int encoding of the given type.
+ *
+ * @param cw
+ * the ClassWriter to which this label belongs.
+ * @param desc
+ * a type descriptor.
+ * @return the int encoding of the given type.
+ */
+ private static int type(final ClassWriter cw, final String desc) {
+ String t;
+ int index = desc.charAt(0) == '(' ? desc.indexOf(')') + 1 : 0;
+ switch (desc.charAt(index)) {
+ case 'V':
+ return 0;
+ case 'Z':
+ case 'C':
+ case 'B':
+ case 'S':
+ case 'I':
+ return INTEGER;
+ case 'F':
+ return FLOAT;
+ case 'J':
+ return LONG;
+ case 'D':
+ return DOUBLE;
+ case 'L':
+ // stores the internal name, not the descriptor!
+ t = desc.substring(index + 1, desc.length() - 1);
+ return OBJECT | cw.addType(t);
+ // case '[':
+ default:
+ // extracts the dimensions and the element type
+ int data;
+ int dims = index + 1;
+ while (desc.charAt(dims) == '[') {
+ ++dims;
+ }
+ switch (desc.charAt(dims)) {
+ case 'Z':
+ data = BOOLEAN;
+ break;
+ case 'C':
+ data = CHAR;
+ break;
+ case 'B':
+ data = BYTE;
+ break;
+ case 'S':
+ data = SHORT;
+ break;
+ case 'I':
+ data = INTEGER;
+ break;
+ case 'F':
+ data = FLOAT;
+ break;
+ case 'J':
+ data = LONG;
+ break;
+ case 'D':
+ data = DOUBLE;
+ break;
+ // case 'L':
+ default:
+ // stores the internal name, not the descriptor
+ t = desc.substring(dims + 1, desc.length() - 1);
+ data = OBJECT | cw.addType(t);
+ }
+ return (dims - index) << 28 | data;
+ }
+ }
+
+ /**
+ * Pops a type from the output frame stack and returns its value.
+ *
+ * @return the type that has been popped from the output frame stack.
+ */
+ private int pop() {
+ if (outputStackTop > 0) {
+ return outputStack[--outputStackTop];
+ } else {
+ // if the output frame stack is empty, pops from the input stack
+ return STACK | -(--owner.inputStackTop);
+ }
+ }
+
+ /**
+ * Pops the given number of types from the output frame stack.
+ *
+ * @param elements
+ * the number of types that must be popped.
+ */
+ private void pop(final int elements) {
+ if (outputStackTop >= elements) {
+ outputStackTop -= elements;
+ } else {
+ // if the number of elements to be popped is greater than the number
+ // of elements in the output stack, clear it, and pops the remaining
+ // elements from the input stack.
+ owner.inputStackTop -= elements - outputStackTop;
+ outputStackTop = 0;
+ }
+ }
+
+ /**
+ * Pops a type from the output frame stack.
+ *
+ * @param desc
+ * the descriptor of the type to be popped. Can also be a method
+ * descriptor (in this case this method pops the types
+ * corresponding to the method arguments).
+ */
+ private void pop(final String desc) {
+ char c = desc.charAt(0);
+ if (c == '(') {
+ pop((Type.getArgumentsAndReturnSizes(desc) >> 2) - 1);
+ } else if (c == 'J' || c == 'D') {
+ pop(2);
+ } else {
+ pop(1);
+ }
+ }
+
+ /**
+ * Adds a new type to the list of types on which a constructor is invoked in
+ * the basic block.
+ *
+ * @param var
+ * a type on a which a constructor is invoked.
+ */
+ private void init(final int var) {
+ // creates and/or resizes the initializations array if necessary
+ if (initializations == null) {
+ initializations = new int[2];
+ }
+ int n = initializations.length;
+ if (initializationCount >= n) {
+ int[] t = new int[Math.max(initializationCount + 1, 2 * n)];
+ System.arraycopy(initializations, 0, t, 0, n);
+ initializations = t;
+ }
+ // stores the type to be initialized
+ initializations[initializationCount++] = var;
+ }
+
+ /**
+ * Replaces the given type with the appropriate type if it is one of the
+ * types on which a constructor is invoked in the basic block.
+ *
+ * @param cw
+ * the ClassWriter to which this label belongs.
+ * @param t
+ * a type
+ * @return t or, if t is one of the types on which a constructor is invoked
+ * in the basic block, the type corresponding to this constructor.
+ */
+ private int init(final ClassWriter cw, final int t) {
+ int s;
+ if (t == UNINITIALIZED_THIS) {
+ s = OBJECT | cw.addType(cw.thisName);
+ } else if ((t & (DIM | BASE_KIND)) == UNINITIALIZED) {
+ String type = cw.typeTable[t & BASE_VALUE].strVal1;
+ s = OBJECT | cw.addType(type);
+ } else {
+ return t;
+ }
+ for (int j = 0; j < initializationCount; ++j) {
+ int u = initializations[j];
+ int dim = u & DIM;
+ int kind = u & KIND;
+ if (kind == LOCAL) {
+ u = dim + inputLocals[u & VALUE];
+ } else if (kind == STACK) {
+ u = dim + inputStack[inputStack.length - (u & VALUE)];
+ }
+ if (t == u) {
+ return s;
+ }
+ }
+ return t;
+ }
+
+ /**
+ * Initializes the input frame of the first basic block from the method
+ * descriptor.
+ *
+ * @param cw
+ * the ClassWriter to which this label belongs.
+ * @param access
+ * the access flags of the method to which this label belongs.
+ * @param args
+ * the formal parameter types of this method.
+ * @param maxLocals
+ * the maximum number of local variables of this method.
+ */
+ void initInputFrame(final ClassWriter cw, final int access,
+ final Type[] args, final int maxLocals) {
+ inputLocals = new int[maxLocals];
+ inputStack = new int[0];
+ int i = 0;
+ if ((access & Opcodes.ACC_STATIC) == 0) {
+ if ((access & MethodWriter.ACC_CONSTRUCTOR) == 0) {
+ inputLocals[i++] = OBJECT | cw.addType(cw.thisName);
+ } else {
+ inputLocals[i++] = UNINITIALIZED_THIS;
+ }
+ }
+ for (int j = 0; j < args.length; ++j) {
+ int t = type(cw, args[j].getDescriptor());
+ inputLocals[i++] = t;
+ if (t == LONG || t == DOUBLE) {
+ inputLocals[i++] = TOP;
+ }
+ }
+ while (i < maxLocals) {
+ inputLocals[i++] = TOP;
+ }
+ }
+
+ /**
+ * Simulates the action of the given instruction on the output stack frame.
+ *
+ * @param opcode
+ * the opcode of the instruction.
+ * @param arg
+ * the operand of the instruction, if any.
+ * @param cw
+ * the class writer to which this label belongs.
+ * @param item
+ * the operand of the instructions, if any.
+ */
+ void execute(final int opcode, final int arg, final ClassWriter cw,
+ final Item item) {
+ int t1, t2, t3, t4;
+ switch (opcode) {
+ case Opcodes.NOP:
+ case Opcodes.INEG:
+ case Opcodes.LNEG:
+ case Opcodes.FNEG:
+ case Opcodes.DNEG:
+ case Opcodes.I2B:
+ case Opcodes.I2C:
+ case Opcodes.I2S:
+ case Opcodes.GOTO:
+ case Opcodes.RETURN:
+ break;
+ case Opcodes.ACONST_NULL:
+ push(NULL);
+ break;
+ case Opcodes.ICONST_M1:
+ case Opcodes.ICONST_0:
+ case Opcodes.ICONST_1:
+ case Opcodes.ICONST_2:
+ case Opcodes.ICONST_3:
+ case Opcodes.ICONST_4:
+ case Opcodes.ICONST_5:
+ case Opcodes.BIPUSH:
+ case Opcodes.SIPUSH:
+ case Opcodes.ILOAD:
+ push(INTEGER);
+ break;
+ case Opcodes.LCONST_0:
+ case Opcodes.LCONST_1:
+ case Opcodes.LLOAD:
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.FCONST_0:
+ case Opcodes.FCONST_1:
+ case Opcodes.FCONST_2:
+ case Opcodes.FLOAD:
+ push(FLOAT);
+ break;
+ case Opcodes.DCONST_0:
+ case Opcodes.DCONST_1:
+ case Opcodes.DLOAD:
+ push(DOUBLE);
+ push(TOP);
+ break;
+ case Opcodes.LDC:
+ switch (item.type) {
+ case ClassWriter.INT:
+ push(INTEGER);
+ break;
+ case ClassWriter.LONG:
+ push(LONG);
+ push(TOP);
+ break;
+ case ClassWriter.FLOAT:
+ push(FLOAT);
+ break;
+ case ClassWriter.DOUBLE:
+ push(DOUBLE);
+ push(TOP);
+ break;
+ case ClassWriter.CLASS:
+ push(OBJECT | cw.addType("java/lang/Class"));
+ break;
+ case ClassWriter.STR:
+ push(OBJECT | cw.addType("java/lang/String"));
+ break;
+ case ClassWriter.MTYPE:
+ push(OBJECT | cw.addType("java/lang/invoke/MethodType"));
+ break;
+ // case ClassWriter.HANDLE_BASE + [1..9]:
+ default:
+ push(OBJECT | cw.addType("java/lang/invoke/MethodHandle"));
+ }
+ break;
+ case Opcodes.ALOAD:
+ push(get(arg));
+ break;
+ case Opcodes.IALOAD:
+ case Opcodes.BALOAD:
+ case Opcodes.CALOAD:
+ case Opcodes.SALOAD:
+ pop(2);
+ push(INTEGER);
+ break;
+ case Opcodes.LALOAD:
+ case Opcodes.D2L:
+ pop(2);
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.FALOAD:
+ pop(2);
+ push(FLOAT);
+ break;
+ case Opcodes.DALOAD:
+ case Opcodes.L2D:
+ pop(2);
+ push(DOUBLE);
+ push(TOP);
+ break;
+ case Opcodes.AALOAD:
+ pop(1);
+ t1 = pop();
+ push(ELEMENT_OF + t1);
+ break;
+ case Opcodes.ISTORE:
+ case Opcodes.FSTORE:
+ case Opcodes.ASTORE:
+ t1 = pop();
+ set(arg, t1);
+ if (arg > 0) {
+ t2 = get(arg - 1);
+ // if t2 is of kind STACK or LOCAL we cannot know its size!
+ if (t2 == LONG || t2 == DOUBLE) {
+ set(arg - 1, TOP);
+ } else if ((t2 & KIND) != BASE) {
+ set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE);
+ }
+ }
+ break;
+ case Opcodes.LSTORE:
+ case Opcodes.DSTORE:
+ pop(1);
+ t1 = pop();
+ set(arg, t1);
+ set(arg + 1, TOP);
+ if (arg > 0) {
+ t2 = get(arg - 1);
+ // if t2 is of kind STACK or LOCAL we cannot know its size!
+ if (t2 == LONG || t2 == DOUBLE) {
+ set(arg - 1, TOP);
+ } else if ((t2 & KIND) != BASE) {
+ set(arg - 1, t2 | TOP_IF_LONG_OR_DOUBLE);
+ }
+ }
+ break;
+ case Opcodes.IASTORE:
+ case Opcodes.BASTORE:
+ case Opcodes.CASTORE:
+ case Opcodes.SASTORE:
+ case Opcodes.FASTORE:
+ case Opcodes.AASTORE:
+ pop(3);
+ break;
+ case Opcodes.LASTORE:
+ case Opcodes.DASTORE:
+ pop(4);
+ break;
+ case Opcodes.POP:
+ case Opcodes.IFEQ:
+ case Opcodes.IFNE:
+ case Opcodes.IFLT:
+ case Opcodes.IFGE:
+ case Opcodes.IFGT:
+ case Opcodes.IFLE:
+ case Opcodes.IRETURN:
+ case Opcodes.FRETURN:
+ case Opcodes.ARETURN:
+ case Opcodes.TABLESWITCH:
+ case Opcodes.LOOKUPSWITCH:
+ case Opcodes.ATHROW:
+ case Opcodes.MONITORENTER:
+ case Opcodes.MONITOREXIT:
+ case Opcodes.IFNULL:
+ case Opcodes.IFNONNULL:
+ pop(1);
+ break;
+ case Opcodes.POP2:
+ case Opcodes.IF_ICMPEQ:
+ case Opcodes.IF_ICMPNE:
+ case Opcodes.IF_ICMPLT:
+ case Opcodes.IF_ICMPGE:
+ case Opcodes.IF_ICMPGT:
+ case Opcodes.IF_ICMPLE:
+ case Opcodes.IF_ACMPEQ:
+ case Opcodes.IF_ACMPNE:
+ case Opcodes.LRETURN:
+ case Opcodes.DRETURN:
+ pop(2);
+ break;
+ case Opcodes.DUP:
+ t1 = pop();
+ push(t1);
+ push(t1);
+ break;
+ case Opcodes.DUP_X1:
+ t1 = pop();
+ t2 = pop();
+ push(t1);
+ push(t2);
+ push(t1);
+ break;
+ case Opcodes.DUP_X2:
+ t1 = pop();
+ t2 = pop();
+ t3 = pop();
+ push(t1);
+ push(t3);
+ push(t2);
+ push(t1);
+ break;
+ case Opcodes.DUP2:
+ t1 = pop();
+ t2 = pop();
+ push(t2);
+ push(t1);
+ push(t2);
+ push(t1);
+ break;
+ case Opcodes.DUP2_X1:
+ t1 = pop();
+ t2 = pop();
+ t3 = pop();
+ push(t2);
+ push(t1);
+ push(t3);
+ push(t2);
+ push(t1);
+ break;
+ case Opcodes.DUP2_X2:
+ t1 = pop();
+ t2 = pop();
+ t3 = pop();
+ t4 = pop();
+ push(t2);
+ push(t1);
+ push(t4);
+ push(t3);
+ push(t2);
+ push(t1);
+ break;
+ case Opcodes.SWAP:
+ t1 = pop();
+ t2 = pop();
+ push(t1);
+ push(t2);
+ break;
+ case Opcodes.IADD:
+ case Opcodes.ISUB:
+ case Opcodes.IMUL:
+ case Opcodes.IDIV:
+ case Opcodes.IREM:
+ case Opcodes.IAND:
+ case Opcodes.IOR:
+ case Opcodes.IXOR:
+ case Opcodes.ISHL:
+ case Opcodes.ISHR:
+ case Opcodes.IUSHR:
+ case Opcodes.L2I:
+ case Opcodes.D2I:
+ case Opcodes.FCMPL:
+ case Opcodes.FCMPG:
+ pop(2);
+ push(INTEGER);
+ break;
+ case Opcodes.LADD:
+ case Opcodes.LSUB:
+ case Opcodes.LMUL:
+ case Opcodes.LDIV:
+ case Opcodes.LREM:
+ case Opcodes.LAND:
+ case Opcodes.LOR:
+ case Opcodes.LXOR:
+ pop(4);
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.FADD:
+ case Opcodes.FSUB:
+ case Opcodes.FMUL:
+ case Opcodes.FDIV:
+ case Opcodes.FREM:
+ case Opcodes.L2F:
+ case Opcodes.D2F:
+ pop(2);
+ push(FLOAT);
+ break;
+ case Opcodes.DADD:
+ case Opcodes.DSUB:
+ case Opcodes.DMUL:
+ case Opcodes.DDIV:
+ case Opcodes.DREM:
+ pop(4);
+ push(DOUBLE);
+ push(TOP);
+ break;
+ case Opcodes.LSHL:
+ case Opcodes.LSHR:
+ case Opcodes.LUSHR:
+ pop(3);
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.IINC:
+ set(arg, INTEGER);
+ break;
+ case Opcodes.I2L:
+ case Opcodes.F2L:
+ pop(1);
+ push(LONG);
+ push(TOP);
+ break;
+ case Opcodes.I2F:
+ pop(1);
+ push(FLOAT);
+ break;
+ case Opcodes.I2D:
+ case Opcodes.F2D:
+ pop(1);
+ push(DOUBLE);
+ push(TOP);
+ break;
+ case Opcodes.F2I:
+ case Opcodes.ARRAYLENGTH:
+ case Opcodes.INSTANCEOF:
+ pop(1);
+ push(INTEGER);
+ break;
+ case Opcodes.LCMP:
+ case Opcodes.DCMPL:
+ case Opcodes.DCMPG:
+ pop(4);
+ push(INTEGER);
+ break;
+ case Opcodes.JSR:
+ case Opcodes.RET:
+ throw new RuntimeException(
+ "JSR/RET are not supported with computeFrames option");
+ case Opcodes.GETSTATIC:
+ push(cw, item.strVal3);
+ break;
+ case Opcodes.PUTSTATIC:
+ pop(item.strVal3);
+ break;
+ case Opcodes.GETFIELD:
+ pop(1);
+ push(cw, item.strVal3);
+ break;
+ case Opcodes.PUTFIELD:
+ pop(item.strVal3);
+ pop();
+ break;
+ case Opcodes.INVOKEVIRTUAL:
+ case Opcodes.INVOKESPECIAL:
+ case Opcodes.INVOKESTATIC:
+ case Opcodes.INVOKEINTERFACE:
+ pop(item.strVal3);
+ if (opcode != Opcodes.INVOKESTATIC) {
+ t1 = pop();
+ if (opcode == Opcodes.INVOKESPECIAL
+ && item.strVal2.charAt(0) == '<') {
+ init(t1);
+ }
+ }
+ push(cw, item.strVal3);
+ break;
+ case Opcodes.INVOKEDYNAMIC:
+ pop(item.strVal2);
+ push(cw, item.strVal2);
+ break;
+ case Opcodes.NEW:
+ push(UNINITIALIZED | cw.addUninitializedType(item.strVal1, arg));
+ break;
+ case Opcodes.NEWARRAY:
+ pop();
+ switch (arg) {
+ case Opcodes.T_BOOLEAN:
+ push(ARRAY_OF | BOOLEAN);
+ break;
+ case Opcodes.T_CHAR:
+ push(ARRAY_OF | CHAR);
+ break;
+ case Opcodes.T_BYTE:
+ push(ARRAY_OF | BYTE);
+ break;
+ case Opcodes.T_SHORT:
+ push(ARRAY_OF | SHORT);
+ break;
+ case Opcodes.T_INT:
+ push(ARRAY_OF | INTEGER);
+ break;
+ case Opcodes.T_FLOAT:
+ push(ARRAY_OF | FLOAT);
+ break;
+ case Opcodes.T_DOUBLE:
+ push(ARRAY_OF | DOUBLE);
+ break;
+ // case Opcodes.T_LONG:
+ default:
+ push(ARRAY_OF | LONG);
+ break;
+ }
+ break;
+ case Opcodes.ANEWARRAY:
+ String s = item.strVal1;
+ pop();
+ if (s.charAt(0) == '[') {
+ push(cw, '[' + s);
+ } else {
+ push(ARRAY_OF | OBJECT | cw.addType(s));
+ }
+ break;
+ case Opcodes.CHECKCAST:
+ s = item.strVal1;
+ pop();
+ if (s.charAt(0) == '[') {
+ push(cw, s);
+ } else {
+ push(OBJECT | cw.addType(s));
+ }
+ break;
+ // case Opcodes.MULTIANEWARRAY:
+ default:
+ pop(arg);
+ push(cw, item.strVal1);
+ break;
+ }
+ }
+
+ /**
+ * Merges the input frame of the given basic block with the input and output
+ * frames of this basic block. Returns <tt>true</tt> if the input frame of
+ * the given label has been changed by this operation.
+ *
+ * @param cw
+ * the ClassWriter to which this label belongs.
+ * @param frame
+ * the basic block whose input frame must be updated.
+ * @param edge
+ * the kind of the {@link Edge} between this label and 'label'.
+ * See {@link Edge#info}.
+ * @return <tt>true</tt> if the input frame of the given label has been
+ * changed by this operation.
+ */
+ boolean merge(final ClassWriter cw, final Frame frame, final int edge) {
+ boolean changed = false;
+ int i, s, dim, kind, t;
+
+ int nLocal = inputLocals.length;
+ int nStack = inputStack.length;
+ if (frame.inputLocals == null) {
+ frame.inputLocals = new int[nLocal];
+ changed = true;
+ }
+
+ for (i = 0; i < nLocal; ++i) {
+ if (outputLocals != null && i < outputLocals.length) {
+ s = outputLocals[i];
+ if (s == 0) {
+ t = inputLocals[i];
+ } else {
+ dim = s & DIM;
+ kind = s & KIND;
+ if (kind == BASE) {
+ t = s;
+ } else {
+ if (kind == LOCAL) {
+ t = dim + inputLocals[s & VALUE];
+ } else {
+ t = dim + inputStack[nStack - (s & VALUE)];
+ }
+ if ((s & TOP_IF_LONG_OR_DOUBLE) != 0
+ && (t == LONG || t == DOUBLE)) {
+ t = TOP;
+ }
+ }
+ }
+ } else {
+ t = inputLocals[i];
+ }
+ if (initializations != null) {
+ t = init(cw, t);
+ }
+ changed |= merge(cw, t, frame.inputLocals, i);
+ }
+
+ if (edge > 0) {
+ for (i = 0; i < nLocal; ++i) {
+ t = inputLocals[i];
+ changed |= merge(cw, t, frame.inputLocals, i);
+ }
+ if (frame.inputStack == null) {
+ frame.inputStack = new int[1];
+ changed = true;
+ }
+ changed |= merge(cw, edge, frame.inputStack, 0);
+ return changed;
+ }
+
+ int nInputStack = inputStack.length + owner.inputStackTop;
+ if (frame.inputStack == null) {
+ frame.inputStack = new int[nInputStack + outputStackTop];
+ changed = true;
+ }
+
+ for (i = 0; i < nInputStack; ++i) {
+ t = inputStack[i];
+ if (initializations != null) {
+ t = init(cw, t);
+ }
+ changed |= merge(cw, t, frame.inputStack, i);
+ }
+ for (i = 0; i < outputStackTop; ++i) {
+ s = outputStack[i];
+ dim = s & DIM;
+ kind = s & KIND;
+ if (kind == BASE) {
+ t = s;
+ } else {
+ if (kind == LOCAL) {
+ t = dim + inputLocals[s & VALUE];
+ } else {
+ t = dim + inputStack[nStack - (s & VALUE)];
+ }
+ if ((s & TOP_IF_LONG_OR_DOUBLE) != 0
+ && (t == LONG || t == DOUBLE)) {
+ t = TOP;
+ }
+ }
+ if (initializations != null) {
+ t = init(cw, t);
+ }
+ changed |= merge(cw, t, frame.inputStack, nInputStack + i);
+ }
+ return changed;
+ }
+
+ /**
+ * Merges the type at the given index in the given type array with the given
+ * type. Returns <tt>true</tt> if the type array has been modified by this
+ * operation.
+ *
+ * @param cw
+ * the ClassWriter to which this label belongs.
+ * @param t
+ * the type with which the type array element must be merged.
+ * @param types
+ * an array of types.
+ * @param index
+ * the index of the type that must be merged in 'types'.
+ * @return <tt>true</tt> if the type array has been modified by this
+ * operation.
+ */
+ private static boolean merge(final ClassWriter cw, int t,
+ final int[] types, final int index) {
+ int u = types[index];
+ if (u == t) {
+ // if the types are equal, merge(u,t)=u, so there is no change
+ return false;
+ }
+ if ((t & ~DIM) == NULL) {
+ if (u == NULL) {
+ return false;
+ }
+ t = NULL;
+ }
+ if (u == 0) {
+ // if types[index] has never been assigned, merge(u,t)=t
+ types[index] = t;
+ return true;
+ }
+ int v;
+ if ((u & BASE_KIND) == OBJECT || (u & DIM) != 0) {
+ // if u is a reference type of any dimension
+ if (t == NULL) {
+ // if t is the NULL type, merge(u,t)=u, so there is no change
+ return false;
+ } else if ((t & (DIM | BASE_KIND)) == (u & (DIM | BASE_KIND))) {
+ // if t and u have the same dimension and same base kind
+ if ((u & BASE_KIND) == OBJECT) {
+ // if t is also a reference type, and if u and t have the
+ // same dimension merge(u,t) = dim(t) | common parent of the
+ // element types of u and t
+ v = (t & DIM) | OBJECT
+ | cw.getMergedType(t & BASE_VALUE, u & BASE_VALUE);
+ } else {
+ // if u and t are array types, but not with the same element
+ // type, merge(u,t) = dim(u) - 1 | java/lang/Object
+ int vdim = ELEMENT_OF + (u & DIM);
+ v = vdim | OBJECT | cw.addType("java/lang/Object");
+ }
+ } else if ((t & BASE_KIND) == OBJECT || (t & DIM) != 0) {
+ // if t is any other reference or array type, the merged type
+ // is min(udim, tdim) | java/lang/Object, where udim is the
+ // array dimension of u, minus 1 if u is an array type with a
+ // primitive element type (and similarly for tdim).
+ int tdim = (((t & DIM) == 0 || (t & BASE_KIND) == OBJECT) ? 0
+ : ELEMENT_OF) + (t & DIM);
+ int udim = (((u & DIM) == 0 || (u & BASE_KIND) == OBJECT) ? 0
+ : ELEMENT_OF) + (u & DIM);
+ v = Math.min(tdim, udim) | OBJECT
+ | cw.addType("java/lang/Object");
+ } else {
+ // if t is any other type, merge(u,t)=TOP
+ v = TOP;
+ }
+ } else if (u == NULL) {
+ // if u is the NULL type, merge(u,t)=t,
+ // or TOP if t is not a reference type
+ v = (t & BASE_KIND) == OBJECT || (t & DIM) != 0 ? t : TOP;
+ } else {
+ // if u is any other type, merge(u,t)=TOP whatever t
+ v = TOP;
+ }
+ if (u != v) {
+ types[index] = v;
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/Handle.java b/spring-core/src/main/java/org/springframework/asm/Handle.java
new file mode 100644
index 00000000..adc6f097
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/Handle.java
@@ -0,0 +1,170 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.springframework.asm;
+
+/**
+ * A reference to a field or a method.
+ *
+ * @author Remi Forax
+ * @author Eric Bruneton
+ */
+public final class Handle {
+
+ /**
+ * The kind of field or method designated by this Handle. Should be
+ * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC},
+ * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC},
+ * {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC},
+ * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or
+ * {@link Opcodes#H_INVOKEINTERFACE}.
+ */
+ final int tag;
+
+ /**
+ * The internal name of the class that owns the field or method designated
+ * by this handle.
+ */
+ final String owner;
+
+ /**
+ * The name of the field or method designated by this handle.
+ */
+ final String name;
+
+ /**
+ * The descriptor of the field or method designated by this handle.
+ */
+ final String desc;
+
+ /**
+ * Constructs a new field or method handle.
+ *
+ * @param tag
+ * the kind of field or method designated by this Handle. Must be
+ * {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC},
+ * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC},
+ * {@link Opcodes#H_INVOKEVIRTUAL},
+ * {@link Opcodes#H_INVOKESTATIC},
+ * {@link Opcodes#H_INVOKESPECIAL},
+ * {@link Opcodes#H_NEWINVOKESPECIAL} or
+ * {@link Opcodes#H_INVOKEINTERFACE}.
+ * @param owner
+ * the internal name of the class that owns the field or method
+ * designated by this handle.
+ * @param name
+ * the name of the field or method designated by this handle.
+ * @param desc
+ * the descriptor of the field or method designated by this
+ * handle.
+ */
+ public Handle(int tag, String owner, String name, String desc) {
+ this.tag = tag;
+ this.owner = owner;
+ this.name = name;
+ this.desc = desc;
+ }
+
+ /**
+ * Returns the kind of field or method designated by this handle.
+ *
+ * @return {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC},
+ * {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC},
+ * {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC},
+ * {@link Opcodes#H_INVOKESPECIAL},
+ * {@link Opcodes#H_NEWINVOKESPECIAL} or
+ * {@link Opcodes#H_INVOKEINTERFACE}.
+ */
+ public int getTag() {
+ return tag;
+ }
+
+ /**
+ * Returns the internal name of the class that owns the field or method
+ * designated by this handle.
+ *
+ * @return the internal name of the class that owns the field or method
+ * designated by this handle.
+ */
+ public String getOwner() {
+ return owner;
+ }
+
+ /**
+ * Returns the name of the field or method designated by this handle.
+ *
+ * @return the name of the field or method designated by this handle.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the descriptor of the field or method designated by this handle.
+ *
+ * @return the descriptor of the field or method designated by this handle.
+ */
+ public String getDesc() {
+ return desc;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof Handle)) {
+ return false;
+ }
+ Handle h = (Handle) obj;
+ return tag == h.tag && owner.equals(h.owner) && name.equals(h.name)
+ && desc.equals(h.desc);
+ }
+
+ @Override
+ public int hashCode() {
+ return tag + owner.hashCode() * name.hashCode() * desc.hashCode();
+ }
+
+ /**
+ * Returns the textual representation of this handle. The textual
+ * representation is:
+ *
+ * <pre>
+ * owner '.' name desc ' ' '(' tag ')'
+ * </pre>
+ *
+ * . As this format is unambiguous, it can be parsed if necessary.
+ */
+ @Override
+ public String toString() {
+ return owner + '.' + name + desc + " (" + tag + ')';
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/Handler.java b/spring-core/src/main/java/org/springframework/asm/Handler.java
new file mode 100644
index 00000000..50b198e8
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/Handler.java
@@ -0,0 +1,121 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.springframework.asm;
+
+/**
+ * Information about an exception handler block.
+ *
+ * @author Eric Bruneton
+ */
+class Handler {
+
+ /**
+ * Beginning of the exception handler's scope (inclusive).
+ */
+ Label start;
+
+ /**
+ * End of the exception handler's scope (exclusive).
+ */
+ Label end;
+
+ /**
+ * Beginning of the exception handler's code.
+ */
+ Label handler;
+
+ /**
+ * Internal name of the type of exceptions handled by this handler, or
+ * <tt>null</tt> to catch any exceptions.
+ */
+ String desc;
+
+ /**
+ * Constant pool index of the internal name of the type of exceptions
+ * handled by this handler, or 0 to catch any exceptions.
+ */
+ int type;
+
+ /**
+ * Next exception handler block info.
+ */
+ Handler next;
+
+ /**
+ * Removes the range between start and end from the given exception
+ * handlers.
+ *
+ * @param h
+ * an exception handler list.
+ * @param start
+ * the start of the range to be removed.
+ * @param end
+ * the end of the range to be removed. Maybe null.
+ * @return the exception handler list with the start-end range removed.
+ */
+ static Handler remove(Handler h, Label start, Label end) {
+ if (h == null) {
+ return null;
+ } else {
+ h.next = remove(h.next, start, end);
+ }
+ int hstart = h.start.position;
+ int hend = h.end.position;
+ int s = start.position;
+ int e = end == null ? Integer.MAX_VALUE : end.position;
+ // if [hstart,hend[ and [s,e[ intervals intersect...
+ if (s < hend && e > hstart) {
+ if (s <= hstart) {
+ if (e >= hend) {
+ // [hstart,hend[ fully included in [s,e[, h removed
+ h = h.next;
+ } else {
+ // [hstart,hend[ minus [s,e[ = [e,hend[
+ h.start = end;
+ }
+ } else if (e >= hend) {
+ // [hstart,hend[ minus [s,e[ = [hstart,s[
+ h.end = start;
+ } else {
+ // [hstart,hend[ minus [s,e[ = [hstart,s[ + [e,hend[
+ Handler g = new Handler();
+ g.start = end;
+ g.end = h.end;
+ g.handler = h.handler;
+ g.desc = h.desc;
+ g.type = h.type;
+ g.next = h.next;
+ h.end = start;
+ h.next = g;
+ }
+ }
+ return h;
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/Item.java b/spring-core/src/main/java/org/springframework/asm/Item.java
new file mode 100644
index 00000000..91dc701d
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/Item.java
@@ -0,0 +1,314 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.springframework.asm;
+
+/**
+ * A constant pool item. Constant pool items can be created with the 'newXXX'
+ * methods in the {@link ClassWriter} class.
+ *
+ * @author Eric Bruneton
+ */
+final class Item {
+
+ /**
+ * Index of this item in the constant pool.
+ */
+ int index;
+
+ /**
+ * Type of this constant pool item. A single class is used to represent all
+ * constant pool item types, in order to minimize the bytecode size of this
+ * package. The value of this field is one of {@link ClassWriter#INT},
+ * {@link ClassWriter#LONG}, {@link ClassWriter#FLOAT},
+ * {@link ClassWriter#DOUBLE}, {@link ClassWriter#UTF8},
+ * {@link ClassWriter#STR}, {@link ClassWriter#CLASS},
+ * {@link ClassWriter#NAME_TYPE}, {@link ClassWriter#FIELD},
+ * {@link ClassWriter#METH}, {@link ClassWriter#IMETH},
+ * {@link ClassWriter#MTYPE}, {@link ClassWriter#INDY}.
+ *
+ * MethodHandle constant 9 variations are stored using a range of 9 values
+ * from {@link ClassWriter#HANDLE_BASE} + 1 to
+ * {@link ClassWriter#HANDLE_BASE} + 9.
+ *
+ * Special Item types are used for Items that are stored in the ClassWriter
+ * {@link ClassWriter#typeTable}, instead of the constant pool, in order to
+ * avoid clashes with normal constant pool items in the ClassWriter constant
+ * pool's hash table. These special item types are
+ * {@link ClassWriter#TYPE_NORMAL}, {@link ClassWriter#TYPE_UNINIT} and
+ * {@link ClassWriter#TYPE_MERGED}.
+ */
+ int type;
+
+ /**
+ * Value of this item, for an integer item.
+ */
+ int intVal;
+
+ /**
+ * Value of this item, for a long item.
+ */
+ long longVal;
+
+ /**
+ * First part of the value of this item, for items that do not hold a
+ * primitive value.
+ */
+ String strVal1;
+
+ /**
+ * Second part of the value of this item, for items that do not hold a
+ * primitive value.
+ */
+ String strVal2;
+
+ /**
+ * Third part of the value of this item, for items that do not hold a
+ * primitive value.
+ */
+ String strVal3;
+
+ /**
+ * The hash code value of this constant pool item.
+ */
+ int hashCode;
+
+ /**
+ * Link to another constant pool item, used for collision lists in the
+ * constant pool's hash table.
+ */
+ Item next;
+
+ /**
+ * Constructs an uninitialized {@link Item}.
+ */
+ Item() {
+ }
+
+ /**
+ * Constructs an uninitialized {@link Item} for constant pool element at
+ * given position.
+ *
+ * @param index
+ * index of the item to be constructed.
+ */
+ Item(final int index) {
+ this.index = index;
+ }
+
+ /**
+ * Constructs a copy of the given item.
+ *
+ * @param index
+ * index of the item to be constructed.
+ * @param i
+ * the item that must be copied into the item to be constructed.
+ */
+ Item(final int index, final Item i) {
+ this.index = index;
+ type = i.type;
+ intVal = i.intVal;
+ longVal = i.longVal;
+ strVal1 = i.strVal1;
+ strVal2 = i.strVal2;
+ strVal3 = i.strVal3;
+ hashCode = i.hashCode;
+ }
+
+ /**
+ * Sets this item to an integer item.
+ *
+ * @param intVal
+ * the value of this item.
+ */
+ void set(final int intVal) {
+ this.type = ClassWriter.INT;
+ this.intVal = intVal;
+ this.hashCode = 0x7FFFFFFF & (type + intVal);
+ }
+
+ /**
+ * Sets this item to a long item.
+ *
+ * @param longVal
+ * the value of this item.
+ */
+ void set(final long longVal) {
+ this.type = ClassWriter.LONG;
+ this.longVal = longVal;
+ this.hashCode = 0x7FFFFFFF & (type + (int) longVal);
+ }
+
+ /**
+ * Sets this item to a float item.
+ *
+ * @param floatVal
+ * the value of this item.
+ */
+ void set(final float floatVal) {
+ this.type = ClassWriter.FLOAT;
+ this.intVal = Float.floatToRawIntBits(floatVal);
+ this.hashCode = 0x7FFFFFFF & (type + (int) floatVal);
+ }
+
+ /**
+ * Sets this item to a double item.
+ *
+ * @param doubleVal
+ * the value of this item.
+ */
+ void set(final double doubleVal) {
+ this.type = ClassWriter.DOUBLE;
+ this.longVal = Double.doubleToRawLongBits(doubleVal);
+ this.hashCode = 0x7FFFFFFF & (type + (int) doubleVal);
+ }
+
+ /**
+ * Sets this item to an item that do not hold a primitive value.
+ *
+ * @param type
+ * the type of this item.
+ * @param strVal1
+ * first part of the value of this item.
+ * @param strVal2
+ * second part of the value of this item.
+ * @param strVal3
+ * third part of the value of this item.
+ */
+ void set(final int type, final String strVal1, final String strVal2,
+ final String strVal3) {
+ this.type = type;
+ this.strVal1 = strVal1;
+ this.strVal2 = strVal2;
+ this.strVal3 = strVal3;
+ switch (type) {
+ case ClassWriter.CLASS:
+ this.intVal = 0; // intVal of a class must be zero, see visitInnerClass
+ hashCode = 0x7FFFFFFF & (type + strVal1.hashCode());
+ return;
+ case ClassWriter.UTF8:
+ case ClassWriter.STR:
+ case ClassWriter.MTYPE:
+ case ClassWriter.TYPE_NORMAL:
+ hashCode = 0x7FFFFFFF & (type + strVal1.hashCode());
+ return;
+ case ClassWriter.NAME_TYPE: {
+ hashCode = 0x7FFFFFFF & (type + strVal1.hashCode()
+ * strVal2.hashCode());
+ return;
+ }
+ // ClassWriter.FIELD:
+ // ClassWriter.METH:
+ // ClassWriter.IMETH:
+ // ClassWriter.HANDLE_BASE + 1..9
+ default:
+ hashCode = 0x7FFFFFFF & (type + strVal1.hashCode()
+ * strVal2.hashCode() * strVal3.hashCode());
+ }
+ }
+
+ /**
+ * Sets the item to an InvokeDynamic item.
+ *
+ * @param name
+ * invokedynamic's name.
+ * @param desc
+ * invokedynamic's desc.
+ * @param bsmIndex
+ * zero based index into the class attribute BootrapMethods.
+ */
+ void set(String name, String desc, int bsmIndex) {
+ this.type = ClassWriter.INDY;
+ this.longVal = bsmIndex;
+ this.strVal1 = name;
+ this.strVal2 = desc;
+ this.hashCode = 0x7FFFFFFF & (ClassWriter.INDY + bsmIndex
+ * strVal1.hashCode() * strVal2.hashCode());
+ }
+
+ /**
+ * Sets the item to a BootstrapMethod item.
+ *
+ * @param position
+ * position in byte in the class attribute BootrapMethods.
+ * @param hashCode
+ * hashcode of the item. This hashcode is processed from the
+ * hashcode of the bootstrap method and the hashcode of all
+ * bootstrap arguments.
+ */
+ void set(int position, int hashCode) {
+ this.type = ClassWriter.BSM;
+ this.intVal = position;
+ this.hashCode = hashCode;
+ }
+
+ /**
+ * Indicates if the given item is equal to this one. <i>This method assumes
+ * that the two items have the same {@link #type}</i>.
+ *
+ * @param i
+ * the item to be compared to this one. Both items must have the
+ * same {@link #type}.
+ * @return <tt>true</tt> if the given item if equal to this one,
+ * <tt>false</tt> otherwise.
+ */
+ boolean isEqualTo(final Item i) {
+ switch (type) {
+ case ClassWriter.UTF8:
+ case ClassWriter.STR:
+ case ClassWriter.CLASS:
+ case ClassWriter.MTYPE:
+ case ClassWriter.TYPE_NORMAL:
+ return i.strVal1.equals(strVal1);
+ case ClassWriter.TYPE_MERGED:
+ case ClassWriter.LONG:
+ case ClassWriter.DOUBLE:
+ return i.longVal == longVal;
+ case ClassWriter.INT:
+ case ClassWriter.FLOAT:
+ return i.intVal == intVal;
+ case ClassWriter.TYPE_UNINIT:
+ return i.intVal == intVal && i.strVal1.equals(strVal1);
+ case ClassWriter.NAME_TYPE:
+ return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2);
+ case ClassWriter.INDY: {
+ return i.longVal == longVal && i.strVal1.equals(strVal1)
+ && i.strVal2.equals(strVal2);
+ }
+ // case ClassWriter.FIELD:
+ // case ClassWriter.METH:
+ // case ClassWriter.IMETH:
+ // case ClassWriter.HANDLE_BASE + 1..9
+ default:
+ return i.strVal1.equals(strVal1) && i.strVal2.equals(strVal2)
+ && i.strVal3.equals(strVal3);
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/Label.java b/spring-core/src/main/java/org/springframework/asm/Label.java
new file mode 100644
index 00000000..deb84a09
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/Label.java
@@ -0,0 +1,565 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.springframework.asm;
+
+/**
+ * A label represents a position in the bytecode of a method. Labels are used
+ * for jump, goto, and switch instructions, and for try catch blocks. A label
+ * designates the <i>instruction</i> that is just after. Note however that there
+ * can be other elements between a label and the instruction it designates (such
+ * as other labels, stack map frames, line numbers, etc.).
+ *
+ * @author Eric Bruneton
+ */
+public class Label {
+
+ /**
+ * Indicates if this label is only used for debug attributes. Such a label
+ * is not the start of a basic block, the target of a jump instruction, or
+ * an exception handler. It can be safely ignored in control flow graph
+ * analysis algorithms (for optimization purposes).
+ */
+ static final int DEBUG = 1;
+
+ /**
+ * Indicates if the position of this label is known.
+ */
+ static final int RESOLVED = 2;
+
+ /**
+ * Indicates if this label has been updated, after instruction resizing.
+ */
+ static final int RESIZED = 4;
+
+ /**
+ * Indicates if this basic block has been pushed in the basic block stack.
+ * See {@link MethodWriter#visitMaxs visitMaxs}.
+ */
+ static final int PUSHED = 8;
+
+ /**
+ * Indicates if this label is the target of a jump instruction, or the start
+ * of an exception handler.
+ */
+ static final int TARGET = 16;
+
+ /**
+ * Indicates if a stack map frame must be stored for this label.
+ */
+ static final int STORE = 32;
+
+ /**
+ * Indicates if this label corresponds to a reachable basic block.
+ */
+ static final int REACHABLE = 64;
+
+ /**
+ * Indicates if this basic block ends with a JSR instruction.
+ */
+ static final int JSR = 128;
+
+ /**
+ * Indicates if this basic block ends with a RET instruction.
+ */
+ static final int RET = 256;
+
+ /**
+ * Indicates if this basic block is the start of a subroutine.
+ */
+ static final int SUBROUTINE = 512;
+
+ /**
+ * Indicates if this subroutine basic block has been visited by a
+ * visitSubroutine(null, ...) call.
+ */
+ static final int VISITED = 1024;
+
+ /**
+ * Indicates if this subroutine basic block has been visited by a
+ * visitSubroutine(!null, ...) call.
+ */
+ static final int VISITED2 = 2048;
+
+ /**
+ * Field used to associate user information to a label. Warning: this field
+ * is used by the ASM tree package. In order to use it with the ASM tree
+ * package you must override the
+ * {@code org.objectweb.asm.tree.MethodNode#getLabelNode} method.
+ */
+ public Object info;
+
+ /**
+ * Flags that indicate the status of this label.
+ *
+ * @see #DEBUG
+ * @see #RESOLVED
+ * @see #RESIZED
+ * @see #PUSHED
+ * @see #TARGET
+ * @see #STORE
+ * @see #REACHABLE
+ * @see #JSR
+ * @see #RET
+ */
+ int status;
+
+ /**
+ * The line number corresponding to this label, if known. If there are
+ * several lines, each line is stored in a separate label, all linked via
+ * their next field (these links are created in ClassReader and removed just
+ * before visitLabel is called, so that this does not impact the rest of the
+ * code).
+ */
+ int line;
+
+ /**
+ * The position of this label in the code, if known.
+ */
+ int position;
+
+ /**
+ * Number of forward references to this label, times two.
+ */
+ private int referenceCount;
+
+ /**
+ * Informations about forward references. Each forward reference is
+ * described by two consecutive integers in this array: the first one is the
+ * position of the first byte of the bytecode instruction that contains the
+ * forward reference, while the second is the position of the first byte of
+ * the forward reference itself. In fact the sign of the first integer
+ * indicates if this reference uses 2 or 4 bytes, and its absolute value
+ * gives the position of the bytecode instruction. This array is also used
+ * as a bitset to store the subroutines to which a basic block belongs. This
+ * information is needed in {@link MethodWriter#visitMaxs}, after all
+ * forward references have been resolved. Hence the same array can be used
+ * for both purposes without problems.
+ */
+ private int[] srcAndRefPositions;
+
+ // ------------------------------------------------------------------------
+
+ /*
+ * Fields for the control flow and data flow graph analysis algorithms (used
+ * to compute the maximum stack size or the stack map frames). A control
+ * flow graph contains one node per "basic block", and one edge per "jump"
+ * from one basic block to another. Each node (i.e., each basic block) is
+ * represented by the Label object that corresponds to the first instruction
+ * of this basic block. Each node also stores the list of its successors in
+ * the graph, as a linked list of Edge objects.
+ *
+ * The control flow analysis algorithms used to compute the maximum stack
+ * size or the stack map frames are similar and use two steps. The first
+ * step, during the visit of each instruction, builds information about the
+ * state of the local variables and the operand stack at the end of each
+ * basic block, called the "output frame", <i>relatively</i> to the frame
+ * state at the beginning of the basic block, which is called the "input
+ * frame", and which is <i>unknown</i> during this step. The second step, in
+ * {@link MethodWriter#visitMaxs}, is a fix point algorithm that computes
+ * information about the input frame of each basic block, from the input
+ * state of the first basic block (known from the method signature), and by
+ * the using the previously computed relative output frames.
+ *
+ * The algorithm used to compute the maximum stack size only computes the
+ * relative output and absolute input stack heights, while the algorithm
+ * used to compute stack map frames computes relative output frames and
+ * absolute input frames.
+ */
+
+ /**
+ * Start of the output stack relatively to the input stack. The exact
+ * semantics of this field depends on the algorithm that is used.
+ *
+ * When only the maximum stack size is computed, this field is the number of
+ * elements in the input stack.
+ *
+ * When the stack map frames are completely computed, this field is the
+ * offset of the first output stack element relatively to the top of the
+ * input stack. This offset is always negative or null. A null offset means
+ * that the output stack must be appended to the input stack. A -n offset
+ * means that the first n output stack elements must replace the top n input
+ * stack elements, and that the other elements must be appended to the input
+ * stack.
+ */
+ int inputStackTop;
+
+ /**
+ * Maximum height reached by the output stack, relatively to the top of the
+ * input stack. This maximum is always positive or null.
+ */
+ int outputStackMax;
+
+ /**
+ * Information about the input and output stack map frames of this basic
+ * block. This field is only used when {@link ClassWriter#COMPUTE_FRAMES}
+ * option is used.
+ */
+ Frame frame;
+
+ /**
+ * The successor of this label, in the order they are visited. This linked
+ * list does not include labels used for debug info only. If
+ * {@link ClassWriter#COMPUTE_FRAMES} option is used then, in addition, it
+ * does not contain successive labels that denote the same bytecode position
+ * (in this case only the first label appears in this list).
+ */
+ Label successor;
+
+ /**
+ * The successors of this node in the control flow graph. These successors
+ * are stored in a linked list of {@link Edge Edge} objects, linked to each
+ * other by their {@link Edge#next} field.
+ */
+ Edge successors;
+
+ /**
+ * The next basic block in the basic block stack. This stack is used in the
+ * main loop of the fix point algorithm used in the second step of the
+ * control flow analysis algorithms. It is also used in
+ * {@link #visitSubroutine} to avoid using a recursive method, and in
+ * ClassReader to temporarily store multiple source lines for a label.
+ *
+ * @see MethodWriter#visitMaxs
+ */
+ Label next;
+
+ // ------------------------------------------------------------------------
+ // Constructor
+ // ------------------------------------------------------------------------
+
+ /**
+ * Constructs a new label.
+ */
+ public Label() {
+ }
+
+ // ------------------------------------------------------------------------
+ // Methods to compute offsets and to manage forward references
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the offset corresponding to this label. This offset is computed
+ * from the start of the method's bytecode. <i>This method is intended for
+ * {@link Attribute} sub classes, and is normally not needed by class
+ * generators or adapters.</i>
+ *
+ * @return the offset corresponding to this label.
+ * @throws IllegalStateException
+ * if this label is not resolved yet.
+ */
+ public int getOffset() {
+ if ((status & RESOLVED) == 0) {
+ throw new IllegalStateException(
+ "Label offset position has not been resolved yet");
+ }
+ return position;
+ }
+
+ /**
+ * Puts a reference to this label in the bytecode of a method. If the
+ * position of the label is known, the offset is computed and written
+ * directly. Otherwise, a null offset is written and a new forward reference
+ * is declared for this label.
+ *
+ * @param owner
+ * the code writer that calls this method.
+ * @param out
+ * the bytecode of the method.
+ * @param source
+ * the position of first byte of the bytecode instruction that
+ * contains this label.
+ * @param wideOffset
+ * <tt>true</tt> if the reference must be stored in 4 bytes, or
+ * <tt>false</tt> if it must be stored with 2 bytes.
+ * @throws IllegalArgumentException
+ * if this label has not been created by the given code writer.
+ */
+ void put(final MethodWriter owner, final ByteVector out, final int source,
+ final boolean wideOffset) {
+ if ((status & RESOLVED) == 0) {
+ if (wideOffset) {
+ addReference(-1 - source, out.length);
+ out.putInt(-1);
+ } else {
+ addReference(source, out.length);
+ out.putShort(-1);
+ }
+ } else {
+ if (wideOffset) {
+ out.putInt(position - source);
+ } else {
+ out.putShort(position - source);
+ }
+ }
+ }
+
+ /**
+ * Adds a forward reference to this label. This method must be called only
+ * for a true forward reference, i.e. only if this label is not resolved
+ * yet. For backward references, the offset of the reference can be, and
+ * must be, computed and stored directly.
+ *
+ * @param sourcePosition
+ * the position of the referencing instruction. This position
+ * will be used to compute the offset of this forward reference.
+ * @param referencePosition
+ * the position where the offset for this forward reference must
+ * be stored.
+ */
+ private void addReference(final int sourcePosition,
+ final int referencePosition) {
+ if (srcAndRefPositions == null) {
+ srcAndRefPositions = new int[6];
+ }
+ if (referenceCount >= srcAndRefPositions.length) {
+ int[] a = new int[srcAndRefPositions.length + 6];
+ System.arraycopy(srcAndRefPositions, 0, a, 0,
+ srcAndRefPositions.length);
+ srcAndRefPositions = a;
+ }
+ srcAndRefPositions[referenceCount++] = sourcePosition;
+ srcAndRefPositions[referenceCount++] = referencePosition;
+ }
+
+ /**
+ * Resolves all forward references to this label. This method must be called
+ * when this label is added to the bytecode of the method, i.e. when its
+ * position becomes known. This method fills in the blanks that where left
+ * in the bytecode by each forward reference previously added to this label.
+ *
+ * @param owner
+ * the code writer that calls this method.
+ * @param position
+ * the position of this label in the bytecode.
+ * @param data
+ * the bytecode of the method.
+ * @return <tt>true</tt> if a blank that was left for this label was to
+ * small to store the offset. In such a case the corresponding jump
+ * instruction is replaced with a pseudo instruction (using unused
+ * opcodes) using an unsigned two bytes offset. These pseudo
+ * instructions will need to be replaced with true instructions with
+ * wider offsets (4 bytes instead of 2). This is done in
+ * {@link MethodWriter#resizeInstructions}.
+ * @throws IllegalArgumentException
+ * if this label has already been resolved, or if it has not
+ * been created by the given code writer.
+ */
+ boolean resolve(final MethodWriter owner, final int position,
+ final byte[] data) {
+ boolean needUpdate = false;
+ this.status |= RESOLVED;
+ this.position = position;
+ int i = 0;
+ while (i < referenceCount) {
+ int source = srcAndRefPositions[i++];
+ int reference = srcAndRefPositions[i++];
+ int offset;
+ if (source >= 0) {
+ offset = position - source;
+ if (offset < Short.MIN_VALUE || offset > Short.MAX_VALUE) {
+ /*
+ * changes the opcode of the jump instruction, in order to
+ * be able to find it later (see resizeInstructions in
+ * MethodWriter). These temporary opcodes are similar to
+ * jump instruction opcodes, except that the 2 bytes offset
+ * is unsigned (and can therefore represent values from 0 to
+ * 65535, which is sufficient since the size of a method is
+ * limited to 65535 bytes).
+ */
+ int opcode = data[reference - 1] & 0xFF;
+ if (opcode <= Opcodes.JSR) {
+ // changes IFEQ ... JSR to opcodes 202 to 217
+ data[reference - 1] = (byte) (opcode + 49);
+ } else {
+ // changes IFNULL and IFNONNULL to opcodes 218 and 219
+ data[reference - 1] = (byte) (opcode + 20);
+ }
+ needUpdate = true;
+ }
+ data[reference++] = (byte) (offset >>> 8);
+ data[reference] = (byte) offset;
+ } else {
+ offset = position + source + 1;
+ data[reference++] = (byte) (offset >>> 24);
+ data[reference++] = (byte) (offset >>> 16);
+ data[reference++] = (byte) (offset >>> 8);
+ data[reference] = (byte) offset;
+ }
+ }
+ return needUpdate;
+ }
+
+ /**
+ * Returns the first label of the series to which this label belongs. For an
+ * isolated label or for the first label in a series of successive labels,
+ * this method returns the label itself. For other labels it returns the
+ * first label of the series.
+ *
+ * @return the first label of the series to which this label belongs.
+ */
+ Label getFirst() {
+ return !ClassReader.FRAMES || frame == null ? this : frame.owner;
+ }
+
+ // ------------------------------------------------------------------------
+ // Methods related to subroutines
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns true is this basic block belongs to the given subroutine.
+ *
+ * @param id
+ * a subroutine id.
+ * @return true is this basic block belongs to the given subroutine.
+ */
+ boolean inSubroutine(final long id) {
+ if ((status & Label.VISITED) != 0) {
+ return (srcAndRefPositions[(int) (id >>> 32)] & (int) id) != 0;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if this basic block and the given one belong to a common
+ * subroutine.
+ *
+ * @param block
+ * another basic block.
+ * @return true if this basic block and the given one belong to a common
+ * subroutine.
+ */
+ boolean inSameSubroutine(final Label block) {
+ if ((status & VISITED) == 0 || (block.status & VISITED) == 0) {
+ return false;
+ }
+ for (int i = 0; i < srcAndRefPositions.length; ++i) {
+ if ((srcAndRefPositions[i] & block.srcAndRefPositions[i]) != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Marks this basic block as belonging to the given subroutine.
+ *
+ * @param id
+ * a subroutine id.
+ * @param nbSubroutines
+ * the total number of subroutines in the method.
+ */
+ void addToSubroutine(final long id, final int nbSubroutines) {
+ if ((status & VISITED) == 0) {
+ status |= VISITED;
+ srcAndRefPositions = new int[nbSubroutines / 32 + 1];
+ }
+ srcAndRefPositions[(int) (id >>> 32)] |= (int) id;
+ }
+
+ /**
+ * Finds the basic blocks that belong to a given subroutine, and marks these
+ * blocks as belonging to this subroutine. This method follows the control
+ * flow graph to find all the blocks that are reachable from the current
+ * block WITHOUT following any JSR target.
+ *
+ * @param JSR
+ * a JSR block that jumps to this subroutine. If this JSR is not
+ * null it is added to the successor of the RET blocks found in
+ * the subroutine.
+ * @param id
+ * the id of this subroutine.
+ * @param nbSubroutines
+ * the total number of subroutines in the method.
+ */
+ void visitSubroutine(final Label JSR, final long id, final int nbSubroutines) {
+ // user managed stack of labels, to avoid using a recursive method
+ // (recursivity can lead to stack overflow with very large methods)
+ Label stack = this;
+ while (stack != null) {
+ // removes a label l from the stack
+ Label l = stack;
+ stack = l.next;
+ l.next = null;
+
+ if (JSR != null) {
+ if ((l.status & VISITED2) != 0) {
+ continue;
+ }
+ l.status |= VISITED2;
+ // adds JSR to the successors of l, if it is a RET block
+ if ((l.status & RET) != 0) {
+ if (!l.inSameSubroutine(JSR)) {
+ Edge e = new Edge();
+ e.info = l.inputStackTop;
+ e.successor = JSR.successors.successor;
+ e.next = l.successors;
+ l.successors = e;
+ }
+ }
+ } else {
+ // if the l block already belongs to subroutine 'id', continue
+ if (l.inSubroutine(id)) {
+ continue;
+ }
+ // marks the l block as belonging to subroutine 'id'
+ l.addToSubroutine(id, nbSubroutines);
+ }
+ // pushes each successor of l on the stack, except JSR targets
+ Edge e = l.successors;
+ while (e != null) {
+ // if the l block is a JSR block, then 'l.successors.next' leads
+ // to the JSR target (see {@link #visitJumpInsn}) and must
+ // therefore not be followed
+ if ((l.status & Label.JSR) == 0 || e != l.successors.next) {
+ // pushes e.successor on the stack if it not already added
+ if (e.successor.next == null) {
+ e.successor.next = stack;
+ stack = e.successor;
+ }
+ }
+ e = e.next;
+ }
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Overriden Object methods
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns a string representation of this label.
+ *
+ * @return a string representation of this label.
+ */
+ @Override
+ public String toString() {
+ return "L" + System.identityHashCode(this);
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/MethodVisitor.java b/spring-core/src/main/java/org/springframework/asm/MethodVisitor.java
new file mode 100644
index 00000000..4d5c50b9
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/MethodVisitor.java
@@ -0,0 +1,890 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.springframework.asm;
+
+/**
+ * A visitor to visit a Java method. The methods of this class must be called in
+ * the following order: ( <tt>visitParameter</tt> )* [
+ * <tt>visitAnnotationDefault</tt> ] ( <tt>visitAnnotation</tt> |
+ * <tt>visitTypeAnnotation</tt> | <tt>visitAttribute</tt> )* [
+ * <tt>visitCode</tt> ( <tt>visitFrame</tt> | <tt>visit<i>X</i>Insn</tt> |
+ * <tt>visitLabel</tt> | <tt>visitInsnAnnotation</tt> |
+ * <tt>visitTryCatchBlock</tt> | <tt>visitTryCatchBlockAnnotation</tt> |
+ * <tt>visitLocalVariable</tt> | <tt>visitLocalVariableAnnotation</tt> |
+ * <tt>visitLineNumber</tt> )* <tt>visitMaxs</tt> ] <tt>visitEnd</tt>. In
+ * addition, the <tt>visit<i>X</i>Insn</tt> and <tt>visitLabel</tt> methods must
+ * be called in the sequential order of the bytecode instructions of the visited
+ * code, <tt>visitInsnAnnotation</tt> must be called <i>after</i> the annotated
+ * instruction, <tt>visitTryCatchBlock</tt> must be called <i>before</i> the
+ * labels passed as arguments have been visited,
+ * <tt>visitTryCatchBlockAnnotation</tt> must be called <i>after</i> the
+ * corresponding try catch block has been visited, and the
+ * <tt>visitLocalVariable</tt>, <tt>visitLocalVariableAnnotation</tt> and
+ * <tt>visitLineNumber</tt> methods must be called <i>after</i> the labels
+ * passed as arguments have been visited.
+ *
+ * @author Eric Bruneton
+ */
+public abstract class MethodVisitor {
+
+ /**
+ * The ASM API version implemented by this visitor. The value of this field
+ * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ */
+ protected final int api;
+
+ /**
+ * The method visitor to which this visitor must delegate method calls. May
+ * be null.
+ */
+ protected MethodVisitor mv;
+
+ /**
+ * Constructs a new {@link MethodVisitor}.
+ *
+ * @param api
+ * the ASM API version implemented by this visitor. Must be one
+ * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ */
+ public MethodVisitor(final int api) {
+ this(api, null);
+ }
+
+ /**
+ * Constructs a new {@link MethodVisitor}.
+ *
+ * @param api
+ * the ASM API version implemented by this visitor. Must be one
+ * of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
+ * @param mv
+ * the method visitor to which this visitor must delegate method
+ * calls. May be null.
+ */
+ public MethodVisitor(final int api, final MethodVisitor mv) {
+ if (api != Opcodes.ASM4 && api != Opcodes.ASM5) {
+ throw new IllegalArgumentException();
+ }
+ this.api = api;
+ this.mv = mv;
+ }
+
+ // -------------------------------------------------------------------------
+ // Parameters, annotations and non standard attributes
+ // -------------------------------------------------------------------------
+
+ /**
+ * Visits a parameter of this method.
+ *
+ * @param name
+ * parameter name or null if none is provided.
+ * @param access
+ * the parameter's access flags, only <tt>ACC_FINAL</tt>,
+ * <tt>ACC_SYNTHETIC</tt> or/and <tt>ACC_MANDATED</tt> are
+ * allowed (see {@link Opcodes}).
+ */
+ public void visitParameter(String name, int access) {
+ /* SPRING PATCH: REMOVED FOR COMPATIBILITY WITH CGLIB 3.1
+ if (api < Opcodes.ASM5) {
+ throw new RuntimeException();
+ }
+ */
+ if (mv != null) {
+ mv.visitParameter(name, access);
+ }
+ }
+
+ /**
+ * Visits the default value of this annotation interface method.
+ *
+ * @return a visitor to the visit the actual default value of this
+ * annotation interface method, or <tt>null</tt> if this visitor is
+ * not interested in visiting this default value. The 'name'
+ * parameters passed to the methods of this annotation visitor are
+ * ignored. Moreover, exacly one visit method must be called on this
+ * annotation visitor, followed by visitEnd.
+ */
+ public AnnotationVisitor visitAnnotationDefault() {
+ if (mv != null) {
+ return mv.visitAnnotationDefault();
+ }
+ return null;
+ }
+
+ /**
+ * Visits an annotation of this method.
+ *
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * <tt>true</tt> if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or <tt>null</tt> if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (mv != null) {
+ return mv.visitAnnotation(desc, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits an annotation on a type in the method signature.
+ *
+ * @param typeRef
+ * a reference to the annotated type. The sort of this type
+ * reference must be {@link TypeReference#METHOD_TYPE_PARAMETER
+ * METHOD_TYPE_PARAMETER},
+ * {@link TypeReference#METHOD_TYPE_PARAMETER_BOUND
+ * METHOD_TYPE_PARAMETER_BOUND},
+ * {@link TypeReference#METHOD_RETURN METHOD_RETURN},
+ * {@link TypeReference#METHOD_RECEIVER METHOD_RECEIVER},
+ * {@link TypeReference#METHOD_FORMAL_PARAMETER
+ * METHOD_FORMAL_PARAMETER} or {@link TypeReference#THROWS
+ * THROWS}. See {@link TypeReference}.
+ * @param typePath
+ * the path to the annotated type argument, wildcard bound, array
+ * element type, or static inner type within 'typeRef'. May be
+ * <tt>null</tt> if the annotation targets 'typeRef' as a whole.
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * <tt>true</tt> if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or <tt>null</tt> if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitTypeAnnotation(int typeRef,
+ TypePath typePath, String desc, boolean visible) {
+ /* SPRING PATCH: REMOVED FOR COMPATIBILITY WITH CGLIB 3.1
+ if (api < Opcodes.ASM5) {
+ throw new RuntimeException();
+ }
+ */
+ if (mv != null) {
+ return mv.visitTypeAnnotation(typeRef, typePath, desc, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits an annotation of a parameter this method.
+ *
+ * @param parameter
+ * the parameter index.
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * <tt>true</tt> if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or <tt>null</tt> if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitParameterAnnotation(int parameter,
+ String desc, boolean visible) {
+ if (mv != null) {
+ return mv.visitParameterAnnotation(parameter, desc, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a non standard attribute of this method.
+ *
+ * @param attr
+ * an attribute.
+ */
+ public void visitAttribute(Attribute attr) {
+ if (mv != null) {
+ mv.visitAttribute(attr);
+ }
+ }
+
+ /**
+ * Starts the visit of the method's code, if any (i.e. non abstract method).
+ */
+ public void visitCode() {
+ if (mv != null) {
+ mv.visitCode();
+ }
+ }
+
+ /**
+ * Visits the current state of the local variables and operand stack
+ * elements. This method must(*) be called <i>just before</i> any
+ * instruction <b>i</b> that follows an unconditional branch instruction
+ * such as GOTO or THROW, that is the target of a jump instruction, or that
+ * starts an exception handler block. The visited types must describe the
+ * values of the local variables and of the operand stack elements <i>just
+ * before</i> <b>i</b> is executed.<br>
+ * <br>
+ * (*) this is mandatory only for classes whose version is greater than or
+ * equal to {@link Opcodes#V1_6 V1_6}. <br>
+ * <br>
+ * The frames of a method must be given either in expanded form, or in
+ * compressed form (all frames must use the same format, i.e. you must not
+ * mix expanded and compressed frames within a single method):
+ * <ul>
+ * <li>In expanded form, all frames must have the F_NEW type.</li>
+ * <li>In compressed form, frames are basically "deltas" from the state of
+ * the previous frame:
+ * <ul>
+ * <li>{@link Opcodes#F_SAME} representing frame with exactly the same
+ * locals as the previous frame and with the empty stack.</li>
+ * <li>{@link Opcodes#F_SAME1} representing frame with exactly the same
+ * locals as the previous frame and with single value on the stack (
+ * <code>nStack</code> is 1 and <code>stack[0]</code> contains value for the
+ * type of the stack item).</li>
+ * <li>{@link Opcodes#F_APPEND} representing frame with current locals are
+ * the same as the locals in the previous frame, except that additional
+ * locals are defined (<code>nLocal</code> is 1, 2 or 3 and
+ * <code>local</code> elements contains values representing added types).</li>
+ * <li>{@link Opcodes#F_CHOP} representing frame with current locals are the
+ * same as the locals in the previous frame, except that the last 1-3 locals
+ * are absent and with the empty stack (<code>nLocals</code> is 1, 2 or 3).</li>
+ * <li>{@link Opcodes#F_FULL} representing complete frame data.</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * <br>
+ * In both cases the first frame, corresponding to the method's parameters
+ * and access flags, is implicit and must not be visited. Also, it is
+ * illegal to visit two or more frames for the same code location (i.e., at
+ * least one instruction must be visited between two calls to visitFrame).
+ *
+ * @param type
+ * the type of this stack map frame. Must be
+ * {@link Opcodes#F_NEW} for expanded frames, or
+ * {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND},
+ * {@link Opcodes#F_CHOP}, {@link Opcodes#F_SAME} or
+ * {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for
+ * compressed frames.
+ * @param nLocal
+ * the number of local variables in the visited frame.
+ * @param local
+ * the local variable types in this frame. This array must not be
+ * modified. Primitive types are represented by
+ * {@link Opcodes#TOP}, {@link Opcodes#INTEGER},
+ * {@link Opcodes#FLOAT}, {@link Opcodes#LONG},
+ * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or
+ * {@link Opcodes#UNINITIALIZED_THIS} (long and double are
+ * represented by a single element). Reference types are
+ * represented by String objects (representing internal names),
+ * and uninitialized types by Label objects (this label
+ * designates the NEW instruction that created this uninitialized
+ * value).
+ * @param nStack
+ * the number of operand stack elements in the visited frame.
+ * @param stack
+ * the operand stack types in this frame. This array must not be
+ * modified. Its content has the same format as the "local"
+ * array.
+ * @throws IllegalStateException
+ * if a frame is visited just after another one, without any
+ * instruction between the two (unless this frame is a
+ * Opcodes#F_SAME frame, in which case it is silently ignored).
+ */
+ public void visitFrame(int type, int nLocal, Object[] local, int nStack,
+ Object[] stack) {
+ if (mv != null) {
+ mv.visitFrame(type, nLocal, local, nStack, stack);
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Normal instructions
+ // -------------------------------------------------------------------------
+
+ /**
+ * Visits a zero operand instruction.
+ *
+ * @param opcode
+ * the opcode of the instruction to be visited. This opcode is
+ * either NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1,
+ * ICONST_2, ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1,
+ * FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD,
+ * LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD,
+ * IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE,
+ * SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1,
+ * DUP2_X2, SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB,
+ * IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, FDIV, DDIV, IREM, LREM,
+ * FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR,
+ * IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D,
+ * L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S,
+ * LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN,
+ * DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER,
+ * or MONITOREXIT.
+ */
+ public void visitInsn(int opcode) {
+ if (mv != null) {
+ mv.visitInsn(opcode);
+ }
+ }
+
+ /**
+ * Visits an instruction with a single int operand.
+ *
+ * @param opcode
+ * the opcode of the instruction to be visited. This opcode is
+ * either BIPUSH, SIPUSH or NEWARRAY.
+ * @param operand
+ * the operand of the instruction to be visited.<br>
+ * When opcode is BIPUSH, operand value should be between
+ * Byte.MIN_VALUE and Byte.MAX_VALUE.<br>
+ * When opcode is SIPUSH, operand value should be between
+ * Short.MIN_VALUE and Short.MAX_VALUE.<br>
+ * When opcode is NEWARRAY, operand value should be one of
+ * {@link Opcodes#T_BOOLEAN}, {@link Opcodes#T_CHAR},
+ * {@link Opcodes#T_FLOAT}, {@link Opcodes#T_DOUBLE},
+ * {@link Opcodes#T_BYTE}, {@link Opcodes#T_SHORT},
+ * {@link Opcodes#T_INT} or {@link Opcodes#T_LONG}.
+ */
+ public void visitIntInsn(int opcode, int operand) {
+ if (mv != null) {
+ mv.visitIntInsn(opcode, operand);
+ }
+ }
+
+ /**
+ * Visits a local variable instruction. A local variable instruction is an
+ * instruction that loads or stores the value of a local variable.
+ *
+ * @param opcode
+ * the opcode of the local variable instruction to be visited.
+ * This opcode is either ILOAD, LLOAD, FLOAD, DLOAD, ALOAD,
+ * ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET.
+ * @param var
+ * the operand of the instruction to be visited. This operand is
+ * the index of a local variable.
+ */
+ public void visitVarInsn(int opcode, int var) {
+ if (mv != null) {
+ mv.visitVarInsn(opcode, var);
+ }
+ }
+
+ /**
+ * Visits a type instruction. A type instruction is an instruction that
+ * takes the internal name of a class as parameter.
+ *
+ * @param opcode
+ * the opcode of the type instruction to be visited. This opcode
+ * is either NEW, ANEWARRAY, CHECKCAST or INSTANCEOF.
+ * @param type
+ * the operand of the instruction to be visited. This operand
+ * must be the internal name of an object or array class (see
+ * {@link Type#getInternalName() getInternalName}).
+ */
+ public void visitTypeInsn(int opcode, String type) {
+ if (mv != null) {
+ mv.visitTypeInsn(opcode, type);
+ }
+ }
+
+ /**
+ * Visits a field instruction. A field instruction is an instruction that
+ * loads or stores the value of a field of an object.
+ *
+ * @param opcode
+ * the opcode of the type instruction to be visited. This opcode
+ * is either GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD.
+ * @param owner
+ * the internal name of the field's owner class (see
+ * {@link Type#getInternalName() getInternalName}).
+ * @param name
+ * the field's name.
+ * @param desc
+ * the field's descriptor (see {@link Type Type}).
+ */
+ public void visitFieldInsn(int opcode, String owner, String name,
+ String desc) {
+ if (mv != null) {
+ mv.visitFieldInsn(opcode, owner, name, desc);
+ }
+ }
+
+ /**
+ * Visits a method instruction. A method instruction is an instruction that
+ * invokes a method.
+ *
+ * @param opcode
+ * the opcode of the type instruction to be visited. This opcode
+ * is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or
+ * INVOKEINTERFACE.
+ * @param owner
+ * the internal name of the method's owner class (see
+ * {@link Type#getInternalName() getInternalName}).
+ * @param name
+ * the method's name.
+ * @param desc
+ * the method's descriptor (see {@link Type Type}).
+ */
+ @Deprecated
+ public void visitMethodInsn(int opcode, String owner, String name,
+ String desc) {
+ if (api >= Opcodes.ASM5) {
+ boolean itf = opcode == Opcodes.INVOKEINTERFACE;
+ visitMethodInsn(opcode, owner, name, desc, itf);
+ return;
+ }
+ if (mv != null) {
+ mv.visitMethodInsn(opcode, owner, name, desc);
+ }
+ }
+
+ /**
+ * Visits a method instruction. A method instruction is an instruction that
+ * invokes a method.
+ *
+ * @param opcode
+ * the opcode of the type instruction to be visited. This opcode
+ * is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or
+ * INVOKEINTERFACE.
+ * @param owner
+ * the internal name of the method's owner class (see
+ * {@link Type#getInternalName() getInternalName}).
+ * @param name
+ * the method's name.
+ * @param desc
+ * the method's descriptor (see {@link Type Type}).
+ * @param itf
+ * if the method's owner class is an interface.
+ */
+ public void visitMethodInsn(int opcode, String owner, String name,
+ String desc, boolean itf) {
+ if (api < Opcodes.ASM5) {
+ if (itf != (opcode == Opcodes.INVOKEINTERFACE)) {
+ throw new IllegalArgumentException(
+ "INVOKESPECIAL/STATIC on interfaces require ASM 5");
+ }
+ visitMethodInsn(opcode, owner, name, desc);
+ return;
+ }
+ if (mv != null) {
+ mv.visitMethodInsn(opcode, owner, name, desc, itf);
+ }
+ }
+
+ /**
+ * Visits an invokedynamic instruction.
+ *
+ * @param name
+ * the method's name.
+ * @param desc
+ * the method's descriptor (see {@link Type Type}).
+ * @param bsm
+ * the bootstrap method.
+ * @param bsmArgs
+ * the bootstrap method constant arguments. Each argument must be
+ * an {@link Integer}, {@link Float}, {@link Long},
+ * {@link Double}, {@link String}, {@link Type} or {@link Handle}
+ * value. This method is allowed to modify the content of the
+ * array so a caller should expect that this array may change.
+ */
+ public void visitInvokeDynamicInsn(String name, String desc, Handle bsm,
+ Object... bsmArgs) {
+ if (mv != null) {
+ mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
+ }
+ }
+
+ /**
+ * Visits a jump instruction. A jump instruction is an instruction that may
+ * jump to another instruction.
+ *
+ * @param opcode
+ * the opcode of the type instruction to be visited. This opcode
+ * is either IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ,
+ * IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE,
+ * IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL.
+ * @param label
+ * the operand of the instruction to be visited. This operand is
+ * a label that designates the instruction to which the jump
+ * instruction may jump.
+ */
+ public void visitJumpInsn(int opcode, Label label) {
+ if (mv != null) {
+ mv.visitJumpInsn(opcode, label);
+ }
+ }
+
+ /**
+ * Visits a label. A label designates the instruction that will be visited
+ * just after it.
+ *
+ * @param label
+ * a {@link Label Label} object.
+ */
+ public void visitLabel(Label label) {
+ if (mv != null) {
+ mv.visitLabel(label);
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Special instructions
+ // -------------------------------------------------------------------------
+
+ /**
+ * Visits a LDC instruction. Note that new constant types may be added in
+ * future versions of the Java Virtual Machine. To easily detect new
+ * constant types, implementations of this method should check for
+ * unexpected constant types, like this:
+ *
+ * <pre>
+ * if (cst instanceof Integer) {
+ * // ...
+ * } else if (cst instanceof Float) {
+ * // ...
+ * } else if (cst instanceof Long) {
+ * // ...
+ * } else if (cst instanceof Double) {
+ * // ...
+ * } else if (cst instanceof String) {
+ * // ...
+ * } else if (cst instanceof Type) {
+ * int sort = ((Type) cst).getSort();
+ * if (sort == Type.OBJECT) {
+ * // ...
+ * } else if (sort == Type.ARRAY) {
+ * // ...
+ * } else if (sort == Type.METHOD) {
+ * // ...
+ * } else {
+ * // throw an exception
+ * }
+ * } else if (cst instanceof Handle) {
+ * // ...
+ * } else {
+ * // throw an exception
+ * }
+ * </pre>
+ *
+ * @param cst
+ * the constant to be loaded on the stack. This parameter must be
+ * a non null {@link Integer}, a {@link Float}, a {@link Long}, a
+ * {@link Double}, a {@link String}, a {@link Type} of OBJECT or
+ * ARRAY sort for <tt>.class</tt> constants, for classes whose
+ * version is 49.0, a {@link Type} of METHOD sort or a
+ * {@link Handle} for MethodType and MethodHandle constants, for
+ * classes whose version is 51.0.
+ */
+ public void visitLdcInsn(Object cst) {
+ if (mv != null) {
+ mv.visitLdcInsn(cst);
+ }
+ }
+
+ /**
+ * Visits an IINC instruction.
+ *
+ * @param var
+ * index of the local variable to be incremented.
+ * @param increment
+ * amount to increment the local variable by.
+ */
+ public void visitIincInsn(int var, int increment) {
+ if (mv != null) {
+ mv.visitIincInsn(var, increment);
+ }
+ }
+
+ /**
+ * Visits a TABLESWITCH instruction.
+ *
+ * @param min
+ * the minimum key value.
+ * @param max
+ * the maximum key value.
+ * @param dflt
+ * beginning of the default handler block.
+ * @param labels
+ * beginnings of the handler blocks. <tt>labels[i]</tt> is the
+ * beginning of the handler block for the <tt>min + i</tt> key.
+ */
+ public void visitTableSwitchInsn(int min, int max, Label dflt,
+ Label... labels) {
+ if (mv != null) {
+ mv.visitTableSwitchInsn(min, max, dflt, labels);
+ }
+ }
+
+ /**
+ * Visits a LOOKUPSWITCH instruction.
+ *
+ * @param dflt
+ * beginning of the default handler block.
+ * @param keys
+ * the values of the keys.
+ * @param labels
+ * beginnings of the handler blocks. <tt>labels[i]</tt> is the
+ * beginning of the handler block for the <tt>keys[i]</tt> key.
+ */
+ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ if (mv != null) {
+ mv.visitLookupSwitchInsn(dflt, keys, labels);
+ }
+ }
+
+ /**
+ * Visits a MULTIANEWARRAY instruction.
+ *
+ * @param desc
+ * an array type descriptor (see {@link Type Type}).
+ * @param dims
+ * number of dimensions of the array to allocate.
+ */
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ if (mv != null) {
+ mv.visitMultiANewArrayInsn(desc, dims);
+ }
+ }
+
+ /**
+ * Visits an annotation on an instruction. This method must be called just
+ * <i>after</i> the annotated instruction. It can be called several times
+ * for the same instruction.
+ *
+ * @param typeRef
+ * a reference to the annotated type. The sort of this type
+ * reference must be {@link TypeReference#INSTANCEOF INSTANCEOF},
+ * {@link TypeReference#NEW NEW},
+ * {@link TypeReference#CONSTRUCTOR_REFERENCE
+ * CONSTRUCTOR_REFERENCE}, {@link TypeReference#METHOD_REFERENCE
+ * METHOD_REFERENCE}, {@link TypeReference#CAST CAST},
+ * {@link TypeReference#CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
+ * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT},
+ * {@link TypeReference#METHOD_INVOCATION_TYPE_ARGUMENT
+ * METHOD_INVOCATION_TYPE_ARGUMENT},
+ * {@link TypeReference#CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
+ * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or
+ * {@link TypeReference#METHOD_REFERENCE_TYPE_ARGUMENT
+ * METHOD_REFERENCE_TYPE_ARGUMENT}. See {@link TypeReference}.
+ * @param typePath
+ * the path to the annotated type argument, wildcard bound, array
+ * element type, or static inner type within 'typeRef'. May be
+ * <tt>null</tt> if the annotation targets 'typeRef' as a whole.
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * <tt>true</tt> if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or <tt>null</tt> if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitInsnAnnotation(int typeRef,
+ TypePath typePath, String desc, boolean visible) {
+ /* SPRING PATCH: REMOVED FOR COMPATIBILITY WITH CGLIB 3.1
+ if (api < Opcodes.ASM5) {
+ throw new RuntimeException();
+ }
+ */
+ if (mv != null) {
+ return mv.visitInsnAnnotation(typeRef, typePath, desc, visible);
+ }
+ return null;
+ }
+
+ // -------------------------------------------------------------------------
+ // Exceptions table entries, debug information, max stack and max locals
+ // -------------------------------------------------------------------------
+
+ /**
+ * Visits a try catch block.
+ *
+ * @param start
+ * beginning of the exception handler's scope (inclusive).
+ * @param end
+ * end of the exception handler's scope (exclusive).
+ * @param handler
+ * beginning of the exception handler's code.
+ * @param type
+ * internal name of the type of exceptions handled by the
+ * handler, or <tt>null</tt> to catch any exceptions (for
+ * "finally" blocks).
+ * @throws IllegalArgumentException
+ * if one of the labels has already been visited by this visitor
+ * (by the {@link #visitLabel visitLabel} method).
+ */
+ public void visitTryCatchBlock(Label start, Label end, Label handler,
+ String type) {
+ if (mv != null) {
+ mv.visitTryCatchBlock(start, end, handler, type);
+ }
+ }
+
+ /**
+ * Visits an annotation on an exception handler type. This method must be
+ * called <i>after</i> the {@link #visitTryCatchBlock} for the annotated
+ * exception handler. It can be called several times for the same exception
+ * handler.
+ *
+ * @param typeRef
+ * a reference to the annotated type. The sort of this type
+ * reference must be {@link TypeReference#EXCEPTION_PARAMETER
+ * EXCEPTION_PARAMETER}. See {@link TypeReference}.
+ * @param typePath
+ * the path to the annotated type argument, wildcard bound, array
+ * element type, or static inner type within 'typeRef'. May be
+ * <tt>null</tt> if the annotation targets 'typeRef' as a whole.
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * <tt>true</tt> if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or <tt>null</tt> if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitTryCatchAnnotation(int typeRef,
+ TypePath typePath, String desc, boolean visible) {
+ /* SPRING PATCH: REMOVED FOR COMPATIBILITY WITH CGLIB 3.1
+ if (api < Opcodes.ASM5) {
+ throw new RuntimeException();
+ }
+ */
+ if (mv != null) {
+ return mv.visitTryCatchAnnotation(typeRef, typePath, desc, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a local variable declaration.
+ *
+ * @param name
+ * the name of a local variable.
+ * @param desc
+ * the type descriptor of this local variable.
+ * @param signature
+ * the type signature of this local variable. May be
+ * <tt>null</tt> if the local variable type does not use generic
+ * types.
+ * @param start
+ * the first instruction corresponding to the scope of this local
+ * variable (inclusive).
+ * @param end
+ * the last instruction corresponding to the scope of this local
+ * variable (exclusive).
+ * @param index
+ * the local variable's index.
+ * @throws IllegalArgumentException
+ * if one of the labels has not already been visited by this
+ * visitor (by the {@link #visitLabel visitLabel} method).
+ */
+ public void visitLocalVariable(String name, String desc, String signature,
+ Label start, Label end, int index) {
+ if (mv != null) {
+ mv.visitLocalVariable(name, desc, signature, start, end, index);
+ }
+ }
+
+ /**
+ * Visits an annotation on a local variable type.
+ *
+ * @param typeRef
+ * a reference to the annotated type. The sort of this type
+ * reference must be {@link TypeReference#LOCAL_VARIABLE
+ * LOCAL_VARIABLE} or {@link TypeReference#RESOURCE_VARIABLE
+ * RESOURCE_VARIABLE}. See {@link TypeReference}.
+ * @param typePath
+ * the path to the annotated type argument, wildcard bound, array
+ * element type, or static inner type within 'typeRef'. May be
+ * <tt>null</tt> if the annotation targets 'typeRef' as a whole.
+ * @param start
+ * the fist instructions corresponding to the continuous ranges
+ * that make the scope of this local variable (inclusive).
+ * @param end
+ * the last instructions corresponding to the continuous ranges
+ * that make the scope of this local variable (exclusive). This
+ * array must have the same size as the 'start' array.
+ * @param index
+ * the local variable's index in each range. This array must have
+ * the same size as the 'start' array.
+ * @param desc
+ * the class descriptor of the annotation class.
+ * @param visible
+ * <tt>true</tt> if the annotation is visible at runtime.
+ * @return a visitor to visit the annotation values, or <tt>null</tt> if
+ * this visitor is not interested in visiting this annotation.
+ */
+ public AnnotationVisitor visitLocalVariableAnnotation(int typeRef,
+ TypePath typePath, Label[] start, Label[] end, int[] index,
+ String desc, boolean visible) {
+ /* SPRING PATCH: REMOVED FOR COMPATIBILITY WITH CGLIB 3.1
+ if (api < Opcodes.ASM5) {
+ throw new RuntimeException();
+ }
+ */
+ if (mv != null) {
+ return mv.visitLocalVariableAnnotation(typeRef, typePath, start,
+ end, index, desc, visible);
+ }
+ return null;
+ }
+
+ /**
+ * Visits a line number declaration.
+ *
+ * @param line
+ * a line number. This number refers to the source file from
+ * which the class was compiled.
+ * @param start
+ * the first instruction corresponding to this line number.
+ * @throws IllegalArgumentException
+ * if <tt>start</tt> has not already been visited by this
+ * visitor (by the {@link #visitLabel visitLabel} method).
+ */
+ public void visitLineNumber(int line, Label start) {
+ if (mv != null) {
+ mv.visitLineNumber(line, start);
+ }
+ }
+
+ /**
+ * Visits the maximum stack size and the maximum number of local variables
+ * of the method.
+ *
+ * @param maxStack
+ * maximum stack size of the method.
+ * @param maxLocals
+ * maximum number of local variables for the method.
+ */
+ public void visitMaxs(int maxStack, int maxLocals) {
+ if (mv != null) {
+ mv.visitMaxs(maxStack, maxLocals);
+ }
+ }
+
+ /**
+ * Visits the end of the method. This method, which is the last one to be
+ * called, is used to inform the visitor that all the annotations and
+ * attributes of the method have been visited.
+ */
+ public void visitEnd() {
+ if (mv != null) {
+ mv.visitEnd();
+ }
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/MethodWriter.java b/spring-core/src/main/java/org/springframework/asm/MethodWriter.java
new file mode 100644
index 00000000..0ba89282
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/MethodWriter.java
@@ -0,0 +1,2913 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.springframework.asm;
+
+/**
+ * A {@link MethodVisitor} that generates methods in bytecode form. Each visit
+ * method of this class appends the bytecode corresponding to the visited
+ * instruction to a byte vector, in the order these methods are called.
+ *
+ * @author Eric Bruneton
+ * @author Eugene Kuleshov
+ */
+class MethodWriter extends MethodVisitor {
+
+ /**
+ * Pseudo access flag used to denote constructors.
+ */
+ static final int ACC_CONSTRUCTOR = 0x80000;
+
+ /**
+ * Frame has exactly the same locals as the previous stack map frame and
+ * number of stack items is zero.
+ */
+ static final int SAME_FRAME = 0; // to 63 (0-3f)
+
+ /**
+ * Frame has exactly the same locals as the previous stack map frame and
+ * number of stack items is 1
+ */
+ static final int SAME_LOCALS_1_STACK_ITEM_FRAME = 64; // to 127 (40-7f)
+
+ /**
+ * Reserved for future use
+ */
+ static final int RESERVED = 128;
+
+ /**
+ * Frame has exactly the same locals as the previous stack map frame and
+ * number of stack items is 1. Offset is bigger then 63;
+ */
+ static final int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247; // f7
+
+ /**
+ * Frame where current locals are the same as the locals in the previous
+ * frame, except that the k last locals are absent. The value of k is given
+ * by the formula 251-frame_type.
+ */
+ static final int CHOP_FRAME = 248; // to 250 (f8-fA)
+
+ /**
+ * Frame has exactly the same locals as the previous stack map frame and
+ * number of stack items is zero. Offset is bigger then 63;
+ */
+ static final int SAME_FRAME_EXTENDED = 251; // fb
+
+ /**
+ * Frame where current locals are the same as the locals in the previous
+ * frame, except that k additional locals are defined. The value of k is
+ * given by the formula frame_type-251.
+ */
+ static final int APPEND_FRAME = 252; // to 254 // fc-fe
+
+ /**
+ * Full frame
+ */
+ static final int FULL_FRAME = 255; // ff
+
+ /**
+ * Indicates that the stack map frames must be recomputed from scratch. In
+ * this case the maximum stack size and number of local variables is also
+ * recomputed from scratch.
+ *
+ * @see #compute
+ */
+ private static final int FRAMES = 0;
+
+ /**
+ * Indicates that the maximum stack size and number of local variables must
+ * be automatically computed.
+ *
+ * @see #compute
+ */
+ private static final int MAXS = 1;
+
+ /**
+ * Indicates that nothing must be automatically computed.
+ *
+ * @see #compute
+ */
+ private static final int NOTHING = 2;
+
+ /**
+ * The class writer to which this method must be added.
+ */
+ final ClassWriter cw;
+
+ /**
+ * Access flags of this method.
+ */
+ private int access;
+
+ /**
+ * The index of the constant pool item that contains the name of this
+ * method.
+ */
+ private final int name;
+
+ /**
+ * The index of the constant pool item that contains the descriptor of this
+ * method.
+ */
+ private final int desc;
+
+ /**
+ * The descriptor of this method.
+ */
+ private final String descriptor;
+
+ /**
+ * The signature of this method.
+ */
+ String signature;
+
+ /**
+ * If not zero, indicates that the code of this method must be copied from
+ * the ClassReader associated to this writer in <code>cw.cr</code>. More
+ * precisely, this field gives the index of the first byte to copied from
+ * <code>cw.cr.b</code>.
+ */
+ int classReaderOffset;
+
+ /**
+ * If not zero, indicates that the code of this method must be copied from
+ * the ClassReader associated to this writer in <code>cw.cr</code>. More
+ * precisely, this field gives the number of bytes to copied from
+ * <code>cw.cr.b</code>.
+ */
+ int classReaderLength;
+
+ /**
+ * Number of exceptions that can be thrown by this method.
+ */
+ int exceptionCount;
+
+ /**
+ * The exceptions that can be thrown by this method. More precisely, this
+ * array contains the indexes of the constant pool items that contain the
+ * internal names of these exception classes.
+ */
+ int[] exceptions;
+
+ /**
+ * The annotation default attribute of this method. May be <tt>null</tt>.
+ */
+ private ByteVector annd;
+
+ /**
+ * The runtime visible annotations of this method. May be <tt>null</tt>.
+ */
+ private AnnotationWriter anns;
+
+ /**
+ * The runtime invisible annotations of this method. May be <tt>null</tt>.
+ */
+ private AnnotationWriter ianns;
+
+ /**
+ * The runtime visible type annotations of this method. May be <tt>null</tt>
+ * .
+ */
+ private AnnotationWriter tanns;
+
+ /**
+ * The runtime invisible type annotations of this method. May be
+ * <tt>null</tt>.
+ */
+ private AnnotationWriter itanns;
+
+ /**
+ * The runtime visible parameter annotations of this method. May be
+ * <tt>null</tt>.
+ */
+ private AnnotationWriter[] panns;
+
+ /**
+ * The runtime invisible parameter annotations of this method. May be
+ * <tt>null</tt>.
+ */
+ private AnnotationWriter[] ipanns;
+
+ /**
+ * The number of synthetic parameters of this method.
+ */
+ private int synthetics;
+
+ /**
+ * The non standard attributes of the method.
+ */
+ private Attribute attrs;
+
+ /**
+ * The bytecode of this method.
+ */
+ private ByteVector code = new ByteVector();
+
+ /**
+ * Maximum stack size of this method.
+ */
+ private int maxStack;
+
+ /**
+ * Maximum number of local variables for this method.
+ */
+ private int maxLocals;
+
+ /**
+ * Number of local variables in the current stack map frame.
+ */
+ private int currentLocals;
+
+ /**
+ * Number of stack map frames in the StackMapTable attribute.
+ */
+ private int frameCount;
+
+ /**
+ * The StackMapTable attribute.
+ */
+ private ByteVector stackMap;
+
+ /**
+ * The offset of the last frame that was written in the StackMapTable
+ * attribute.
+ */
+ private int previousFrameOffset;
+
+ /**
+ * The last frame that was written in the StackMapTable attribute.
+ *
+ * @see #frame
+ */
+ private int[] previousFrame;
+
+ /**
+ * The current stack map frame. The first element contains the offset of the
+ * instruction to which the frame corresponds, the second element is the
+ * number of locals and the third one is the number of stack elements. The
+ * local variables start at index 3 and are followed by the operand stack
+ * values. In summary frame[0] = offset, frame[1] = nLocal, frame[2] =
+ * nStack, frame[3] = nLocal. All types are encoded as integers, with the
+ * same format as the one used in {@link Label}, but limited to BASE types.
+ */
+ private int[] frame;
+
+ /**
+ * Number of elements in the exception handler list.
+ */
+ private int handlerCount;
+
+ /**
+ * The first element in the exception handler list.
+ */
+ private Handler firstHandler;
+
+ /**
+ * The last element in the exception handler list.
+ */
+ private Handler lastHandler;
+
+ /**
+ * Number of entries in the MethodParameters attribute.
+ */
+ private int methodParametersCount;
+
+ /**
+ * The MethodParameters attribute.
+ */
+ private ByteVector methodParameters;
+
+ /**
+ * Number of entries in the LocalVariableTable attribute.
+ */
+ private int localVarCount;
+
+ /**
+ * The LocalVariableTable attribute.
+ */
+ private ByteVector localVar;
+
+ /**
+ * Number of entries in the LocalVariableTypeTable attribute.
+ */
+ private int localVarTypeCount;
+
+ /**
+ * The LocalVariableTypeTable attribute.
+ */
+ private ByteVector localVarType;
+
+ /**
+ * Number of entries in the LineNumberTable attribute.
+ */
+ private int lineNumberCount;
+
+ /**
+ * The LineNumberTable attribute.
+ */
+ private ByteVector lineNumber;
+
+ /**
+ * The start offset of the last visited instruction.
+ */
+ private int lastCodeOffset;
+
+ /**
+ * The runtime visible type annotations of the code. May be <tt>null</tt>.
+ */
+ private AnnotationWriter ctanns;
+
+ /**
+ * The runtime invisible type annotations of the code. May be <tt>null</tt>.
+ */
+ private AnnotationWriter ictanns;
+
+ /**
+ * The non standard attributes of the method's code.
+ */
+ private Attribute cattrs;
+
+ /**
+ * Indicates if some jump instructions are too small and need to be resized.
+ */
+ private boolean resize;
+
+ /**
+ * The number of subroutines in this method.
+ */
+ private int subroutines;
+
+ // ------------------------------------------------------------------------
+
+ /*
+ * Fields for the control flow graph analysis algorithm (used to compute the
+ * maximum stack size). A control flow graph contains one node per "basic
+ * block", and one edge per "jump" from one basic block to another. Each
+ * node (i.e., each basic block) is represented by the Label object that
+ * corresponds to the first instruction of this basic block. Each node also
+ * stores the list of its successors in the graph, as a linked list of Edge
+ * objects.
+ */
+
+ /**
+ * Indicates what must be automatically computed.
+ *
+ * @see #FRAMES
+ * @see #MAXS
+ * @see #NOTHING
+ */
+ private final int compute;
+
+ /**
+ * A list of labels. This list is the list of basic blocks in the method,
+ * i.e. a list of Label objects linked to each other by their
+ * {@link Label#successor} field, in the order they are visited by
+ * {@link MethodVisitor#visitLabel}, and starting with the first basic
+ * block.
+ */
+ private Label labels;
+
+ /**
+ * The previous basic block.
+ */
+ private Label previousBlock;
+
+ /**
+ * The current basic block.
+ */
+ private Label currentBlock;
+
+ /**
+ * The (relative) stack size after the last visited instruction. This size
+ * is relative to the beginning of the current basic block, i.e., the true
+ * stack size after the last visited instruction is equal to the
+ * {@link Label#inputStackTop beginStackSize} of the current basic block
+ * plus <tt>stackSize</tt>.
+ */
+ private int stackSize;
+
+ /**
+ * The (relative) maximum stack size after the last visited instruction.
+ * This size is relative to the beginning of the current basic block, i.e.,
+ * the true maximum stack size after the last visited instruction is equal
+ * to the {@link Label#inputStackTop beginStackSize} of the current basic
+ * block plus <tt>stackSize</tt>.
+ */
+ private int maxStackSize;
+
+ // ------------------------------------------------------------------------
+ // Constructor
+ // ------------------------------------------------------------------------
+
+ /**
+ * Constructs a new {@link MethodWriter}.
+ *
+ * @param cw
+ * the class writer in which the method must be added.
+ * @param access
+ * the method's access flags (see {@link Opcodes}).
+ * @param name
+ * the method's name.
+ * @param desc
+ * the method's descriptor (see {@link Type}).
+ * @param signature
+ * the method's signature. May be <tt>null</tt>.
+ * @param exceptions
+ * the internal names of the method's exceptions. May be
+ * <tt>null</tt>.
+ * @param computeMaxs
+ * <tt>true</tt> if the maximum stack size and number of local
+ * variables must be automatically computed.
+ * @param computeFrames
+ * <tt>true</tt> if the stack map tables must be recomputed from
+ * scratch.
+ */
+ MethodWriter(final ClassWriter cw, final int access, final String name,
+ final String desc, final String signature,
+ final String[] exceptions, final boolean computeMaxs,
+ final boolean computeFrames) {
+ super(Opcodes.ASM5);
+ if (cw.firstMethod == null) {
+ cw.firstMethod = this;
+ } else {
+ cw.lastMethod.mv = this;
+ }
+ cw.lastMethod = this;
+ this.cw = cw;
+ this.access = access;
+ if ("<init>".equals(name)) {
+ this.access |= ACC_CONSTRUCTOR;
+ }
+ this.name = cw.newUTF8(name);
+ this.desc = cw.newUTF8(desc);
+ this.descriptor = desc;
+ if (ClassReader.SIGNATURES) {
+ this.signature = signature;
+ }
+ if (exceptions != null && exceptions.length > 0) {
+ exceptionCount = exceptions.length;
+ this.exceptions = new int[exceptionCount];
+ for (int i = 0; i < exceptionCount; ++i) {
+ this.exceptions[i] = cw.newClass(exceptions[i]);
+ }
+ }
+ this.compute = computeFrames ? FRAMES : (computeMaxs ? MAXS : NOTHING);
+ if (computeMaxs || computeFrames) {
+ // updates maxLocals
+ int size = Type.getArgumentsAndReturnSizes(descriptor) >> 2;
+ if ((access & Opcodes.ACC_STATIC) != 0) {
+ --size;
+ }
+ maxLocals = size;
+ currentLocals = size;
+ // creates and visits the label for the first basic block
+ labels = new Label();
+ labels.status |= Label.PUSHED;
+ visitLabel(labels);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Implementation of the MethodVisitor abstract class
+ // ------------------------------------------------------------------------
+
+ @Override
+ public void visitParameter(String name, int access) {
+ if (methodParameters == null) {
+ methodParameters = new ByteVector();
+ }
+ ++methodParametersCount;
+ methodParameters.putShort((name == null) ? 0 : cw.newUTF8(name))
+ .putShort(access);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotationDefault() {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ annd = new ByteVector();
+ return new AnnotationWriter(cw, false, annd, null, 0);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(final String desc,
+ final boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ // write type, and reserve space for values count
+ bv.putShort(cw.newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2);
+ if (visible) {
+ aw.next = anns;
+ anns = aw;
+ } else {
+ aw.next = ianns;
+ ianns = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(final int typeRef,
+ final TypePath typePath, final String desc, final boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ // write target_type and target_info
+ AnnotationWriter.putTarget(typeRef, typePath, bv);
+ // write type, and reserve space for values count
+ bv.putShort(cw.newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv,
+ bv.length - 2);
+ if (visible) {
+ aw.next = tanns;
+ tanns = aw;
+ } else {
+ aw.next = itanns;
+ itanns = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public AnnotationVisitor visitParameterAnnotation(final int parameter,
+ final String desc, final boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ if ("Ljava/lang/Synthetic;".equals(desc)) {
+ // workaround for a bug in javac with synthetic parameters
+ // see ClassReader.readParameterAnnotations
+ synthetics = Math.max(synthetics, parameter + 1);
+ return new AnnotationWriter(cw, false, bv, null, 0);
+ }
+ // write type, and reserve space for values count
+ bv.putShort(cw.newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv, 2);
+ if (visible) {
+ if (panns == null) {
+ panns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length];
+ }
+ aw.next = panns[parameter];
+ panns[parameter] = aw;
+ } else {
+ if (ipanns == null) {
+ ipanns = new AnnotationWriter[Type.getArgumentTypes(descriptor).length];
+ }
+ aw.next = ipanns[parameter];
+ ipanns[parameter] = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public void visitAttribute(final Attribute attr) {
+ if (attr.isCodeAttribute()) {
+ attr.next = cattrs;
+ cattrs = attr;
+ } else {
+ attr.next = attrs;
+ attrs = attr;
+ }
+ }
+
+ @Override
+ public void visitCode() {
+ }
+
+ @Override
+ public void visitFrame(final int type, final int nLocal,
+ final Object[] local, final int nStack, final Object[] stack) {
+ if (!ClassReader.FRAMES || compute == FRAMES) {
+ return;
+ }
+
+ if (type == Opcodes.F_NEW) {
+ if (previousFrame == null) {
+ visitImplicitFirstFrame();
+ }
+ currentLocals = nLocal;
+ int frameIndex = startFrame(code.length, nLocal, nStack);
+ for (int i = 0; i < nLocal; ++i) {
+ if (local[i] instanceof String) {
+ frame[frameIndex++] = Frame.OBJECT
+ | cw.addType((String) local[i]);
+ } else if (local[i] instanceof Integer) {
+ frame[frameIndex++] = ((Integer) local[i]).intValue();
+ } else {
+ frame[frameIndex++] = Frame.UNINITIALIZED
+ | cw.addUninitializedType("",
+ ((Label) local[i]).position);
+ }
+ }
+ for (int i = 0; i < nStack; ++i) {
+ if (stack[i] instanceof String) {
+ frame[frameIndex++] = Frame.OBJECT
+ | cw.addType((String) stack[i]);
+ } else if (stack[i] instanceof Integer) {
+ frame[frameIndex++] = ((Integer) stack[i]).intValue();
+ } else {
+ frame[frameIndex++] = Frame.UNINITIALIZED
+ | cw.addUninitializedType("",
+ ((Label) stack[i]).position);
+ }
+ }
+ endFrame();
+ } else {
+ int delta;
+ if (stackMap == null) {
+ stackMap = new ByteVector();
+ delta = code.length;
+ } else {
+ delta = code.length - previousFrameOffset - 1;
+ if (delta < 0) {
+ if (type == Opcodes.F_SAME) {
+ return;
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+ }
+
+ switch (type) {
+ case Opcodes.F_FULL:
+ currentLocals = nLocal;
+ stackMap.putByte(FULL_FRAME).putShort(delta).putShort(nLocal);
+ for (int i = 0; i < nLocal; ++i) {
+ writeFrameType(local[i]);
+ }
+ stackMap.putShort(nStack);
+ for (int i = 0; i < nStack; ++i) {
+ writeFrameType(stack[i]);
+ }
+ break;
+ case Opcodes.F_APPEND:
+ currentLocals += nLocal;
+ stackMap.putByte(SAME_FRAME_EXTENDED + nLocal).putShort(delta);
+ for (int i = 0; i < nLocal; ++i) {
+ writeFrameType(local[i]);
+ }
+ break;
+ case Opcodes.F_CHOP:
+ currentLocals -= nLocal;
+ stackMap.putByte(SAME_FRAME_EXTENDED - nLocal).putShort(delta);
+ break;
+ case Opcodes.F_SAME:
+ if (delta < 64) {
+ stackMap.putByte(delta);
+ } else {
+ stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta);
+ }
+ break;
+ case Opcodes.F_SAME1:
+ if (delta < 64) {
+ stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta);
+ } else {
+ stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED)
+ .putShort(delta);
+ }
+ writeFrameType(stack[0]);
+ break;
+ }
+
+ previousFrameOffset = code.length;
+ ++frameCount;
+ }
+
+ maxStack = Math.max(maxStack, nStack);
+ maxLocals = Math.max(maxLocals, currentLocals);
+ }
+
+ @Override
+ public void visitInsn(final int opcode) {
+ lastCodeOffset = code.length;
+ // adds the instruction to the bytecode of the method
+ code.putByte(opcode);
+ // update currentBlock
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(opcode, 0, null, null);
+ } else {
+ // updates current and max stack sizes
+ int size = stackSize + Frame.SIZE[opcode];
+ if (size > maxStackSize) {
+ maxStackSize = size;
+ }
+ stackSize = size;
+ }
+ // if opcode == ATHROW or xRETURN, ends current block (no successor)
+ if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)
+ || opcode == Opcodes.ATHROW) {
+ noSuccessor();
+ }
+ }
+ }
+
+ @Override
+ public void visitIntInsn(final int opcode, final int operand) {
+ lastCodeOffset = code.length;
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(opcode, operand, null, null);
+ } else if (opcode != Opcodes.NEWARRAY) {
+ // updates current and max stack sizes only for NEWARRAY
+ // (stack size variation = 0 for BIPUSH or SIPUSH)
+ int size = stackSize + 1;
+ if (size > maxStackSize) {
+ maxStackSize = size;
+ }
+ stackSize = size;
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ if (opcode == Opcodes.SIPUSH) {
+ code.put12(opcode, operand);
+ } else { // BIPUSH or NEWARRAY
+ code.put11(opcode, operand);
+ }
+ }
+
+ @Override
+ public void visitVarInsn(final int opcode, final int var) {
+ lastCodeOffset = code.length;
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(opcode, var, null, null);
+ } else {
+ // updates current and max stack sizes
+ if (opcode == Opcodes.RET) {
+ // no stack change, but end of current block (no successor)
+ currentBlock.status |= Label.RET;
+ // save 'stackSize' here for future use
+ // (see {@link #findSubroutineSuccessors})
+ currentBlock.inputStackTop = stackSize;
+ noSuccessor();
+ } else { // xLOAD or xSTORE
+ int size = stackSize + Frame.SIZE[opcode];
+ if (size > maxStackSize) {
+ maxStackSize = size;
+ }
+ stackSize = size;
+ }
+ }
+ }
+ if (compute != NOTHING) {
+ // updates max locals
+ int n;
+ if (opcode == Opcodes.LLOAD || opcode == Opcodes.DLOAD
+ || opcode == Opcodes.LSTORE || opcode == Opcodes.DSTORE) {
+ n = var + 2;
+ } else {
+ n = var + 1;
+ }
+ if (n > maxLocals) {
+ maxLocals = n;
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ if (var < 4 && opcode != Opcodes.RET) {
+ int opt;
+ if (opcode < Opcodes.ISTORE) {
+ /* ILOAD_0 */
+ opt = 26 + ((opcode - Opcodes.ILOAD) << 2) + var;
+ } else {
+ /* ISTORE_0 */
+ opt = 59 + ((opcode - Opcodes.ISTORE) << 2) + var;
+ }
+ code.putByte(opt);
+ } else if (var >= 256) {
+ code.putByte(196 /* WIDE */).put12(opcode, var);
+ } else {
+ code.put11(opcode, var);
+ }
+ if (opcode >= Opcodes.ISTORE && compute == FRAMES && handlerCount > 0) {
+ visitLabel(new Label());
+ }
+ }
+
+ @Override
+ public void visitTypeInsn(final int opcode, final String type) {
+ lastCodeOffset = code.length;
+ Item i = cw.newClassItem(type);
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(opcode, code.length, cw, i);
+ } else if (opcode == Opcodes.NEW) {
+ // updates current and max stack sizes only if opcode == NEW
+ // (no stack change for ANEWARRAY, CHECKCAST, INSTANCEOF)
+ int size = stackSize + 1;
+ if (size > maxStackSize) {
+ maxStackSize = size;
+ }
+ stackSize = size;
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ code.put12(opcode, i.index);
+ }
+
+ @Override
+ public void visitFieldInsn(final int opcode, final String owner,
+ final String name, final String desc) {
+ lastCodeOffset = code.length;
+ Item i = cw.newFieldItem(owner, name, desc);
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(opcode, 0, cw, i);
+ } else {
+ int size;
+ // computes the stack size variation
+ char c = desc.charAt(0);
+ switch (opcode) {
+ case Opcodes.GETSTATIC:
+ size = stackSize + (c == 'D' || c == 'J' ? 2 : 1);
+ break;
+ case Opcodes.PUTSTATIC:
+ size = stackSize + (c == 'D' || c == 'J' ? -2 : -1);
+ break;
+ case Opcodes.GETFIELD:
+ size = stackSize + (c == 'D' || c == 'J' ? 1 : 0);
+ break;
+ // case Constants.PUTFIELD:
+ default:
+ size = stackSize + (c == 'D' || c == 'J' ? -3 : -2);
+ break;
+ }
+ // updates current and max stack sizes
+ if (size > maxStackSize) {
+ maxStackSize = size;
+ }
+ stackSize = size;
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ code.put12(opcode, i.index);
+ }
+
+ @Override
+ public void visitMethodInsn(final int opcode, final String owner,
+ final String name, final String desc, final boolean itf) {
+ lastCodeOffset = code.length;
+ Item i = cw.newMethodItem(owner, name, desc, itf);
+ int argSize = i.intVal;
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(opcode, 0, cw, i);
+ } else {
+ /*
+ * computes the stack size variation. In order not to recompute
+ * several times this variation for the same Item, we use the
+ * intVal field of this item to store this variation, once it
+ * has been computed. More precisely this intVal field stores
+ * the sizes of the arguments and of the return value
+ * corresponding to desc.
+ */
+ if (argSize == 0) {
+ // the above sizes have not been computed yet,
+ // so we compute them...
+ argSize = Type.getArgumentsAndReturnSizes(desc);
+ // ... and we save them in order
+ // not to recompute them in the future
+ i.intVal = argSize;
+ }
+ int size;
+ if (opcode == Opcodes.INVOKESTATIC) {
+ size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1;
+ } else {
+ size = stackSize - (argSize >> 2) + (argSize & 0x03);
+ }
+ // updates current and max stack sizes
+ if (size > maxStackSize) {
+ maxStackSize = size;
+ }
+ stackSize = size;
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ if (opcode == Opcodes.INVOKEINTERFACE) {
+ if (argSize == 0) {
+ argSize = Type.getArgumentsAndReturnSizes(desc);
+ i.intVal = argSize;
+ }
+ code.put12(Opcodes.INVOKEINTERFACE, i.index).put11(argSize >> 2, 0);
+ } else {
+ code.put12(opcode, i.index);
+ }
+ }
+
+ @Override
+ public void visitInvokeDynamicInsn(final String name, final String desc,
+ final Handle bsm, final Object... bsmArgs) {
+ lastCodeOffset = code.length;
+ Item i = cw.newInvokeDynamicItem(name, desc, bsm, bsmArgs);
+ int argSize = i.intVal;
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(Opcodes.INVOKEDYNAMIC, 0, cw, i);
+ } else {
+ /*
+ * computes the stack size variation. In order not to recompute
+ * several times this variation for the same Item, we use the
+ * intVal field of this item to store this variation, once it
+ * has been computed. More precisely this intVal field stores
+ * the sizes of the arguments and of the return value
+ * corresponding to desc.
+ */
+ if (argSize == 0) {
+ // the above sizes have not been computed yet,
+ // so we compute them...
+ argSize = Type.getArgumentsAndReturnSizes(desc);
+ // ... and we save them in order
+ // not to recompute them in the future
+ i.intVal = argSize;
+ }
+ int size = stackSize - (argSize >> 2) + (argSize & 0x03) + 1;
+
+ // updates current and max stack sizes
+ if (size > maxStackSize) {
+ maxStackSize = size;
+ }
+ stackSize = size;
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ code.put12(Opcodes.INVOKEDYNAMIC, i.index);
+ code.putShort(0);
+ }
+
+ @Override
+ public void visitJumpInsn(final int opcode, final Label label) {
+ lastCodeOffset = code.length;
+ Label nextInsn = null;
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(opcode, 0, null, null);
+ // 'label' is the target of a jump instruction
+ label.getFirst().status |= Label.TARGET;
+ // adds 'label' as a successor of this basic block
+ addSuccessor(Edge.NORMAL, label);
+ if (opcode != Opcodes.GOTO) {
+ // creates a Label for the next basic block
+ nextInsn = new Label();
+ }
+ } else {
+ if (opcode == Opcodes.JSR) {
+ if ((label.status & Label.SUBROUTINE) == 0) {
+ label.status |= Label.SUBROUTINE;
+ ++subroutines;
+ }
+ currentBlock.status |= Label.JSR;
+ addSuccessor(stackSize + 1, label);
+ // creates a Label for the next basic block
+ nextInsn = new Label();
+ /*
+ * note that, by construction in this method, a JSR block
+ * has at least two successors in the control flow graph:
+ * the first one leads the next instruction after the JSR,
+ * while the second one leads to the JSR target.
+ */
+ } else {
+ // updates current stack size (max stack size unchanged
+ // because stack size variation always negative in this
+ // case)
+ stackSize += Frame.SIZE[opcode];
+ addSuccessor(stackSize, label);
+ }
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ if ((label.status & Label.RESOLVED) != 0
+ && label.position - code.length < Short.MIN_VALUE) {
+ /*
+ * case of a backward jump with an offset < -32768. In this case we
+ * automatically replace GOTO with GOTO_W, JSR with JSR_W and IFxxx
+ * <l> with IFNOTxxx <l'> GOTO_W <l>, where IFNOTxxx is the
+ * "opposite" opcode of IFxxx (i.e., IFNE for IFEQ) and where <l'>
+ * designates the instruction just after the GOTO_W.
+ */
+ if (opcode == Opcodes.GOTO) {
+ code.putByte(200); // GOTO_W
+ } else if (opcode == Opcodes.JSR) {
+ code.putByte(201); // JSR_W
+ } else {
+ // if the IF instruction is transformed into IFNOT GOTO_W the
+ // next instruction becomes the target of the IFNOT instruction
+ if (nextInsn != null) {
+ nextInsn.status |= Label.TARGET;
+ }
+ code.putByte(opcode <= 166 ? ((opcode + 1) ^ 1) - 1
+ : opcode ^ 1);
+ code.putShort(8); // jump offset
+ code.putByte(200); // GOTO_W
+ }
+ label.put(this, code, code.length - 1, true);
+ } else {
+ /*
+ * case of a backward jump with an offset >= -32768, or of a forward
+ * jump with, of course, an unknown offset. In these cases we store
+ * the offset in 2 bytes (which will be increased in
+ * resizeInstructions, if needed).
+ */
+ code.putByte(opcode);
+ label.put(this, code, code.length - 1, false);
+ }
+ if (currentBlock != null) {
+ if (nextInsn != null) {
+ // if the jump instruction is not a GOTO, the next instruction
+ // is also a successor of this instruction. Calling visitLabel
+ // adds the label of this next instruction as a successor of the
+ // current block, and starts a new basic block
+ visitLabel(nextInsn);
+ }
+ if (opcode == Opcodes.GOTO) {
+ noSuccessor();
+ }
+ }
+ }
+
+ @Override
+ public void visitLabel(final Label label) {
+ // resolves previous forward references to label, if any
+ resize |= label.resolve(this, code.length, code.data);
+ // updates currentBlock
+ if ((label.status & Label.DEBUG) != 0) {
+ return;
+ }
+ if (compute == FRAMES) {
+ if (currentBlock != null) {
+ if (label.position == currentBlock.position) {
+ // successive labels, do not start a new basic block
+ currentBlock.status |= (label.status & Label.TARGET);
+ label.frame = currentBlock.frame;
+ return;
+ }
+ // ends current block (with one new successor)
+ addSuccessor(Edge.NORMAL, label);
+ }
+ // begins a new current block
+ currentBlock = label;
+ if (label.frame == null) {
+ label.frame = new Frame();
+ label.frame.owner = label;
+ }
+ // updates the basic block list
+ if (previousBlock != null) {
+ if (label.position == previousBlock.position) {
+ previousBlock.status |= (label.status & Label.TARGET);
+ label.frame = previousBlock.frame;
+ currentBlock = previousBlock;
+ return;
+ }
+ previousBlock.successor = label;
+ }
+ previousBlock = label;
+ } else if (compute == MAXS) {
+ if (currentBlock != null) {
+ // ends current block (with one new successor)
+ currentBlock.outputStackMax = maxStackSize;
+ addSuccessor(stackSize, label);
+ }
+ // begins a new current block
+ currentBlock = label;
+ // resets the relative current and max stack sizes
+ stackSize = 0;
+ maxStackSize = 0;
+ // updates the basic block list
+ if (previousBlock != null) {
+ previousBlock.successor = label;
+ }
+ previousBlock = label;
+ }
+ }
+
+ @Override
+ public void visitLdcInsn(final Object cst) {
+ lastCodeOffset = code.length;
+ Item i = cw.newConstItem(cst);
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(Opcodes.LDC, 0, cw, i);
+ } else {
+ int size;
+ // computes the stack size variation
+ if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) {
+ size = stackSize + 2;
+ } else {
+ size = stackSize + 1;
+ }
+ // updates current and max stack sizes
+ if (size > maxStackSize) {
+ maxStackSize = size;
+ }
+ stackSize = size;
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ int index = i.index;
+ if (i.type == ClassWriter.LONG || i.type == ClassWriter.DOUBLE) {
+ code.put12(20 /* LDC2_W */, index);
+ } else if (index >= 256) {
+ code.put12(19 /* LDC_W */, index);
+ } else {
+ code.put11(Opcodes.LDC, index);
+ }
+ }
+
+ @Override
+ public void visitIincInsn(final int var, final int increment) {
+ lastCodeOffset = code.length;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(Opcodes.IINC, var, null, null);
+ }
+ }
+ if (compute != NOTHING) {
+ // updates max locals
+ int n = var + 1;
+ if (n > maxLocals) {
+ maxLocals = n;
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ if ((var > 255) || (increment > 127) || (increment < -128)) {
+ code.putByte(196 /* WIDE */).put12(Opcodes.IINC, var)
+ .putShort(increment);
+ } else {
+ code.putByte(Opcodes.IINC).put11(var, increment);
+ }
+ }
+
+ @Override
+ public void visitTableSwitchInsn(final int min, final int max,
+ final Label dflt, final Label... labels) {
+ lastCodeOffset = code.length;
+ // adds the instruction to the bytecode of the method
+ int source = code.length;
+ code.putByte(Opcodes.TABLESWITCH);
+ code.putByteArray(null, 0, (4 - code.length % 4) % 4);
+ dflt.put(this, code, source, true);
+ code.putInt(min).putInt(max);
+ for (int i = 0; i < labels.length; ++i) {
+ labels[i].put(this, code, source, true);
+ }
+ // updates currentBlock
+ visitSwitchInsn(dflt, labels);
+ }
+
+ @Override
+ public void visitLookupSwitchInsn(final Label dflt, final int[] keys,
+ final Label[] labels) {
+ lastCodeOffset = code.length;
+ // adds the instruction to the bytecode of the method
+ int source = code.length;
+ code.putByte(Opcodes.LOOKUPSWITCH);
+ code.putByteArray(null, 0, (4 - code.length % 4) % 4);
+ dflt.put(this, code, source, true);
+ code.putInt(labels.length);
+ for (int i = 0; i < labels.length; ++i) {
+ code.putInt(keys[i]);
+ labels[i].put(this, code, source, true);
+ }
+ // updates currentBlock
+ visitSwitchInsn(dflt, labels);
+ }
+
+ private void visitSwitchInsn(final Label dflt, final Label[] labels) {
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(Opcodes.LOOKUPSWITCH, 0, null, null);
+ // adds current block successors
+ addSuccessor(Edge.NORMAL, dflt);
+ dflt.getFirst().status |= Label.TARGET;
+ for (int i = 0; i < labels.length; ++i) {
+ addSuccessor(Edge.NORMAL, labels[i]);
+ labels[i].getFirst().status |= Label.TARGET;
+ }
+ } else {
+ // updates current stack size (max stack size unchanged)
+ --stackSize;
+ // adds current block successors
+ addSuccessor(stackSize, dflt);
+ for (int i = 0; i < labels.length; ++i) {
+ addSuccessor(stackSize, labels[i]);
+ }
+ }
+ // ends current block
+ noSuccessor();
+ }
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(final String desc, final int dims) {
+ lastCodeOffset = code.length;
+ Item i = cw.newClassItem(desc);
+ // Label currentBlock = this.currentBlock;
+ if (currentBlock != null) {
+ if (compute == FRAMES) {
+ currentBlock.frame.execute(Opcodes.MULTIANEWARRAY, dims, cw, i);
+ } else {
+ // updates current stack size (max stack size unchanged because
+ // stack size variation always negative or null)
+ stackSize += 1 - dims;
+ }
+ }
+ // adds the instruction to the bytecode of the method
+ code.put12(Opcodes.MULTIANEWARRAY, i.index).putByte(dims);
+ }
+
+ @Override
+ public AnnotationVisitor visitInsnAnnotation(int typeRef,
+ TypePath typePath, String desc, boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ // write target_type and target_info
+ typeRef = (typeRef & 0xFF0000FF) | (lastCodeOffset << 8);
+ AnnotationWriter.putTarget(typeRef, typePath, bv);
+ // write type, and reserve space for values count
+ bv.putShort(cw.newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv,
+ bv.length - 2);
+ if (visible) {
+ aw.next = ctanns;
+ ctanns = aw;
+ } else {
+ aw.next = ictanns;
+ ictanns = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public void visitTryCatchBlock(final Label start, final Label end,
+ final Label handler, final String type) {
+ ++handlerCount;
+ Handler h = new Handler();
+ h.start = start;
+ h.end = end;
+ h.handler = handler;
+ h.desc = type;
+ h.type = type != null ? cw.newClass(type) : 0;
+ if (lastHandler == null) {
+ firstHandler = h;
+ } else {
+ lastHandler.next = h;
+ }
+ lastHandler = h;
+ }
+
+ @Override
+ public AnnotationVisitor visitTryCatchAnnotation(int typeRef,
+ TypePath typePath, String desc, boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ // write target_type and target_info
+ AnnotationWriter.putTarget(typeRef, typePath, bv);
+ // write type, and reserve space for values count
+ bv.putShort(cw.newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv,
+ bv.length - 2);
+ if (visible) {
+ aw.next = ctanns;
+ ctanns = aw;
+ } else {
+ aw.next = ictanns;
+ ictanns = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public void visitLocalVariable(final String name, final String desc,
+ final String signature, final Label start, final Label end,
+ final int index) {
+ if (signature != null) {
+ if (localVarType == null) {
+ localVarType = new ByteVector();
+ }
+ ++localVarTypeCount;
+ localVarType.putShort(start.position)
+ .putShort(end.position - start.position)
+ .putShort(cw.newUTF8(name)).putShort(cw.newUTF8(signature))
+ .putShort(index);
+ }
+ if (localVar == null) {
+ localVar = new ByteVector();
+ }
+ ++localVarCount;
+ localVar.putShort(start.position)
+ .putShort(end.position - start.position)
+ .putShort(cw.newUTF8(name)).putShort(cw.newUTF8(desc))
+ .putShort(index);
+ if (compute != NOTHING) {
+ // updates max locals
+ char c = desc.charAt(0);
+ int n = index + (c == 'J' || c == 'D' ? 2 : 1);
+ if (n > maxLocals) {
+ maxLocals = n;
+ }
+ }
+ }
+
+ @Override
+ public AnnotationVisitor visitLocalVariableAnnotation(int typeRef,
+ TypePath typePath, Label[] start, Label[] end, int[] index,
+ String desc, boolean visible) {
+ if (!ClassReader.ANNOTATIONS) {
+ return null;
+ }
+ ByteVector bv = new ByteVector();
+ // write target_type and target_info
+ bv.putByte(typeRef >>> 24).putShort(start.length);
+ for (int i = 0; i < start.length; ++i) {
+ bv.putShort(start[i].position)
+ .putShort(end[i].position - start[i].position)
+ .putShort(index[i]);
+ }
+ if (typePath == null) {
+ bv.putByte(0);
+ } else {
+ int length = typePath.b[typePath.offset] * 2 + 1;
+ bv.putByteArray(typePath.b, typePath.offset, length);
+ }
+ // write type, and reserve space for values count
+ bv.putShort(cw.newUTF8(desc)).putShort(0);
+ AnnotationWriter aw = new AnnotationWriter(cw, true, bv, bv,
+ bv.length - 2);
+ if (visible) {
+ aw.next = ctanns;
+ ctanns = aw;
+ } else {
+ aw.next = ictanns;
+ ictanns = aw;
+ }
+ return aw;
+ }
+
+ @Override
+ public void visitLineNumber(final int line, final Label start) {
+ if (lineNumber == null) {
+ lineNumber = new ByteVector();
+ }
+ ++lineNumberCount;
+ lineNumber.putShort(start.position);
+ lineNumber.putShort(line);
+ }
+
+ @Override
+ public void visitMaxs(final int maxStack, final int maxLocals) {
+ if (resize) {
+ // replaces the temporary jump opcodes introduced by Label.resolve.
+ if (ClassReader.RESIZE) {
+ resizeInstructions();
+ } else {
+ throw new RuntimeException("Method code too large!");
+ }
+ }
+ if (ClassReader.FRAMES && compute == FRAMES) {
+ // completes the control flow graph with exception handler blocks
+ Handler handler = firstHandler;
+ while (handler != null) {
+ Label l = handler.start.getFirst();
+ Label h = handler.handler.getFirst();
+ Label e = handler.end.getFirst();
+ // computes the kind of the edges to 'h'
+ String t = handler.desc == null ? "java/lang/Throwable"
+ : handler.desc;
+ int kind = Frame.OBJECT | cw.addType(t);
+ // h is an exception handler
+ h.status |= Label.TARGET;
+ // adds 'h' as a successor of labels between 'start' and 'end'
+ while (l != e) {
+ // creates an edge to 'h'
+ Edge b = new Edge();
+ b.info = kind;
+ b.successor = h;
+ // adds it to the successors of 'l'
+ b.next = l.successors;
+ l.successors = b;
+ // goes to the next label
+ l = l.successor;
+ }
+ handler = handler.next;
+ }
+
+ // creates and visits the first (implicit) frame
+ Frame f = labels.frame;
+ Type[] args = Type.getArgumentTypes(descriptor);
+ f.initInputFrame(cw, access, args, this.maxLocals);
+ visitFrame(f);
+
+ /*
+ * fix point algorithm: mark the first basic block as 'changed'
+ * (i.e. put it in the 'changed' list) and, while there are changed
+ * basic blocks, choose one, mark it as unchanged, and update its
+ * successors (which can be changed in the process).
+ */
+ int max = 0;
+ Label changed = labels;
+ while (changed != null) {
+ // removes a basic block from the list of changed basic blocks
+ Label l = changed;
+ changed = changed.next;
+ l.next = null;
+ f = l.frame;
+ // a reachable jump target must be stored in the stack map
+ if ((l.status & Label.TARGET) != 0) {
+ l.status |= Label.STORE;
+ }
+ // all visited labels are reachable, by definition
+ l.status |= Label.REACHABLE;
+ // updates the (absolute) maximum stack size
+ int blockMax = f.inputStack.length + l.outputStackMax;
+ if (blockMax > max) {
+ max = blockMax;
+ }
+ // updates the successors of the current basic block
+ Edge e = l.successors;
+ while (e != null) {
+ Label n = e.successor.getFirst();
+ boolean change = f.merge(cw, n.frame, e.info);
+ if (change && n.next == null) {
+ // if n has changed and is not already in the 'changed'
+ // list, adds it to this list
+ n.next = changed;
+ changed = n;
+ }
+ e = e.next;
+ }
+ }
+
+ // visits all the frames that must be stored in the stack map
+ Label l = labels;
+ while (l != null) {
+ f = l.frame;
+ if ((l.status & Label.STORE) != 0) {
+ visitFrame(f);
+ }
+ if ((l.status & Label.REACHABLE) == 0) {
+ // finds start and end of dead basic block
+ Label k = l.successor;
+ int start = l.position;
+ int end = (k == null ? code.length : k.position) - 1;
+ // if non empty basic block
+ if (end >= start) {
+ max = Math.max(max, 1);
+ // replaces instructions with NOP ... NOP ATHROW
+ for (int i = start; i < end; ++i) {
+ code.data[i] = Opcodes.NOP;
+ }
+ code.data[end] = (byte) Opcodes.ATHROW;
+ // emits a frame for this unreachable block
+ int frameIndex = startFrame(start, 0, 1);
+ frame[frameIndex] = Frame.OBJECT
+ | cw.addType("java/lang/Throwable");
+ endFrame();
+ // removes the start-end range from the exception
+ // handlers
+ firstHandler = Handler.remove(firstHandler, l, k);
+ }
+ }
+ l = l.successor;
+ }
+
+ handler = firstHandler;
+ handlerCount = 0;
+ while (handler != null) {
+ handlerCount += 1;
+ handler = handler.next;
+ }
+
+ this.maxStack = max;
+ } else if (compute == MAXS) {
+ // completes the control flow graph with exception handler blocks
+ Handler handler = firstHandler;
+ while (handler != null) {
+ Label l = handler.start;
+ Label h = handler.handler;
+ Label e = handler.end;
+ // adds 'h' as a successor of labels between 'start' and 'end'
+ while (l != e) {
+ // creates an edge to 'h'
+ Edge b = new Edge();
+ b.info = Edge.EXCEPTION;
+ b.successor = h;
+ // adds it to the successors of 'l'
+ if ((l.status & Label.JSR) == 0) {
+ b.next = l.successors;
+ l.successors = b;
+ } else {
+ // if l is a JSR block, adds b after the first two edges
+ // to preserve the hypothesis about JSR block successors
+ // order (see {@link #visitJumpInsn})
+ b.next = l.successors.next.next;
+ l.successors.next.next = b;
+ }
+ // goes to the next label
+ l = l.successor;
+ }
+ handler = handler.next;
+ }
+
+ if (subroutines > 0) {
+ // completes the control flow graph with the RET successors
+ /*
+ * first step: finds the subroutines. This step determines, for
+ * each basic block, to which subroutine(s) it belongs.
+ */
+ // finds the basic blocks that belong to the "main" subroutine
+ int id = 0;
+ labels.visitSubroutine(null, 1, subroutines);
+ // finds the basic blocks that belong to the real subroutines
+ Label l = labels;
+ while (l != null) {
+ if ((l.status & Label.JSR) != 0) {
+ // the subroutine is defined by l's TARGET, not by l
+ Label subroutine = l.successors.next.successor;
+ // if this subroutine has not been visited yet...
+ if ((subroutine.status & Label.VISITED) == 0) {
+ // ...assigns it a new id and finds its basic blocks
+ id += 1;
+ subroutine.visitSubroutine(null, (id / 32L) << 32
+ | (1L << (id % 32)), subroutines);
+ }
+ }
+ l = l.successor;
+ }
+ // second step: finds the successors of RET blocks
+ l = labels;
+ while (l != null) {
+ if ((l.status & Label.JSR) != 0) {
+ Label L = labels;
+ while (L != null) {
+ L.status &= ~Label.VISITED2;
+ L = L.successor;
+ }
+ // the subroutine is defined by l's TARGET, not by l
+ Label subroutine = l.successors.next.successor;
+ subroutine.visitSubroutine(l, 0, subroutines);
+ }
+ l = l.successor;
+ }
+ }
+
+ /*
+ * control flow analysis algorithm: while the block stack is not
+ * empty, pop a block from this stack, update the max stack size,
+ * compute the true (non relative) begin stack size of the
+ * successors of this block, and push these successors onto the
+ * stack (unless they have already been pushed onto the stack).
+ * Note: by hypothesis, the {@link Label#inputStackTop} of the
+ * blocks in the block stack are the true (non relative) beginning
+ * stack sizes of these blocks.
+ */
+ int max = 0;
+ Label stack = labels;
+ while (stack != null) {
+ // pops a block from the stack
+ Label l = stack;
+ stack = stack.next;
+ // computes the true (non relative) max stack size of this block
+ int start = l.inputStackTop;
+ int blockMax = start + l.outputStackMax;
+ // updates the global max stack size
+ if (blockMax > max) {
+ max = blockMax;
+ }
+ // analyzes the successors of the block
+ Edge b = l.successors;
+ if ((l.status & Label.JSR) != 0) {
+ // ignores the first edge of JSR blocks (virtual successor)
+ b = b.next;
+ }
+ while (b != null) {
+ l = b.successor;
+ // if this successor has not already been pushed...
+ if ((l.status & Label.PUSHED) == 0) {
+ // computes its true beginning stack size...
+ l.inputStackTop = b.info == Edge.EXCEPTION ? 1 : start
+ + b.info;
+ // ...and pushes it onto the stack
+ l.status |= Label.PUSHED;
+ l.next = stack;
+ stack = l;
+ }
+ b = b.next;
+ }
+ }
+ this.maxStack = Math.max(maxStack, max);
+ } else {
+ this.maxStack = maxStack;
+ this.maxLocals = maxLocals;
+ }
+ }
+
+ @Override
+ public void visitEnd() {
+ }
+
+ // ------------------------------------------------------------------------
+ // Utility methods: control flow analysis algorithm
+ // ------------------------------------------------------------------------
+
+ /**
+ * Adds a successor to the {@link #currentBlock currentBlock} block.
+ *
+ * @param info
+ * information about the control flow edge to be added.
+ * @param successor
+ * the successor block to be added to the current block.
+ */
+ private void addSuccessor(final int info, final Label successor) {
+ // creates and initializes an Edge object...
+ Edge b = new Edge();
+ b.info = info;
+ b.successor = successor;
+ // ...and adds it to the successor list of the currentBlock block
+ b.next = currentBlock.successors;
+ currentBlock.successors = b;
+ }
+
+ /**
+ * Ends the current basic block. This method must be used in the case where
+ * the current basic block does not have any successor.
+ */
+ private void noSuccessor() {
+ if (compute == FRAMES) {
+ Label l = new Label();
+ l.frame = new Frame();
+ l.frame.owner = l;
+ l.resolve(this, code.length, code.data);
+ previousBlock.successor = l;
+ previousBlock = l;
+ } else {
+ currentBlock.outputStackMax = maxStackSize;
+ }
+ currentBlock = null;
+ }
+
+ // ------------------------------------------------------------------------
+ // Utility methods: stack map frames
+ // ------------------------------------------------------------------------
+
+ /**
+ * Visits a frame that has been computed from scratch.
+ *
+ * @param f
+ * the frame that must be visited.
+ */
+ private void visitFrame(final Frame f) {
+ int i, t;
+ int nTop = 0;
+ int nLocal = 0;
+ int nStack = 0;
+ int[] locals = f.inputLocals;
+ int[] stacks = f.inputStack;
+ // computes the number of locals (ignores TOP types that are just after
+ // a LONG or a DOUBLE, and all trailing TOP types)
+ for (i = 0; i < locals.length; ++i) {
+ t = locals[i];
+ if (t == Frame.TOP) {
+ ++nTop;
+ } else {
+ nLocal += nTop + 1;
+ nTop = 0;
+ }
+ if (t == Frame.LONG || t == Frame.DOUBLE) {
+ ++i;
+ }
+ }
+ // computes the stack size (ignores TOP types that are just after
+ // a LONG or a DOUBLE)
+ for (i = 0; i < stacks.length; ++i) {
+ t = stacks[i];
+ ++nStack;
+ if (t == Frame.LONG || t == Frame.DOUBLE) {
+ ++i;
+ }
+ }
+ // visits the frame and its content
+ int frameIndex = startFrame(f.owner.position, nLocal, nStack);
+ for (i = 0; nLocal > 0; ++i, --nLocal) {
+ t = locals[i];
+ frame[frameIndex++] = t;
+ if (t == Frame.LONG || t == Frame.DOUBLE) {
+ ++i;
+ }
+ }
+ for (i = 0; i < stacks.length; ++i) {
+ t = stacks[i];
+ frame[frameIndex++] = t;
+ if (t == Frame.LONG || t == Frame.DOUBLE) {
+ ++i;
+ }
+ }
+ endFrame();
+ }
+
+ /**
+ * Visit the implicit first frame of this method.
+ */
+ private void visitImplicitFirstFrame() {
+ // There can be at most descriptor.length() + 1 locals
+ int frameIndex = startFrame(0, descriptor.length() + 1, 0);
+ if ((access & Opcodes.ACC_STATIC) == 0) {
+ if ((access & ACC_CONSTRUCTOR) == 0) {
+ frame[frameIndex++] = Frame.OBJECT | cw.addType(cw.thisName);
+ } else {
+ frame[frameIndex++] = 6; // Opcodes.UNINITIALIZED_THIS;
+ }
+ }
+ int i = 1;
+ loop: while (true) {
+ int j = i;
+ switch (descriptor.charAt(i++)) {
+ case 'Z':
+ case 'C':
+ case 'B':
+ case 'S':
+ case 'I':
+ frame[frameIndex++] = 1; // Opcodes.INTEGER;
+ break;
+ case 'F':
+ frame[frameIndex++] = 2; // Opcodes.FLOAT;
+ break;
+ case 'J':
+ frame[frameIndex++] = 4; // Opcodes.LONG;
+ break;
+ case 'D':
+ frame[frameIndex++] = 3; // Opcodes.DOUBLE;
+ break;
+ case '[':
+ while (descriptor.charAt(i) == '[') {
+ ++i;
+ }
+ if (descriptor.charAt(i) == 'L') {
+ ++i;
+ while (descriptor.charAt(i) != ';') {
+ ++i;
+ }
+ }
+ frame[frameIndex++] = Frame.OBJECT
+ | cw.addType(descriptor.substring(j, ++i));
+ break;
+ case 'L':
+ while (descriptor.charAt(i) != ';') {
+ ++i;
+ }
+ frame[frameIndex++] = Frame.OBJECT
+ | cw.addType(descriptor.substring(j + 1, i++));
+ break;
+ default:
+ break loop;
+ }
+ }
+ frame[1] = frameIndex - 3;
+ endFrame();
+ }
+
+ /**
+ * Starts the visit of a stack map frame.
+ *
+ * @param offset
+ * the offset of the instruction to which the frame corresponds.
+ * @param nLocal
+ * the number of local variables in the frame.
+ * @param nStack
+ * the number of stack elements in the frame.
+ * @return the index of the next element to be written in this frame.
+ */
+ private int startFrame(final int offset, final int nLocal, final int nStack) {
+ int n = 3 + nLocal + nStack;
+ if (frame == null || frame.length < n) {
+ frame = new int[n];
+ }
+ frame[0] = offset;
+ frame[1] = nLocal;
+ frame[2] = nStack;
+ return 3;
+ }
+
+ /**
+ * Checks if the visit of the current frame {@link #frame} is finished, and
+ * if yes, write it in the StackMapTable attribute.
+ */
+ private void endFrame() {
+ if (previousFrame != null) { // do not write the first frame
+ if (stackMap == null) {
+ stackMap = new ByteVector();
+ }
+ writeFrame();
+ ++frameCount;
+ }
+ previousFrame = frame;
+ frame = null;
+ }
+
+ /**
+ * Compress and writes the current frame {@link #frame} in the StackMapTable
+ * attribute.
+ */
+ private void writeFrame() {
+ int clocalsSize = frame[1];
+ int cstackSize = frame[2];
+ if ((cw.version & 0xFFFF) < Opcodes.V1_6) {
+ stackMap.putShort(frame[0]).putShort(clocalsSize);
+ writeFrameTypes(3, 3 + clocalsSize);
+ stackMap.putShort(cstackSize);
+ writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize);
+ return;
+ }
+ int localsSize = previousFrame[1];
+ int type = FULL_FRAME;
+ int k = 0;
+ int delta;
+ if (frameCount == 0) {
+ delta = frame[0];
+ } else {
+ delta = frame[0] - previousFrame[0] - 1;
+ }
+ if (cstackSize == 0) {
+ k = clocalsSize - localsSize;
+ switch (k) {
+ case -3:
+ case -2:
+ case -1:
+ type = CHOP_FRAME;
+ localsSize = clocalsSize;
+ break;
+ case 0:
+ type = delta < 64 ? SAME_FRAME : SAME_FRAME_EXTENDED;
+ break;
+ case 1:
+ case 2:
+ case 3:
+ type = APPEND_FRAME;
+ break;
+ }
+ } else if (clocalsSize == localsSize && cstackSize == 1) {
+ type = delta < 63 ? SAME_LOCALS_1_STACK_ITEM_FRAME
+ : SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED;
+ }
+ if (type != FULL_FRAME) {
+ // verify if locals are the same
+ int l = 3;
+ for (int j = 0; j < localsSize; j++) {
+ if (frame[l] != previousFrame[l]) {
+ type = FULL_FRAME;
+ break;
+ }
+ l++;
+ }
+ }
+ switch (type) {
+ case SAME_FRAME:
+ stackMap.putByte(delta);
+ break;
+ case SAME_LOCALS_1_STACK_ITEM_FRAME:
+ stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME + delta);
+ writeFrameTypes(3 + clocalsSize, 4 + clocalsSize);
+ break;
+ case SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED:
+ stackMap.putByte(SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED).putShort(
+ delta);
+ writeFrameTypes(3 + clocalsSize, 4 + clocalsSize);
+ break;
+ case SAME_FRAME_EXTENDED:
+ stackMap.putByte(SAME_FRAME_EXTENDED).putShort(delta);
+ break;
+ case CHOP_FRAME:
+ stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta);
+ break;
+ case APPEND_FRAME:
+ stackMap.putByte(SAME_FRAME_EXTENDED + k).putShort(delta);
+ writeFrameTypes(3 + localsSize, 3 + clocalsSize);
+ break;
+ // case FULL_FRAME:
+ default:
+ stackMap.putByte(FULL_FRAME).putShort(delta).putShort(clocalsSize);
+ writeFrameTypes(3, 3 + clocalsSize);
+ stackMap.putShort(cstackSize);
+ writeFrameTypes(3 + clocalsSize, 3 + clocalsSize + cstackSize);
+ }
+ }
+
+ /**
+ * Writes some types of the current frame {@link #frame} into the
+ * StackMapTableAttribute. This method converts types from the format used
+ * in {@link Label} to the format used in StackMapTable attributes. In
+ * particular, it converts type table indexes to constant pool indexes.
+ *
+ * @param start
+ * index of the first type in {@link #frame} to write.
+ * @param end
+ * index of last type in {@link #frame} to write (exclusive).
+ */
+ private void writeFrameTypes(final int start, final int end) {
+ for (int i = start; i < end; ++i) {
+ int t = frame[i];
+ int d = t & Frame.DIM;
+ if (d == 0) {
+ int v = t & Frame.BASE_VALUE;
+ switch (t & Frame.BASE_KIND) {
+ case Frame.OBJECT:
+ stackMap.putByte(7).putShort(
+ cw.newClass(cw.typeTable[v].strVal1));
+ break;
+ case Frame.UNINITIALIZED:
+ stackMap.putByte(8).putShort(cw.typeTable[v].intVal);
+ break;
+ default:
+ stackMap.putByte(v);
+ }
+ } else {
+ StringBuilder sb = new StringBuilder();
+ d >>= 28;
+ while (d-- > 0) {
+ sb.append('[');
+ }
+ if ((t & Frame.BASE_KIND) == Frame.OBJECT) {
+ sb.append('L');
+ sb.append(cw.typeTable[t & Frame.BASE_VALUE].strVal1);
+ sb.append(';');
+ } else {
+ switch (t & 0xF) {
+ case 1:
+ sb.append('I');
+ break;
+ case 2:
+ sb.append('F');
+ break;
+ case 3:
+ sb.append('D');
+ break;
+ case 9:
+ sb.append('Z');
+ break;
+ case 10:
+ sb.append('B');
+ break;
+ case 11:
+ sb.append('C');
+ break;
+ case 12:
+ sb.append('S');
+ break;
+ default:
+ sb.append('J');
+ }
+ }
+ stackMap.putByte(7).putShort(cw.newClass(sb.toString()));
+ }
+ }
+ }
+
+ private void writeFrameType(final Object type) {
+ if (type instanceof String) {
+ stackMap.putByte(7).putShort(cw.newClass((String) type));
+ } else if (type instanceof Integer) {
+ stackMap.putByte(((Integer) type).intValue());
+ } else {
+ stackMap.putByte(8).putShort(((Label) type).position);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Utility methods: dump bytecode array
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the size of the bytecode of this method.
+ *
+ * @return the size of the bytecode of this method.
+ */
+ final int getSize() {
+ if (classReaderOffset != 0) {
+ return 6 + classReaderLength;
+ }
+ int size = 8;
+ if (code.length > 0) {
+ if (code.length > 65536) {
+ throw new RuntimeException("Method code too large!");
+ }
+ cw.newUTF8("Code");
+ size += 18 + code.length + 8 * handlerCount;
+ if (localVar != null) {
+ cw.newUTF8("LocalVariableTable");
+ size += 8 + localVar.length;
+ }
+ if (localVarType != null) {
+ cw.newUTF8("LocalVariableTypeTable");
+ size += 8 + localVarType.length;
+ }
+ if (lineNumber != null) {
+ cw.newUTF8("LineNumberTable");
+ size += 8 + lineNumber.length;
+ }
+ if (stackMap != null) {
+ boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6;
+ cw.newUTF8(zip ? "StackMapTable" : "StackMap");
+ size += 8 + stackMap.length;
+ }
+ if (ClassReader.ANNOTATIONS && ctanns != null) {
+ cw.newUTF8("RuntimeVisibleTypeAnnotations");
+ size += 8 + ctanns.getSize();
+ }
+ if (ClassReader.ANNOTATIONS && ictanns != null) {
+ cw.newUTF8("RuntimeInvisibleTypeAnnotations");
+ size += 8 + ictanns.getSize();
+ }
+ if (cattrs != null) {
+ size += cattrs.getSize(cw, code.data, code.length, maxStack,
+ maxLocals);
+ }
+ }
+ if (exceptionCount > 0) {
+ cw.newUTF8("Exceptions");
+ size += 8 + 2 * exceptionCount;
+ }
+ if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
+ if ((cw.version & 0xFFFF) < Opcodes.V1_5
+ || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
+ cw.newUTF8("Synthetic");
+ size += 6;
+ }
+ }
+ if ((access & Opcodes.ACC_DEPRECATED) != 0) {
+ cw.newUTF8("Deprecated");
+ size += 6;
+ }
+ if (ClassReader.SIGNATURES && signature != null) {
+ cw.newUTF8("Signature");
+ cw.newUTF8(signature);
+ size += 8;
+ }
+ if (methodParameters != null) {
+ cw.newUTF8("MethodParameters");
+ size += 7 + methodParameters.length;
+ }
+ if (ClassReader.ANNOTATIONS && annd != null) {
+ cw.newUTF8("AnnotationDefault");
+ size += 6 + annd.length;
+ }
+ if (ClassReader.ANNOTATIONS && anns != null) {
+ cw.newUTF8("RuntimeVisibleAnnotations");
+ size += 8 + anns.getSize();
+ }
+ if (ClassReader.ANNOTATIONS && ianns != null) {
+ cw.newUTF8("RuntimeInvisibleAnnotations");
+ size += 8 + ianns.getSize();
+ }
+ if (ClassReader.ANNOTATIONS && tanns != null) {
+ cw.newUTF8("RuntimeVisibleTypeAnnotations");
+ size += 8 + tanns.getSize();
+ }
+ if (ClassReader.ANNOTATIONS && itanns != null) {
+ cw.newUTF8("RuntimeInvisibleTypeAnnotations");
+ size += 8 + itanns.getSize();
+ }
+ if (ClassReader.ANNOTATIONS && panns != null) {
+ cw.newUTF8("RuntimeVisibleParameterAnnotations");
+ size += 7 + 2 * (panns.length - synthetics);
+ for (int i = panns.length - 1; i >= synthetics; --i) {
+ size += panns[i] == null ? 0 : panns[i].getSize();
+ }
+ }
+ if (ClassReader.ANNOTATIONS && ipanns != null) {
+ cw.newUTF8("RuntimeInvisibleParameterAnnotations");
+ size += 7 + 2 * (ipanns.length - synthetics);
+ for (int i = ipanns.length - 1; i >= synthetics; --i) {
+ size += ipanns[i] == null ? 0 : ipanns[i].getSize();
+ }
+ }
+ if (attrs != null) {
+ size += attrs.getSize(cw, null, 0, -1, -1);
+ }
+ return size;
+ }
+
+ /**
+ * Puts the bytecode of this method in the given byte vector.
+ *
+ * @param out
+ * the byte vector into which the bytecode of this method must be
+ * copied.
+ */
+ final void put(final ByteVector out) {
+ final int FACTOR = ClassWriter.TO_ACC_SYNTHETIC;
+ int mask = ACC_CONSTRUCTOR | Opcodes.ACC_DEPRECATED
+ | ClassWriter.ACC_SYNTHETIC_ATTRIBUTE
+ | ((access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) / FACTOR);
+ out.putShort(access & ~mask).putShort(name).putShort(desc);
+ if (classReaderOffset != 0) {
+ out.putByteArray(cw.cr.b, classReaderOffset, classReaderLength);
+ return;
+ }
+ int attributeCount = 0;
+ if (code.length > 0) {
+ ++attributeCount;
+ }
+ if (exceptionCount > 0) {
+ ++attributeCount;
+ }
+ if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
+ if ((cw.version & 0xFFFF) < Opcodes.V1_5
+ || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
+ ++attributeCount;
+ }
+ }
+ if ((access & Opcodes.ACC_DEPRECATED) != 0) {
+ ++attributeCount;
+ }
+ if (ClassReader.SIGNATURES && signature != null) {
+ ++attributeCount;
+ }
+ if (methodParameters != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && annd != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && anns != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && ianns != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && tanns != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && itanns != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && panns != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && ipanns != null) {
+ ++attributeCount;
+ }
+ if (attrs != null) {
+ attributeCount += attrs.getCount();
+ }
+ out.putShort(attributeCount);
+ if (code.length > 0) {
+ int size = 12 + code.length + 8 * handlerCount;
+ if (localVar != null) {
+ size += 8 + localVar.length;
+ }
+ if (localVarType != null) {
+ size += 8 + localVarType.length;
+ }
+ if (lineNumber != null) {
+ size += 8 + lineNumber.length;
+ }
+ if (stackMap != null) {
+ size += 8 + stackMap.length;
+ }
+ if (ClassReader.ANNOTATIONS && ctanns != null) {
+ size += 8 + ctanns.getSize();
+ }
+ if (ClassReader.ANNOTATIONS && ictanns != null) {
+ size += 8 + ictanns.getSize();
+ }
+ if (cattrs != null) {
+ size += cattrs.getSize(cw, code.data, code.length, maxStack,
+ maxLocals);
+ }
+ out.putShort(cw.newUTF8("Code")).putInt(size);
+ out.putShort(maxStack).putShort(maxLocals);
+ out.putInt(code.length).putByteArray(code.data, 0, code.length);
+ out.putShort(handlerCount);
+ if (handlerCount > 0) {
+ Handler h = firstHandler;
+ while (h != null) {
+ out.putShort(h.start.position).putShort(h.end.position)
+ .putShort(h.handler.position).putShort(h.type);
+ h = h.next;
+ }
+ }
+ attributeCount = 0;
+ if (localVar != null) {
+ ++attributeCount;
+ }
+ if (localVarType != null) {
+ ++attributeCount;
+ }
+ if (lineNumber != null) {
+ ++attributeCount;
+ }
+ if (stackMap != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && ctanns != null) {
+ ++attributeCount;
+ }
+ if (ClassReader.ANNOTATIONS && ictanns != null) {
+ ++attributeCount;
+ }
+ if (cattrs != null) {
+ attributeCount += cattrs.getCount();
+ }
+ out.putShort(attributeCount);
+ if (localVar != null) {
+ out.putShort(cw.newUTF8("LocalVariableTable"));
+ out.putInt(localVar.length + 2).putShort(localVarCount);
+ out.putByteArray(localVar.data, 0, localVar.length);
+ }
+ if (localVarType != null) {
+ out.putShort(cw.newUTF8("LocalVariableTypeTable"));
+ out.putInt(localVarType.length + 2).putShort(localVarTypeCount);
+ out.putByteArray(localVarType.data, 0, localVarType.length);
+ }
+ if (lineNumber != null) {
+ out.putShort(cw.newUTF8("LineNumberTable"));
+ out.putInt(lineNumber.length + 2).putShort(lineNumberCount);
+ out.putByteArray(lineNumber.data, 0, lineNumber.length);
+ }
+ if (stackMap != null) {
+ boolean zip = (cw.version & 0xFFFF) >= Opcodes.V1_6;
+ out.putShort(cw.newUTF8(zip ? "StackMapTable" : "StackMap"));
+ out.putInt(stackMap.length + 2).putShort(frameCount);
+ out.putByteArray(stackMap.data, 0, stackMap.length);
+ }
+ if (ClassReader.ANNOTATIONS && ctanns != null) {
+ out.putShort(cw.newUTF8("RuntimeVisibleTypeAnnotations"));
+ ctanns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && ictanns != null) {
+ out.putShort(cw.newUTF8("RuntimeInvisibleTypeAnnotations"));
+ ictanns.put(out);
+ }
+ if (cattrs != null) {
+ cattrs.put(cw, code.data, code.length, maxLocals, maxStack, out);
+ }
+ }
+ if (exceptionCount > 0) {
+ out.putShort(cw.newUTF8("Exceptions")).putInt(
+ 2 * exceptionCount + 2);
+ out.putShort(exceptionCount);
+ for (int i = 0; i < exceptionCount; ++i) {
+ out.putShort(exceptions[i]);
+ }
+ }
+ if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
+ if ((cw.version & 0xFFFF) < Opcodes.V1_5
+ || (access & ClassWriter.ACC_SYNTHETIC_ATTRIBUTE) != 0) {
+ out.putShort(cw.newUTF8("Synthetic")).putInt(0);
+ }
+ }
+ if ((access & Opcodes.ACC_DEPRECATED) != 0) {
+ out.putShort(cw.newUTF8("Deprecated")).putInt(0);
+ }
+ if (ClassReader.SIGNATURES && signature != null) {
+ out.putShort(cw.newUTF8("Signature")).putInt(2)
+ .putShort(cw.newUTF8(signature));
+ }
+ if (methodParameters != null) {
+ out.putShort(cw.newUTF8("MethodParameters"));
+ out.putInt(methodParameters.length + 1).putByte(
+ methodParametersCount);
+ out.putByteArray(methodParameters.data, 0, methodParameters.length);
+ }
+ if (ClassReader.ANNOTATIONS && annd != null) {
+ out.putShort(cw.newUTF8("AnnotationDefault"));
+ out.putInt(annd.length);
+ out.putByteArray(annd.data, 0, annd.length);
+ }
+ if (ClassReader.ANNOTATIONS && anns != null) {
+ out.putShort(cw.newUTF8("RuntimeVisibleAnnotations"));
+ anns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && ianns != null) {
+ out.putShort(cw.newUTF8("RuntimeInvisibleAnnotations"));
+ ianns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && tanns != null) {
+ out.putShort(cw.newUTF8("RuntimeVisibleTypeAnnotations"));
+ tanns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && itanns != null) {
+ out.putShort(cw.newUTF8("RuntimeInvisibleTypeAnnotations"));
+ itanns.put(out);
+ }
+ if (ClassReader.ANNOTATIONS && panns != null) {
+ out.putShort(cw.newUTF8("RuntimeVisibleParameterAnnotations"));
+ AnnotationWriter.put(panns, synthetics, out);
+ }
+ if (ClassReader.ANNOTATIONS && ipanns != null) {
+ out.putShort(cw.newUTF8("RuntimeInvisibleParameterAnnotations"));
+ AnnotationWriter.put(ipanns, synthetics, out);
+ }
+ if (attrs != null) {
+ attrs.put(cw, null, 0, -1, -1, out);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Utility methods: instruction resizing (used to handle GOTO_W and JSR_W)
+ // ------------------------------------------------------------------------
+
+ /**
+ * Resizes and replaces the temporary instructions inserted by
+ * {@link Label#resolve} for wide forward jumps, while keeping jump offsets
+ * and instruction addresses consistent. This may require to resize other
+ * existing instructions, or even to introduce new instructions: for
+ * example, increasing the size of an instruction by 2 at the middle of a
+ * method can increases the offset of an IFEQ instruction from 32766 to
+ * 32768, in which case IFEQ 32766 must be replaced with IFNEQ 8 GOTO_W
+ * 32765. This, in turn, may require to increase the size of another jump
+ * instruction, and so on... All these operations are handled automatically
+ * by this method.
+ * <p>
+ * <i>This method must be called after all the method that is being built
+ * has been visited</i>. In particular, the {@link Label Label} objects used
+ * to construct the method are no longer valid after this method has been
+ * called.
+ */
+ private void resizeInstructions() {
+ byte[] b = code.data; // bytecode of the method
+ int u, v, label; // indexes in b
+ int i, j; // loop indexes
+ /*
+ * 1st step: As explained above, resizing an instruction may require to
+ * resize another one, which may require to resize yet another one, and
+ * so on. The first step of the algorithm consists in finding all the
+ * instructions that need to be resized, without modifying the code.
+ * This is done by the following "fix point" algorithm:
+ *
+ * Parse the code to find the jump instructions whose offset will need
+ * more than 2 bytes to be stored (the future offset is computed from
+ * the current offset and from the number of bytes that will be inserted
+ * or removed between the source and target instructions). For each such
+ * instruction, adds an entry in (a copy of) the indexes and sizes
+ * arrays (if this has not already been done in a previous iteration!).
+ *
+ * If at least one entry has been added during the previous step, go
+ * back to the beginning, otherwise stop.
+ *
+ * In fact the real algorithm is complicated by the fact that the size
+ * of TABLESWITCH and LOOKUPSWITCH instructions depends on their
+ * position in the bytecode (because of padding). In order to ensure the
+ * convergence of the algorithm, the number of bytes to be added or
+ * removed from these instructions is over estimated during the previous
+ * loop, and computed exactly only after the loop is finished (this
+ * requires another pass to parse the bytecode of the method).
+ */
+ int[] allIndexes = new int[0]; // copy of indexes
+ int[] allSizes = new int[0]; // copy of sizes
+ boolean[] resize; // instructions to be resized
+ int newOffset; // future offset of a jump instruction
+
+ resize = new boolean[code.length];
+
+ // 3 = loop again, 2 = loop ended, 1 = last pass, 0 = done
+ int state = 3;
+ do {
+ if (state == 3) {
+ state = 2;
+ }
+ u = 0;
+ while (u < b.length) {
+ int opcode = b[u] & 0xFF; // opcode of current instruction
+ int insert = 0; // bytes to be added after this instruction
+
+ switch (ClassWriter.TYPE[opcode]) {
+ case ClassWriter.NOARG_INSN:
+ case ClassWriter.IMPLVAR_INSN:
+ u += 1;
+ break;
+ case ClassWriter.LABEL_INSN:
+ if (opcode > 201) {
+ // converts temporary opcodes 202 to 217, 218 and
+ // 219 to IFEQ ... JSR (inclusive), IFNULL and
+ // IFNONNULL
+ opcode = opcode < 218 ? opcode - 49 : opcode - 20;
+ label = u + readUnsignedShort(b, u + 1);
+ } else {
+ label = u + readShort(b, u + 1);
+ }
+ newOffset = getNewOffset(allIndexes, allSizes, u, label);
+ if (newOffset < Short.MIN_VALUE
+ || newOffset > Short.MAX_VALUE) {
+ if (!resize[u]) {
+ if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) {
+ // two additional bytes will be required to
+ // replace this GOTO or JSR instruction with
+ // a GOTO_W or a JSR_W
+ insert = 2;
+ } else {
+ // five additional bytes will be required to
+ // replace this IFxxx <l> instruction with
+ // IFNOTxxx <l'> GOTO_W <l>, where IFNOTxxx
+ // is the "opposite" opcode of IFxxx (i.e.,
+ // IFNE for IFEQ) and where <l'> designates
+ // the instruction just after the GOTO_W.
+ insert = 5;
+ }
+ resize[u] = true;
+ }
+ }
+ u += 3;
+ break;
+ case ClassWriter.LABELW_INSN:
+ u += 5;
+ break;
+ case ClassWriter.TABL_INSN:
+ if (state == 1) {
+ // true number of bytes to be added (or removed)
+ // from this instruction = (future number of padding
+ // bytes - current number of padding byte) -
+ // previously over estimated variation =
+ // = ((3 - newOffset%4) - (3 - u%4)) - u%4
+ // = (-newOffset%4 + u%4) - u%4
+ // = -(newOffset & 3)
+ newOffset = getNewOffset(allIndexes, allSizes, 0, u);
+ insert = -(newOffset & 3);
+ } else if (!resize[u]) {
+ // over estimation of the number of bytes to be
+ // added to this instruction = 3 - current number
+ // of padding bytes = 3 - (3 - u%4) = u%4 = u & 3
+ insert = u & 3;
+ resize[u] = true;
+ }
+ // skips instruction
+ u = u + 4 - (u & 3);
+ u += 4 * (readInt(b, u + 8) - readInt(b, u + 4) + 1) + 12;
+ break;
+ case ClassWriter.LOOK_INSN:
+ if (state == 1) {
+ // like TABL_INSN
+ newOffset = getNewOffset(allIndexes, allSizes, 0, u);
+ insert = -(newOffset & 3);
+ } else if (!resize[u]) {
+ // like TABL_INSN
+ insert = u & 3;
+ resize[u] = true;
+ }
+ // skips instruction
+ u = u + 4 - (u & 3);
+ u += 8 * readInt(b, u + 4) + 8;
+ break;
+ case ClassWriter.WIDE_INSN:
+ opcode = b[u + 1] & 0xFF;
+ if (opcode == Opcodes.IINC) {
+ u += 6;
+ } else {
+ u += 4;
+ }
+ break;
+ case ClassWriter.VAR_INSN:
+ case ClassWriter.SBYTE_INSN:
+ case ClassWriter.LDC_INSN:
+ u += 2;
+ break;
+ case ClassWriter.SHORT_INSN:
+ case ClassWriter.LDCW_INSN:
+ case ClassWriter.FIELDORMETH_INSN:
+ case ClassWriter.TYPE_INSN:
+ case ClassWriter.IINC_INSN:
+ u += 3;
+ break;
+ case ClassWriter.ITFMETH_INSN:
+ case ClassWriter.INDYMETH_INSN:
+ u += 5;
+ break;
+ // case ClassWriter.MANA_INSN:
+ default:
+ u += 4;
+ break;
+ }
+ if (insert != 0) {
+ // adds a new (u, insert) entry in the allIndexes and
+ // allSizes arrays
+ int[] newIndexes = new int[allIndexes.length + 1];
+ int[] newSizes = new int[allSizes.length + 1];
+ System.arraycopy(allIndexes, 0, newIndexes, 0,
+ allIndexes.length);
+ System.arraycopy(allSizes, 0, newSizes, 0, allSizes.length);
+ newIndexes[allIndexes.length] = u;
+ newSizes[allSizes.length] = insert;
+ allIndexes = newIndexes;
+ allSizes = newSizes;
+ if (insert > 0) {
+ state = 3;
+ }
+ }
+ }
+ if (state < 3) {
+ --state;
+ }
+ } while (state != 0);
+
+ // 2nd step:
+ // copies the bytecode of the method into a new bytevector, updates the
+ // offsets, and inserts (or removes) bytes as requested.
+
+ ByteVector newCode = new ByteVector(code.length);
+
+ u = 0;
+ while (u < code.length) {
+ int opcode = b[u] & 0xFF;
+ switch (ClassWriter.TYPE[opcode]) {
+ case ClassWriter.NOARG_INSN:
+ case ClassWriter.IMPLVAR_INSN:
+ newCode.putByte(opcode);
+ u += 1;
+ break;
+ case ClassWriter.LABEL_INSN:
+ if (opcode > 201) {
+ // changes temporary opcodes 202 to 217 (inclusive), 218
+ // and 219 to IFEQ ... JSR (inclusive), IFNULL and
+ // IFNONNULL
+ opcode = opcode < 218 ? opcode - 49 : opcode - 20;
+ label = u + readUnsignedShort(b, u + 1);
+ } else {
+ label = u + readShort(b, u + 1);
+ }
+ newOffset = getNewOffset(allIndexes, allSizes, u, label);
+ if (resize[u]) {
+ // replaces GOTO with GOTO_W, JSR with JSR_W and IFxxx
+ // <l> with IFNOTxxx <l'> GOTO_W <l>, where IFNOTxxx is
+ // the "opposite" opcode of IFxxx (i.e., IFNE for IFEQ)
+ // and where <l'> designates the instruction just after
+ // the GOTO_W.
+ if (opcode == Opcodes.GOTO) {
+ newCode.putByte(200); // GOTO_W
+ } else if (opcode == Opcodes.JSR) {
+ newCode.putByte(201); // JSR_W
+ } else {
+ newCode.putByte(opcode <= 166 ? ((opcode + 1) ^ 1) - 1
+ : opcode ^ 1);
+ newCode.putShort(8); // jump offset
+ newCode.putByte(200); // GOTO_W
+ // newOffset now computed from start of GOTO_W
+ newOffset -= 3;
+ }
+ newCode.putInt(newOffset);
+ } else {
+ newCode.putByte(opcode);
+ newCode.putShort(newOffset);
+ }
+ u += 3;
+ break;
+ case ClassWriter.LABELW_INSN:
+ label = u + readInt(b, u + 1);
+ newOffset = getNewOffset(allIndexes, allSizes, u, label);
+ newCode.putByte(opcode);
+ newCode.putInt(newOffset);
+ u += 5;
+ break;
+ case ClassWriter.TABL_INSN:
+ // skips 0 to 3 padding bytes
+ v = u;
+ u = u + 4 - (v & 3);
+ // reads and copies instruction
+ newCode.putByte(Opcodes.TABLESWITCH);
+ newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4);
+ label = v + readInt(b, u);
+ u += 4;
+ newOffset = getNewOffset(allIndexes, allSizes, v, label);
+ newCode.putInt(newOffset);
+ j = readInt(b, u);
+ u += 4;
+ newCode.putInt(j);
+ j = readInt(b, u) - j + 1;
+ u += 4;
+ newCode.putInt(readInt(b, u - 4));
+ for (; j > 0; --j) {
+ label = v + readInt(b, u);
+ u += 4;
+ newOffset = getNewOffset(allIndexes, allSizes, v, label);
+ newCode.putInt(newOffset);
+ }
+ break;
+ case ClassWriter.LOOK_INSN:
+ // skips 0 to 3 padding bytes
+ v = u;
+ u = u + 4 - (v & 3);
+ // reads and copies instruction
+ newCode.putByte(Opcodes.LOOKUPSWITCH);
+ newCode.putByteArray(null, 0, (4 - newCode.length % 4) % 4);
+ label = v + readInt(b, u);
+ u += 4;
+ newOffset = getNewOffset(allIndexes, allSizes, v, label);
+ newCode.putInt(newOffset);
+ j = readInt(b, u);
+ u += 4;
+ newCode.putInt(j);
+ for (; j > 0; --j) {
+ newCode.putInt(readInt(b, u));
+ u += 4;
+ label = v + readInt(b, u);
+ u += 4;
+ newOffset = getNewOffset(allIndexes, allSizes, v, label);
+ newCode.putInt(newOffset);
+ }
+ break;
+ case ClassWriter.WIDE_INSN:
+ opcode = b[u + 1] & 0xFF;
+ if (opcode == Opcodes.IINC) {
+ newCode.putByteArray(b, u, 6);
+ u += 6;
+ } else {
+ newCode.putByteArray(b, u, 4);
+ u += 4;
+ }
+ break;
+ case ClassWriter.VAR_INSN:
+ case ClassWriter.SBYTE_INSN:
+ case ClassWriter.LDC_INSN:
+ newCode.putByteArray(b, u, 2);
+ u += 2;
+ break;
+ case ClassWriter.SHORT_INSN:
+ case ClassWriter.LDCW_INSN:
+ case ClassWriter.FIELDORMETH_INSN:
+ case ClassWriter.TYPE_INSN:
+ case ClassWriter.IINC_INSN:
+ newCode.putByteArray(b, u, 3);
+ u += 3;
+ break;
+ case ClassWriter.ITFMETH_INSN:
+ case ClassWriter.INDYMETH_INSN:
+ newCode.putByteArray(b, u, 5);
+ u += 5;
+ break;
+ // case MANA_INSN:
+ default:
+ newCode.putByteArray(b, u, 4);
+ u += 4;
+ break;
+ }
+ }
+
+ // updates the stack map frame labels
+ if (compute == FRAMES) {
+ Label l = labels;
+ while (l != null) {
+ /*
+ * Detects the labels that are just after an IF instruction that
+ * has been resized with the IFNOT GOTO_W pattern. These labels
+ * are now the target of a jump instruction (the IFNOT
+ * instruction). Note that we need the original label position
+ * here. getNewOffset must therefore never have been called for
+ * this label.
+ */
+ u = l.position - 3;
+ if (u >= 0 && resize[u]) {
+ l.status |= Label.TARGET;
+ }
+ getNewOffset(allIndexes, allSizes, l);
+ l = l.successor;
+ }
+ // Update the offsets in the uninitialized types
+ for (i = 0; i < cw.typeTable.length; ++i) {
+ Item item = cw.typeTable[i];
+ if (item != null && item.type == ClassWriter.TYPE_UNINIT) {
+ item.intVal = getNewOffset(allIndexes, allSizes, 0,
+ item.intVal);
+ }
+ }
+ // The stack map frames are not serialized yet, so we don't need
+ // to update them. They will be serialized in visitMaxs.
+ } else if (frameCount > 0) {
+ /*
+ * Resizing an existing stack map frame table is really hard. Not
+ * only the table must be parsed to update the offets, but new
+ * frames may be needed for jump instructions that were inserted by
+ * this method. And updating the offsets or inserting frames can
+ * change the format of the following frames, in case of packed
+ * frames. In practice the whole table must be recomputed. For this
+ * the frames are marked as potentially invalid. This will cause the
+ * whole class to be reread and rewritten with the COMPUTE_FRAMES
+ * option (see the ClassWriter.toByteArray method). This is not very
+ * efficient but is much easier and requires much less code than any
+ * other method I can think of.
+ */
+ cw.invalidFrames = true;
+ }
+ // updates the exception handler block labels
+ Handler h = firstHandler;
+ while (h != null) {
+ getNewOffset(allIndexes, allSizes, h.start);
+ getNewOffset(allIndexes, allSizes, h.end);
+ getNewOffset(allIndexes, allSizes, h.handler);
+ h = h.next;
+ }
+ // updates the instructions addresses in the
+ // local var and line number tables
+ for (i = 0; i < 2; ++i) {
+ ByteVector bv = i == 0 ? localVar : localVarType;
+ if (bv != null) {
+ b = bv.data;
+ u = 0;
+ while (u < bv.length) {
+ label = readUnsignedShort(b, u);
+ newOffset = getNewOffset(allIndexes, allSizes, 0, label);
+ writeShort(b, u, newOffset);
+ label += readUnsignedShort(b, u + 2);
+ newOffset = getNewOffset(allIndexes, allSizes, 0, label)
+ - newOffset;
+ writeShort(b, u + 2, newOffset);
+ u += 10;
+ }
+ }
+ }
+ if (lineNumber != null) {
+ b = lineNumber.data;
+ u = 0;
+ while (u < lineNumber.length) {
+ writeShort(
+ b,
+ u,
+ getNewOffset(allIndexes, allSizes, 0,
+ readUnsignedShort(b, u)));
+ u += 4;
+ }
+ }
+ // updates the labels of the other attributes
+ Attribute attr = cattrs;
+ while (attr != null) {
+ Label[] labels = attr.getLabels();
+ if (labels != null) {
+ for (i = labels.length - 1; i >= 0; --i) {
+ getNewOffset(allIndexes, allSizes, labels[i]);
+ }
+ }
+ attr = attr.next;
+ }
+
+ // replaces old bytecodes with new ones
+ code = newCode;
+ }
+
+ /**
+ * Reads an unsigned short value in the given byte array.
+ *
+ * @param b
+ * a byte array.
+ * @param index
+ * the start index of the value to be read.
+ * @return the read value.
+ */
+ static int readUnsignedShort(final byte[] b, final int index) {
+ return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
+ }
+
+ /**
+ * Reads a signed short value in the given byte array.
+ *
+ * @param b
+ * a byte array.
+ * @param index
+ * the start index of the value to be read.
+ * @return the read value.
+ */
+ static short readShort(final byte[] b, final int index) {
+ return (short) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF));
+ }
+
+ /**
+ * Reads a signed int value in the given byte array.
+ *
+ * @param b
+ * a byte array.
+ * @param index
+ * the start index of the value to be read.
+ * @return the read value.
+ */
+ static int readInt(final byte[] b, final int index) {
+ return ((b[index] & 0xFF) << 24) | ((b[index + 1] & 0xFF) << 16)
+ | ((b[index + 2] & 0xFF) << 8) | (b[index + 3] & 0xFF);
+ }
+
+ /**
+ * Writes a short value in the given byte array.
+ *
+ * @param b
+ * a byte array.
+ * @param index
+ * where the first byte of the short value must be written.
+ * @param s
+ * the value to be written in the given byte array.
+ */
+ static void writeShort(final byte[] b, final int index, final int s) {
+ b[index] = (byte) (s >>> 8);
+ b[index + 1] = (byte) s;
+ }
+
+ /**
+ * Computes the future value of a bytecode offset.
+ * <p>
+ * Note: it is possible to have several entries for the same instruction in
+ * the <tt>indexes</tt> and <tt>sizes</tt>: two entries (index=a,size=b) and
+ * (index=a,size=b') are equivalent to a single entry (index=a,size=b+b').
+ *
+ * @param indexes
+ * current positions of the instructions to be resized. Each
+ * instruction must be designated by the index of its <i>last</i>
+ * byte, plus one (or, in other words, by the index of the
+ * <i>first</i> byte of the <i>next</i> instruction).
+ * @param sizes
+ * the number of bytes to be <i>added</i> to the above
+ * instructions. More precisely, for each i < <tt>len</tt>,
+ * <tt>sizes</tt>[i] bytes will be added at the end of the
+ * instruction designated by <tt>indexes</tt>[i] or, if
+ * <tt>sizes</tt>[i] is negative, the <i>last</i> |
+ * <tt>sizes[i]</tt>| bytes of the instruction will be removed
+ * (the instruction size <i>must not</i> become negative or
+ * null).
+ * @param begin
+ * index of the first byte of the source instruction.
+ * @param end
+ * index of the first byte of the target instruction.
+ * @return the future value of the given bytecode offset.
+ */
+ static int getNewOffset(final int[] indexes, final int[] sizes,
+ final int begin, final int end) {
+ int offset = end - begin;
+ for (int i = 0; i < indexes.length; ++i) {
+ if (begin < indexes[i] && indexes[i] <= end) {
+ // forward jump
+ offset += sizes[i];
+ } else if (end < indexes[i] && indexes[i] <= begin) {
+ // backward jump
+ offset -= sizes[i];
+ }
+ }
+ return offset;
+ }
+
+ /**
+ * Updates the offset of the given label.
+ *
+ * @param indexes
+ * current positions of the instructions to be resized. Each
+ * instruction must be designated by the index of its <i>last</i>
+ * byte, plus one (or, in other words, by the index of the
+ * <i>first</i> byte of the <i>next</i> instruction).
+ * @param sizes
+ * the number of bytes to be <i>added</i> to the above
+ * instructions. More precisely, for each i < <tt>len</tt>,
+ * <tt>sizes</tt>[i] bytes will be added at the end of the
+ * instruction designated by <tt>indexes</tt>[i] or, if
+ * <tt>sizes</tt>[i] is negative, the <i>last</i> |
+ * <tt>sizes[i]</tt>| bytes of the instruction will be removed
+ * (the instruction size <i>must not</i> become negative or
+ * null).
+ * @param label
+ * the label whose offset must be updated.
+ */
+ static void getNewOffset(final int[] indexes, final int[] sizes,
+ final Label label) {
+ if ((label.status & Label.RESIZED) == 0) {
+ label.position = getNewOffset(indexes, sizes, 0, label.position);
+ label.status |= Label.RESIZED;
+ }
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/Opcodes.java b/spring-core/src/main/java/org/springframework/asm/Opcodes.java
new file mode 100644
index 00000000..1f4f4825
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/Opcodes.java
@@ -0,0 +1,361 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.springframework.asm;
+
+/**
+ * Defines the JVM opcodes, access flags and array type codes. This interface
+ * does not define all the JVM opcodes because some opcodes are automatically
+ * handled. For example, the xLOAD and xSTORE opcodes are automatically replaced
+ * by xLOAD_n and xSTORE_n opcodes when possible. The xLOAD_n and xSTORE_n
+ * opcodes are therefore not defined in this interface. Likewise for LDC,
+ * automatically replaced by LDC_W or LDC2_W when necessary, WIDE, GOTO_W and
+ * JSR_W.
+ *
+ * @author Eric Bruneton
+ * @author Eugene Kuleshov
+ */
+public interface Opcodes {
+
+ // ASM API versions
+
+ int ASM4 = 4 << 16 | 0 << 8 | 0;
+ int ASM5 = 5 << 16 | 0 << 8 | 0;
+
+ // versions
+
+ int V1_1 = 3 << 16 | 45;
+ int V1_2 = 0 << 16 | 46;
+ int V1_3 = 0 << 16 | 47;
+ int V1_4 = 0 << 16 | 48;
+ int V1_5 = 0 << 16 | 49;
+ int V1_6 = 0 << 16 | 50;
+ int V1_7 = 0 << 16 | 51;
+ int V1_8 = 0 << 16 | 52;
+
+ // access flags
+
+ int ACC_PUBLIC = 0x0001; // class, field, method
+ int ACC_PRIVATE = 0x0002; // class, field, method
+ int ACC_PROTECTED = 0x0004; // class, field, method
+ int ACC_STATIC = 0x0008; // field, method
+ int ACC_FINAL = 0x0010; // class, field, method, parameter
+ int ACC_SUPER = 0x0020; // class
+ int ACC_SYNCHRONIZED = 0x0020; // method
+ int ACC_VOLATILE = 0x0040; // field
+ int ACC_BRIDGE = 0x0040; // method
+ int ACC_VARARGS = 0x0080; // method
+ int ACC_TRANSIENT = 0x0080; // field
+ int ACC_NATIVE = 0x0100; // method
+ int ACC_INTERFACE = 0x0200; // class
+ int ACC_ABSTRACT = 0x0400; // class, method
+ int ACC_STRICT = 0x0800; // method
+ int ACC_SYNTHETIC = 0x1000; // class, field, method, parameter
+ int ACC_ANNOTATION = 0x2000; // class
+ int ACC_ENUM = 0x4000; // class(?) field inner
+ int ACC_MANDATED = 0x8000; // parameter
+
+ // ASM specific pseudo access flags
+
+ int ACC_DEPRECATED = 0x20000; // class, field, method
+
+ // types for NEWARRAY
+
+ int T_BOOLEAN = 4;
+ int T_CHAR = 5;
+ int T_FLOAT = 6;
+ int T_DOUBLE = 7;
+ int T_BYTE = 8;
+ int T_SHORT = 9;
+ int T_INT = 10;
+ int T_LONG = 11;
+
+ // tags for Handle
+
+ int H_GETFIELD = 1;
+ int H_GETSTATIC = 2;
+ int H_PUTFIELD = 3;
+ int H_PUTSTATIC = 4;
+ int H_INVOKEVIRTUAL = 5;
+ int H_INVOKESTATIC = 6;
+ int H_INVOKESPECIAL = 7;
+ int H_NEWINVOKESPECIAL = 8;
+ int H_INVOKEINTERFACE = 9;
+
+ // stack map frame types
+
+ /**
+ * Represents an expanded frame. See {@link ClassReader#EXPAND_FRAMES}.
+ */
+ int F_NEW = -1;
+
+ /**
+ * Represents a compressed frame with complete frame data.
+ */
+ int F_FULL = 0;
+
+ /**
+ * Represents a compressed frame where locals are the same as the locals in
+ * the previous frame, except that additional 1-3 locals are defined, and
+ * with an empty stack.
+ */
+ int F_APPEND = 1;
+
+ /**
+ * Represents a compressed frame where locals are the same as the locals in
+ * the previous frame, except that the last 1-3 locals are absent and with
+ * an empty stack.
+ */
+ int F_CHOP = 2;
+
+ /**
+ * Represents a compressed frame with exactly the same locals as the
+ * previous frame and with an empty stack.
+ */
+ int F_SAME = 3;
+
+ /**
+ * Represents a compressed frame with exactly the same locals as the
+ * previous frame and with a single value on the stack.
+ */
+ int F_SAME1 = 4;
+
+ Integer TOP = new Integer(0);
+ Integer INTEGER = new Integer(1);
+ Integer FLOAT = new Integer(2);
+ Integer DOUBLE = new Integer(3);
+ Integer LONG = new Integer(4);
+ Integer NULL = new Integer(5);
+ Integer UNINITIALIZED_THIS = new Integer(6);
+
+ // opcodes // visit method (- = idem)
+
+ int NOP = 0; // visitInsn
+ int ACONST_NULL = 1; // -
+ int ICONST_M1 = 2; // -
+ int ICONST_0 = 3; // -
+ int ICONST_1 = 4; // -
+ int ICONST_2 = 5; // -
+ int ICONST_3 = 6; // -
+ int ICONST_4 = 7; // -
+ int ICONST_5 = 8; // -
+ int LCONST_0 = 9; // -
+ int LCONST_1 = 10; // -
+ int FCONST_0 = 11; // -
+ int FCONST_1 = 12; // -
+ int FCONST_2 = 13; // -
+ int DCONST_0 = 14; // -
+ int DCONST_1 = 15; // -
+ int BIPUSH = 16; // visitIntInsn
+ int SIPUSH = 17; // -
+ int LDC = 18; // visitLdcInsn
+ // int LDC_W = 19; // -
+ // int LDC2_W = 20; // -
+ int ILOAD = 21; // visitVarInsn
+ int LLOAD = 22; // -
+ int FLOAD = 23; // -
+ int DLOAD = 24; // -
+ int ALOAD = 25; // -
+ // int ILOAD_0 = 26; // -
+ // int ILOAD_1 = 27; // -
+ // int ILOAD_2 = 28; // -
+ // int ILOAD_3 = 29; // -
+ // int LLOAD_0 = 30; // -
+ // int LLOAD_1 = 31; // -
+ // int LLOAD_2 = 32; // -
+ // int LLOAD_3 = 33; // -
+ // int FLOAD_0 = 34; // -
+ // int FLOAD_1 = 35; // -
+ // int FLOAD_2 = 36; // -
+ // int FLOAD_3 = 37; // -
+ // int DLOAD_0 = 38; // -
+ // int DLOAD_1 = 39; // -
+ // int DLOAD_2 = 40; // -
+ // int DLOAD_3 = 41; // -
+ // int ALOAD_0 = 42; // -
+ // int ALOAD_1 = 43; // -
+ // int ALOAD_2 = 44; // -
+ // int ALOAD_3 = 45; // -
+ int IALOAD = 46; // visitInsn
+ int LALOAD = 47; // -
+ int FALOAD = 48; // -
+ int DALOAD = 49; // -
+ int AALOAD = 50; // -
+ int BALOAD = 51; // -
+ int CALOAD = 52; // -
+ int SALOAD = 53; // -
+ int ISTORE = 54; // visitVarInsn
+ int LSTORE = 55; // -
+ int FSTORE = 56; // -
+ int DSTORE = 57; // -
+ int ASTORE = 58; // -
+ // int ISTORE_0 = 59; // -
+ // int ISTORE_1 = 60; // -
+ // int ISTORE_2 = 61; // -
+ // int ISTORE_3 = 62; // -
+ // int LSTORE_0 = 63; // -
+ // int LSTORE_1 = 64; // -
+ // int LSTORE_2 = 65; // -
+ // int LSTORE_3 = 66; // -
+ // int FSTORE_0 = 67; // -
+ // int FSTORE_1 = 68; // -
+ // int FSTORE_2 = 69; // -
+ // int FSTORE_3 = 70; // -
+ // int DSTORE_0 = 71; // -
+ // int DSTORE_1 = 72; // -
+ // int DSTORE_2 = 73; // -
+ // int DSTORE_3 = 74; // -
+ // int ASTORE_0 = 75; // -
+ // int ASTORE_1 = 76; // -
+ // int ASTORE_2 = 77; // -
+ // int ASTORE_3 = 78; // -
+ int IASTORE = 79; // visitInsn
+ int LASTORE = 80; // -
+ int FASTORE = 81; // -
+ int DASTORE = 82; // -
+ int AASTORE = 83; // -
+ int BASTORE = 84; // -
+ int CASTORE = 85; // -
+ int SASTORE = 86; // -
+ int POP = 87; // -
+ int POP2 = 88; // -
+ int DUP = 89; // -
+ int DUP_X1 = 90; // -
+ int DUP_X2 = 91; // -
+ int DUP2 = 92; // -
+ int DUP2_X1 = 93; // -
+ int DUP2_X2 = 94; // -
+ int SWAP = 95; // -
+ int IADD = 96; // -
+ int LADD = 97; // -
+ int FADD = 98; // -
+ int DADD = 99; // -
+ int ISUB = 100; // -
+ int LSUB = 101; // -
+ int FSUB = 102; // -
+ int DSUB = 103; // -
+ int IMUL = 104; // -
+ int LMUL = 105; // -
+ int FMUL = 106; // -
+ int DMUL = 107; // -
+ int IDIV = 108; // -
+ int LDIV = 109; // -
+ int FDIV = 110; // -
+ int DDIV = 111; // -
+ int IREM = 112; // -
+ int LREM = 113; // -
+ int FREM = 114; // -
+ int DREM = 115; // -
+ int INEG = 116; // -
+ int LNEG = 117; // -
+ int FNEG = 118; // -
+ int DNEG = 119; // -
+ int ISHL = 120; // -
+ int LSHL = 121; // -
+ int ISHR = 122; // -
+ int LSHR = 123; // -
+ int IUSHR = 124; // -
+ int LUSHR = 125; // -
+ int IAND = 126; // -
+ int LAND = 127; // -
+ int IOR = 128; // -
+ int LOR = 129; // -
+ int IXOR = 130; // -
+ int LXOR = 131; // -
+ int IINC = 132; // visitIincInsn
+ int I2L = 133; // visitInsn
+ int I2F = 134; // -
+ int I2D = 135; // -
+ int L2I = 136; // -
+ int L2F = 137; // -
+ int L2D = 138; // -
+ int F2I = 139; // -
+ int F2L = 140; // -
+ int F2D = 141; // -
+ int D2I = 142; // -
+ int D2L = 143; // -
+ int D2F = 144; // -
+ int I2B = 145; // -
+ int I2C = 146; // -
+ int I2S = 147; // -
+ int LCMP = 148; // -
+ int FCMPL = 149; // -
+ int FCMPG = 150; // -
+ int DCMPL = 151; // -
+ int DCMPG = 152; // -
+ int IFEQ = 153; // visitJumpInsn
+ int IFNE = 154; // -
+ int IFLT = 155; // -
+ int IFGE = 156; // -
+ int IFGT = 157; // -
+ int IFLE = 158; // -
+ int IF_ICMPEQ = 159; // -
+ int IF_ICMPNE = 160; // -
+ int IF_ICMPLT = 161; // -
+ int IF_ICMPGE = 162; // -
+ int IF_ICMPGT = 163; // -
+ int IF_ICMPLE = 164; // -
+ int IF_ACMPEQ = 165; // -
+ int IF_ACMPNE = 166; // -
+ int GOTO = 167; // -
+ int JSR = 168; // -
+ int RET = 169; // visitVarInsn
+ int TABLESWITCH = 170; // visiTableSwitchInsn
+ int LOOKUPSWITCH = 171; // visitLookupSwitch
+ int IRETURN = 172; // visitInsn
+ int LRETURN = 173; // -
+ int FRETURN = 174; // -
+ int DRETURN = 175; // -
+ int ARETURN = 176; // -
+ int RETURN = 177; // -
+ int GETSTATIC = 178; // visitFieldInsn
+ int PUTSTATIC = 179; // -
+ int GETFIELD = 180; // -
+ int PUTFIELD = 181; // -
+ int INVOKEVIRTUAL = 182; // visitMethodInsn
+ int INVOKESPECIAL = 183; // -
+ int INVOKESTATIC = 184; // -
+ int INVOKEINTERFACE = 185; // -
+ int INVOKEDYNAMIC = 186; // visitInvokeDynamicInsn
+ int NEW = 187; // visitTypeInsn
+ int NEWARRAY = 188; // visitIntInsn
+ int ANEWARRAY = 189; // visitTypeInsn
+ int ARRAYLENGTH = 190; // visitInsn
+ int ATHROW = 191; // -
+ int CHECKCAST = 192; // visitTypeInsn
+ int INSTANCEOF = 193; // -
+ int MONITORENTER = 194; // visitInsn
+ int MONITOREXIT = 195; // -
+ // int WIDE = 196; // NOT VISITED
+ int MULTIANEWARRAY = 197; // visitMultiANewArrayInsn
+ int IFNULL = 198; // visitJumpInsn
+ int IFNONNULL = 199; // -
+ // int GOTO_W = 200; // -
+ // int JSR_W = 201; // -
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/Type.java b/spring-core/src/main/java/org/springframework/asm/Type.java
new file mode 100644
index 00000000..6a835c25
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/Type.java
@@ -0,0 +1,896 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2011 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.springframework.asm;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/**
+ * A Java field or method type. This class can be used to make it easier to
+ * manipulate type and method descriptors.
+ *
+ * @author Eric Bruneton
+ * @author Chris Nokleberg
+ */
+public class Type {
+
+ /**
+ * The sort of the <tt>void</tt> type. See {@link #getSort getSort}.
+ */
+ public static final int VOID = 0;
+
+ /**
+ * The sort of the <tt>boolean</tt> type. See {@link #getSort getSort}.
+ */
+ public static final int BOOLEAN = 1;
+
+ /**
+ * The sort of the <tt>char</tt> type. See {@link #getSort getSort}.
+ */
+ public static final int CHAR = 2;
+
+ /**
+ * The sort of the <tt>byte</tt> type. See {@link #getSort getSort}.
+ */
+ public static final int BYTE = 3;
+
+ /**
+ * The sort of the <tt>short</tt> type. See {@link #getSort getSort}.
+ */
+ public static final int SHORT = 4;
+
+ /**
+ * The sort of the <tt>int</tt> type. See {@link #getSort getSort}.
+ */
+ public static final int INT = 5;
+
+ /**
+ * The sort of the <tt>float</tt> type. See {@link #getSort getSort}.
+ */
+ public static final int FLOAT = 6;
+
+ /**
+ * The sort of the <tt>long</tt> type. See {@link #getSort getSort}.
+ */
+ public static final int LONG = 7;
+
+ /**
+ * The sort of the <tt>double</tt> type. See {@link #getSort getSort}.
+ */
+ public static final int DOUBLE = 8;
+
+ /**
+ * The sort of array reference types. See {@link #getSort getSort}.
+ */
+ public static final int ARRAY = 9;
+
+ /**
+ * The sort of object reference types. See {@link #getSort getSort}.
+ */
+ public static final int OBJECT = 10;
+
+ /**
+ * The sort of method types. See {@link #getSort getSort}.
+ */
+ public static final int METHOD = 11;
+
+ /**
+ * The <tt>void</tt> type.
+ */
+ public static final Type VOID_TYPE = new Type(VOID, null, ('V' << 24)
+ | (5 << 16) | (0 << 8) | 0, 1);
+
+ /**
+ * The <tt>boolean</tt> type.
+ */
+ public static final Type BOOLEAN_TYPE = new Type(BOOLEAN, null, ('Z' << 24)
+ | (0 << 16) | (5 << 8) | 1, 1);
+
+ /**
+ * The <tt>char</tt> type.
+ */
+ public static final Type CHAR_TYPE = new Type(CHAR, null, ('C' << 24)
+ | (0 << 16) | (6 << 8) | 1, 1);
+
+ /**
+ * The <tt>byte</tt> type.
+ */
+ public static final Type BYTE_TYPE = new Type(BYTE, null, ('B' << 24)
+ | (0 << 16) | (5 << 8) | 1, 1);
+
+ /**
+ * The <tt>short</tt> type.
+ */
+ public static final Type SHORT_TYPE = new Type(SHORT, null, ('S' << 24)
+ | (0 << 16) | (7 << 8) | 1, 1);
+
+ /**
+ * The <tt>int</tt> type.
+ */
+ public static final Type INT_TYPE = new Type(INT, null, ('I' << 24)
+ | (0 << 16) | (0 << 8) | 1, 1);
+
+ /**
+ * The <tt>float</tt> type.
+ */
+ public static final Type FLOAT_TYPE = new Type(FLOAT, null, ('F' << 24)
+ | (2 << 16) | (2 << 8) | 1, 1);
+
+ /**
+ * The <tt>long</tt> type.
+ */
+ public static final Type LONG_TYPE = new Type(LONG, null, ('J' << 24)
+ | (1 << 16) | (1 << 8) | 2, 1);
+
+ /**
+ * The <tt>double</tt> type.
+ */
+ public static final Type DOUBLE_TYPE = new Type(DOUBLE, null, ('D' << 24)
+ | (3 << 16) | (3 << 8) | 2, 1);
+
+ // ------------------------------------------------------------------------
+ // Fields
+ // ------------------------------------------------------------------------
+
+ /**
+ * The sort of this Java type.
+ */
+ private final int sort;
+
+ /**
+ * A buffer containing the internal name of this Java type. This field is
+ * only used for reference types.
+ */
+ private final char[] buf;
+
+ /**
+ * The offset of the internal name of this Java type in {@link #buf buf} or,
+ * for primitive types, the size, descriptor and getOpcode offsets for this
+ * type (byte 0 contains the size, byte 1 the descriptor, byte 2 the offset
+ * for IALOAD or IASTORE, byte 3 the offset for all other instructions).
+ */
+ private final int off;
+
+ /**
+ * The length of the internal name of this Java type.
+ */
+ private final int len;
+
+ // ------------------------------------------------------------------------
+ // Constructors
+ // ------------------------------------------------------------------------
+
+ /**
+ * Constructs a reference type.
+ *
+ * @param sort
+ * the sort of the reference type to be constructed.
+ * @param buf
+ * a buffer containing the descriptor of the previous type.
+ * @param off
+ * the offset of this descriptor in the previous buffer.
+ * @param len
+ * the length of this descriptor.
+ */
+ private Type(final int sort, final char[] buf, final int off, final int len) {
+ this.sort = sort;
+ this.buf = buf;
+ this.off = off;
+ this.len = len;
+ }
+
+ /**
+ * Returns the Java type corresponding to the given type descriptor.
+ *
+ * @param typeDescriptor
+ * a field or method type descriptor.
+ * @return the Java type corresponding to the given type descriptor.
+ */
+ public static Type getType(final String typeDescriptor) {
+ return getType(typeDescriptor.toCharArray(), 0);
+ }
+
+ /**
+ * Returns the Java type corresponding to the given internal name.
+ *
+ * @param internalName
+ * an internal name.
+ * @return the Java type corresponding to the given internal name.
+ */
+ public static Type getObjectType(final String internalName) {
+ char[] buf = internalName.toCharArray();
+ return new Type(buf[0] == '[' ? ARRAY : OBJECT, buf, 0, buf.length);
+ }
+
+ /**
+ * Returns the Java type corresponding to the given method descriptor.
+ * Equivalent to <code>Type.getType(methodDescriptor)</code>.
+ *
+ * @param methodDescriptor
+ * a method descriptor.
+ * @return the Java type corresponding to the given method descriptor.
+ */
+ public static Type getMethodType(final String methodDescriptor) {
+ return getType(methodDescriptor.toCharArray(), 0);
+ }
+
+ /**
+ * Returns the Java method type corresponding to the given argument and
+ * return types.
+ *
+ * @param returnType
+ * the return type of the method.
+ * @param argumentTypes
+ * the argument types of the method.
+ * @return the Java type corresponding to the given argument and return
+ * types.
+ */
+ public static Type getMethodType(final Type returnType,
+ final Type... argumentTypes) {
+ return getType(getMethodDescriptor(returnType, argumentTypes));
+ }
+
+ /**
+ * Returns the Java type corresponding to the given class.
+ *
+ * @param c
+ * a class.
+ * @return the Java type corresponding to the given class.
+ */
+ public static Type getType(final Class<?> c) {
+ if (c.isPrimitive()) {
+ if (c == Integer.TYPE) {
+ return INT_TYPE;
+ } else if (c == Void.TYPE) {
+ return VOID_TYPE;
+ } else if (c == Boolean.TYPE) {
+ return BOOLEAN_TYPE;
+ } else if (c == Byte.TYPE) {
+ return BYTE_TYPE;
+ } else if (c == Character.TYPE) {
+ return CHAR_TYPE;
+ } else if (c == Short.TYPE) {
+ return SHORT_TYPE;
+ } else if (c == Double.TYPE) {
+ return DOUBLE_TYPE;
+ } else if (c == Float.TYPE) {
+ return FLOAT_TYPE;
+ } else /* if (c == Long.TYPE) */{
+ return LONG_TYPE;
+ }
+ } else {
+ return getType(getDescriptor(c));
+ }
+ }
+
+ /**
+ * Returns the Java method type corresponding to the given constructor.
+ *
+ * @param c
+ * a {@link Constructor Constructor} object.
+ * @return the Java method type corresponding to the given constructor.
+ */
+ public static Type getType(final Constructor<?> c) {
+ return getType(getConstructorDescriptor(c));
+ }
+
+ /**
+ * Returns the Java method type corresponding to the given method.
+ *
+ * @param m
+ * a {@link Method Method} object.
+ * @return the Java method type corresponding to the given method.
+ */
+ public static Type getType(final Method m) {
+ return getType(getMethodDescriptor(m));
+ }
+
+ /**
+ * Returns the Java types corresponding to the argument types of the given
+ * method descriptor.
+ *
+ * @param methodDescriptor
+ * a method descriptor.
+ * @return the Java types corresponding to the argument types of the given
+ * method descriptor.
+ */
+ public static Type[] getArgumentTypes(final String methodDescriptor) {
+ char[] buf = methodDescriptor.toCharArray();
+ int off = 1;
+ int size = 0;
+ while (true) {
+ char car = buf[off++];
+ if (car == ')') {
+ break;
+ } else if (car == 'L') {
+ while (buf[off++] != ';') {
+ }
+ ++size;
+ } else if (car != '[') {
+ ++size;
+ }
+ }
+ Type[] args = new Type[size];
+ off = 1;
+ size = 0;
+ while (buf[off] != ')') {
+ args[size] = getType(buf, off);
+ off += args[size].len + (args[size].sort == OBJECT ? 2 : 0);
+ size += 1;
+ }
+ return args;
+ }
+
+ /**
+ * Returns the Java types corresponding to the argument types of the given
+ * method.
+ *
+ * @param method
+ * a method.
+ * @return the Java types corresponding to the argument types of the given
+ * method.
+ */
+ public static Type[] getArgumentTypes(final Method method) {
+ Class<?>[] classes = method.getParameterTypes();
+ Type[] types = new Type[classes.length];
+ for (int i = classes.length - 1; i >= 0; --i) {
+ types[i] = getType(classes[i]);
+ }
+ return types;
+ }
+
+ /**
+ * Returns the Java type corresponding to the return type of the given
+ * method descriptor.
+ *
+ * @param methodDescriptor
+ * a method descriptor.
+ * @return the Java type corresponding to the return type of the given
+ * method descriptor.
+ */
+ public static Type getReturnType(final String methodDescriptor) {
+ char[] buf = methodDescriptor.toCharArray();
+ return getType(buf, methodDescriptor.indexOf(')') + 1);
+ }
+
+ /**
+ * Returns the Java type corresponding to the return type of the given
+ * method.
+ *
+ * @param method
+ * a method.
+ * @return the Java type corresponding to the return type of the given
+ * method.
+ */
+ public static Type getReturnType(final Method method) {
+ return getType(method.getReturnType());
+ }
+
+ /**
+ * Computes the size of the arguments and of the return value of a method.
+ *
+ * @param desc
+ * the descriptor of a method.
+ * @return the size of the arguments of the method (plus one for the
+ * implicit this argument), argSize, and the size of its return
+ * value, retSize, packed into a single int i =
+ * <tt>(argSize &lt;&lt; 2) | retSize</tt> (argSize is therefore equal to
+ * <tt>i &gt;&gt; 2</tt>, and retSize to <tt>i &amp; 0x03</tt>).
+ */
+ public static int getArgumentsAndReturnSizes(final String desc) {
+ int n = 1;
+ int c = 1;
+ while (true) {
+ char car = desc.charAt(c++);
+ if (car == ')') {
+ car = desc.charAt(c);
+ return n << 2
+ | (car == 'V' ? 0 : (car == 'D' || car == 'J' ? 2 : 1));
+ } else if (car == 'L') {
+ while (desc.charAt(c++) != ';') {
+ }
+ n += 1;
+ } else if (car == '[') {
+ while ((car = desc.charAt(c)) == '[') {
+ ++c;
+ }
+ if (car == 'D' || car == 'J') {
+ n -= 1;
+ }
+ } else if (car == 'D' || car == 'J') {
+ n += 2;
+ } else {
+ n += 1;
+ }
+ }
+ }
+
+ /**
+ * Returns the Java type corresponding to the given type descriptor. For
+ * method descriptors, buf is supposed to contain nothing more than the
+ * descriptor itself.
+ *
+ * @param buf
+ * a buffer containing a type descriptor.
+ * @param off
+ * the offset of this descriptor in the previous buffer.
+ * @return the Java type corresponding to the given type descriptor.
+ */
+ private static Type getType(final char[] buf, final int off) {
+ int len;
+ switch (buf[off]) {
+ case 'V':
+ return VOID_TYPE;
+ case 'Z':
+ return BOOLEAN_TYPE;
+ case 'C':
+ return CHAR_TYPE;
+ case 'B':
+ return BYTE_TYPE;
+ case 'S':
+ return SHORT_TYPE;
+ case 'I':
+ return INT_TYPE;
+ case 'F':
+ return FLOAT_TYPE;
+ case 'J':
+ return LONG_TYPE;
+ case 'D':
+ return DOUBLE_TYPE;
+ case '[':
+ len = 1;
+ while (buf[off + len] == '[') {
+ ++len;
+ }
+ if (buf[off + len] == 'L') {
+ ++len;
+ while (buf[off + len] != ';') {
+ ++len;
+ }
+ }
+ return new Type(ARRAY, buf, off, len + 1);
+ case 'L':
+ len = 1;
+ while (buf[off + len] != ';') {
+ ++len;
+ }
+ return new Type(OBJECT, buf, off + 1, len - 1);
+ // case '(':
+ default:
+ return new Type(METHOD, buf, off, buf.length - off);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Accessors
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the sort of this Java type.
+ *
+ * @return {@link #VOID VOID}, {@link #BOOLEAN BOOLEAN}, {@link #CHAR CHAR},
+ * {@link #BYTE BYTE}, {@link #SHORT SHORT}, {@link #INT INT},
+ * {@link #FLOAT FLOAT}, {@link #LONG LONG}, {@link #DOUBLE DOUBLE},
+ * {@link #ARRAY ARRAY}, {@link #OBJECT OBJECT} or {@link #METHOD
+ * METHOD}.
+ */
+ public int getSort() {
+ return sort;
+ }
+
+ /**
+ * Returns the number of dimensions of this array type. This method should
+ * only be used for an array type.
+ *
+ * @return the number of dimensions of this array type.
+ */
+ public int getDimensions() {
+ int i = 1;
+ while (buf[off + i] == '[') {
+ ++i;
+ }
+ return i;
+ }
+
+ /**
+ * Returns the type of the elements of this array type. This method should
+ * only be used for an array type.
+ *
+ * @return Returns the type of the elements of this array type.
+ */
+ public Type getElementType() {
+ return getType(buf, off + getDimensions());
+ }
+
+ /**
+ * Returns the binary name of the class corresponding to this type. This
+ * method must not be used on method types.
+ *
+ * @return the binary name of the class corresponding to this type.
+ */
+ public String getClassName() {
+ switch (sort) {
+ case VOID:
+ return "void";
+ case BOOLEAN:
+ return "boolean";
+ case CHAR:
+ return "char";
+ case BYTE:
+ return "byte";
+ case SHORT:
+ return "short";
+ case INT:
+ return "int";
+ case FLOAT:
+ return "float";
+ case LONG:
+ return "long";
+ case DOUBLE:
+ return "double";
+ case ARRAY:
+ StringBuilder sb = new StringBuilder(getElementType().getClassName());
+ for (int i = getDimensions(); i > 0; --i) {
+ sb.append("[]");
+ }
+ return sb.toString();
+ case OBJECT:
+ return new String(buf, off, len).replace('/', '.');
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Returns the internal name of the class corresponding to this object or
+ * array type. The internal name of a class is its fully qualified name (as
+ * returned by Class.getName(), where '.' are replaced by '/'. This method
+ * should only be used for an object or array type.
+ *
+ * @return the internal name of the class corresponding to this object type.
+ */
+ public String getInternalName() {
+ return new String(buf, off, len);
+ }
+
+ /**
+ * Returns the argument types of methods of this type. This method should
+ * only be used for method types.
+ *
+ * @return the argument types of methods of this type.
+ */
+ public Type[] getArgumentTypes() {
+ return getArgumentTypes(getDescriptor());
+ }
+
+ /**
+ * Returns the return type of methods of this type. This method should only
+ * be used for method types.
+ *
+ * @return the return type of methods of this type.
+ */
+ public Type getReturnType() {
+ return getReturnType(getDescriptor());
+ }
+
+ /**
+ * Returns the size of the arguments and of the return value of methods of
+ * this type. This method should only be used for method types.
+ *
+ * @return the size of the arguments (plus one for the implicit this
+ * argument), argSize, and the size of the return value, retSize,
+ * packed into a single
+ * int i = <tt>(argSize &lt;&lt; 2) | retSize</tt>
+ * (argSize is therefore equal to <tt>i &gt;&gt; 2</tt>,
+ * and retSize to <tt>i &amp; 0x03</tt>).
+ */
+ public int getArgumentsAndReturnSizes() {
+ return getArgumentsAndReturnSizes(getDescriptor());
+ }
+
+ // ------------------------------------------------------------------------
+ // Conversion to type descriptors
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the descriptor corresponding to this Java type.
+ *
+ * @return the descriptor corresponding to this Java type.
+ */
+ public String getDescriptor() {
+ StringBuilder sb = new StringBuilder();
+ getDescriptor(sb);
+ return sb.toString();
+ }
+
+ /**
+ * Returns the descriptor corresponding to the given argument and return
+ * types.
+ *
+ * @param returnType
+ * the return type of the method.
+ * @param argumentTypes
+ * the argument types of the method.
+ * @return the descriptor corresponding to the given argument and return
+ * types.
+ */
+ public static String getMethodDescriptor(final Type returnType,
+ final Type... argumentTypes) {
+ StringBuilder sb = new StringBuilder();
+ sb.append('(');
+ for (int i = 0; i < argumentTypes.length; ++i) {
+ argumentTypes[i].getDescriptor(sb);
+ }
+ sb.append(')');
+ returnType.getDescriptor(sb);
+ return sb.toString();
+ }
+
+ /**
+ * Appends the descriptor corresponding to this Java type to the given
+ * string builder.
+ *
+ * @param sb
+ * the string builder to which the descriptor must be appended.
+ */
+ private void getDescriptor(final StringBuilder sb) {
+ if (this.buf == null) {
+ // descriptor is in byte 3 of 'off' for primitive types (buf ==
+ // null)
+ sb.append((char) ((off & 0xFF000000) >>> 24));
+ } else if (sort == OBJECT) {
+ sb.append('L');
+ sb.append(this.buf, off, len);
+ sb.append(';');
+ } else { // sort == ARRAY || sort == METHOD
+ sb.append(this.buf, off, len);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Direct conversion from classes to type descriptors,
+ // without intermediate Type objects
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the internal name of the given class. The internal name of a
+ * class is its fully qualified name, as returned by Class.getName(), where
+ * '.' are replaced by '/'.
+ *
+ * @param c
+ * an object or array class.
+ * @return the internal name of the given class.
+ */
+ public static String getInternalName(final Class<?> c) {
+ return c.getName().replace('.', '/');
+ }
+
+ /**
+ * Returns the descriptor corresponding to the given Java type.
+ *
+ * @param c
+ * an object class, a primitive class or an array class.
+ * @return the descriptor corresponding to the given class.
+ */
+ public static String getDescriptor(final Class<?> c) {
+ StringBuilder sb = new StringBuilder();
+ getDescriptor(sb, c);
+ return sb.toString();
+ }
+
+ /**
+ * Returns the descriptor corresponding to the given constructor.
+ *
+ * @param c
+ * a {@link Constructor Constructor} object.
+ * @return the descriptor of the given constructor.
+ */
+ public static String getConstructorDescriptor(final Constructor<?> c) {
+ Class<?>[] parameters = c.getParameterTypes();
+ StringBuilder sb = new StringBuilder();
+ sb.append('(');
+ for (int i = 0; i < parameters.length; ++i) {
+ getDescriptor(sb, parameters[i]);
+ }
+ return sb.append(")V").toString();
+ }
+
+ /**
+ * Returns the descriptor corresponding to the given method.
+ *
+ * @param m
+ * a {@link Method Method} object.
+ * @return the descriptor of the given method.
+ */
+ public static String getMethodDescriptor(final Method m) {
+ Class<?>[] parameters = m.getParameterTypes();
+ StringBuilder sb = new StringBuilder();
+ sb.append('(');
+ for (int i = 0; i < parameters.length; ++i) {
+ getDescriptor(sb, parameters[i]);
+ }
+ sb.append(')');
+ getDescriptor(sb, m.getReturnType());
+ return sb.toString();
+ }
+
+ /**
+ * Appends the descriptor of the given class to the given string builder.
+ *
+ * @param sb
+ * the string buffer to which the descriptor must be appended.
+ * @param c
+ * the class whose descriptor must be computed.
+ */
+ private static void getDescriptor(final StringBuilder sb, final Class<?> c) {
+ Class<?> d = c;
+ while (true) {
+ if (d.isPrimitive()) {
+ char car;
+ if (d == Integer.TYPE) {
+ car = 'I';
+ } else if (d == Void.TYPE) {
+ car = 'V';
+ } else if (d == Boolean.TYPE) {
+ car = 'Z';
+ } else if (d == Byte.TYPE) {
+ car = 'B';
+ } else if (d == Character.TYPE) {
+ car = 'C';
+ } else if (d == Short.TYPE) {
+ car = 'S';
+ } else if (d == Double.TYPE) {
+ car = 'D';
+ } else if (d == Float.TYPE) {
+ car = 'F';
+ } else /* if (d == Long.TYPE) */{
+ car = 'J';
+ }
+ sb.append(car);
+ return;
+ } else if (d.isArray()) {
+ sb.append('[');
+ d = d.getComponentType();
+ } else {
+ sb.append('L');
+ String name = d.getName();
+ int len = name.length();
+ for (int i = 0; i < len; ++i) {
+ char car = name.charAt(i);
+ sb.append(car == '.' ? '/' : car);
+ }
+ sb.append(';');
+ return;
+ }
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Corresponding size and opcodes
+ // ------------------------------------------------------------------------
+
+ /**
+ * Returns the size of values of this type. This method must not be used for
+ * method types.
+ *
+ * @return the size of values of this type, i.e., 2 for <tt>long</tt> and
+ * <tt>double</tt>, 0 for <tt>void</tt> and 1 otherwise.
+ */
+ public int getSize() {
+ // the size is in byte 0 of 'off' for primitive types (buf == null)
+ return buf == null ? (off & 0xFF) : 1;
+ }
+
+ /**
+ * Returns a JVM instruction opcode adapted to this Java type. This method
+ * must not be used for method types.
+ *
+ * @param opcode
+ * a JVM instruction opcode. This opcode must be one of ILOAD,
+ * ISTORE, IALOAD, IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG,
+ * ISHL, ISHR, IUSHR, IAND, IOR, IXOR and IRETURN.
+ * @return an opcode that is similar to the given opcode, but adapted to
+ * this Java type. For example, if this type is <tt>float</tt> and
+ * <tt>opcode</tt> is IRETURN, this method returns FRETURN.
+ */
+ public int getOpcode(final int opcode) {
+ if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) {
+ // the offset for IALOAD or IASTORE is in byte 1 of 'off' for
+ // primitive types (buf == null)
+ return opcode + (buf == null ? (off & 0xFF00) >> 8 : 4);
+ } else {
+ // the offset for other instructions is in byte 2 of 'off' for
+ // primitive types (buf == null)
+ return opcode + (buf == null ? (off & 0xFF0000) >> 16 : 4);
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // Equals, hashCode and toString
+ // ------------------------------------------------------------------------
+
+ /**
+ * Tests if the given object is equal to this type.
+ *
+ * @param o
+ * the object to be compared to this type.
+ * @return <tt>true</tt> if the given object is equal to this type.
+ */
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Type)) {
+ return false;
+ }
+ Type t = (Type) o;
+ if (sort != t.sort) {
+ return false;
+ }
+ if (sort >= ARRAY) {
+ if (len != t.len) {
+ return false;
+ }
+ for (int i = off, j = t.off, end = i + len; i < end; i++, j++) {
+ if (buf[i] != t.buf[j]) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns a hash code value for this type.
+ *
+ * @return a hash code value for this type.
+ */
+ @Override
+ public int hashCode() {
+ int hc = 13 * sort;
+ if (sort >= ARRAY) {
+ for (int i = off, end = i + len; i < end; i++) {
+ hc = 17 * (hc + buf[i]);
+ }
+ }
+ return hc;
+ }
+
+ /**
+ * Returns a string representation of this type.
+ *
+ * @return the descriptor of this type.
+ */
+ @Override
+ public String toString() {
+ return getDescriptor();
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/TypePath.java b/spring-core/src/main/java/org/springframework/asm/TypePath.java
new file mode 100644
index 00000000..87df7c2a
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/TypePath.java
@@ -0,0 +1,196 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2013 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.springframework.asm;
+
+/**
+ * The path to a type argument, wildcard bound, array element type, or static
+ * inner type within an enclosing type.
+ *
+ * @author Eric Bruneton
+ */
+public class TypePath {
+
+ /**
+ * A type path step that steps into the element type of an array type. See
+ * {@link #getStep getStep}.
+ */
+ public final static int ARRAY_ELEMENT = 0;
+
+ /**
+ * A type path step that steps into the nested type of a class type. See
+ * {@link #getStep getStep}.
+ */
+ public final static int INNER_TYPE = 1;
+
+ /**
+ * A type path step that steps into the bound of a wildcard type. See
+ * {@link #getStep getStep}.
+ */
+ public final static int WILDCARD_BOUND = 2;
+
+ /**
+ * A type path step that steps into a type argument of a generic type. See
+ * {@link #getStep getStep}.
+ */
+ public final static int TYPE_ARGUMENT = 3;
+
+ /**
+ * The byte array where the path is stored, in Java class file format.
+ */
+ byte[] b;
+
+ /**
+ * The offset of the first byte of the type path in 'b'.
+ */
+ int offset;
+
+ /**
+ * Creates a new type path.
+ *
+ * @param b
+ * the byte array containing the type path in Java class file
+ * format.
+ * @param offset
+ * the offset of the first byte of the type path in 'b'.
+ */
+ TypePath(byte[] b, int offset) {
+ this.b = b;
+ this.offset = offset;
+ }
+
+ /**
+ * Returns the length of this path.
+ *
+ * @return the length of this path.
+ */
+ public int getLength() {
+ return b[offset];
+ }
+
+ /**
+ * Returns the value of the given step of this path.
+ *
+ * @param index
+ * an index between 0 and {@link #getLength()}, exclusive.
+ * @return {@link #ARRAY_ELEMENT ARRAY_ELEMENT}, {@link #INNER_TYPE
+ * INNER_TYPE}, {@link #WILDCARD_BOUND WILDCARD_BOUND}, or
+ * {@link #TYPE_ARGUMENT TYPE_ARGUMENT}.
+ */
+ public int getStep(int index) {
+ return b[offset + 2 * index + 1];
+ }
+
+ /**
+ * Returns the index of the type argument that the given step is stepping
+ * into. This method should only be used for steps whose value is
+ * {@link #TYPE_ARGUMENT TYPE_ARGUMENT}.
+ *
+ * @param index
+ * an index between 0 and {@link #getLength()}, exclusive.
+ * @return the index of the type argument that the given step is stepping
+ * into.
+ */
+ public int getStepArgument(int index) {
+ return b[offset + 2 * index + 2];
+ }
+
+ /**
+ * Converts a type path in string form, in the format used by
+ * {@link #toString()}, into a TypePath object.
+ *
+ * @param typePath
+ * a type path in string form, in the format used by
+ * {@link #toString()}. May be null or empty.
+ * @return the corresponding TypePath object, or null if the path is empty.
+ */
+ public static TypePath fromString(final String typePath) {
+ if (typePath == null || typePath.length() == 0) {
+ return null;
+ }
+ int n = typePath.length();
+ ByteVector out = new ByteVector(n);
+ out.putByte(0);
+ for (int i = 0; i < n;) {
+ char c = typePath.charAt(i++);
+ if (c == '[') {
+ out.put11(ARRAY_ELEMENT, 0);
+ } else if (c == '.') {
+ out.put11(INNER_TYPE, 0);
+ } else if (c == '*') {
+ out.put11(WILDCARD_BOUND, 0);
+ } else if (c >= '0' && c <= '9') {
+ int typeArg = c - '0';
+ while (i < n && (c = typePath.charAt(i)) >= '0' && c <= '9') {
+ typeArg = typeArg * 10 + c - '0';
+ i += 1;
+ }
+ if (i < n && typePath.charAt(i) == ';') {
+ i += 1;
+ }
+ out.put11(TYPE_ARGUMENT, typeArg);
+ }
+ }
+ out.data[0] = (byte) (out.length / 2);
+ return new TypePath(out.data, 0);
+ }
+
+ /**
+ * Returns a string representation of this type path. {@link #ARRAY_ELEMENT
+ * ARRAY_ELEMENT} steps are represented with '[', {@link #INNER_TYPE
+ * INNER_TYPE} steps with '.', {@link #WILDCARD_BOUND WILDCARD_BOUND} steps
+ * with '*' and {@link #TYPE_ARGUMENT TYPE_ARGUMENT} steps with their type
+ * argument index in decimal form followed by ';'.
+ */
+ @Override
+ public String toString() {
+ int length = getLength();
+ StringBuilder result = new StringBuilder(length * 2);
+ for (int i = 0; i < length; ++i) {
+ switch (getStep(i)) {
+ case ARRAY_ELEMENT:
+ result.append('[');
+ break;
+ case INNER_TYPE:
+ result.append('.');
+ break;
+ case WILDCARD_BOUND:
+ result.append('*');
+ break;
+ case TYPE_ARGUMENT:
+ result.append(getStepArgument(i)).append(';');
+ break;
+ default:
+ result.append('_');
+ }
+ }
+ return result.toString();
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/TypeReference.java b/spring-core/src/main/java/org/springframework/asm/TypeReference.java
new file mode 100644
index 00000000..a7149283
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/asm/TypeReference.java
@@ -0,0 +1,452 @@
+/***
+ * ASM: a very small and fast Java bytecode manipulation framework
+ * Copyright (c) 2000-2013 INRIA, France Telecom
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.springframework.asm;
+
+/**
+ * A reference to a type appearing in a class, field or method declaration, or
+ * on an instruction. Such a reference designates the part of the class where
+ * the referenced type is appearing (e.g. an 'extends', 'implements' or 'throws'
+ * clause, a 'new' instruction, a 'catch' clause, a type cast, a local variable
+ * declaration, etc).
+ *
+ * @author Eric Bruneton
+ */
+public class TypeReference {
+
+ /**
+ * The sort of type references that target a type parameter of a generic
+ * class. See {@link #getSort getSort}.
+ */
+ public final static int CLASS_TYPE_PARAMETER = 0x00;
+
+ /**
+ * The sort of type references that target a type parameter of a generic
+ * method. See {@link #getSort getSort}.
+ */
+ public final static int METHOD_TYPE_PARAMETER = 0x01;
+
+ /**
+ * The sort of type references that target the super class of a class or one
+ * of the interfaces it implements. See {@link #getSort getSort}.
+ */
+ public final static int CLASS_EXTENDS = 0x10;
+
+ /**
+ * The sort of type references that target a bound of a type parameter of a
+ * generic class. See {@link #getSort getSort}.
+ */
+ public final static int CLASS_TYPE_PARAMETER_BOUND = 0x11;
+
+ /**
+ * The sort of type references that target a bound of a type parameter of a
+ * generic method. See {@link #getSort getSort}.
+ */
+ public final static int METHOD_TYPE_PARAMETER_BOUND = 0x12;
+
+ /**
+ * The sort of type references that target the type of a field. See
+ * {@link #getSort getSort}.
+ */
+ public final static int FIELD = 0x13;
+
+ /**
+ * The sort of type references that target the return type of a method. See
+ * {@link #getSort getSort}.
+ */
+ public final static int METHOD_RETURN = 0x14;
+
+ /**
+ * The sort of type references that target the receiver type of a method.
+ * See {@link #getSort getSort}.
+ */
+ public final static int METHOD_RECEIVER = 0x15;
+
+ /**
+ * The sort of type references that target the type of a formal parameter of
+ * a method. See {@link #getSort getSort}.
+ */
+ public final static int METHOD_FORMAL_PARAMETER = 0x16;
+
+ /**
+ * The sort of type references that target the type of an exception declared
+ * in the throws clause of a method. See {@link #getSort getSort}.
+ */
+ public final static int THROWS = 0x17;
+
+ /**
+ * The sort of type references that target the type of a local variable in a
+ * method. See {@link #getSort getSort}.
+ */
+ public final static int LOCAL_VARIABLE = 0x40;
+
+ /**
+ * The sort of type references that target the type of a resource variable
+ * in a method. See {@link #getSort getSort}.
+ */
+ public final static int RESOURCE_VARIABLE = 0x41;
+
+ /**
+ * The sort of type references that target the type of the exception of a
+ * 'catch' clause in a method. See {@link #getSort getSort}.
+ */
+ public final static int EXCEPTION_PARAMETER = 0x42;
+
+ /**
+ * The sort of type references that target the type declared in an
+ * 'instanceof' instruction. See {@link #getSort getSort}.
+ */
+ public final static int INSTANCEOF = 0x43;
+
+ /**
+ * The sort of type references that target the type of the object created by
+ * a 'new' instruction. See {@link #getSort getSort}.
+ */
+ public final static int NEW = 0x44;
+
+ /**
+ * The sort of type references that target the receiver type of a
+ * constructor reference. See {@link #getSort getSort}.
+ */
+ public final static int CONSTRUCTOR_REFERENCE = 0x45;
+
+ /**
+ * The sort of type references that target the receiver type of a method
+ * reference. See {@link #getSort getSort}.
+ */
+ public final static int METHOD_REFERENCE = 0x46;
+
+ /**
+ * The sort of type references that target the type declared in an explicit
+ * or implicit cast instruction. See {@link #getSort getSort}.
+ */
+ public final static int CAST = 0x47;
+
+ /**
+ * The sort of type references that target a type parameter of a generic
+ * constructor in a constructor call. See {@link #getSort getSort}.
+ */
+ public final static int CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT = 0x48;
+
+ /**
+ * The sort of type references that target a type parameter of a generic
+ * method in a method call. See {@link #getSort getSort}.
+ */
+ public final static int METHOD_INVOCATION_TYPE_ARGUMENT = 0x49;
+
+ /**
+ * The sort of type references that target a type parameter of a generic
+ * constructor in a constructor reference. See {@link #getSort getSort}.
+ */
+ public final static int CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT = 0x4A;
+
+ /**
+ * The sort of type references that target a type parameter of a generic
+ * method in a method reference. See {@link #getSort getSort}.
+ */
+ public final static int METHOD_REFERENCE_TYPE_ARGUMENT = 0x4B;
+
+ /**
+ * The type reference value in Java class file format.
+ */
+ private int value;
+
+ /**
+ * Creates a new TypeReference.
+ *
+ * @param typeRef
+ * the int encoded value of the type reference, as received in a
+ * visit method related to type annotations, like
+ * visitTypeAnnotation.
+ */
+ public TypeReference(int typeRef) {
+ this.value = typeRef;
+ }
+
+ /**
+ * Returns a type reference of the given sort.
+ *
+ * @param sort
+ * {@link #FIELD FIELD}, {@link #METHOD_RETURN METHOD_RETURN},
+ * {@link #METHOD_RECEIVER METHOD_RECEIVER},
+ * {@link #LOCAL_VARIABLE LOCAL_VARIABLE},
+ * {@link #RESOURCE_VARIABLE RESOURCE_VARIABLE},
+ * {@link #INSTANCEOF INSTANCEOF}, {@link #NEW NEW},
+ * {@link #CONSTRUCTOR_REFERENCE CONSTRUCTOR_REFERENCE}, or
+ * {@link #METHOD_REFERENCE METHOD_REFERENCE}.
+ * @return a type reference of the given sort.
+ */
+ public static TypeReference newTypeReference(int sort) {
+ return new TypeReference(sort << 24);
+ }
+
+ /**
+ * Returns a reference to a type parameter of a generic class or method.
+ *
+ * @param sort
+ * {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER} or
+ * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER}.
+ * @param paramIndex
+ * the type parameter index.
+ * @return a reference to the given generic class or method type parameter.
+ */
+ public static TypeReference newTypeParameterReference(int sort,
+ int paramIndex) {
+ return new TypeReference((sort << 24) | (paramIndex << 16));
+ }
+
+ /**
+ * Returns a reference to a type parameter bound of a generic class or
+ * method.
+ *
+ * @param sort
+ * {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER} or
+ * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER}.
+ * @param paramIndex
+ * the type parameter index.
+ * @param boundIndex
+ * the type bound index within the above type parameters.
+ * @return a reference to the given generic class or method type parameter
+ * bound.
+ */
+ public static TypeReference newTypeParameterBoundReference(int sort,
+ int paramIndex, int boundIndex) {
+ return new TypeReference((sort << 24) | (paramIndex << 16)
+ | (boundIndex << 8));
+ }
+
+ /**
+ * Returns a reference to the super class or to an interface of the
+ * 'implements' clause of a class.
+ *
+ * @param itfIndex
+ * the index of an interface in the 'implements' clause of a
+ * class, or -1 to reference the super class of the class.
+ * @return a reference to the given super type of a class.
+ */
+ public static TypeReference newSuperTypeReference(int itfIndex) {
+ itfIndex &= 0xFFFF;
+ return new TypeReference((CLASS_EXTENDS << 24) | (itfIndex << 8));
+ }
+
+ /**
+ * Returns a reference to the type of a formal parameter of a method.
+ *
+ * @param paramIndex
+ * the formal parameter index.
+ *
+ * @return a reference to the type of the given method formal parameter.
+ */
+ public static TypeReference newFormalParameterReference(int paramIndex) {
+ return new TypeReference((METHOD_FORMAL_PARAMETER << 24)
+ | (paramIndex << 16));
+ }
+
+ /**
+ * Returns a reference to the type of an exception, in a 'throws' clause of
+ * a method.
+ *
+ * @param exceptionIndex
+ * the index of an exception in a 'throws' clause of a method.
+ *
+ * @return a reference to the type of the given exception.
+ */
+ public static TypeReference newExceptionReference(int exceptionIndex) {
+ return new TypeReference((THROWS << 24) | (exceptionIndex << 8));
+ }
+
+ /**
+ * Returns a reference to the type of the exception declared in a 'catch'
+ * clause of a method.
+ *
+ * @param tryCatchBlockIndex
+ * the index of a try catch block (using the order in which they
+ * are visited with visitTryCatchBlock).
+ *
+ * @return a reference to the type of the given exception.
+ */
+ public static TypeReference newTryCatchReference(int tryCatchBlockIndex) {
+ return new TypeReference((EXCEPTION_PARAMETER << 24)
+ | (tryCatchBlockIndex << 8));
+ }
+
+ /**
+ * Returns a reference to the type of a type argument in a constructor or
+ * method call or reference.
+ *
+ * @param sort
+ * {@link #CAST CAST},
+ * {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
+ * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT},
+ * {@link #METHOD_INVOCATION_TYPE_ARGUMENT
+ * METHOD_INVOCATION_TYPE_ARGUMENT},
+ * {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
+ * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or
+ * {@link #METHOD_REFERENCE_TYPE_ARGUMENT
+ * METHOD_REFERENCE_TYPE_ARGUMENT}.
+ * @param argIndex
+ * the type argument index.
+ *
+ * @return a reference to the type of the given type argument.
+ */
+ public static TypeReference newTypeArgumentReference(int sort, int argIndex) {
+ return new TypeReference((sort << 24) | argIndex);
+ }
+
+ /**
+ * Returns the sort of this type reference.
+ *
+ * @return {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER},
+ * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER},
+ * {@link #CLASS_EXTENDS CLASS_EXTENDS},
+ * {@link #CLASS_TYPE_PARAMETER_BOUND CLASS_TYPE_PARAMETER_BOUND},
+ * {@link #METHOD_TYPE_PARAMETER_BOUND METHOD_TYPE_PARAMETER_BOUND},
+ * {@link #FIELD FIELD}, {@link #METHOD_RETURN METHOD_RETURN},
+ * {@link #METHOD_RECEIVER METHOD_RECEIVER},
+ * {@link #METHOD_FORMAL_PARAMETER METHOD_FORMAL_PARAMETER},
+ * {@link #THROWS THROWS}, {@link #LOCAL_VARIABLE LOCAL_VARIABLE},
+ * {@link #RESOURCE_VARIABLE RESOURCE_VARIABLE},
+ * {@link #EXCEPTION_PARAMETER EXCEPTION_PARAMETER},
+ * {@link #INSTANCEOF INSTANCEOF}, {@link #NEW NEW},
+ * {@link #CONSTRUCTOR_REFERENCE CONSTRUCTOR_REFERENCE},
+ * {@link #METHOD_REFERENCE METHOD_REFERENCE}, {@link #CAST CAST},
+ * {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
+ * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT},
+ * {@link #METHOD_INVOCATION_TYPE_ARGUMENT
+ * METHOD_INVOCATION_TYPE_ARGUMENT},
+ * {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
+ * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or
+ * {@link #METHOD_REFERENCE_TYPE_ARGUMENT
+ * METHOD_REFERENCE_TYPE_ARGUMENT}.
+ */
+ public int getSort() {
+ return value >>> 24;
+ }
+
+ /**
+ * Returns the index of the type parameter referenced by this type
+ * reference. This method must only be used for type references whose sort
+ * is {@link #CLASS_TYPE_PARAMETER CLASS_TYPE_PARAMETER},
+ * {@link #METHOD_TYPE_PARAMETER METHOD_TYPE_PARAMETER},
+ * {@link #CLASS_TYPE_PARAMETER_BOUND CLASS_TYPE_PARAMETER_BOUND} or
+ * {@link #METHOD_TYPE_PARAMETER_BOUND METHOD_TYPE_PARAMETER_BOUND}.
+ *
+ * @return a type parameter index.
+ */
+ public int getTypeParameterIndex() {
+ return (value & 0x00FF0000) >> 16;
+ }
+
+ /**
+ * Returns the index of the type parameter bound, within the type parameter
+ * {@link #getTypeParameterIndex}, referenced by this type reference. This
+ * method must only be used for type references whose sort is
+ * {@link #CLASS_TYPE_PARAMETER_BOUND CLASS_TYPE_PARAMETER_BOUND} or
+ * {@link #METHOD_TYPE_PARAMETER_BOUND METHOD_TYPE_PARAMETER_BOUND}.
+ *
+ * @return a type parameter bound index.
+ */
+ public int getTypeParameterBoundIndex() {
+ return (value & 0x0000FF00) >> 8;
+ }
+
+ /**
+ * Returns the index of the "super type" of a class that is referenced by
+ * this type reference. This method must only be used for type references
+ * whose sort is {@link #CLASS_EXTENDS CLASS_EXTENDS}.
+ *
+ * @return the index of an interface in the 'implements' clause of a class,
+ * or -1 if this type reference references the type of the super
+ * class.
+ */
+ public int getSuperTypeIndex() {
+ return (short) ((value & 0x00FFFF00) >> 8);
+ }
+
+ /**
+ * Returns the index of the formal parameter whose type is referenced by
+ * this type reference. This method must only be used for type references
+ * whose sort is {@link #METHOD_FORMAL_PARAMETER METHOD_FORMAL_PARAMETER}.
+ *
+ * @return a formal parameter index.
+ */
+ public int getFormalParameterIndex() {
+ return (value & 0x00FF0000) >> 16;
+ }
+
+ /**
+ * Returns the index of the exception, in a 'throws' clause of a method,
+ * whose type is referenced by this type reference. This method must only be
+ * used for type references whose sort is {@link #THROWS THROWS}.
+ *
+ * @return the index of an exception in the 'throws' clause of a method.
+ */
+ public int getExceptionIndex() {
+ return (value & 0x00FFFF00) >> 8;
+ }
+
+ /**
+ * Returns the index of the try catch block (using the order in which they
+ * are visited with visitTryCatchBlock), whose 'catch' type is referenced by
+ * this type reference. This method must only be used for type references
+ * whose sort is {@link #EXCEPTION_PARAMETER EXCEPTION_PARAMETER} .
+ *
+ * @return the index of an exception in the 'throws' clause of a method.
+ */
+ public int getTryCatchBlockIndex() {
+ return (value & 0x00FFFF00) >> 8;
+ }
+
+ /**
+ * Returns the index of the type argument referenced by this type reference.
+ * This method must only be used for type references whose sort is
+ * {@link #CAST CAST}, {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
+ * CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT},
+ * {@link #METHOD_INVOCATION_TYPE_ARGUMENT METHOD_INVOCATION_TYPE_ARGUMENT},
+ * {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
+ * CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or
+ * {@link #METHOD_REFERENCE_TYPE_ARGUMENT METHOD_REFERENCE_TYPE_ARGUMENT}.
+ *
+ * @return a type parameter index.
+ */
+ public int getTypeArgumentIndex() {
+ return value & 0xFF;
+ }
+
+ /**
+ * Returns the int encoded value of this type reference, suitable for use in
+ * visit methods related to type annotations, like visitTypeAnnotation.
+ *
+ * @return the int encoded value of this type reference.
+ */
+ public int getValue() {
+ return value;
+ }
+}
diff --git a/spring-core/src/main/java/org/springframework/cglib/SpringCglibInfo.java b/spring-core/src/main/java/org/springframework/cglib/SpringCglibInfo.java
index 0ed64ca5..6922898b 100644
--- a/spring-core/src/main/java/org/springframework/cglib/SpringCglibInfo.java
+++ b/spring-core/src/main/java/org/springframework/cglib/SpringCglibInfo.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * 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.
@@ -17,10 +17,10 @@
package org.springframework.cglib;
/**
- * Empty class used to ensure that the {@code org.springframework.cglib} package is
- * processed during Javadoc generation.
+ * 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
+ * <p>See <a href="package-summary.html">package-level javadocs</a> for more
* information on {@code org.springframework.cglib}.
*
* @author Chris Beams
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
index a7f25ce2..6246aae8 100644
--- a/spring-core/src/main/java/org/springframework/cglib/package-info.java
+++ b/spring-core/src/main/java/org/springframework/cglib/package-info.java
@@ -1,28 +1,16 @@
-/*
- * 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).
+ * Spring's repackaging of
+ * <a href="http://cglib.sourceforge.net">net.sf.cglib 3.1</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
+ * dependencies on CGLIB at the application level or from 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>
+ *
+ * <p>As this repackaging happens at the class file level, sources
+ * and javadocs are not available here. See the original
+ * <a href="http://cglib.sourceforge.net/apidocs">CGLIB 3.1 javadocs</a>
* for details when working with these classes.
*
* @since 3.2
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
index 28f92352..decc7640 100644
--- 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
@@ -30,7 +30,10 @@ import org.springframework.cglib.transform.TransformingClassGenerator;
*
* @author Phillip Webb
* @since 3.2.4
+ * @deprecated as of Spring 4.0.2, in favor of CGLIB 3.1's default strategy.
+ * Kept around for external code depending on it; to be removed in Spring 4.1.
*/
+@Deprecated
public class MemorySafeUndeclaredThrowableStrategy extends DefaultGeneratorStrategy {
private static final MethodFilter TRANSFORM_FILTER = new MethodFilter() {
diff --git a/spring-core/src/main/java/org/springframework/core/AttributeAccessorSupport.java b/spring-core/src/main/java/org/springframework/core/AttributeAccessorSupport.java
index cf351f42..dbbe1aff 100644
--- a/spring-core/src/main/java/org/springframework/core/AttributeAccessorSupport.java
+++ b/spring-core/src/main/java/org/springframework/core/AttributeAccessorSupport.java
@@ -39,6 +39,7 @@ public abstract class AttributeAccessorSupport implements AttributeAccessor, Ser
private final Map<String, Object> attributes = new LinkedHashMap<String, Object>(0);
+ @Override
public void setAttribute(String name, Object value) {
Assert.notNull(name, "Name must not be null");
if (value != null) {
@@ -49,21 +50,25 @@ public abstract class AttributeAccessorSupport implements AttributeAccessor, Ser
}
}
+ @Override
public Object getAttribute(String name) {
Assert.notNull(name, "Name must not be null");
return this.attributes.get(name);
}
+ @Override
public Object removeAttribute(String name) {
Assert.notNull(name, "Name must not be null");
return this.attributes.remove(name);
}
+ @Override
public boolean hasAttribute(String name) {
Assert.notNull(name, "Name must not be null");
return this.attributes.containsKey(name);
}
+ @Override
public String[] attributeNames() {
return this.attributes.keySet().toArray(new String[this.attributes.size()]);
}
diff --git a/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java b/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java
index 62c33d1a..2f44c305 100644
--- a/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java
+++ b/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java
@@ -16,14 +16,11 @@
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;
@@ -44,6 +41,7 @@ import org.springframework.util.ReflectionUtils;
*
* @author Rob Harrop
* @author Juergen Hoeller
+ * @author Phillip Webb
* @since 2.0
*/
public abstract class BridgeMethodResolver {
@@ -87,6 +85,18 @@ public abstract class BridgeMethodResolver {
}
/**
+ * 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);
+ }
+
+ /**
* Searches for the bridged method in the given candidates.
* @param candidateMethods the List of candidate Methods
* @param bridgeMethod the bridge method
@@ -96,11 +106,10 @@ public abstract class BridgeMethodResolver {
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)) {
+ if (isBridgeMethodFor(bridgeMethod, candidateMethod, bridgeMethod.getDeclaringClass())) {
return candidateMethod;
}
else if (previousMethod != null) {
@@ -113,27 +122,15 @@ public abstract class BridgeMethodResolver {
}
/**
- * 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)) {
+ static boolean isBridgeMethodFor(Method bridgeMethod, Method candidateMethod, Class<?> declaringClass) {
+ if (isResolvedTypeMatch(candidateMethod, bridgeMethod, declaringClass)) {
return true;
}
Method method = findGenericDeclaration(bridgeMethod);
- return (method != null && isResolvedTypeMatch(method, candidateMethod, typeVariableMap));
+ return (method != null && isResolvedTypeMatch(method, candidateMethod, declaringClass));
}
/**
@@ -167,34 +164,27 @@ public abstract class BridgeMethodResolver {
/**
* 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}.
+ * are equal after resolving all types against the declaringType, otherwise
+ * returns {@code false}.
*/
private static boolean isResolvedTypeMatch(
- Method genericMethod, Method candidateMethod, Map<TypeVariable, Type> typeVariableMap) {
-
+ Method genericMethod, Method candidateMethod, Class<?> declaringClass) {
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];
+ for (int i = 0; i < candidateParameters.length; i++) {
+ ResolvableType genericParameter = ResolvableType.forMethodParameter(genericMethod, i, declaringClass);
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;
+ if (!candidateParameter.getComponentType().equals(genericParameter.getComponentType().resolve(Object.class))) {
+ return false;
}
}
// A non-array type: compare the type itself.
- Class<?> resolvedParameter = GenericTypeResolver.resolveType(genericParameter, typeVariableMap);
- if (!candidateParameter.equals(resolvedParameter)) {
+ if (!candidateParameter.equals(genericParameter.resolve(Object.class))) {
return false;
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/CollectionFactory.java b/spring-core/src/main/java/org/springframework/core/CollectionFactory.java
index 23335434..998bd813 100644
--- a/spring-core/src/main/java/org/springframework/core/CollectionFactory.java
+++ b/spring-core/src/main/java/org/springframework/core/CollectionFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * 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.
@@ -20,22 +20,19 @@ 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.NavigableMap;
+import java.util.NavigableSet;
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;
@@ -53,13 +50,9 @@ import org.springframework.util.MultiValueMap;
*/
public abstract class CollectionFactory {
- private static Class navigableSetClass = null;
+ private static final Set<Class<?>> approximableCollectionTypes = new HashSet<Class<?>>(10);
- 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);
+ private static final Set<Class<?>> approximableMapTypes = new HashSet<Class<?>>(6);
static {
@@ -68,20 +61,10 @@ public abstract class CollectionFactory {
approximableCollectionTypes.add(List.class);
approximableCollectionTypes.add(Set.class);
approximableCollectionTypes.add(SortedSet.class);
+ approximableCollectionTypes.add(NavigableSet.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...
- }
+ approximableMapTypes.add(NavigableMap.class);
// Common concrete collection classes
approximableCollectionTypes.add(ArrayList.class);
@@ -96,95 +79,6 @@ public abstract class CollectionFactory {
/**
- * 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
@@ -207,18 +101,18 @@ public abstract class CollectionFactory {
* @see java.util.LinkedHashSet
*/
@SuppressWarnings("unchecked")
- public static Collection createApproximateCollection(Object collection, int initialCapacity) {
+ public static <E> Collection<E> createApproximateCollection(Object collection, int initialCapacity) {
if (collection instanceof LinkedList) {
- return new LinkedList();
+ return new LinkedList<E>();
}
else if (collection instanceof List) {
- return new ArrayList(initialCapacity);
+ return new ArrayList<E>(initialCapacity);
}
else if (collection instanceof SortedSet) {
- return new TreeSet(((SortedSet) collection).comparator());
+ return new TreeSet<E>(((SortedSet<E>) collection).comparator());
}
else {
- return new LinkedHashSet(initialCapacity);
+ return new LinkedHashSet<E>(initialCapacity);
}
}
@@ -233,16 +127,17 @@ public abstract class CollectionFactory {
* @see java.util.TreeSet
* @see java.util.LinkedHashSet
*/
- public static Collection createCollection(Class<?> collectionType, int initialCapacity) {
+ @SuppressWarnings("unchecked")
+ public static <E> Collection<E> createCollection(Class<?> collectionType, int initialCapacity) {
if (collectionType.isInterface()) {
if (List.class.equals(collectionType)) {
- return new ArrayList(initialCapacity);
+ return new ArrayList<E>(initialCapacity);
}
- else if (SortedSet.class.equals(collectionType) || collectionType.equals(navigableSetClass)) {
- return new TreeSet();
+ else if (SortedSet.class.equals(collectionType) || NavigableSet.class.equals(collectionType)) {
+ return new TreeSet<E>();
}
else if (Set.class.equals(collectionType) || Collection.class.equals(collectionType)) {
- return new LinkedHashSet(initialCapacity);
+ return new LinkedHashSet<E>(initialCapacity);
}
else {
throw new IllegalArgumentException("Unsupported Collection interface: " + collectionType.getName());
@@ -253,7 +148,7 @@ public abstract class CollectionFactory {
throw new IllegalArgumentException("Unsupported Collection type: " + collectionType.getName());
}
try {
- return (Collection) collectionType.newInstance();
+ return (Collection<E>) collectionType.newInstance();
}
catch (Exception ex) {
throw new IllegalArgumentException("Could not instantiate Collection type: " +
@@ -283,12 +178,12 @@ public abstract class CollectionFactory {
* @see java.util.LinkedHashMap
*/
@SuppressWarnings("unchecked")
- public static Map createApproximateMap(Object map, int initialCapacity) {
+ public static <K, V> Map<K, V> createApproximateMap(Object map, int initialCapacity) {
if (map instanceof SortedMap) {
- return new TreeMap(((SortedMap) map).comparator());
+ return new TreeMap<K, V>(((SortedMap<K, V>) map).comparator());
}
else {
- return new LinkedHashMap(initialCapacity);
+ return new LinkedHashMap<K, V>(initialCapacity);
}
}
@@ -301,13 +196,14 @@ public abstract class CollectionFactory {
* @see java.util.TreeMap
* @see java.util.LinkedHashMap
*/
- public static Map createMap(Class<?> mapType, int initialCapacity) {
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public static <K, V> Map<K, V> createMap(Class<?> mapType, int initialCapacity) {
if (mapType.isInterface()) {
if (Map.class.equals(mapType)) {
- return new LinkedHashMap(initialCapacity);
+ return new LinkedHashMap<K, V>(initialCapacity);
}
- else if (SortedMap.class.equals(mapType) || mapType.equals(navigableMapClass)) {
- return new TreeMap();
+ else if (SortedMap.class.equals(mapType) || NavigableMap.class.equals(mapType)) {
+ return new TreeMap<K, V>();
}
else if (MultiValueMap.class.equals(mapType)) {
return new LinkedMultiValueMap();
@@ -321,7 +217,7 @@ public abstract class CollectionFactory {
throw new IllegalArgumentException("Unsupported Map type: " + mapType.getName());
}
try {
- return (Map) mapType.newInstance();
+ return (Map<K, V>) mapType.newInstance();
}
catch (Exception ex) {
throw new IllegalArgumentException("Could not instantiate Map type: " +
@@ -330,17 +226,4 @@ public abstract class CollectionFactory {
}
}
-
- /**
- * 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
deleted file mode 100644
index 64597193..00000000
--- a/spring-core/src/main/java/org/springframework/core/ConcurrentMap.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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
index 4d48b044..5af282f6 100644
--- a/spring-core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java
+++ b/spring-core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java
@@ -68,7 +68,7 @@ public class ConfigurableObjectInputStream extends ObjectInputStream {
@Override
- protected Class resolveClass(ObjectStreamClass classDesc) throws IOException, ClassNotFoundException {
+ protected Class<?> resolveClass(ObjectStreamClass classDesc) throws IOException, ClassNotFoundException {
try {
if (this.classLoader != null) {
// Use the specified ClassLoader to resolve local classes.
@@ -85,13 +85,13 @@ public class ConfigurableObjectInputStream extends ObjectInputStream {
}
@Override
- protected Class resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
+ 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];
+ Class<?>[] resolvedInterfaces = new Class<?>[interfaces.length];
for (int i = 0; i < interfaces.length; i++) {
try {
resolvedInterfaces[i] = ClassUtils.forName(interfaces[i], this.classLoader);
@@ -113,7 +113,7 @@ public class ConfigurableObjectInputStream extends ObjectInputStream {
return super.resolveProxyClass(interfaces);
}
catch (ClassNotFoundException ex) {
- Class[] resolvedInterfaces = new Class[interfaces.length];
+ Class<?>[] resolvedInterfaces = new Class<?>[interfaces.length];
for (int i = 0; i < interfaces.length; i++) {
resolvedInterfaces[i] = resolveFallbackIfPossible(interfaces[i], ex);
}
@@ -131,7 +131,7 @@ public class ConfigurableObjectInputStream extends ObjectInputStream {
* @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)
+ protected Class<?> resolveFallbackIfPossible(String className, ClassNotFoundException ex)
throws IOException, ClassNotFoundException{
throw ex;
diff --git a/spring-core/src/main/java/org/springframework/core/ControlFlow.java b/spring-core/src/main/java/org/springframework/core/ControlFlow.java
index bc754211..a650c6cb 100644
--- a/spring-core/src/main/java/org/springframework/core/ControlFlow.java
+++ b/spring-core/src/main/java/org/springframework/core/ControlFlow.java
@@ -31,7 +31,7 @@ public interface ControlFlow {
* according to the current stack trace.
* @param clazz the clazz to look for
*/
- boolean under(Class clazz);
+ boolean under(Class<?> clazz);
/**
* Detect whether we're under the given class and method,
@@ -39,7 +39,7 @@ public interface ControlFlow {
* @param clazz the clazz to look for
* @param methodName the name of the method to look for
*/
- boolean under(Class clazz, String methodName);
+ boolean under(Class<?> clazz, String methodName);
/**
* Detect whether the current stack trace contains the given 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
index 6430ff7f..167de0f7 100644
--- a/spring-core/src/main/java/org/springframework/core/ControlFlowFactory.java
+++ b/spring-core/src/main/java/org/springframework/core/ControlFlowFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2008 the original author or authors.
+ * 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.
@@ -61,7 +61,8 @@ public abstract class ControlFlowFactory {
/**
* Searches for class name match in a StackTraceElement.
*/
- public boolean under(Class clazz) {
+ @Override
+ 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++) {
@@ -76,7 +77,8 @@ public abstract class ControlFlowFactory {
* Searches for class name match plus method name match
* in a StackTraceElement.
*/
- public boolean under(Class clazz, String methodName) {
+ @Override
+ 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();
@@ -93,6 +95,7 @@ public abstract class ControlFlowFactory {
* Leave it up to the caller to decide what matches.
* Caller must understand stack trace format, so there's less abstraction.
*/
+ @Override
public boolean underToken(String token) {
if (token == null) {
return false;
diff --git a/spring-core/src/main/java/org/springframework/core/Conventions.java b/spring-core/src/main/java/org/springframework/core/Conventions.java
index 7ea7c825..99e3406f 100644
--- a/spring-core/src/main/java/org/springframework/core/Conventions.java
+++ b/spring-core/src/main/java/org/springframework/core/Conventions.java
@@ -20,7 +20,9 @@ import java.io.Externalizable;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
@@ -43,18 +45,18 @@ public abstract class Conventions {
*/
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>();
-
+ private static final Set<Class<?>> IGNORED_INTERFACES;
static {
- ignoredInterfaces.add(Serializable.class);
- ignoredInterfaces.add(Externalizable.class);
- ignoredInterfaces.add(Cloneable.class);
- ignoredInterfaces.add(Comparable.class);
+ IGNORED_INTERFACES = Collections.unmodifiableSet(
+ new HashSet<Class<?>>(Arrays.<Class<?>> asList(
+ Serializable.class,
+ Externalizable.class,
+ Cloneable.class,
+ Comparable.class)));
}
@@ -75,7 +77,7 @@ public abstract class Conventions {
*/
public static String getVariableName(Object value) {
Assert.notNull(value, "Value must not be null");
- Class valueClass;
+ Class<?> valueClass;
boolean pluralize = false;
if (value.getClass().isArray()) {
@@ -83,7 +85,7 @@ public abstract class Conventions {
pluralize = true;
}
else if (value instanceof Collection) {
- Collection collection = (Collection) value;
+ Collection<?> collection = (Collection<?>) value;
if (collection.isEmpty()) {
throw new IllegalArgumentException("Cannot generate variable name for an empty Collection");
}
@@ -107,7 +109,7 @@ public abstract class Conventions {
*/
public static String getVariableNameForParameter(MethodParameter parameter) {
Assert.notNull(parameter, "MethodParameter must not be null");
- Class valueClass;
+ Class<?> valueClass;
boolean pluralize = false;
if (parameter.getParameterType().isArray()) {
@@ -163,7 +165,7 @@ public abstract class Conventions {
* @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) {
+ public static String getVariableNameForReturnType(Method method, Class<?> resolvedType, Object value) {
Assert.notNull(method, "Method must not be null");
if (Object.class.equals(resolvedType)) {
@@ -173,7 +175,7 @@ public abstract class Conventions {
return getVariableName(value);
}
- Class valueClass;
+ Class<?> valueClass;
boolean pluralize = false;
if (resolvedType.isArray()) {
@@ -187,7 +189,7 @@ public abstract class Conventions {
throw new IllegalArgumentException(
"Cannot generate variable name for non-typed Collection return type and a non-Collection value");
}
- Collection collection = (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");
@@ -239,7 +241,7 @@ public abstract class Conventions {
* 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) {
+ 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;
@@ -255,12 +257,12 @@ public abstract class Conventions {
* @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();
+ 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)) {
+ Class<?>[] ifcs = valueClass.getInterfaces();
+ for (Class<?> ifc : ifcs) {
+ if (!IGNORED_INTERFACES.contains(ifc)) {
return ifc;
}
}
@@ -285,13 +287,13 @@ public abstract class Conventions {
* 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();
+ private static <E> E peekAhead(Collection<E> collection) {
+ Iterator<E> it = collection.iterator();
if (!it.hasNext()) {
throw new IllegalStateException(
"Unable to peek ahead in non-empty collection - no element found");
}
- Object value = it.next();
+ E value = it.next();
if (value == null) {
throw new IllegalStateException(
"Unable to peek ahead in non-empty collection - only null element found");
diff --git a/spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java b/spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java
new file mode 100644
index 00000000..3bb8759a
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+/**
+ * Default implementation of the {@link ParameterNameDiscoverer} strategy interface,
+ * using the Java 8 standard reflection mechanism (if available), and falling back
+ * to the ASM-based {@link LocalVariableTableParameterNameDiscoverer} for checking
+ * debug information in the class file.
+ *
+ * <p>Further discoverers may be added through {@link #addDiscoverer(ParameterNameDiscoverer)}.
+ *
+ * @author Juergen Hoeller
+ * @since 4.0
+ * @see StandardReflectionParameterNameDiscoverer
+ * @see LocalVariableTableParameterNameDiscoverer
+ */
+public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {
+
+ private static final boolean standardReflectionAvailable =
+ (JdkVersion.getMajorJavaVersion() >= JdkVersion.JAVA_18);
+
+
+ public DefaultParameterNameDiscoverer() {
+ if (standardReflectionAvailable) {
+ addDiscoverer(new StandardReflectionParameterNameDiscoverer());
+ }
+ addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/ExceptionDepthComparator.java b/spring-core/src/main/java/org/springframework/core/ExceptionDepthComparator.java
index 53e3425f..ca014d89 100644
--- a/spring-core/src/main/java/org/springframework/core/ExceptionDepthComparator.java
+++ b/spring-core/src/main/java/org/springframework/core/ExceptionDepthComparator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * 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.
@@ -55,13 +55,14 @@ public class ExceptionDepthComparator implements Comparator<Class<? extends Thro
}
+ @Override
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) {
+ private int getDepth(Class<?> declaredException, Class<?> exceptionToMatch, int depth) {
if (declaredException.equals(exceptionToMatch)) {
// Found it!
return depth;
diff --git a/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java
index 8b4c280a..22d2a873 100644
--- a/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java
+++ b/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java
@@ -16,15 +16,8 @@
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;
@@ -36,7 +29,9 @@ import java.util.Map;
* (to be able to attempt type conversion if appropriate).
*
* @author Juergen Hoeller
+ * @author Phillip Webb
* @since 2.0
+ * @see ResolvableType
*/
public abstract class GenericCollectionTypeResolver {
@@ -46,8 +41,9 @@ public abstract class GenericCollectionTypeResolver {
* @param collectionClass the collection class to introspect
* @return the generic type, or {@code null} if none
*/
+ @SuppressWarnings("rawtypes")
public static Class<?> getCollectionType(Class<? extends Collection> collectionClass) {
- return extractTypeFromClass(collectionClass, Collection.class, 0);
+ return ResolvableType.forClass(collectionClass).asCollection().resolveGeneric();
}
/**
@@ -56,8 +52,9 @@ public abstract class GenericCollectionTypeResolver {
* @param mapClass the map class to introspect
* @return the generic type, or {@code null} if none
*/
+ @SuppressWarnings("rawtypes")
public static Class<?> getMapKeyType(Class<? extends Map> mapClass) {
- return extractTypeFromClass(mapClass, Map.class, 0);
+ return ResolvableType.forClass(mapClass).asMap().resolveGeneric(0);
}
/**
@@ -66,8 +63,9 @@ public abstract class GenericCollectionTypeResolver {
* @param mapClass the map class to introspect
* @return the generic type, or {@code null} if none
*/
+ @SuppressWarnings("rawtypes")
public static Class<?> getMapValueType(Class<? extends Map> mapClass) {
- return extractTypeFromClass(mapClass, Map.class, 1);
+ return ResolvableType.forClass(mapClass).asMap().resolveGeneric(1);
}
/**
@@ -76,7 +74,7 @@ public abstract class GenericCollectionTypeResolver {
* @return the generic type, or {@code null} if none
*/
public static Class<?> getCollectionFieldType(Field collectionField) {
- return getGenericFieldType(collectionField, Collection.class, 0, null, 1);
+ return ResolvableType.forField(collectionField).asCollection().resolveGeneric();
}
/**
@@ -88,7 +86,7 @@ public abstract class GenericCollectionTypeResolver {
* @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);
+ return ResolvableType.forField(collectionField).getNested(nestingLevel).asCollection().resolveGeneric();
}
/**
@@ -100,9 +98,11 @@ public abstract class GenericCollectionTypeResolver {
* @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
+ * @deprecated as of 4.0, in favor of using {@link ResolvableType} for arbitrary nesting levels
*/
+ @Deprecated
public static Class<?> getCollectionFieldType(Field collectionField, int nestingLevel, Map<Integer, Integer> typeIndexesPerLevel) {
- return getGenericFieldType(collectionField, Collection.class, 0, typeIndexesPerLevel, nestingLevel);
+ return ResolvableType.forField(collectionField).getNested(nestingLevel, typeIndexesPerLevel).asCollection().resolveGeneric();
}
/**
@@ -111,7 +111,7 @@ public abstract class GenericCollectionTypeResolver {
* @return the generic type, or {@code null} if none
*/
public static Class<?> getMapKeyFieldType(Field mapField) {
- return getGenericFieldType(mapField, Map.class, 0, null, 1);
+ return ResolvableType.forField(mapField).asMap().resolveGeneric(0);
}
/**
@@ -123,7 +123,7 @@ public abstract class GenericCollectionTypeResolver {
* @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);
+ return ResolvableType.forField(mapField).getNested(nestingLevel).asMap().resolveGeneric(0);
}
/**
@@ -135,9 +135,11 @@ public abstract class GenericCollectionTypeResolver {
* @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
+ * @deprecated as of 4.0, in favor of using {@link ResolvableType} for arbitrary nesting levels
*/
+ @Deprecated
public static Class<?> getMapKeyFieldType(Field mapField, int nestingLevel, Map<Integer, Integer> typeIndexesPerLevel) {
- return getGenericFieldType(mapField, Map.class, 0, typeIndexesPerLevel, nestingLevel);
+ return ResolvableType.forField(mapField).getNested(nestingLevel, typeIndexesPerLevel).asMap().resolveGeneric(0);
}
/**
@@ -146,7 +148,7 @@ public abstract class GenericCollectionTypeResolver {
* @return the generic type, or {@code null} if none
*/
public static Class<?> getMapValueFieldType(Field mapField) {
- return getGenericFieldType(mapField, Map.class, 1, null, 1);
+ return ResolvableType.forField(mapField).asMap().resolveGeneric(1);
}
/**
@@ -158,7 +160,7 @@ public abstract class GenericCollectionTypeResolver {
* @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);
+ return ResolvableType.forField(mapField).getNested(nestingLevel).asMap().resolveGeneric(1);
}
/**
@@ -170,9 +172,11 @@ public abstract class GenericCollectionTypeResolver {
* @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
+ * @deprecated as of 4.0, in favor of using {@link ResolvableType} for arbitrary nesting levels
*/
+ @Deprecated
public static Class<?> getMapValueFieldType(Field mapField, int nestingLevel, Map<Integer, Integer> typeIndexesPerLevel) {
- return getGenericFieldType(mapField, Map.class, 1, typeIndexesPerLevel, nestingLevel);
+ return ResolvableType.forField(mapField).getNested(nestingLevel, typeIndexesPerLevel).asMap().resolveGeneric(1);
}
/**
@@ -181,7 +185,7 @@ public abstract class GenericCollectionTypeResolver {
* @return the generic type, or {@code null} if none
*/
public static Class<?> getCollectionParameterType(MethodParameter methodParam) {
- return getGenericParameterType(methodParam, Collection.class, 0);
+ return ResolvableType.forMethodParameter(methodParam).asCollection().resolveGeneric();
}
/**
@@ -190,7 +194,7 @@ public abstract class GenericCollectionTypeResolver {
* @return the generic type, or {@code null} if none
*/
public static Class<?> getMapKeyParameterType(MethodParameter methodParam) {
- return getGenericParameterType(methodParam, Map.class, 0);
+ return ResolvableType.forMethodParameter(methodParam).asMap().resolveGeneric(0);
}
/**
@@ -199,7 +203,7 @@ public abstract class GenericCollectionTypeResolver {
* @return the generic type, or {@code null} if none
*/
public static Class<?> getMapValueParameterType(MethodParameter methodParam) {
- return getGenericParameterType(methodParam, Map.class, 1);
+ return ResolvableType.forMethodParameter(methodParam).asMap().resolveGeneric(1);
}
/**
@@ -208,7 +212,7 @@ public abstract class GenericCollectionTypeResolver {
* @return the generic type, or {@code null} if none
*/
public static Class<?> getCollectionReturnType(Method method) {
- return getGenericReturnType(method, Collection.class, 0, 1);
+ return ResolvableType.forMethodReturnType(method).asCollection().resolveGeneric();
}
/**
@@ -222,7 +226,7 @@ public abstract class GenericCollectionTypeResolver {
* @return the generic type, or {@code null} if none
*/
public static Class<?> getCollectionReturnType(Method method, int nestingLevel) {
- return getGenericReturnType(method, Collection.class, 0, nestingLevel);
+ return ResolvableType.forMethodReturnType(method).getNested(nestingLevel).asCollection().resolveGeneric();
}
/**
@@ -231,7 +235,7 @@ public abstract class GenericCollectionTypeResolver {
* @return the generic type, or {@code null} if none
*/
public static Class<?> getMapKeyReturnType(Method method) {
- return getGenericReturnType(method, Map.class, 0, 1);
+ return ResolvableType.forMethodReturnType(method).asMap().resolveGeneric(0);
}
/**
@@ -243,7 +247,7 @@ public abstract class GenericCollectionTypeResolver {
* @return the generic type, or {@code null} if none
*/
public static Class<?> getMapKeyReturnType(Method method, int nestingLevel) {
- return getGenericReturnType(method, Map.class, 0, nestingLevel);
+ return ResolvableType.forMethodReturnType(method).getNested(nestingLevel).asMap().resolveGeneric(0);
}
/**
@@ -252,7 +256,7 @@ public abstract class GenericCollectionTypeResolver {
* @return the generic type, or {@code null} if none
*/
public static Class<?> getMapValueReturnType(Method method) {
- return getGenericReturnType(method, Map.class, 1, 1);
+ return ResolvableType.forMethodReturnType(method).asMap().resolveGeneric(1);
}
/**
@@ -264,220 +268,7 @@ public abstract class GenericCollectionTypeResolver {
* @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));
+ return ResolvableType.forMethodReturnType(method).getNested(nestingLevel).asMap().resolveGeneric(1);
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java
index b860aaaf..bcef2720 100644
--- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java
+++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java
@@ -16,14 +16,12 @@
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.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -39,34 +37,28 @@ import org.springframework.util.ConcurrentReferenceHashMap;
* @author Juergen Hoeller
* @author Rob Harrop
* @author Sam Brannen
+ * @author Phillip Webb
* @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>>();
+ @SuppressWarnings("rawtypes")
+ 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
+ * @deprecated as of Spring 4.0, use {@link MethodParameter#getGenericParameterType()}
*/
+ @Deprecated
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();
- }
- }
+ return methodParam.getGenericParameterType();
}
/**
@@ -76,14 +68,11 @@ public abstract class GenericTypeResolver {
* @return the corresponding generic parameter or return type
*/
public static Class<?> resolveParameterType(MethodParameter methodParam, Class<?> clazz) {
- Type genericType = getTargetType(methodParam);
+ Assert.notNull(methodParam, "MethodParameter must not be null");
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;
+ methodParam.setContainingClass(clazz);
+ methodParam.setParameterType(ResolvableType.forMethodParameter(methodParam).resolve());
+ return methodParam.getParameterType();
}
/**
@@ -96,11 +85,8 @@ public abstract class GenericTypeResolver {
*/
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());
+ return ResolvableType.forMethodReturnType(method, clazz).resolve(method.getReturnType());
}
/**
@@ -112,7 +98,7 @@ public abstract class GenericTypeResolver {
* 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>
+ * <pre class="code">{@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>
@@ -128,14 +114,14 @@ public abstract class GenericTypeResolver {
* </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}
+ * invoked (never {@code null})
+ * @param classLoader the ClassLoader to resolve class names against, if necessary
+ * (may be {@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!
+ * @since 3.2.5
+ * @see #resolveReturnType
*/
- @Deprecated
- public static Class<?> resolveReturnTypeForGenericMethod(Method method, Object[] args) {
+ public static Class<?> resolveReturnTypeForGenericMethod(Method method, Object[] args, ClassLoader classLoader) {
Assert.notNull(method, "Method must not be null");
Assert.notNull(args, "Argument array must not be null");
@@ -175,8 +161,18 @@ public abstract class GenericTypeResolver {
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type typeArg : actualTypeArguments) {
if (typeArg.equals(genericReturnType)) {
- if (args[i] instanceof Class) {
- return (Class<?>) args[i];
+ Object arg = args[i];
+ if (arg instanceof Class) {
+ return (Class<?>) arg;
+ }
+ else if (arg instanceof String && classLoader != null) {
+ try {
+ return classLoader.loadClass((String) arg);
+ }
+ catch (ClassNotFoundException ex) {
+ throw new IllegalStateException(
+ "Could not resolve specific class name argument [" + arg + "]", ex);
+ }
}
else {
// Consider adding logic to determine the class of the typeArg, if possible.
@@ -204,22 +200,11 @@ public abstract class GenericTypeResolver {
*/
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;
- }
+ ResolvableType resolvableType = ResolvableType.forMethodReturnType(method).as(genericIfc);
+ if (!resolvableType.hasGenerics() || resolvableType.getType() instanceof WildcardType) {
+ return null;
}
- return resolveTypeArgument((Class<?>) returnType, genericIfc);
+ return getSingleGeneric(resolvableType);
}
/**
@@ -231,281 +216,114 @@ public abstract class GenericTypeResolver {
* @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) {
+ ResolvableType resolvableType = ResolvableType.forClass(clazz).as(genericIfc);
+ if (!resolvableType.hasGenerics()) {
return null;
}
- if (typeArgs.length != 1) {
+ return getSingleGeneric(resolvableType);
+ }
+
+ private static Class<?> getSingleGeneric(ResolvableType resolvableType) {
+ if (resolvableType.getGenerics().length > 1) {
throw new IllegalArgumentException("Expected 1 type argument on generic interface [" +
- genericIfc.getName() + "] but found " + typeArgs.length);
+ resolvableType + "] but found " + resolvableType.getGenerics().length);
}
- return typeArgs[0];
+ return resolvableType.getGeneric().resolve();
}
+
/**
* 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);
- }
+ ResolvableType type = ResolvableType.forClass(clazz).as(genericIfc);
+ if (!type.hasGenerics() || type.isEntirelyUnresolvable()) {
+ return null;
}
- return (arg instanceof Class ? (Class) arg : Object.class);
+ return type.resolveGenerics(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
+ * @param map the TypeVariable Map to resolved against
* @return the type if it resolves to a Class, or {@code Object.class} otherwise
+ * @deprecated as of Spring 4.0 in favor of {@link ResolvableType}
*/
- 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;
- }
+ @Deprecated
+ @SuppressWarnings("rawtypes")
+ public static Class<?> resolveType(Type genericType, Map<TypeVariable, Type> map) {
+ return ResolvableType.forType(genericType, new TypeVariableMapVariableResolver(map)).resolve(Object.class);
}
/**
* 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.
+ * @deprecated as of Spring 4.0 in favor of {@link ResolvableType}
*/
+ @Deprecated
+ @SuppressWarnings("rawtypes")
public static Map<TypeVariable, Type> getTypeVariableMap(Class<?> clazz) {
Map<TypeVariable, Type> typeVariableMap = typeVariableCache.get(clazz);
-
if (typeVariableMap == null) {
typeVariableMap = new HashMap<TypeVariable, Type>();
+ buildTypeVariableMap(ResolvableType.forClass(clazz), typeVariableMap);
+ typeVariableCache.put(clazz, Collections.unmodifiableMap(typeVariableMap));
+ }
+ return typeVariableMap;
+ }
- // 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);
+ @SuppressWarnings("rawtypes")
+ private static void buildTypeVariableMap(ResolvableType type, Map<TypeVariable, Type> typeVariableMap) {
+ if (type != ResolvableType.NONE) {
+ if (type.getType() instanceof ParameterizedType) {
+ TypeVariable<?>[] variables = type.resolve().getTypeParameters();
+ for (int i = 0; i < variables.length; i++) {
+ ResolvableType generic = type.getGeneric(i);
+ while (generic.getType() instanceof TypeVariable<?>) {
+ generic = generic.resolveType();
}
- 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);
+ if (generic != ResolvableType.NONE) {
+ typeVariableMap.put(variables[i], generic.getType());
}
- type = type.getEnclosingClass();
}
}
- catch (MalformedParameterizedTypeException ex) {
- // from getGenericSuperclass() - ignore and preserve previously accumulated type variables
+ buildTypeVariableMap(type.getSuperType(), typeVariableMap);
+ for (ResolvableType interfaceType : type.getInterfaces()) {
+ buildTypeVariableMap(interfaceType, typeVariableMap);
+ }
+ if (type.resolve().isMemberClass()) {
+ buildTypeVariableMap(ResolvableType.forClass(type.resolve().getEnclosingClass()), typeVariableMap);
}
-
- 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);
+
+ @SuppressWarnings({"serial", "rawtypes"})
+ private static class TypeVariableMapVariableResolver implements ResolvableType.VariableResolver {
+
+ private final Map<TypeVariable, Type> typeVariableMap;
+
+ public TypeVariableMapVariableResolver(Map<TypeVariable, Type> typeVariableMap) {
+ this.typeVariableMap = typeVariableMap;
}
- 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);
- }
+ @Override
+ public ResolvableType resolveVariable(TypeVariable<?> variable) {
+ Type type = this.typeVariableMap.get(variable);
+ return (type != null ? ResolvableType.forType(type) : null);
}
- }
- /**
- * 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);
- }
- }
+ @Override
+ public Object getSource() {
+ return this.typeVariableMap;
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/JdkVersion.java b/spring-core/src/main/java/org/springframework/core/JdkVersion.java
index a77f75ff..78ebc8b9 100644
--- a/spring-core/src/main/java/org/springframework/core/JdkVersion.java
+++ b/spring-core/src/main/java/org/springframework/core/JdkVersion.java
@@ -17,15 +17,16 @@
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.
+ * 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.
+ * <p>Note that Spring requires JVM 1.6 or higher, as of Spring 4.0.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Rick Evans
+ * @author Sam Brannen
*/
public abstract class JdkVersion {
@@ -59,6 +60,11 @@ public abstract class JdkVersion {
*/
public static final int JAVA_18 = 5;
+ /**
+ * Constant identifying the 1.9 JVM (Java 9).
+ */
+ public static final int JAVA_19 = 6;
+
private static final String javaVersion;
@@ -67,18 +73,18 @@ public abstract class JdkVersion {
static {
javaVersion = System.getProperty("java.version");
// version String should look like "1.4.2_10"
- if (javaVersion.contains("1.8.")) {
+ if (javaVersion.contains("1.9.")) {
+ majorJavaVersion = JAVA_19;
+ }
+ else 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;
+ // else leave 1.6 as default (it's either 1.6 or unknown)
+ majorJavaVersion = JAVA_16;
}
}
@@ -96,61 +102,14 @@ public abstract class JdkVersion {
/**
* 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
+ * @return a code comparable to the {@code JAVA_XX} codes in this class
* @see #JAVA_16
- * @see #JAVA_17
+ * @see #JAVA_17
+ * @see #JAVA_18
+ * @see #JAVA_19
*/
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
index 151dcb30..cc09fd80 100644
--- a/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java
+++ b/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java
@@ -64,6 +64,7 @@ public class LocalVariableTableParameterNameDiscoverer implements ParameterNameD
new ConcurrentHashMap<Class<?>, Map<Member, String[]>>(32);
+ @Override
public String[] getParameterNames(Method method) {
Method originalMethod = BridgeMethodResolver.findBridgedMethod(method);
Class<?> declaringClass = originalMethod.getDeclaringClass();
@@ -78,6 +79,7 @@ public class LocalVariableTableParameterNameDiscoverer implements ParameterNameD
return null;
}
+ @Override
public String[] getParameterNames(Constructor<?> ctor) {
Class<?> declaringClass = ctor.getDeclaringClass();
Map<Member, String[]> map = this.parameterNamesCache.get(declaringClass);
diff --git a/spring-core/src/main/java/org/springframework/core/MethodParameter.java b/spring-core/src/main/java/org/springframework/core/MethodParameter.java
index 48e72a1e..0174d137 100644
--- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java
+++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java
@@ -23,7 +23,6 @@ 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;
@@ -48,6 +47,13 @@ public class MethodParameter {
private final int parameterIndex;
+ private int nestingLevel = 1;
+
+ /** Map from Integer level to Integer type index */
+ Map<Integer, Integer> typeIndexesPerLevel;
+
+ private volatile Class<?> containingClass;
+
private volatile Class<?> parameterType;
private volatile Type genericParameterType;
@@ -58,13 +64,6 @@ public class MethodParameter {
private volatile 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.
@@ -128,14 +127,14 @@ public class MethodParameter {
this.method = original.method;
this.constructor = original.constructor;
this.parameterIndex = original.parameterIndex;
+ this.nestingLevel = original.nestingLevel;
+ this.typeIndexesPerLevel = original.typeIndexesPerLevel;
+ this.containingClass = original.containingClass;
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;
}
@@ -161,16 +160,32 @@ public class MethodParameter {
* Returns the wrapped member.
* @return the Method or Constructor as Member
*/
- private Member getMember() {
- return (this.method != null ? this.method : this.constructor);
+ public Member getMember() {
+ // NOTE: no ternary expression to retain JDK <8 compatibility even when using
+ // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable
+ // as common type, with that new base class not available on older JDKs)
+ if (this.method != null) {
+ return this.method;
+ }
+ else {
+ return 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);
+ public AnnotatedElement getAnnotatedElement() {
+ // NOTE: no ternary expression to retain JDK <8 compatibility even when using
+ // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable
+ // as common type, with that new base class not available on older JDKs)
+ if (this.method != null) {
+ return this.method;
+ }
+ else {
+ return this.constructor;
+ }
}
/**
@@ -189,6 +204,84 @@ public class MethodParameter {
}
/**
+ * 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;
+ }
+
+
+ /**
+ * Set a containing class to resolve the parameter type against.
+ */
+ void setContainingClass(Class<?> containingClass) {
+ this.containingClass = containingClass;
+ }
+
+ public Class<?> getContainingClass() {
+ return (this.containingClass != null ? this.containingClass : getDeclaringClass());
+ }
+
+ /**
* Set a resolved (generic) parameter type.
*/
void setParameterType(Class<?> parameterType) {
@@ -236,7 +329,8 @@ public class MethodParameter {
Type type = getGenericParameterType();
if (type instanceof ParameterizedType) {
Integer index = getTypeIndexForCurrentLevel();
- Type arg = ((ParameterizedType) type).getActualTypeArguments()[index != null ? index : 0];
+ Type[] args = ((ParameterizedType) type).getActualTypeArguments();
+ Type arg = args[index != null ? index : args.length - 1];
if (arg instanceof Class) {
return (Class<?>) arg;
}
@@ -347,71 +441,6 @@ public class MethodParameter {
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 other) {
diff --git a/spring-core/src/main/java/org/springframework/core/NestedCheckedException.java b/spring-core/src/main/java/org/springframework/core/NestedCheckedException.java
index 8f72b5ea..23c7ceb1 100644
--- a/spring-core/src/main/java/org/springframework/core/NestedCheckedException.java
+++ b/spring-core/src/main/java/org/springframework/core/NestedCheckedException.java
@@ -109,7 +109,7 @@ public abstract class NestedCheckedException extends Exception {
* @param exType the exception type to look for
* @return whether there is a nested exception of the specified type
*/
- public boolean contains(Class exType) {
+ public boolean contains(Class<?> exType) {
if (exType == null) {
return false;
}
diff --git a/spring-core/src/main/java/org/springframework/core/NestedIOException.java b/spring-core/src/main/java/org/springframework/core/NestedIOException.java
index 9bd7b88e..1b9927ea 100644
--- a/spring-core/src/main/java/org/springframework/core/NestedIOException.java
+++ b/spring-core/src/main/java/org/springframework/core/NestedIOException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * 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.
@@ -60,8 +60,7 @@ public class NestedIOException extends IOException {
* @param cause the nested exception
*/
public NestedIOException(String msg, Throwable cause) {
- super(msg);
- initCause(cause);
+ super(msg, cause);
}
diff --git a/spring-core/src/main/java/org/springframework/core/NestedRuntimeException.java b/spring-core/src/main/java/org/springframework/core/NestedRuntimeException.java
index 520c8bc9..8d20d81f 100644
--- a/spring-core/src/main/java/org/springframework/core/NestedRuntimeException.java
+++ b/spring-core/src/main/java/org/springframework/core/NestedRuntimeException.java
@@ -110,7 +110,7 @@ public abstract class NestedRuntimeException extends RuntimeException {
* @param exType the exception type to look for
* @return whether there is a nested exception of the specified type
*/
- public boolean contains(Class exType) {
+ public boolean contains(Class<?> exType) {
if (exType == null) {
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
index 16158ca1..0a261279 100644
--- a/spring-core/src/main/java/org/springframework/core/OrderComparator.java
+++ b/spring-core/src/main/java/org/springframework/core/OrderComparator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * 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.
@@ -43,6 +43,7 @@ public class OrderComparator implements Comparator<Object> {
public static final OrderComparator INSTANCE = new OrderComparator();
+ @Override
public int compare(Object o1, Object o2) {
boolean p1 = (o1 instanceof PriorityOrdered);
boolean p2 = (o2 instanceof PriorityOrdered);
@@ -97,4 +98,21 @@ public class OrderComparator implements Comparator<Object> {
}
}
+ /**
+ * Sort the given array or List with a default OrderComparator,
+ * if necessary. Simply skips sorting when given any other value.
+ * <p>Optimized to skip sorting for lists with size 0 or 1,
+ * in order to avoid unnecessary array extraction.
+ * @param value the array or List to sort
+ * @see java.util.Arrays#sort(Object[], java.util.Comparator)
+ */
+ public static void sortIfNecessary(Object value) {
+ if (value instanceof Object[]) {
+ sort((Object[]) value);
+ }
+ else if (value instanceof List) {
+ sort((List<?>) value);
+ }
+ }
+
}
diff --git a/spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java b/spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java
index 597a3923..9fbf0eed 100644
--- a/spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java
+++ b/spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java
@@ -55,8 +55,8 @@ public class OverridingClassLoader extends DecoratingClassLoader {
@Override
- protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
- Class result = null;
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ Class<?> result = null;
if (isEligibleForOverriding(name)) {
result = loadClassForOverriding(name);
}
@@ -90,8 +90,8 @@ public class OverridingClassLoader extends DecoratingClassLoader {
* @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);
+ protected Class<?> loadClassForOverriding(String name) throws ClassNotFoundException {
+ Class<?> result = findLoadedClass(name);
if (result == null) {
byte[] bytes = loadBytesForClass(name);
if (bytes != null) {
diff --git a/spring-core/src/main/java/org/springframework/core/ParameterizedTypeReference.java b/spring-core/src/main/java/org/springframework/core/ParameterizedTypeReference.java
index d7165b3e..e64f5890 100644
--- a/spring-core/src/main/java/org/springframework/core/ParameterizedTypeReference.java
+++ b/spring-core/src/main/java/org/springframework/core/ParameterizedTypeReference.java
@@ -61,7 +61,7 @@ public abstract class ParameterizedTypeReference<T> {
@Override
public boolean equals(Object obj) {
return (this == obj || (obj instanceof ParameterizedTypeReference &&
- this.type.equals(((ParameterizedTypeReference) obj).type)));
+ this.type.equals(((ParameterizedTypeReference<?>) obj).type)));
}
@Override
diff --git a/spring-core/src/main/java/org/springframework/core/PrioritizedParameterNameDiscoverer.java b/spring-core/src/main/java/org/springframework/core/PrioritizedParameterNameDiscoverer.java
index e65e88cd..086a0cc9 100644
--- a/spring-core/src/main/java/org/springframework/core/PrioritizedParameterNameDiscoverer.java
+++ b/spring-core/src/main/java/org/springframework/core/PrioritizedParameterNameDiscoverer.java
@@ -48,6 +48,7 @@ public class PrioritizedParameterNameDiscoverer implements ParameterNameDiscover
}
+ @Override
public String[] getParameterNames(Method method) {
for (ParameterNameDiscoverer pnd : this.parameterNameDiscoverers) {
String[] result = pnd.getParameterNames(method);
@@ -58,7 +59,8 @@ public class PrioritizedParameterNameDiscoverer implements ParameterNameDiscover
return null;
}
- public String[] getParameterNames(Constructor ctor) {
+ @Override
+ public String[] getParameterNames(Constructor<?> ctor) {
for (ParameterNameDiscoverer pnd : this.parameterNameDiscoverers) {
String[] result = pnd.getParameterNames(ctor);
if (result != null) {
diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java
new file mode 100644
index 00000000..78c7e88f
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java
@@ -0,0 +1,1365 @@
+/*
+ * 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.ObjectStreamException;
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+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.IdentityHashMap;
+import java.util.Map;
+
+import org.springframework.core.SerializableTypeWrapper.FieldTypeProvider;
+import org.springframework.core.SerializableTypeWrapper.MethodParameterTypeProvider;
+import org.springframework.core.SerializableTypeWrapper.TypeProvider;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ConcurrentReferenceHashMap;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Encapsulates a Java {@link java.lang.reflect.Type}, providing access to
+ * {@link #getSuperType() supertypes}, {@link #getInterfaces() interfaces}, and
+ * {@link #getGeneric(int...) generic parameters} along with the ability to ultimately
+ * {@link #resolve() resolve} to a {@link java.lang.Class}.
+ *
+ * <p>{@code ResolvableTypes} may be obtained from {@link #forField(Field) fields},
+ * {@link #forMethodParameter(Method, int) method parameters},
+ * {@link #forMethodReturnType(Method) method returns} or
+ * {@link #forClass(Class) classes}. Most methods on this class will themselves return
+ * {@link ResolvableType}s, allowing easy navigation. For example:
+ * <pre class="code">
+ * private HashMap&lt;Integer, List&lt;String&gt;&gt; myMap;
+ *
+ * public void example() {
+ * ResolvableType t = ResolvableType.forField(getClass().getDeclaredField("myMap"));
+ * t.getSuperType(); // AbstractMap&lt;Integer, List&lt;String&gt;&gt;
+ * t.asMap(); // Map&lt;Integer, List&lt;String&gt;&gt;
+ * t.getGeneric(0).resolve(); // Integer
+ * t.getGeneric(1).resolve(); // List
+ * t.getGeneric(1); // List&lt;String&gt;
+ * t.resolveGeneric(1, 0); // String
+ * }
+ * </pre>
+ *
+ * @author Phillip Webb
+ * @author Juergen Hoeller
+ * @since 4.0
+ * @see #forField(Field)
+ * @see #forMethodParameter(Method, int)
+ * @see #forMethodReturnType(Method)
+ * @see #forConstructorParameter(Constructor, int)
+ * @see #forClass(Class)
+ * @see #forType(Type)
+ */
+@SuppressWarnings("serial")
+public final class ResolvableType implements Serializable {
+
+ /**
+ * {@code ResolvableType} returned when no value is available. {@code NONE} is used
+ * in preference to {@code null} so that multiple method calls can be safely chained.
+ */
+ public static final ResolvableType NONE = new ResolvableType(null, null, null, null);
+
+ private static final ResolvableType[] EMPTY_TYPES_ARRAY = new ResolvableType[0];
+
+ private static final ConcurrentReferenceHashMap<ResolvableType, ResolvableType> cache =
+ new ConcurrentReferenceHashMap<ResolvableType, ResolvableType>(256);
+
+
+ /**
+ * The underlying Java type being managed (only ever {@code null} for {@link #NONE}).
+ */
+ private final Type type;
+
+ /**
+ * Optional provider for the type.
+ */
+ private final TypeProvider typeProvider;
+
+ /**
+ * The {@code VariableResolver} to use or {@code null} if no resolver is available.
+ */
+ private final VariableResolver variableResolver;
+
+ /**
+ * The component type for an array or {@code null} if the type should be deduced.
+ */
+ private final ResolvableType componentType;
+
+ /**
+ * Copy of the resolved value.
+ */
+ private final Class<?> resolved;
+
+ private ResolvableType superType;
+
+ private ResolvableType[] interfaces;
+
+ private ResolvableType[] generics;
+
+
+ /**
+ * Private constructor used to create a new {@link ResolvableType} for resolution purposes.
+ */
+ private ResolvableType(
+ Type type, TypeProvider typeProvider, VariableResolver variableResolver, ResolvableType componentType) {
+
+ this.type = type;
+ this.typeProvider = typeProvider;
+ this.variableResolver = variableResolver;
+ this.componentType = componentType;
+ this.resolved = resolveClass();
+ }
+
+ /**
+ * Private constructor used to create a new {@link ResolvableType} for cache key purposes.
+ */
+ private ResolvableType(Type type, TypeProvider typeProvider, VariableResolver variableResolver) {
+ this.type = type;
+ this.typeProvider = typeProvider;
+ this.variableResolver = variableResolver;
+ this.componentType = null;
+ this.resolved = null;
+ }
+
+
+ /**
+ * Return the underling Java {@link Type} being managed. With the exception of
+ * the {@link #NONE} constant, this method will never return {@code null}.
+ */
+ public Type getType() {
+ return SerializableTypeWrapper.unwrap(this.type);
+ }
+
+ /**
+ * Return the underlying Java {@link Class} being managed, if available;
+ * otherwise {@code null}.
+ */
+ public Class<?> getRawClass() {
+ Type rawType = this.type;
+ if (rawType instanceof ParameterizedType) {
+ rawType = ((ParameterizedType) rawType).getRawType();
+ }
+ return (rawType instanceof Class ? (Class<?>) rawType : null);
+ }
+
+ /**
+ * Return the underlying source of the resolvable type. Will return a {@link Field},
+ * {@link MethodParameter} or {@link Type} depending on how the {@link ResolvableType}
+ * was constructed. With the exception of the {@link #NONE} constant, this method will
+ * never return {@code null}. This method is primarily to provide access to additional
+ * type information or meta-data that alternative JVM languages may provide.
+ */
+ public Object getSource() {
+ Object source = (this.typeProvider != null ? this.typeProvider.getSource() : null);
+ return (source != null ? source : this.type);
+ }
+
+ /**
+ * Determine whether this {@code ResolvableType} is assignable from the
+ * specified other type.
+ * <p>Attempts to follow the same rules as the Java compiler, considering
+ * whether both the {@link #resolve() resolved} {@code Class} is
+ * {@link Class#isAssignableFrom(Class) assignable from} the given type
+ * as well as whether all {@link #getGenerics() generics} are assignable.
+ * @param other the type to be checked against
+ * @return {@code true} if the specified other type can be assigned to this
+ * {@code ResolvableType}; {@code false} otherwise
+ */
+ public boolean isAssignableFrom(ResolvableType other) {
+ return isAssignableFrom(other, null);
+ }
+
+ private boolean isAssignableFrom(ResolvableType other, Map<Type, Type> matchedBefore) {
+ Assert.notNull(other, "ResolvableType must not be null");
+
+ // If we cannot resolve types, we are not assignable
+ if (this == NONE || other == NONE) {
+ return false;
+ }
+
+ // Deal with array by delegating to the component type
+ if (isArray()) {
+ return (other.isArray() && getComponentType().isAssignableFrom(other.getComponentType()));
+ }
+
+ if (matchedBefore != null && matchedBefore.get(this.type) == other.type) {
+ return true;
+ }
+
+ // Deal with wildcard bounds
+ WildcardBounds ourBounds = WildcardBounds.get(this);
+ WildcardBounds typeBounds = WildcardBounds.get(other);
+
+ // In the from X is assignable to <? extends Number>
+ if (typeBounds != null) {
+ return (ourBounds != null && ourBounds.isSameKind(typeBounds) &&
+ ourBounds.isAssignableFrom(typeBounds.getBounds()));
+ }
+
+ // In the form <? extends Number> is assignable to X...
+ if (ourBounds != null) {
+ return ourBounds.isAssignableFrom(other);
+ }
+
+ // Main assignability check about to follow
+ boolean exactMatch = (matchedBefore != null); // We're checking nested generic variables now...
+ boolean checkGenerics = true;
+ Class<?> ourResolved = null;
+ if (this.type instanceof TypeVariable) {
+ TypeVariable<?> variable = (TypeVariable<?>) this.type;
+ // Try default variable resolution
+ if (this.variableResolver != null) {
+ ResolvableType resolved = this.variableResolver.resolveVariable(variable);
+ if (resolved != null) {
+ ourResolved = resolved.resolve();
+ }
+ }
+ if (ourResolved == null) {
+ // Try variable resolution against target type
+ if (other.variableResolver != null) {
+ ResolvableType resolved = other.variableResolver.resolveVariable(variable);
+ if (resolved != null) {
+ ourResolved = resolved.resolve();
+ checkGenerics = false;
+ }
+ }
+ }
+ if (ourResolved == null) {
+ // Unresolved type variable, potentially nested -> never insist on exact match
+ exactMatch = false;
+ }
+ }
+ if (ourResolved == null) {
+ ourResolved = resolve(Object.class);
+ }
+ Class<?> otherResolved = other.resolve(Object.class);
+
+ // We need an exact type match for generics
+ // List<CharSequence> is not assignable from List<String>
+ if (exactMatch ? !ourResolved.equals(otherResolved) : !ClassUtils.isAssignable(ourResolved, otherResolved)) {
+ return false;
+ }
+
+ if (checkGenerics) {
+ // Recursively check each generic
+ ResolvableType[] ourGenerics = getGenerics();
+ ResolvableType[] typeGenerics = other.as(ourResolved).getGenerics();
+ if (ourGenerics.length != typeGenerics.length) {
+ return false;
+ }
+ if (matchedBefore == null) {
+ matchedBefore = new IdentityHashMap<Type, Type>(1);
+ }
+ matchedBefore.put(this.type, other.type);
+ for (int i = 0; i < ourGenerics.length; i++) {
+ if (!ourGenerics[i].isAssignableFrom(typeGenerics[i], matchedBefore)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Return {@code true} if this type resolves to a Class that represents an array.
+ * @see #getComponentType()
+ */
+ public boolean isArray() {
+ if (this == NONE) {
+ return false;
+ }
+ return (((this.type instanceof Class && ((Class<?>) this.type).isArray())) ||
+ this.type instanceof GenericArrayType || resolveType().isArray());
+ }
+
+ /**
+ * Return the ResolvableType representing the component type of the array or
+ * {@link #NONE} if this type does not represent an array.
+ * @see #isArray()
+ */
+ public ResolvableType getComponentType() {
+ if (this == NONE) {
+ return NONE;
+ }
+ if (this.componentType != null) {
+ return this.componentType;
+ }
+ if (this.type instanceof Class) {
+ Class<?> componentType = ((Class<?>) this.type).getComponentType();
+ return forType(componentType, this.variableResolver);
+ }
+ if (this.type instanceof GenericArrayType) {
+ return forType(((GenericArrayType) this.type).getGenericComponentType(), this.variableResolver);
+ }
+ return resolveType().getComponentType();
+ }
+
+ /**
+ * Convenience method to return this type as a resolvable {@link Collection} type.
+ * Returns {@link #NONE} if this type does not implement or extend
+ * {@link Collection}.
+ * @see #as(Class)
+ * @see #asMap()
+ */
+ public ResolvableType asCollection() {
+ return as(Collection.class);
+ }
+
+ /**
+ * Convenience method to return this type as a resolvable {@link Map} type.
+ * Returns {@link #NONE} if this type does not implement or extend
+ * {@link Map}.
+ * @see #as(Class)
+ * @see #asCollection()
+ */
+ public ResolvableType asMap() {
+ return as(Map.class);
+ }
+
+ /**
+ * Return this type as a {@link ResolvableType} of the specified class. Searches
+ * {@link #getSuperType() supertype} and {@link #getInterfaces() interface}
+ * hierarchies to find a match, returning {@link #NONE} if this type does not
+ * implement or extend the specified class.
+ * @param type the required class type
+ * @return a {@link ResolvableType} representing this object as the specified
+ * type or {@link #NONE}
+ * @see #asCollection()
+ * @see #asMap()
+ * @see #getSuperType()
+ * @see #getInterfaces()
+ */
+ public ResolvableType as(Class<?> type) {
+ if (this == NONE) {
+ return NONE;
+ }
+ if (ObjectUtils.nullSafeEquals(resolve(), type)) {
+ return this;
+ }
+ for (ResolvableType interfaceType : getInterfaces()) {
+ ResolvableType interfaceAsType = interfaceType.as(type);
+ if (interfaceAsType != NONE) {
+ return interfaceAsType;
+ }
+ }
+ return getSuperType().as(type);
+ }
+
+ /**
+ * Return a {@link ResolvableType} representing the direct supertype of this type.
+ * If no supertype is available this method returns {@link #NONE}.
+ * @see #getInterfaces()
+ */
+ public ResolvableType getSuperType() {
+ Class<?> resolved = resolve();
+ if (resolved == null || resolved.getGenericSuperclass() == null) {
+ return NONE;
+ }
+ if (this.superType == null) {
+ this.superType = forType(SerializableTypeWrapper.forGenericSuperclass(resolved),
+ asVariableResolver());
+ }
+ return this.superType;
+ }
+
+ /**
+ * Return a {@link ResolvableType} array representing the direct interfaces
+ * implemented by this type. If this type does not implement any interfaces an
+ * empty array is returned.
+ * @see #getSuperType()
+ */
+ public ResolvableType[] getInterfaces() {
+ Class<?> resolved = resolve();
+ if (resolved == null || ObjectUtils.isEmpty(resolved.getGenericInterfaces())) {
+ return EMPTY_TYPES_ARRAY;
+ }
+ if (this.interfaces == null) {
+ this.interfaces = forTypes(SerializableTypeWrapper.forGenericInterfaces(resolved),
+ asVariableResolver());
+ }
+ return this.interfaces;
+ }
+
+ /**
+ * Return {@code true} if this type contains generic parameters.
+ * @see #getGeneric(int...)
+ * @see #getGenerics()
+ */
+ public boolean hasGenerics() {
+ return (getGenerics().length > 0);
+ }
+
+ /**
+ * Return {@code true} if this type contains unresolvable generics only,
+ * that is, no substitute for any of its declared type variables.
+ */
+ boolean isEntirelyUnresolvable() {
+ if (this == NONE) {
+ return false;
+ }
+ ResolvableType[] generics = getGenerics();
+ for (ResolvableType generic : generics) {
+ if (!generic.isUnresolvableTypeVariable() && !generic.isWildcardWithoutBounds()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Determine whether the underlying type has any unresolvable generics:
+ * either through an unresolvable type variable on the type itself
+ * or through implementing a generic interface in a raw fashion,
+ * i.e. without substituting that interface's type variables.
+ * The result will be {@code true} only in those two scenarios.
+ */
+ public boolean hasUnresolvableGenerics() {
+ if (this == NONE) {
+ return false;
+ }
+ ResolvableType[] generics = getGenerics();
+ for (ResolvableType generic : generics) {
+ if (generic.isUnresolvableTypeVariable() || generic.isWildcardWithoutBounds()) {
+ return true;
+ }
+ }
+ Class<?> resolved = resolve();
+ if (resolved != null) {
+ for (Type genericInterface : resolved.getGenericInterfaces()) {
+ if (genericInterface instanceof Class) {
+ if (forClass((Class<?>) genericInterface).hasGenerics()) {
+ return true;
+ }
+ }
+ }
+ return getSuperType().hasUnresolvableGenerics();
+ }
+ return false;
+ }
+
+ /**
+ * Determine whether the underlying type is a type variable that
+ * cannot be resolved through the associated variable resolver.
+ */
+ private boolean isUnresolvableTypeVariable() {
+ if (this.type instanceof TypeVariable) {
+ if (this.variableResolver == null) {
+ return true;
+ }
+ TypeVariable<?> variable = (TypeVariable<?>) this.type;
+ ResolvableType resolved = this.variableResolver.resolveVariable(variable);
+ if (resolved == null || resolved.isUnresolvableTypeVariable()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Determine whether the underlying type represents a wildcard
+ * without specific bounds (i.e., equal to {@code ? extends Object}).
+ */
+ private boolean isWildcardWithoutBounds() {
+ if (this.type instanceof WildcardType) {
+ WildcardType wt = (WildcardType) this.type;
+ if (wt.getLowerBounds().length == 0) {
+ Type[] upperBounds = wt.getUpperBounds();
+ if (upperBounds.length == 0 || (upperBounds.length == 1 && Object.class.equals(upperBounds[0]))) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified nesting level. See
+ * {@link #getNested(int, Map)} for details.
+ * @param nestingLevel the nesting level
+ * @return the {@link ResolvableType} type, or {@code #NONE}
+ */
+ public ResolvableType getNested(int nestingLevel) {
+ return getNested(nestingLevel, null);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified nesting level. The nesting level
+ * refers to the specific generic parameter that should be returned. A nesting level
+ * of 1 indicates this type; 2 indicates the first nested generic; 3 the second; and so
+ * on. For example, given {@code List<Set<Integer>>} level 1 refers to the
+ * {@code List}, level 2 the {@code Set}, and level 3 the {@code Integer}.
+ * <p>The {@code typeIndexesPerLevel} map can be used to reference a specific generic
+ * for the given level. For example, an index of 0 would refer to a {@code Map} key;
+ * whereas, 1 would refer to the value. If the map does not contain a value for a
+ * specific level the last generic will be used (e.g. a {@code Map} value).
+ * <p>Nesting levels may also apply to array types; for example given
+ * {@code String[]}, a nesting level of 2 refers to {@code String}.
+ * <p>If a type does not {@link #hasGenerics() contain} generics the
+ * {@link #getSuperType() supertype} hierarchy will be considered.
+ * @param nestingLevel the required nesting level, indexed from 1 for the current
+ * type, 2 for the first nested generic, 3 for the second and so on
+ * @param typeIndexesPerLevel a map containing the generic index for a given nesting
+ * level (may be {@code null})
+ * @return a {@link ResolvableType} for the nested level or {@link #NONE}
+ */
+ public ResolvableType getNested(int nestingLevel, Map<Integer, Integer> typeIndexesPerLevel) {
+ ResolvableType result = this;
+ for (int i = 2; i <= nestingLevel; i++) {
+ if (result.isArray()) {
+ result = result.getComponentType();
+ }
+ else {
+ // Handle derived types
+ while (result != ResolvableType.NONE && !result.hasGenerics()) {
+ result = result.getSuperType();
+ }
+ Integer index = (typeIndexesPerLevel != null ? typeIndexesPerLevel.get(i) : null);
+ index = (index == null ? result.getGenerics().length - 1 : index);
+ result = result.getGeneric(index);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Return a {@link ResolvableType} representing the generic parameter for the given
+ * indexes. Indexes are zero based; for example given the type
+ * {@code Map<Integer, List<String>>}, {@code getGeneric(0)} will access the
+ * {@code Integer}. Nested generics can be accessed by specifying multiple indexes;
+ * for example {@code getGeneric(1, 0)} will access the {@code String} from the nested
+ * {@code List}. For convenience, if no indexes are specified the first generic is
+ * returned.
+ * <p>If no generic is available at the specified indexes {@link #NONE} is returned.
+ * @param indexes the indexes that refer to the generic parameter (may be omitted to
+ * return the first generic)
+ * @return a {@link ResolvableType} for the specified generic or {@link #NONE}
+ * @see #hasGenerics()
+ * @see #getGenerics()
+ * @see #resolveGeneric(int...)
+ * @see #resolveGenerics()
+ */
+ public ResolvableType getGeneric(int... indexes) {
+ try {
+ if (indexes == null || indexes.length == 0) {
+ return getGenerics()[0];
+ }
+ ResolvableType generic = this;
+ for (int index : indexes) {
+ generic = generic.getGenerics()[index];
+ }
+ return generic;
+ }
+ catch (IndexOutOfBoundsException ex) {
+ return NONE;
+ }
+ }
+
+ /**
+ * Return an array of {@link ResolvableType}s representing the generic parameters of
+ * this type. If no generics are available an empty array is returned. If you need to
+ * access a specific generic consider using the {@link #getGeneric(int...)} method as
+ * it allows access to nested generics and protects against
+ * {@code IndexOutOfBoundsExceptions}.
+ * @return an array of {@link ResolvableType}s representing the generic parameters
+ * (never {@code null})
+ * @see #hasGenerics()
+ * @see #getGeneric(int...)
+ * @see #resolveGeneric(int...)
+ * @see #resolveGenerics()
+ */
+ public ResolvableType[] getGenerics() {
+ if (this == NONE) {
+ return EMPTY_TYPES_ARRAY;
+ }
+ if (this.generics == null) {
+ if (this.type instanceof Class<?>) {
+ Class<?> typeClass = (Class<?>) this.type;
+ this.generics = forTypes(SerializableTypeWrapper.forTypeParameters(typeClass), this.variableResolver);
+ }
+ else if (this.type instanceof ParameterizedType) {
+ Type[] actualTypeArguments = ((ParameterizedType) this.type).getActualTypeArguments();
+ ResolvableType[] generics = new ResolvableType[actualTypeArguments.length];
+ for (int i = 0; i < actualTypeArguments.length; i++) {
+ generics[i] = forType(actualTypeArguments[i], this.variableResolver);
+ }
+ this.generics = generics;
+ }
+ else {
+ this.generics = resolveType().getGenerics();
+ }
+ }
+ return this.generics;
+ }
+
+ /**
+ * Convenience method that will {@link #getGenerics() get} and
+ * {@link #resolve() resolve} generic parameters.
+ * @return an array of resolved generic parameters (the resulting array
+ * will never be {@code null}, but it may contain {@code null} elements})
+ * @see #getGenerics()
+ * @see #resolve()
+ */
+ public Class<?>[] resolveGenerics() {
+ return resolveGenerics(null);
+ }
+
+ /**
+ * Convenience method that will {@link #getGenerics() get} and {@link #resolve()
+ * resolve} generic parameters, using the specified {@code fallback} if any type
+ * cannot be resolved.
+ * @param fallback the fallback class to use if resolution fails (may be {@code null})
+ * @return an array of resolved generic parameters (the resulting array will never be
+ * {@code null}, but it may contain {@code null} elements})
+ * @see #getGenerics()
+ * @see #resolve()
+ */
+ public Class<?>[] resolveGenerics(Class<?> fallback) {
+ ResolvableType[] generics = getGenerics();
+ Class<?>[] resolvedGenerics = new Class<?>[generics.length];
+ for (int i = 0; i < generics.length; i++) {
+ resolvedGenerics[i] = generics[i].resolve(fallback);
+ }
+ return resolvedGenerics;
+ }
+
+ /**
+ * Convenience method that will {@link #getGeneric(int...) get} and
+ * {@link #resolve() resolve} a specific generic parameters.
+ * @param indexes the indexes that refer to the generic parameter
+ * (may be omitted to return the first generic)
+ * @return a resolved {@link Class} or {@code null}
+ * @see #getGeneric(int...)
+ * @see #resolve()
+ */
+ public Class<?> resolveGeneric(int... indexes) {
+ return getGeneric(indexes).resolve();
+ }
+
+ /**
+ * Resolve this type to a {@link java.lang.Class}, returning {@code null}
+ * if the type cannot be resolved. This method will consider bounds of
+ * {@link TypeVariable}s and {@link WildcardType}s if direct resolution fails;
+ * however, bounds of {@code Object.class} will be ignored.
+ * @return the resolved {@link Class}, or {@code null} if not resolvable
+ * @see #resolve(Class)
+ * @see #resolveGeneric(int...)
+ * @see #resolveGenerics()
+ */
+ public Class<?> resolve() {
+ return resolve(null);
+ }
+
+ /**
+ * Resolve this type to a {@link java.lang.Class}, returning the specified
+ * {@code fallback} if the type cannot be resolved. This method will consider bounds
+ * of {@link TypeVariable}s and {@link WildcardType}s if direct resolution fails;
+ * however, bounds of {@code Object.class} will be ignored.
+ * @param fallback the fallback class to use if resolution fails (may be {@code null})
+ * @return the resolved {@link Class} or the {@code fallback}
+ * @see #resolve()
+ * @see #resolveGeneric(int...)
+ * @see #resolveGenerics()
+ */
+ public Class<?> resolve(Class<?> fallback) {
+ return (this.resolved != null ? this.resolved : fallback);
+ }
+
+ private Class<?> resolveClass() {
+ if (this.type instanceof Class<?> || this.type == null) {
+ return (Class<?>) this.type;
+ }
+ if (this.type instanceof GenericArrayType) {
+ Class<?> resolvedComponent = getComponentType().resolve();
+ return (resolvedComponent != null ? Array.newInstance(resolvedComponent, 0).getClass() : null);
+ }
+ return resolveType().resolve();
+ }
+
+ /**
+ * Resolve this type by a single level, returning the resolved value or {@link #NONE}.
+ * <p>Note: The returned {@link ResolvableType} should only be used as an intermediary
+ * as it cannot be serialized.
+ */
+ ResolvableType resolveType() {
+ if (this.type instanceof ParameterizedType) {
+ return forType(((ParameterizedType) this.type).getRawType(), this.variableResolver);
+ }
+ if (this.type instanceof WildcardType) {
+ Type resolved = resolveBounds(((WildcardType) this.type).getUpperBounds());
+ if (resolved == null) {
+ resolved = resolveBounds(((WildcardType) this.type).getLowerBounds());
+ }
+ return forType(resolved, this.variableResolver);
+ }
+ if (this.type instanceof TypeVariable) {
+ TypeVariable<?> variable = (TypeVariable<?>) this.type;
+ // Try default variable resolution
+ if (this.variableResolver != null) {
+ ResolvableType resolved = this.variableResolver.resolveVariable(variable);
+ if (resolved != null) {
+ return resolved;
+ }
+ }
+ // Fallback to bounds
+ return forType(resolveBounds(variable.getBounds()), this.variableResolver);
+ }
+ return NONE;
+ }
+
+ private Type resolveBounds(Type[] bounds) {
+ if (ObjectUtils.isEmpty(bounds) || Object.class.equals(bounds[0])) {
+ return null;
+ }
+ return bounds[0];
+ }
+
+ private ResolvableType resolveVariable(TypeVariable<?> variable) {
+ if (this.type instanceof TypeVariable) {
+ return resolveType().resolveVariable(variable);
+ }
+ if (this.type instanceof ParameterizedType) {
+ ParameterizedType parameterizedType = (ParameterizedType) this.type;
+ TypeVariable<?>[] variables = resolve().getTypeParameters();
+ for (int i = 0; i < variables.length; i++) {
+ if (ObjectUtils.nullSafeEquals(variables[i].getName(), variable.getName())) {
+ Type actualType = parameterizedType.getActualTypeArguments()[i];
+ return forType(actualType, this.variableResolver);
+ }
+ }
+ if (parameterizedType.getOwnerType() != null) {
+ return forType(parameterizedType.getOwnerType(), this.variableResolver).resolveVariable(variable);
+ }
+ }
+ if (this.variableResolver != null) {
+ return this.variableResolver.resolveVariable(variable);
+ }
+ return null;
+ }
+
+ /**
+ * Return a String representation of this type in its fully resolved form
+ * (including any generic parameters).
+ */
+ @Override
+ public String toString() {
+ if (isArray()) {
+ return getComponentType() + "[]";
+ }
+ if (this.resolved == null) {
+ return "?";
+ }
+ if (this.type instanceof TypeVariable) {
+ TypeVariable<?> variable = (TypeVariable<?>) this.type;
+ if (this.variableResolver == null || this.variableResolver.resolveVariable(variable) == null) {
+ // Don't bother with variable boundaries for toString()...
+ // Can cause infinite recursions in case of self-references
+ return "?";
+ }
+ }
+ StringBuilder result = new StringBuilder(this.resolved.getName());
+ if (hasGenerics()) {
+ result.append('<');
+ result.append(StringUtils.arrayToDelimitedString(getGenerics(), ", "));
+ result.append('>');
+ }
+ return result.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof ResolvableType)) {
+ return false;
+ }
+ ResolvableType other = (ResolvableType) obj;
+ return (ObjectUtils.nullSafeEquals(this.type, other.type) &&
+ ObjectUtils.nullSafeEquals(getSource(), other.getSource()) &&
+ variableResolverSourceEquals(other.variableResolver) &&
+ ObjectUtils.nullSafeEquals(this.componentType, other.componentType));
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = ObjectUtils.nullSafeHashCode(this.type);
+ hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(getSource());
+ hashCode = 31 * hashCode + variableResolverSourceHashCode();
+ hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.componentType);
+ return hashCode;
+ }
+
+ /**
+ * Custom serialization support for {@link #NONE}.
+ */
+ private Object readResolve() throws ObjectStreamException {
+ return (this.type == null ? NONE : this);
+ }
+
+ /**
+ * Adapts this {@link ResolvableType} to a {@link VariableResolver}.
+ */
+ VariableResolver asVariableResolver() {
+ if (this == NONE) {
+ return null;
+ }
+ return new DefaultVariableResolver();
+ }
+
+ private boolean variableResolverSourceEquals(VariableResolver other) {
+ if (this.variableResolver == null) {
+ return (other == null);
+ }
+ if (other == null) {
+ return false;
+ }
+ return ObjectUtils.nullSafeEquals(this.variableResolver.getSource(), other.getSource());
+ }
+
+ private int variableResolverSourceHashCode() {
+ int hashCode = 0;
+ if (this.variableResolver != null) {
+ hashCode = ObjectUtils.nullSafeHashCode(this.variableResolver.getSource());
+ }
+ return hashCode;
+ }
+
+ private static ResolvableType[] forTypes(Type[] types, VariableResolver owner) {
+ ResolvableType[] result = new ResolvableType[types.length];
+ for (int i = 0; i < types.length; i++) {
+ result[i] = forType(types[i], owner);
+ }
+ return result;
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Class}. For example
+ * {@code ResolvableType.forClass(MyArrayList.class)}.
+ * @param sourceClass the source class (must not be {@code null}
+ * @return a {@link ResolvableType} for the specified class
+ * @see #forClass(Class, Class)
+ * @see #forClassWithGenerics(Class, Class...)
+ */
+ public static ResolvableType forClass(Class<?> sourceClass) {
+ Assert.notNull(sourceClass, "Source class must not be null");
+ return forType(sourceClass);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Class} with a given
+ * implementation. For example
+ * {@code ResolvableType.forClass(List.class, MyArrayList.class)}.
+ * @param sourceClass the source class (must not be {@code null}
+ * @param implementationClass the implementation class
+ * @return a {@link ResolvableType} for the specified class backed by the given
+ * implementation class
+ * @see #forClass(Class)
+ * @see #forClassWithGenerics(Class, Class...)
+ */
+ public static ResolvableType forClass(Class<?> sourceClass, Class<?> implementationClass) {
+ Assert.notNull(sourceClass, "Source class must not be null");
+ ResolvableType asType = forType(implementationClass).as(sourceClass);
+ return (asType == NONE ? forType(sourceClass) : asType);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Field}.
+ * @param field the source field
+ * @return a {@link ResolvableType} for the specified field
+ * @see #forField(Field, Class)
+ */
+ public static ResolvableType forField(Field field) {
+ Assert.notNull(field, "Field must not be null");
+ return forType(null, new FieldTypeProvider(field), null);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Field} with a given
+ * implementation.
+ * <p>Use this variant when the class that declares the field includes generic
+ * parameter variables that are satisfied by the implementation class.
+ * @param field the source field
+ * @param implementationClass the implementation class
+ * @return a {@link ResolvableType} for the specified field
+ * @see #forField(Field)
+ */
+ public static ResolvableType forField(Field field, Class<?> implementationClass) {
+ Assert.notNull(field, "Field must not be null");
+ ResolvableType owner = forType(implementationClass).as(field.getDeclaringClass());
+ return forType(null, new FieldTypeProvider(field), owner.asVariableResolver());
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Field} with a given
+ * implementation.
+ * <p>Use this variant when the class that declares the field includes generic
+ * parameter variables that are satisfied by the implementation type.
+ * @param field the source field
+ * @param implementationType the implementation type
+ * @return a {@link ResolvableType} for the specified field
+ * @see #forField(Field)
+ */
+ public static ResolvableType forField(Field field, ResolvableType implementationType) {
+ Assert.notNull(field, "Field must not be null");
+ implementationType = (implementationType == null ? NONE : implementationType);
+ ResolvableType owner = implementationType.as(field.getDeclaringClass());
+ return forType(null, new FieldTypeProvider(field), owner.asVariableResolver());
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Field} with the
+ * given nesting level.
+ * @param field the source field
+ * @param nestingLevel the nesting level (1 for the outer level; 2 for a nested
+ * generic type; etc)
+ * @see #forField(Field)
+ */
+ public static ResolvableType forField(Field field, int nestingLevel) {
+ Assert.notNull(field, "Field must not be null");
+ return forType(null, new FieldTypeProvider(field), null).getNested(nestingLevel);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Field} with a given
+ * implementation and the given nesting level.
+ * <p>Use this variant when the class that declares the field includes generic
+ * parameter variables that are satisfied by the implementation class.
+ * @param field the source field
+ * @param nestingLevel the nesting level (1 for the outer level; 2 for a nested
+ * generic type; etc)
+ * @param implementationClass the implementation class
+ * @return a {@link ResolvableType} for the specified field
+ * @see #forField(Field)
+ */
+ public static ResolvableType forField(Field field, int nestingLevel, Class<?> implementationClass) {
+ Assert.notNull(field, "Field must not be null");
+ ResolvableType owner = forType(implementationClass).as(field.getDeclaringClass());
+ return forType(null, new FieldTypeProvider(field), owner.asVariableResolver()).getNested(nestingLevel);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Constructor} parameter.
+ * @param constructor the source constructor (must not be {@code null})
+ * @param parameterIndex the parameter index
+ * @return a {@link ResolvableType} for the specified constructor parameter
+ * @see #forConstructorParameter(Constructor, int, Class)
+ */
+ public static ResolvableType forConstructorParameter(Constructor<?> constructor, int parameterIndex) {
+ Assert.notNull(constructor, "Constructor must not be null");
+ return forMethodParameter(new MethodParameter(constructor, parameterIndex));
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Constructor} parameter
+ * with a given implementation. Use this variant when the class that declares the
+ * constructor includes generic parameter variables that are satisfied by the
+ * implementation class.
+ * @param constructor the source constructor (must not be {@code null})
+ * @param parameterIndex the parameter index
+ * @param implementationClass the implementation class
+ * @return a {@link ResolvableType} for the specified constructor parameter
+ * @see #forConstructorParameter(Constructor, int)
+ */
+ public static ResolvableType forConstructorParameter(Constructor<?> constructor, int parameterIndex,
+ Class<?> implementationClass) {
+
+ Assert.notNull(constructor, "Constructor must not be null");
+ MethodParameter methodParameter = new MethodParameter(constructor, parameterIndex);
+ methodParameter.setContainingClass(implementationClass);
+ return forMethodParameter(methodParameter);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Method} return type.
+ * @param method the source for the method return type
+ * @return a {@link ResolvableType} for the specified method return
+ * @see #forMethodReturnType(Method, Class)
+ */
+ public static ResolvableType forMethodReturnType(Method method) {
+ Assert.notNull(method, "Method must not be null");
+ return forMethodParameter(MethodParameter.forMethodOrConstructor(method, -1));
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Method} return type.
+ * Use this variant when the class that declares the method includes generic
+ * parameter variables that are satisfied by the implementation class.
+ * @param method the source for the method return type
+ * @param implementationClass the implementation class
+ * @return a {@link ResolvableType} for the specified method return
+ * @see #forMethodReturnType(Method)
+ */
+ public static ResolvableType forMethodReturnType(Method method, Class<?> implementationClass) {
+ Assert.notNull(method, "Method must not be null");
+ MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, -1);
+ methodParameter.setContainingClass(implementationClass);
+ return forMethodParameter(methodParameter);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Method} parameter.
+ * @param method the source method (must not be {@code null})
+ * @param parameterIndex the parameter index
+ * @return a {@link ResolvableType} for the specified method parameter
+ * @see #forMethodParameter(Method, int, Class)
+ * @see #forMethodParameter(MethodParameter)
+ */
+ public static ResolvableType forMethodParameter(Method method, int parameterIndex) {
+ Assert.notNull(method, "Method must not be null");
+ return forMethodParameter(new MethodParameter(method, parameterIndex));
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Method} parameter with a
+ * given implementation. Use this variant when the class that declares the method
+ * includes generic parameter variables that are satisfied by the implementation class.
+ * @param method the source method (must not be {@code null})
+ * @param parameterIndex the parameter index
+ * @param implementationClass the implementation class
+ * @return a {@link ResolvableType} for the specified method parameter
+ * @see #forMethodParameter(Method, int, Class)
+ * @see #forMethodParameter(MethodParameter)
+ */
+ public static ResolvableType forMethodParameter(Method method, int parameterIndex, Class<?> implementationClass) {
+ Assert.notNull(method, "Method must not be null");
+ MethodParameter methodParameter = new MethodParameter(method, parameterIndex);
+ methodParameter.setContainingClass(implementationClass);
+ return forMethodParameter(methodParameter);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link MethodParameter}.
+ * @param methodParameter the source method parameter (must not be {@code null})
+ * @return a {@link ResolvableType} for the specified method parameter
+ * @see #forMethodParameter(Method, int)
+ */
+ public static ResolvableType forMethodParameter(MethodParameter methodParameter) {
+ return forMethodParameter(methodParameter, (Type) null);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link MethodParameter} with a
+ * given implementation type. Use this variant when the class that declares the method
+ * includes generic parameter variables that are satisfied by the implementation type.
+ * @param methodParameter the source method parameter (must not be {@code null})
+ * @param implementationType the implementation type
+ * @return a {@link ResolvableType} for the specified method parameter
+ * @see #forMethodParameter(MethodParameter)
+ */
+ public static ResolvableType forMethodParameter(MethodParameter methodParameter, ResolvableType implementationType) {
+ Assert.notNull(methodParameter, "MethodParameter must not be null");
+ implementationType = (implementationType == null ? forType(methodParameter.getContainingClass()) : implementationType);
+ ResolvableType owner = implementationType.as(methodParameter.getDeclaringClass());
+ return forType(null, new MethodParameterTypeProvider(methodParameter),
+ owner.asVariableResolver()).getNested(methodParameter.getNestingLevel(),
+ methodParameter.typeIndexesPerLevel);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link MethodParameter},
+ * overriding the target type to resolve with a specific given type.
+ * @param methodParameter the source method parameter (must not be {@code null})
+ * @param targetType the type to resolve (a part of the method parameter's type)
+ * @return a {@link ResolvableType} for the specified method parameter
+ * @see #forMethodParameter(Method, int)
+ */
+ public static ResolvableType forMethodParameter(MethodParameter methodParameter, Type targetType) {
+ Assert.notNull(methodParameter, "MethodParameter must not be null");
+ ResolvableType owner = forType(methodParameter.getContainingClass()).as(methodParameter.getDeclaringClass());
+ return forType(targetType, new MethodParameterTypeProvider(methodParameter),
+ owner.asVariableResolver()).getNested(methodParameter.getNestingLevel(),
+ methodParameter.typeIndexesPerLevel);
+ }
+
+ /**
+ * Return a {@link ResolvableType} as a array of the specified {@code componentType}.
+ * @param componentType the component type
+ * @return a {@link ResolvableType} as an array of the specified component type
+ */
+ public static ResolvableType forArrayComponent(ResolvableType componentType) {
+ Assert.notNull(componentType, "ComponentType must not be null");
+ Class<?> arrayClass = Array.newInstance(componentType.resolve(), 0).getClass();
+ return new ResolvableType(arrayClass, null, null, componentType);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Class} with pre-declared generics.
+ * @param sourceClass the source class
+ * @param generics the generics of the class
+ * @return a {@link ResolvableType} for the specific class and generics
+ * @see #forClassWithGenerics(Class, ResolvableType...)
+ */
+ public static ResolvableType forClassWithGenerics(Class<?> sourceClass, Class<?>... generics) {
+ Assert.notNull(sourceClass, "Source class must not be null");
+ Assert.notNull(generics, "Generics must not be null");
+ ResolvableType[] resolvableGenerics = new ResolvableType[generics.length];
+ for (int i = 0; i < generics.length; i++) {
+ resolvableGenerics[i] = forClass(generics[i]);
+ }
+ return forClassWithGenerics(sourceClass, resolvableGenerics);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Class} with pre-declared generics.
+ * @param sourceClass the source class
+ * @param generics the generics of the class
+ * @return a {@link ResolvableType} for the specific class and generics
+ * @see #forClassWithGenerics(Class, Class...)
+ */
+ public static ResolvableType forClassWithGenerics(Class<?> sourceClass, ResolvableType... generics) {
+ Assert.notNull(sourceClass, "Source class must not be null");
+ Assert.notNull(generics, "Generics must not be null");
+ TypeVariable<?>[] typeVariables = sourceClass.getTypeParameters();
+ return forType(sourceClass, new TypeVariablesVariableResolver(typeVariables, generics));
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Type}.
+ * Note: The resulting {@link ResolvableType} may not be {@link Serializable}.
+ * @param type the source type or {@code null}
+ * @return a {@link ResolvableType} for the specified {@link Type}
+ * @see #forType(Type, ResolvableType)
+ */
+ public static ResolvableType forType(Type type) {
+ return forType(type, null, null);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Type} backed by the given
+ * owner type. Note: The resulting {@link ResolvableType} may not be {@link Serializable}.
+ * @param type the source type or {@code null}
+ * @param owner the owner type used to resolve variables
+ * @return a {@link ResolvableType} for the specified {@link Type} and owner
+ * @see #forType(Type)
+ */
+ public static ResolvableType forType(Type type, ResolvableType owner) {
+ VariableResolver variableResolver = null;
+ if (owner != null) {
+ variableResolver = owner.asVariableResolver();
+ }
+ return forType(type, variableResolver);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Type} backed by a given
+ * {@link VariableResolver}.
+ * @param type the source type or {@code null}
+ * @param variableResolver the variable resolver or {@code null}
+ * @return a {@link ResolvableType} for the specified {@link Type} and {@link VariableResolver}
+ */
+ static ResolvableType forType(Type type, VariableResolver variableResolver) {
+ return forType(type, null, variableResolver);
+ }
+
+ /**
+ * Return a {@link ResolvableType} for the specified {@link Type} backed by a given
+ * {@link VariableResolver}.
+ * @param type the source type or {@code null}
+ * @param typeProvider the type provider or {@code null}
+ * @param variableResolver the variable resolver or {@code null}
+ * @return a {@link ResolvableType} for the specified {@link Type} and {@link VariableResolver}
+ */
+ static ResolvableType forType(Type type, TypeProvider typeProvider, VariableResolver variableResolver) {
+ if (type == null && typeProvider != null) {
+ type = SerializableTypeWrapper.forTypeProvider(typeProvider);
+ }
+ if (type == null) {
+ return NONE;
+ }
+
+ // Purge empty entries on access since we don't have a clean-up thread or the like.
+ cache.purgeUnreferencedEntries();
+
+ // For simple Class references, build the wrapper right away -
+ // no expensive resolution necessary, so not worth caching...
+ if (type instanceof Class<?>) {
+ return new ResolvableType(type, typeProvider, variableResolver, null);
+ }
+
+ // Check the cache - we may have a ResolvableType which has been resolved before...
+ ResolvableType key = new ResolvableType(type, typeProvider, variableResolver);
+ ResolvableType resolvableType = cache.get(key);
+ if (resolvableType == null) {
+ resolvableType = new ResolvableType(type, typeProvider, variableResolver, null);
+ cache.put(resolvableType, resolvableType);
+ }
+ return resolvableType;
+ }
+
+
+ /**
+ * Strategy interface used to resolve {@link TypeVariable}s.
+ */
+ static interface VariableResolver extends Serializable {
+
+ /**
+ * Return the source of the resolver (used for hashCode and equals).
+ */
+ Object getSource();
+
+ /**
+ * Resolve the specified variable.
+ * @param variable the variable to resolve
+ * @return the resolved variable or {@code null}
+ */
+ ResolvableType resolveVariable(TypeVariable<?> variable);
+ }
+
+
+ @SuppressWarnings("serial")
+ private class DefaultVariableResolver implements VariableResolver {
+
+ @Override
+ public ResolvableType resolveVariable(TypeVariable<?> variable) {
+ return ResolvableType.this.resolveVariable(variable);
+ }
+
+ @Override
+ public Object getSource() {
+ return ResolvableType.this;
+ }
+ }
+
+
+ @SuppressWarnings("serial")
+ private static class TypeVariablesVariableResolver implements VariableResolver {
+
+ private final TypeVariable<?>[] typeVariables;
+
+ private final ResolvableType[] generics;
+
+ public TypeVariablesVariableResolver(TypeVariable<?>[] typeVariables, ResolvableType[] generics) {
+ Assert.isTrue(typeVariables.length == generics.length, "Mismatched number of generics specified");
+ this.typeVariables = typeVariables;
+ this.generics = generics;
+ }
+
+ @Override
+ public ResolvableType resolveVariable(TypeVariable<?> variable) {
+ for (int i = 0; i < this.typeVariables.length; i++) {
+ if (SerializableTypeWrapper.unwrap(this.typeVariables[i]).equals(
+ SerializableTypeWrapper.unwrap(variable))) {
+ return this.generics[i];
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Object getSource() {
+ return this.generics;
+ }
+ }
+
+
+ /**
+ * Internal helper to handle bounds from {@link WildcardType}s.
+ */
+ private static class WildcardBounds {
+
+ private final Kind kind;
+
+ private final ResolvableType[] bounds;
+
+ /**
+ * Internal constructor to create a new {@link WildcardBounds} instance.
+ * @param kind the kind of bounds
+ * @param bounds the bounds
+ * @see #get(ResolvableType)
+ */
+ public WildcardBounds(Kind kind, ResolvableType[] bounds) {
+ this.kind = kind;
+ this.bounds = bounds;
+ }
+
+ /**
+ * Return {@code true} if this bounds is the same kind as the specified bounds.
+ */
+ public boolean isSameKind(WildcardBounds bounds) {
+ return this.kind == bounds.kind;
+ }
+
+ /**
+ * Return {@code true} if this bounds is assignable to all the specified types.
+ * @param types the types to test against
+ * @return {@code true} if this bounds is assignable to all types
+ */
+ public boolean isAssignableFrom(ResolvableType... types) {
+ for (ResolvableType bound : this.bounds) {
+ for (ResolvableType type : types) {
+ if (!isAssignable(bound, type)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private boolean isAssignable(ResolvableType source, ResolvableType from) {
+ return (this.kind == Kind.UPPER ? source.isAssignableFrom(from) : from.isAssignableFrom(source));
+ }
+
+ /**
+ * Return the underlying bounds.
+ */
+ public ResolvableType[] getBounds() {
+ return this.bounds;
+ }
+
+ /**
+ * Get a {@link WildcardBounds} instance for the specified type, returning
+ * {@code null} if the specified type cannot be resolved to a {@link WildcardType}.
+ * @param type the source type
+ * @return a {@link WildcardBounds} instance or {@code null}
+ */
+ public static WildcardBounds get(ResolvableType type) {
+ ResolvableType resolveToWildcard = type;
+ while (!(resolveToWildcard.getType() instanceof WildcardType)) {
+ if (resolveToWildcard == NONE) {
+ return null;
+ }
+ resolveToWildcard = resolveToWildcard.resolveType();
+ }
+ WildcardType wildcardType = (WildcardType) resolveToWildcard.type;
+ Kind boundsType = (wildcardType.getLowerBounds().length > 0 ? Kind.LOWER : Kind.UPPER);
+ Type[] bounds = boundsType == Kind.UPPER ? wildcardType.getUpperBounds() : wildcardType.getLowerBounds();
+ ResolvableType[] resolvableBounds = new ResolvableType[bounds.length];
+ for (int i = 0; i < bounds.length; i++) {
+ resolvableBounds[i] = ResolvableType.forType(bounds[i], type.variableResolver);
+ }
+ return new WildcardBounds(boundsType, resolvableBounds);
+ }
+
+ /**
+ * The various kinds of bounds.
+ */
+ static enum Kind {UPPER, LOWER}
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java
new file mode 100644
index 00000000..439f66b9
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java
@@ -0,0 +1,408 @@
+/*
+ * 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.ObjectInputStream;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+
+import org.springframework.util.Assert;
+import org.springframework.util.ConcurrentReferenceHashMap;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * Internal utility class that can be used to obtain wrapped {@link Serializable} variants
+ * of {@link java.lang.reflect.Type}s.
+ *
+ * <p>{@link #forField(Field) Fields} or {@link #forMethodParameter(MethodParameter)
+ * MethodParameters} can be used as the root source for a serializable type. Alternatively
+ * the {@link #forGenericSuperclass(Class) superclass},
+ * {@link #forGenericInterfaces(Class) interfaces} or {@link #forTypeParameters(Class)
+ * type parameters} or a regular {@link Class} can also be used as source.
+ *
+ * <p>The returned type will either be a {@link Class} or a serializable proxy of
+ * {@link GenericArrayType}, {@link ParameterizedType}, {@link TypeVariable} or
+ * {@link WildcardType}. With the exception of {@link Class} (which is final) calls to
+ * methods that return further {@link Type}s (for example
+ * {@link GenericArrayType#getGenericComponentType()}) will be automatically wrapped.
+ *
+ * @author Phillip Webb
+ * @since 4.0
+ */
+abstract class SerializableTypeWrapper {
+
+ private static final Class<?>[] SUPPORTED_SERIALIZABLE_TYPES = {
+ GenericArrayType.class, ParameterizedType.class, TypeVariable.class, WildcardType.class};
+
+ private static final Method EQUALS_METHOD = ReflectionUtils.findMethod(Object.class,
+ "equals", Object.class);
+
+ private static final Method GET_TYPE_PROVIDER_METHOD = ReflectionUtils.findMethod(
+ SerializableTypeProxy.class, "getTypeProvider");
+
+ private static final ConcurrentReferenceHashMap<Type, Type> cache =
+ new ConcurrentReferenceHashMap<Type, Type>(256);
+
+ /**
+ * Return a {@link Serializable} variant of {@link Field#getGenericType()}.
+ */
+ public static Type forField(Field field) {
+ Assert.notNull(field, "Field must not be null");
+ return forTypeProvider(new FieldTypeProvider(field));
+ }
+
+ /**
+ * Return a {@link Serializable} variant of
+ * {@link MethodParameter#getGenericParameterType()}.
+ */
+ public static Type forMethodParameter(MethodParameter methodParameter) {
+ return forTypeProvider(new MethodParameterTypeProvider(methodParameter));
+ }
+
+ /**
+ * Return a {@link Serializable} variant of {@link Class#getGenericSuperclass()}.
+ */
+ @SuppressWarnings("serial")
+ public static Type forGenericSuperclass(final Class<?> type) {
+ return forTypeProvider(new DefaultTypeProvider() {
+ @Override
+ public Type getType() {
+ return type.getGenericSuperclass();
+ }
+ });
+ }
+
+ /**
+ * Return a {@link Serializable} variant of {@link Class#getGenericInterfaces()}.
+ */
+ @SuppressWarnings("serial")
+ public static Type[] forGenericInterfaces(final Class<?> type) {
+ Type[] result = new Type[type.getGenericInterfaces().length];
+ for (int i = 0; i < result.length; i++) {
+ final int index = i;
+ result[i] = forTypeProvider(new DefaultTypeProvider() {
+ @Override
+ public Type getType() {
+ return type.getGenericInterfaces()[index];
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Return a {@link Serializable} variant of {@link Class#getTypeParameters()}.
+ */
+ @SuppressWarnings("serial")
+ public static Type[] forTypeParameters(final Class<?> type) {
+ Type[] result = new Type[type.getTypeParameters().length];
+ for (int i = 0; i < result.length; i++) {
+ final int index = i;
+ result[i] = forTypeProvider(new DefaultTypeProvider() {
+ @Override
+ public Type getType() {
+ return type.getTypeParameters()[index];
+ }
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Unwrap the given type, effectively returning the original non-serializable type.
+ * @param type the type to unwrap
+ * @return the original non-serializable type
+ */
+ @SuppressWarnings("unchecked")
+ public static <T extends Type> T unwrap(T type) {
+ Type unwrapped = type;
+ while (unwrapped instanceof SerializableTypeProxy) {
+ unwrapped = ((SerializableTypeProxy) type).getTypeProvider().getType();
+ }
+ return (T) unwrapped;
+ }
+
+ /**
+ * Return a {@link Serializable} {@link Type} backed by a {@link TypeProvider} .
+ */
+ static Type forTypeProvider(final TypeProvider provider) {
+ Assert.notNull(provider, "Provider must not be null");
+ if (provider.getType() instanceof Serializable || provider.getType() == null) {
+ return provider.getType();
+ }
+ Type cached = cache.get(provider.getType());
+ if (cached != null) {
+ return cached;
+ }
+ for (Class<?> type : SUPPORTED_SERIALIZABLE_TYPES) {
+ if (type.isAssignableFrom(provider.getType().getClass())) {
+ ClassLoader classLoader = provider.getClass().getClassLoader();
+ Class<?>[] interfaces = new Class<?>[] { type,
+ SerializableTypeProxy.class, Serializable.class };
+ InvocationHandler handler = new TypeProxyInvocationHandler(provider);
+ cached = (Type) Proxy.newProxyInstance(classLoader, interfaces, handler);
+ cache.put(provider.getType(), cached);
+ return cached;
+ }
+ }
+ throw new IllegalArgumentException("Unsupported Type class " + provider.getType().getClass().getName());
+ }
+
+
+ /**
+ * Additional interface implemented by the type proxy.
+ */
+ static interface SerializableTypeProxy {
+
+ /**
+ * Return the underlying type provider.
+ */
+ TypeProvider getTypeProvider();
+
+ }
+
+
+ /**
+ * A {@link Serializable} interface providing access to a {@link Type}.
+ */
+ static interface TypeProvider extends Serializable {
+
+ /**
+ * Return the (possibly non {@link Serializable}) {@link Type}.
+ */
+ Type getType();
+
+ /**
+ * Return the source of the type or {@code null}.
+ */
+ Object getSource();
+ }
+
+
+ /**
+ * Default implementation of {@link TypeProvider} with a {@code null} source.
+ */
+ @SuppressWarnings("serial")
+ private static abstract class DefaultTypeProvider implements TypeProvider {
+
+ @Override
+ public Object getSource() {
+ return null;
+ }
+
+ }
+
+
+ /**
+ * {@link Serializable} {@link InvocationHandler} used by the Proxied {@link Type}.
+ * Provides serialization support and enhances any methods that return {@code Type}
+ * or {@code Type[]}.
+ */
+ @SuppressWarnings("serial")
+ private static class TypeProxyInvocationHandler implements InvocationHandler, Serializable {
+
+ private final TypeProvider provider;
+
+ public TypeProxyInvocationHandler(TypeProvider provider) {
+ this.provider = provider;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ if (GET_TYPE_PROVIDER_METHOD.equals(method)) {
+ return this.provider;
+ }
+ if (EQUALS_METHOD.equals(method)) {
+ Object other = args[0];
+ // Unwrap proxies for speed
+ if (other instanceof Type) {
+ other = unwrap((Type) other);
+ }
+ return this.provider.getType().equals(other);
+ }
+ if (Type.class.equals(method.getReturnType()) && args == null) {
+ return forTypeProvider(new MethodInvokeTypeProvider(this.provider, method, -1));
+ }
+ if (Type[].class.equals(method.getReturnType()) && args == null) {
+ Type[] result = new Type[((Type[]) method.invoke(this.provider.getType(), args)).length];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = forTypeProvider(new MethodInvokeTypeProvider(this.provider, method, i));
+ }
+ return result;
+ }
+ try {
+ return method.invoke(this.provider.getType(), args);
+ }
+ catch (InvocationTargetException ex) {
+ throw ex.getTargetException();
+ }
+ }
+ }
+
+
+ /**
+ * {@link TypeProvider} for {@link Type}s obtained from a {@link Field}.
+ */
+ @SuppressWarnings("serial")
+ static class FieldTypeProvider implements TypeProvider {
+
+ private final String fieldName;
+
+ private final Class<?> declaringClass;
+
+ private transient Field field;
+
+ public FieldTypeProvider(Field field) {
+ this.fieldName = field.getName();
+ this.declaringClass = field.getDeclaringClass();
+ this.field = field;
+ }
+
+ @Override
+ public Type getType() {
+ return this.field.getGenericType();
+ }
+
+ @Override
+ public Object getSource() {
+ return this.field;
+ }
+
+ private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
+ inputStream.defaultReadObject();
+ try {
+ this.field = this.declaringClass.getDeclaredField(this.fieldName);
+ }
+ catch (Throwable ex) {
+ throw new IllegalStateException("Could not find original class structure", ex);
+ }
+ }
+ }
+
+
+ /**
+ * {@link TypeProvider} for {@link Type}s obtained from a {@link MethodParameter}.
+ */
+ @SuppressWarnings("serial")
+ static class MethodParameterTypeProvider implements TypeProvider {
+
+ private final String methodName;
+
+ private final Class<?>[] parameterTypes;
+
+ private final Class<?> declaringClass;
+
+ private final int parameterIndex;
+
+ private transient MethodParameter methodParameter;
+
+ public MethodParameterTypeProvider(MethodParameter methodParameter) {
+ if (methodParameter.getMethod() != null) {
+ this.methodName = methodParameter.getMethod().getName();
+ this.parameterTypes = methodParameter.getMethod().getParameterTypes();
+ }
+ else {
+ this.methodName = null;
+ this.parameterTypes = methodParameter.getConstructor().getParameterTypes();
+ }
+ this.declaringClass = methodParameter.getDeclaringClass();
+ this.parameterIndex = methodParameter.getParameterIndex();
+ this.methodParameter = methodParameter;
+ }
+
+
+ @Override
+ public Type getType() {
+ return this.methodParameter.getGenericParameterType();
+ }
+
+ @Override
+ public Object getSource() {
+ return this.methodParameter;
+ }
+
+ private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
+ inputStream.defaultReadObject();
+ try {
+ if (this.methodName != null) {
+ this.methodParameter = new MethodParameter(
+ this.declaringClass.getDeclaredMethod(this.methodName, this.parameterTypes), this.parameterIndex);
+ }
+ else {
+ this.methodParameter = new MethodParameter(
+ this.declaringClass.getDeclaredConstructor(this.parameterTypes), this.parameterIndex);
+ }
+ }
+ catch (Throwable ex) {
+ throw new IllegalStateException("Could not find original class structure", ex);
+ }
+ }
+ }
+
+
+ /**
+ * {@link TypeProvider} for {@link Type}s obtained by invoking a no-arg method.
+ */
+ @SuppressWarnings("serial")
+ static class MethodInvokeTypeProvider implements TypeProvider {
+
+ private final TypeProvider provider;
+
+ private final String methodName;
+
+ private final int index;
+
+ private transient Object result;
+
+ public MethodInvokeTypeProvider(TypeProvider provider, Method method, int index) {
+ this.provider = provider;
+ this.methodName = method.getName();
+ this.index = index;
+ this.result = ReflectionUtils.invokeMethod(method, provider.getType());
+ }
+
+ @Override
+ public Type getType() {
+ if (this.result instanceof Type || this.result == null) {
+ return (Type) this.result;
+ }
+ return ((Type[])this.result)[this.index];
+ }
+
+ @Override
+ public Object getSource() {
+ return null;
+ }
+
+ private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
+ inputStream.defaultReadObject();
+ Method method = ReflectionUtils.findMethod(this.provider.getType().getClass(), this.methodName);
+ this.result = ReflectionUtils.invokeMethod(method, this.provider.getType());
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java b/spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java
index 24b33f76..82fe6615 100644
--- a/spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java
+++ b/spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java
@@ -41,6 +41,7 @@ public class SimpleAliasRegistry implements AliasRegistry {
private final Map<String, String> aliasMap = new ConcurrentHashMap<String, String>(16);
+ @Override
public void registerAlias(String name, String alias) {
Assert.hasText(name, "'name' must not be empty");
Assert.hasText(alias, "'alias' must not be empty");
@@ -68,6 +69,7 @@ public class SimpleAliasRegistry implements AliasRegistry {
return true;
}
+ @Override
public void removeAlias(String alias) {
String name = this.aliasMap.remove(alias);
if (name == null) {
@@ -75,10 +77,12 @@ public class SimpleAliasRegistry implements AliasRegistry {
}
}
+ @Override
public boolean isAlias(String name) {
return this.aliasMap.containsKey(name);
}
+ @Override
public String[] getAliases(String name) {
List<String> result = new ArrayList<String>();
synchronized (this.aliasMap) {
diff --git a/spring-core/src/main/java/org/springframework/core/SmartClassLoader.java b/spring-core/src/main/java/org/springframework/core/SmartClassLoader.java
index c036f27a..a0f72d0b 100644
--- a/spring-core/src/main/java/org/springframework/core/SmartClassLoader.java
+++ b/spring-core/src/main/java/org/springframework/core/SmartClassLoader.java
@@ -38,6 +38,6 @@ public interface SmartClassLoader {
* @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);
+ boolean isClassReloadable(Class<?> clazz);
}
diff --git a/spring-core/src/main/java/org/springframework/core/StandardReflectionParameterNameDiscoverer.java b/spring-core/src/main/java/org/springframework/core/StandardReflectionParameterNameDiscoverer.java
new file mode 100644
index 00000000..d8a39bd4
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/StandardReflectionParameterNameDiscoverer.java
@@ -0,0 +1,61 @@
+/*
+ * 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.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+
+/**
+ * {@link ParameterNameDiscoverer} implementation which uses JDK 8's reflection facilities
+ * for introspecting parameter names (based on the "-parameters" compiler flag).
+ *
+ * @author Juergen Hoeller
+ * @since 4.0
+ * @see java.lang.reflect.Parameter#getName()
+ */
+public class StandardReflectionParameterNameDiscoverer implements ParameterNameDiscoverer {
+
+ @Override
+ public String[] getParameterNames(Method method) {
+ Parameter[] parameters = method.getParameters();
+ String[] parameterNames = new String[parameters.length];
+ for (int i = 0; i < parameters.length; i++) {
+ Parameter param = parameters[i];
+ if (!param.isNamePresent()) {
+ return null;
+ }
+ parameterNames[i] = param.getName();
+ }
+ return parameterNames;
+ }
+
+ @Override
+ public String[] getParameterNames(Constructor<?> ctor) {
+ Parameter[] parameters = ctor.getParameters();
+ String[] parameterNames = new String[parameters.length];
+ for (int i = 0; i < parameters.length; i++) {
+ Parameter param = parameters[i];
+ if (!param.isNamePresent()) {
+ return null;
+ }
+ parameterNames[i] = param.getName();
+ }
+ return parameterNames;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java
new file mode 100644
index 00000000..af84f7b5
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java
@@ -0,0 +1,253 @@
+/*
+ * 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.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+/**
+ * Utility class used to collect all annotation values including those declared on
+ * meta-annotations.
+ *
+ * @author Phillip Webb
+ * @author Juergen Hoeller
+ * @author Sam Brannen
+ * @since 4.0
+ */
+public class AnnotatedElementUtils {
+
+ public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
+ final Set<String> types = new LinkedHashSet<String>();
+ process(element, annotationType, false, new Processor<Object>() {
+ @Override
+ public Object process(Annotation annotation, int metaDepth) {
+ if (metaDepth > 0) {
+ types.add(annotation.annotationType().getName());
+ }
+ return null;
+ }
+ @Override
+ public void postProcess(Annotation annotation, Object result) {
+ }
+ });
+ return (types.isEmpty() ? null : types);
+ }
+
+ public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
+ return Boolean.TRUE.equals(process(element, annotationType, false, new Processor<Boolean>() {
+ @Override
+ public Boolean process(Annotation annotation, int metaDepth) {
+ if (metaDepth > 0) {
+ return Boolean.TRUE;
+ }
+ return null;
+ }
+ @Override
+ public void postProcess(Annotation annotation, Boolean result) {
+ }
+ }));
+ }
+
+ public static boolean isAnnotated(AnnotatedElement element, String annotationType) {
+ return Boolean.TRUE.equals(process(element, annotationType, false, new Processor<Boolean>() {
+ @Override
+ public Boolean process(Annotation annotation, int metaDepth) {
+ return Boolean.TRUE;
+ }
+ @Override
+ public void postProcess(Annotation annotation, Boolean result) {
+ }
+ }));
+ }
+
+ public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType) {
+ return getAnnotationAttributes(element, annotationType, false, false);
+ }
+
+ public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType,
+ final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
+
+ return process(element, annotationType, false, new Processor<AnnotationAttributes>() {
+ @Override
+ public AnnotationAttributes process(Annotation annotation, int metaDepth) {
+ return AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap);
+ }
+ @Override
+ public void postProcess(Annotation annotation, AnnotationAttributes result) {
+ for (String key : result.keySet()) {
+ if (!AnnotationUtils.VALUE.equals(key)) {
+ Object value = AnnotationUtils.getValue(annotation, key);
+ if (value != null) {
+ result.put(key, AnnotationUtils.adaptValue(value, classValuesAsString, nestedAnnotationsAsMap));
+ }
+ }
+ }
+ }
+ });
+ }
+
+ public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element, String annotationType) {
+ return getAllAnnotationAttributes(element, annotationType, false, false);
+ }
+
+ public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element,
+ final String annotationType, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
+
+ final MultiValueMap<String, Object> attributes = new LinkedMultiValueMap<String, Object>();
+ process(element, annotationType, false, new Processor<Void>() {
+ @Override
+ public Void process(Annotation annotation, int metaDepth) {
+ if (annotation.annotationType().getName().equals(annotationType)) {
+ for (Map.Entry<String, Object> entry : AnnotationUtils.getAnnotationAttributes(
+ annotation, classValuesAsString, nestedAnnotationsAsMap).entrySet()) {
+ attributes.add(entry.getKey(), entry.getValue());
+ }
+ }
+ return null;
+ }
+ @Override
+ public void postProcess(Annotation annotation, Void result) {
+ for (String key : attributes.keySet()) {
+ if (!AnnotationUtils.VALUE.equals(key)) {
+ Object value = AnnotationUtils.getValue(annotation, key);
+ if (value != null) {
+ attributes.add(key, value);
+ }
+ }
+ }
+ }
+ });
+ return (attributes.isEmpty() ? null : attributes);
+ }
+
+ /**
+ * Process all annotations of the specified {@code annotationType} and
+ * recursively all meta-annotations on the specified {@code element}.
+ * <p>If the {@code traverseClassHierarchy} flag is {@code true} and the sought
+ * annotation is neither <em>directly present</em> on the given element nor
+ * present on the given element as a meta-annotation, then the algorithm will
+ * recursively search through the class hierarchy of the given element.
+ * @param element the annotated element
+ * @param annotationType the annotation type to find
+ * @param traverseClassHierarchy whether or not to traverse up the class
+ * hierarchy recursively
+ * @param processor the processor to delegate to
+ * @return the result of the processor
+ */
+ private static <T> T process(AnnotatedElement element, String annotationType, boolean traverseClassHierarchy,
+ Processor<T> processor) {
+
+ try {
+ return doProcess(element, annotationType, traverseClassHierarchy, processor,
+ new HashSet<AnnotatedElement>(), 0);
+ }
+ catch (Throwable ex) {
+ throw new IllegalStateException("Failed to introspect annotations: " + element, ex);
+ }
+ }
+
+ /**
+ * Perform the search algorithm for the {@link #process} method, avoiding
+ * endless recursion by tracking which annotated elements have already been
+ * <em>visited</em>.
+ * <p>The {@code metaDepth} parameter represents the depth of the annotation
+ * relative to the initial element. For example, an annotation that is
+ * <em>present</em> on the element will have a depth of 0; a meta-annotation
+ * will have a depth of 1; and a meta-meta-annotation will have a depth of 2.
+ * @param element the annotated element
+ * @param annotationType the annotation type to find
+ * @param traverseClassHierarchy whether or not to traverse up the class
+ * hierarchy recursively
+ * @param processor the processor to delegate to
+ * @param visited the set of annotated elements that have already been visited
+ * @param metaDepth the depth of the annotation relative to the initial element
+ * @return the result of the processor
+ */
+ private static <T> T doProcess(AnnotatedElement element, String annotationType, boolean traverseClassHierarchy,
+ Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
+
+ if (visited.add(element)) {
+ Annotation[] annotations =
+ (traverseClassHierarchy ? element.getDeclaredAnnotations() : element.getAnnotations());
+ for (Annotation annotation : annotations) {
+ if (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0) {
+ T result = processor.process(annotation, metaDepth);
+ if (result != null) {
+ return result;
+ }
+ result = doProcess(annotation.annotationType(), annotationType, traverseClassHierarchy,
+ processor, visited, metaDepth + 1);
+ if (result != null) {
+ processor.postProcess(annotation, result);
+ return result;
+ }
+ }
+ }
+ for (Annotation annotation : annotations) {
+ if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
+ T result = doProcess(annotation.annotationType(), annotationType, traverseClassHierarchy,
+ processor, visited, metaDepth);
+ if (result != null) {
+ processor.postProcess(annotation, result);
+ return result;
+ }
+ }
+ }
+ if (traverseClassHierarchy && element instanceof Class) {
+ Class<?> superclass = ((Class<?>) element).getSuperclass();
+ if (superclass != null && !superclass.equals(Object.class)) {
+ T result = doProcess(superclass, annotationType, true, processor, visited, metaDepth);
+ if (result != null) {
+ return result;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * Callback interface used to process an annotation.
+ * @param <T> the result type
+ */
+ private static interface Processor<T> {
+
+ /**
+ * Called to process the annotation.
+ * <p>The {@code metaDepth} parameter represents the depth of the
+ * annotation relative to the initial element. For example, an annotation
+ * that is <em>present</em> on the element will have a depth of 0; a
+ * meta-annotation will have a depth of 1; and a meta-meta-annotation
+ * will have a depth of 2.
+ * @param annotation the annotation to process
+ * @param metaDepth the depth of the annotation relative to the initial element
+ * @return the result of the processing, or {@code null} to continue
+ */
+ T process(Annotation annotation, int metaDepth);
+
+ void postProcess(Annotation annotation, T result);
+ }
+
+}
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
index 40041e8d..090621d8 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java
@@ -16,8 +16,7 @@
package org.springframework.core.annotation;
-import static java.lang.String.format;
-
+import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -27,10 +26,9 @@ 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.
+ * as read by Spring's reflection- or ASM-based {@link org.springframework.core.type.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
@@ -63,24 +61,6 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
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);
@@ -96,7 +76,7 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
@SuppressWarnings("unchecked")
public <N extends Number> N getNumber(String attributeName) {
- return (N) doGet(attributeName, Integer.class);
+ return (N) doGet(attributeName, Number.class);
}
@SuppressWarnings("unchecked")
@@ -124,14 +104,24 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
@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: ",
+ Object value = get(attributeName);
+ Assert.notNull(value, String.format("Attribute '%s' not found", attributeName));
+ if (!expectedType.isInstance(value)) {
+ if (expectedType.isArray() && expectedType.getComponentType().isInstance(value)) {
+ Object arrayValue = Array.newInstance(expectedType.getComponentType(), 1);
+ Array.set(arrayValue, 0, value);
+ value = arrayValue;
+ }
+ else {
+ throw new IllegalArgumentException(
+ String.format("Attribute '%s' is of type [%s], but [%s] was expected. Cause: ",
attributeName, value.getClass().getSimpleName(), expectedType.getSimpleName()));
+ }
+ }
return (T) value;
}
+ @Override
public String toString() {
Iterator<Map.Entry<String, Object>> entries = entrySet().iterator();
StringBuilder sb = new StringBuilder("{");
@@ -155,4 +145,23 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
}
return String.valueOf(value);
}
+
+
+ /**
+ * 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);
+ }
+
}
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
index 19ba5fec..f4a83b8f 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java
@@ -50,7 +50,7 @@ public class AnnotationAwareOrderComparator extends OrderComparator {
return ((Ordered) obj).getOrder();
}
if (obj != null) {
- Class<?> clazz = (obj instanceof Class ? (Class) obj : obj.getClass());
+ Class<?> clazz = (obj instanceof Class ? (Class<?>) obj : obj.getClass());
Order order = AnnotationUtils.findAnnotation(clazz, Order.class);
if (order != null) {
return order.value();
@@ -86,4 +86,21 @@ public class AnnotationAwareOrderComparator extends OrderComparator {
}
}
+ /**
+ * Sort the given array or List with a default AnnotationAwareOrderComparator,
+ * if necessary. Simply skips sorting when given any other value.
+ * <p>Optimized to skip sorting for lists with size 0 or 1,
+ * in order to avoid unnecessary array extraction.
+ * @param value the array or List to sort
+ * @see java.util.Arrays#sort(Object[], java.util.Comparator)
+ */
+ public static void sortIfNecessary(Object value) {
+ if (value instanceof Object[]) {
+ sort((Object[]) value);
+ }
+ else if (value instanceof List) {
+ sort((List<?>) value);
+ }
+ }
+
}
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
index 3b4def63..45d7c48c 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
@@ -19,31 +19,44 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.WeakHashMap;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
import org.springframework.core.BridgeMethodResolver;
import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
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.
+ * 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)}).
+ * <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 a <em>get</em> lookup on the given class level only
+ * ({@link #getAnnotation(Method, Class)}) and a <em>find</em> 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
+ * @author Phillip Webb
* @since 2.0
* @see java.lang.reflect.Method#getAnnotations()
* @see java.lang.reflect.Method#getAnnotation(Class)
@@ -51,42 +64,100 @@ import org.springframework.util.ReflectionUtils;
public abstract class AnnotationUtils {
/** The attribute name for annotations with a single element */
- static final String VALUE = "value";
+ public static final String VALUE = "value";
+
private static final Map<Class<?>, Boolean> annotatedInterfaceCache = new WeakHashMap<Class<?>, Boolean>();
+ private static transient Log logger;
+
+
+ /**
+ * Get a single {@link Annotation} of {@code annotationType} from the supplied
+ * annotation: either the given annotation itself or a meta-annotation thereof.
+ * @param ann the Annotation to check
+ * @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 4.0
+ */
+ @SuppressWarnings("unchecked")
+ public static <T extends Annotation> T getAnnotation(Annotation ann, Class<T> annotationType) {
+ if (annotationType.isInstance(ann)) {
+ return (T) ann;
+ }
+ try {
+ return ann.annotationType().getAnnotation(annotationType);
+ }
+ catch (Exception ex) {
+ // Assuming nested Class values not resolvable within annotation attributes...
+ logIntrospectionFailure(ann.annotationType(), ex);
+ return null;
+ }
+ }
/**
* 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 annotatedElement 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;
+ public static <T extends Annotation> T getAnnotation(AnnotatedElement annotatedElement, Class<T> annotationType) {
+ try {
+ T ann = annotatedElement.getAnnotation(annotationType);
+ if (ann == null) {
+ for (Annotation metaAnn : annotatedElement.getAnnotations()) {
+ ann = metaAnn.annotationType().getAnnotation(annotationType);
+ if (ann != null) {
+ break;
+ }
}
}
+ return ann;
+ }
+ catch (Exception ex) {
+ // Assuming nested Class values not resolvable within annotation attributes...
+ logIntrospectionFailure(annotatedElement, ex);
+ return null;
+ }
+ }
+
+ /**
+ * Get all {@link Annotation Annotations} from the supplied Method, Constructor or Field.
+ * @param annotatedElement the Method, Constructor or Field to retrieve annotations from
+ * @return the annotations found, or {@code null} if not resolvable (e.g. because nested
+ * Class values in annotation attributes failed to resolve at runtime)
+ * @since 4.0.8
+ */
+ public static Annotation[] getAnnotations(AnnotatedElement annotatedElement) {
+ try {
+ return annotatedElement.getAnnotations();
+ }
+ catch (Exception ex) {
+ // Assuming nested Class values not resolvable within annotation attributes...
+ logIntrospectionFailure(annotatedElement, ex);
+ return null;
}
- 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
+ * @param method the Method to retrieve annotations from
* @return the annotations found
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method)
*/
public static Annotation[] getAnnotations(Method method) {
- return BridgeMethodResolver.findBridgedMethod(method).getAnnotations();
+ try {
+ return BridgeMethodResolver.findBridgedMethod(method).getAnnotations();
+ }
+ catch (Exception ex) {
+ // Assuming nested Class values not resolvable within annotation attributes...
+ logIntrospectionFailure(method, ex);
+ return null;
+ }
}
/**
@@ -99,22 +170,61 @@ public abstract class AnnotationUtils {
*/
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 getAnnotation((AnnotatedElement) resolvedMethod, annotationType);
+ }
+
+ /**
+ * Get the possibly repeating {@link Annotation}s of {@code annotationType} from the
+ * supplied {@link Method}. Deals with both a single direct annotation and repeating
+ * annotations nested within a containing annotation.
+ * <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
+ * @param method the method to look for annotations on
+ * @param containerAnnotationType the class of the container that holds the annotations
+ * @param annotationType the annotation type to look for
+ * @return the annotations found
+ * @since 4.0
+ * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method)
+ */
+ public static <A extends Annotation> Set<A> getRepeatableAnnotation(Method method,
+ Class<? extends Annotation> containerAnnotationType, Class<A> annotationType) {
+
+ Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
+ return getRepeatableAnnotation((AnnotatedElement) resolvedMethod, containerAnnotationType, annotationType);
+ }
+
+ /**
+ * Get the possibly repeating {@link Annotation}s of {@code annotationType} from the
+ * supplied {@link AnnotatedElement}. Deals with both a single direct annotation and
+ * repeating annotations nested within a containing annotation.
+ * <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
+ * @param annotatedElement the element to look for annotations on
+ * @param containerAnnotationType the class of the container that holds the annotations
+ * @param annotationType the annotation type to look for
+ * @return the annotations found
+ * @since 4.0
+ * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method)
+ */
+ public static <A extends Annotation> Set<A> getRepeatableAnnotation(AnnotatedElement annotatedElement,
+ Class<? extends Annotation> containerAnnotationType, Class<A> annotationType) {
+
+ try {
+ if (annotatedElement.getAnnotations().length > 0) {
+ return new AnnotationCollector<A>(containerAnnotationType, annotationType).getResult(annotatedElement);
}
}
- return ann;
+ catch (Exception ex) {
+ // Assuming nested Class values not resolvable within annotation attributes...
+ logIntrospectionFailure(annotatedElement, ex);
+ }
+ return Collections.emptySet();
}
/**
- * 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.
+ * Find a single {@link Annotation} of {@code annotationType} from the supplied
+ * {@link Method}, traversing its super methods (i.e., from superclasses and
+ * interfaces) 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
@@ -171,9 +281,15 @@ public abstract class AnnotationUtils {
}
boolean found = false;
for (Method ifcMethod : iface.getMethods()) {
- if (ifcMethod.getAnnotations().length > 0) {
- found = true;
- break;
+ try {
+ if (ifcMethod.getAnnotations().length > 0) {
+ found = true;
+ break;
+ }
+ }
+ catch (Exception ex) {
+ // Assuming nested Class values not resolvable within annotation attributes...
+ logIntrospectionFailure(ifcMethod, ex);
}
}
annotatedInterfaceCache.put(iface, found);
@@ -182,45 +298,82 @@ public abstract class AnnotationUtils {
}
/**
- * 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.
+ * Find a single {@link Annotation} of {@code annotationType} on the
+ * supplied {@link Class}, traversing its interfaces, annotations, and
+ * superclasses if the annotation is not <em>present</em> on the given class
+ * itself.
+ * <p>This method explicitly handles class-level annotations which are not
+ * declared as {@link java.lang.annotation.Inherited inherited} <em>as well
+ * as meta-annotations and annotations on interfaces</em>.
+ * <p>The algorithm operates as follows:
+ * <ol>
+ * <li>Search for the annotation on the given class and return it if found.
+ * <li>Recursively search through all interfaces that the given class declares.
+ * <li>Recursively search through all annotations that the given class declares.
+ * <li>Recursively search through the superclass hierarchy of the given class.
+ * </ol>
+ * <p>Note: in this context, the term <em>recursively</em> means that the search
+ * process continues by returning to step #1 with the current interface,
+ * annotation, or superclass as the class to look for annotations on.
* @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
+ * @param annotationType the type of annotation to look for
+ * @return the annotation if found, or {@code null} if not found
*/
public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
+ return findAnnotation(clazz, annotationType, new HashSet<Annotation>());
+ }
+
+ /**
+ * Perform the search algorithm for {@link #findAnnotation(Class, Class)},
+ * avoiding endless recursion by tracking which annotations have already
+ * been <em>visited</em>.
+ * @param clazz the class to look for annotations on
+ * @param annotationType the type of annotation to look for
+ * @param visited the set of annotations that have already been visited
+ * @return the annotation if found, or {@code null} if not found
+ */
+ private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType, Set<Annotation> visited) {
Assert.notNull(clazz, "Class must not be null");
- A annotation = clazz.getAnnotation(annotationType);
- if (annotation != null) {
- return annotation;
+
+ if (isAnnotationDeclaredLocally(annotationType, clazz)) {
+ try {
+ return clazz.getAnnotation(annotationType);
+ }
+ catch (Exception ex) {
+ // Assuming nested Class values not resolvable within annotation attributes...
+ logIntrospectionFailure(clazz, ex);
+ return null;
+ }
}
+
for (Class<?> ifc : clazz.getInterfaces()) {
- annotation = findAnnotation(ifc, annotationType);
+ A annotation = findAnnotation(ifc, annotationType, visited);
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;
+
+ try {
+ for (Annotation ann : clazz.getDeclaredAnnotations()) {
+ if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
+ A annotation = findAnnotation(ann.annotationType(), annotationType, visited);
+ if (annotation != null) {
+ return annotation;
+ }
}
}
}
+ catch (Exception ex) {
+ // Assuming nested Class values not resolvable within annotation attributes...
+ logIntrospectionFailure(clazz, ex);
+ return null;
+ }
+
Class<?> superclass = clazz.getSuperclass();
if (superclass == null || superclass.equals(Object.class)) {
return null;
}
- return findAnnotation(superclass, annotationType);
+ return findAnnotation(superclass, annotationType, visited);
}
/**
@@ -312,12 +465,18 @@ public abstract class AnnotationUtils {
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;
+ try {
+ for (Annotation ann : clazz.getDeclaredAnnotations()) {
+ if (ann.annotationType().equals(annotationType)) {
+ declaredLocally = true;
+ break;
+ }
}
}
+ catch (Exception ex) {
+ // Assuming nested Class values not resolvable within annotation attributes...
+ logIntrospectionFailure(clazz, ex);
+ }
return declaredLocally;
}
@@ -343,11 +502,21 @@ public abstract class AnnotationUtils {
}
/**
- * 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.
+ * Determine if the supplied {@link Annotation} is defined in the core JDK
+ * {@code java.lang.annotation} package.
+ * @param annotation the annotation to check (never {@code null})
+ * @return {@code true} if the annotation is in the {@code java.lang.annotation} package
+ */
+ public static boolean isInJavaLangAnnotationPackage(Annotation annotation) {
+ Assert.notNull(annotation, "Annotation must not be null");
+ return annotation.annotationType().getName().startsWith("java.lang.annotation");
+ }
+
+ /**
+ * Retrieve the given annotation's attributes as a {@link Map}, preserving all
+ * attribute types as-is.
+ * <p>Note: This method actually returns an {@link AnnotationAttributes} instance.
+ * However, the {@code Map} signature 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
@@ -357,16 +526,15 @@ public abstract class AnnotationUtils {
}
/**
- * Retrieve the given annotation's attributes as a Map. Equivalent to calling
- * {@link #getAnnotationAttributes(Annotation, boolean, boolean)} with
+ * Retrieve the given annotation's attributes as a {@link 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.
+ * <p>Note: This method actually returns an {@link AnnotationAttributes} instance.
+ * However, the {@code Map} signature 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
+ * 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
*/
@@ -376,13 +544,13 @@ public abstract class AnnotationUtils {
/**
* 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}.
+ * map structure.
+ * <p>This method provides fully recursive annotation reading capabilities on par with
+ * 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
+ * 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
@@ -400,34 +568,7 @@ public abstract class AnnotationUtils {
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);
- }
+ attrs.put(method.getName(), adaptValue(value, classValuesAsString, nestedAnnotationsAsMap));
}
catch (Exception ex) {
throw new IllegalStateException("Could not obtain annotation attribute values", ex);
@@ -438,6 +579,48 @@ public abstract class AnnotationUtils {
}
/**
+ * Adapt the given value according to the given class and nested annotation settings.
+ * @param value the annotation attribute value
+ * @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 adapted value, or the original value if no adaptation is needed
+ */
+ static Object adaptValue(Object value, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+ 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) {
+ return 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);
+ }
+ return mappedAnnotations;
+ }
+ else {
+ return value;
+ }
+ }
+
+ /**
* 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
@@ -449,7 +632,7 @@ public abstract class AnnotationUtils {
}
/**
- * Retrieve the <em>value</em> of a named Annotation attribute, given an annotation instance.
+ * Retrieve the <em>value</em> of a named 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
@@ -478,7 +661,7 @@ public abstract class AnnotationUtils {
}
/**
- * Retrieve the <em>default value</em> of a named Annotation attribute, given an annotation instance.
+ * Retrieve the <em>default value</em> of a named 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
@@ -500,7 +683,8 @@ public abstract class AnnotationUtils {
}
/**
- * Retrieve the <em>default value</em> of a named Annotation attribute, given the {@link Class annotation type}.
+ * Retrieve the <em>default value</em> of a named 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
@@ -515,4 +699,68 @@ public abstract class AnnotationUtils {
}
}
+
+ private static void logIntrospectionFailure(AnnotatedElement annotatedElement, Exception ex) {
+ Log loggerToUse = logger;
+ if (loggerToUse == null) {
+ loggerToUse = LogFactory.getLog(AnnotationUtils.class);
+ logger = loggerToUse;
+ }
+ if (loggerToUse.isInfoEnabled()) {
+ loggerToUse.info("Failed to introspect annotations on [" + annotatedElement + "]: " + ex);
+ }
+ }
+
+
+ private static class AnnotationCollector<A extends Annotation> {
+
+ private final Class<? extends Annotation> containerAnnotationType;
+
+ private final Class<A> annotationType;
+
+ private final Set<AnnotatedElement> visited = new HashSet<AnnotatedElement>();
+
+ private final Set<A> result = new LinkedHashSet<A>();
+
+ public AnnotationCollector(Class<? extends Annotation> containerAnnotationType, Class<A> annotationType) {
+ this.containerAnnotationType = containerAnnotationType;
+ this.annotationType = annotationType;
+ }
+
+ public Set<A> getResult(AnnotatedElement element) {
+ process(element);
+ return Collections.unmodifiableSet(this.result);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void process(AnnotatedElement annotatedElement) {
+ if (this.visited.add(annotatedElement)) {
+ for (Annotation ann : annotatedElement.getAnnotations()) {
+ if (ObjectUtils.nullSafeEquals(this.annotationType, ann.annotationType())) {
+ this.result.add((A) ann);
+ }
+ else if (ObjectUtils.nullSafeEquals(this.containerAnnotationType, ann.annotationType())) {
+ this.result.addAll(getValue(ann));
+ }
+ else if (!isInJavaLangAnnotationPackage(ann)) {
+ process(ann.annotationType());
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private List<A> getValue(Annotation annotation) {
+ try {
+ Method method = annotation.annotationType().getDeclaredMethod("value");
+ ReflectionUtils.makeAccessible(method);
+ return Arrays.asList((A[]) method.invoke(annotation));
+ }
+ catch (Exception ex) {
+ // Unable to read value from repeating annotation container -> ignore it.
+ return Collections.emptyList();
+ }
+ }
+ }
+
}
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
deleted file mode 100644
index 86995f32..00000000
--- a/spring-core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 8be6b0e2..00000000
--- a/spring-core/src/main/java/org/springframework/core/convert/BeanPropertyDescriptor.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 87f8e066..00000000
--- a/spring-core/src/main/java/org/springframework/core/convert/ClassDescriptor.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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/FieldDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/FieldDescriptor.java
deleted file mode 100644
index 9d951bb3..00000000
--- a/spring-core/src/main/java/org/springframework/core/convert/FieldDescriptor.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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
deleted file mode 100644
index c4ccebe7..00000000
--- a/spring-core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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/TypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java
index 39d0f847..b605032d 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java
@@ -18,13 +18,14 @@ 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.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.core.MethodParameter;
+import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
@@ -44,37 +45,23 @@ 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);
+ private static final Map<Class<?>, TypeDescriptor> commonTypesCache = new HashMap<Class<?>, TypeDescriptor>(18);
+
+ private static final Class<?>[] CACHED_COMMON_TYPES = {
+ boolean.class, Boolean.class, byte.class, Byte.class, char.class, Character.class,
+ double.class, Double.class, int.class, Integer.class, long.class, Long.class,
+ float.class, Float.class, short.class, Short.class, String.class, Object.class};
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));
+ for (Class<?> preCachedClass : CACHED_COMMON_TYPES) {
+ commonTypesCache.put(preCachedClass, valueOf(preCachedClass));
+ }
}
private final Class<?> type;
- private final TypeDescriptor elementTypeDescriptor;
-
- private final TypeDescriptor mapKeyTypeDescriptor;
-
- private final TypeDescriptor mapValueTypeDescriptor;
+ private final ResolvableType resolvableType;
private final Annotation[] annotations;
@@ -86,7 +73,12 @@ public class TypeDescriptor implements Serializable {
* @param methodParameter the method parameter
*/
public TypeDescriptor(MethodParameter methodParameter) {
- this(new ParameterDescriptor(methodParameter));
+ Assert.notNull(methodParameter, "MethodParameter must not be null");
+ this.resolvableType = ResolvableType.forMethodParameter(methodParameter);
+ this.type = this.resolvableType.resolve(methodParameter.getParameterType());
+ this.annotations = (methodParameter.getParameterIndex() == -1 ?
+ nullSafeAnnotations(methodParameter.getMethodAnnotations()) :
+ nullSafeAnnotations(methodParameter.getParameterAnnotations()));
}
/**
@@ -95,7 +87,10 @@ public class TypeDescriptor implements Serializable {
* @param field the field
*/
public TypeDescriptor(Field field) {
- this(new FieldDescriptor(field));
+ Assert.notNull(field, "Field must not be null");
+ this.resolvableType = ResolvableType.forField(field);
+ this.type = this.resolvableType.resolve(field.getType());
+ this.annotations = nullSafeAnnotations(field.getAnnotations());
}
/**
@@ -105,180 +100,47 @@ public class TypeDescriptor implements Serializable {
* @param property the property
*/
public TypeDescriptor(Property property) {
- this(new BeanPropertyDescriptor(property));
+ Assert.notNull(property, "Property must not be null");
+ this.resolvableType = ResolvableType.forMethodParameter(property.getMethodParameter());
+ this.type = this.resolvableType.resolve(property.getType());
+ this.annotations = nullSafeAnnotations(property.getAnnotations());
}
-
/**
- * 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
+ * Create a new type descriptor from a {@link ResolvableType}. This protected
+ * constructor is used internally and may also be used by subclasses that support
+ * non-Java languages with extended type systems.
+ * @param resolvableType the resolvable type
+ * @param type the backing type or {@code null} if should be resolved
+ * @param annotations the type annotations
*/
- 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);
+ protected TypeDescriptor(ResolvableType resolvableType, Class<?> type, Annotation[] annotations) {
+ this.resolvableType = resolvableType;
+ this.type = (type != null ? type : resolvableType.resolve(Object.class));
+ this.annotations = nullSafeAnnotations(annotations);
}
- /**
- * 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);
+ private Annotation[] nullSafeAnnotations(Annotation[] annotations) {
+ return (annotations != null ? annotations : EMPTY_ANNOTATION_ARRAY);
}
/**
- * 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
+ * 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 static TypeDescriptor forObject(Object source) {
- return (source != null ? valueOf(source.getClass()) : null);
+ public Class<?> getObjectType() {
+ return ClassUtils.resolvePrimitiveIfNecessary(getType());
}
-
/**
- * The type of the backing class, method parameter, field, or property described by this TypeDescriptor.
+ * 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.
+ * <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()
*/
@@ -287,32 +149,46 @@ public class TypeDescriptor implements Serializable {
}
/**
- * 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.
+ * Return the underlying {@link ResolvableType}.
+ * @since 4.0
*/
- public Class<?> getObjectType() {
- return ClassUtils.resolvePrimitiveIfNecessary(getType());
+ public ResolvableType getResolvableType() {
+ return this.resolvableType;
+ }
+
+ /**
+ * Return the underlying source of the descriptor. Will return a {@link Field},
+ * {@link MethodParameter} or {@link Type} depending on how the {@link TypeDescriptor}
+ * was constructed. This method is primarily to provide access to additional
+ * type information or meta-data that alternative JVM languages may provide.
+ * @since 4.0
+ */
+ public Object getSource() {
+ return (this.resolvableType != null ? this.resolvableType.getSource() : null);
}
/**
- * 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.
+ * 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)
+ * @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);
+ ResolvableType narrowed = ResolvableType.forType(value.getClass(), this.resolvableType);
+ return new TypeDescriptor(narrowed, null, this.annotations);
}
/**
@@ -328,8 +204,7 @@ public class TypeDescriptor implements Serializable {
return null;
}
Assert.isAssignable(superType, getType());
- return new TypeDescriptor(superType, this.elementTypeDescriptor,
- this.mapKeyTypeDescriptor, this.mapValueTypeDescriptor, this.annotations);
+ return new TypeDescriptor(this.resolvableType.as(superType), superType, this.annotations);
}
/**
@@ -360,7 +235,7 @@ public class TypeDescriptor implements Serializable {
* @return <tt>true</tt> if the annotation is present
*/
public boolean hasAnnotation(Class<? extends Annotation> annotationType) {
- return getAnnotation(annotationType) != null;
+ return (getAnnotation(annotationType) != null);
}
/**
@@ -370,12 +245,12 @@ public class TypeDescriptor implements Serializable {
*/
@SuppressWarnings("unchecked")
public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
- for (Annotation annotation : this.annotations) {
+ for (Annotation annotation : getAnnotations()) {
if (annotation.annotationType().equals(annotationType)) {
return (T) annotation;
}
}
- for (Annotation metaAnn : this.annotations) {
+ for (Annotation metaAnn : getAnnotations()) {
T ann = metaAnn.annotationType().getAnnotation(annotationType);
if (ann != null) {
return ann;
@@ -385,16 +260,17 @@ public class TypeDescriptor implements Serializable {
}
/**
- * 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.
+ * Returns true if an object of this type descriptor can be assigned to the location
+ * described by the given type descriptor.
+ * <p>For example, {@code valueOf(String.class).isAssignableTo(valueOf(CharSequence.class))}
+ * returns {@code true} because a String value can be assigned to a CharSequence variable.
+ * On the other hand, {@code valueOf(Number.class).isAssignableTo(valueOf(Integer.class))}
+ * returns {@code 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
+ * @return {@code true} if this type is assignable to the type represented by the provided
+ * type descriptor
* @see #getObjectType()
*/
public boolean isAssignableTo(TypeDescriptor typeDescriptor) {
@@ -417,8 +293,12 @@ public class TypeDescriptor implements Serializable {
}
}
-
- // indexable type descriptor operations
+ private boolean isNestedAssignable(TypeDescriptor nestedTypeDescriptor, TypeDescriptor otherNestedTypeDescriptor) {
+ if (nestedTypeDescriptor == null || otherNestedTypeDescriptor == null) {
+ return true;
+ }
+ return nestedTypeDescriptor.isAssignableTo(otherNestedTypeDescriptor);
+ }
/**
* Is this type a {@link Collection} type?
@@ -437,37 +317,40 @@ public class TypeDescriptor implements Serializable {
/**
* 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.
+ * If the Collection is not parameterized, returns {@code 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
+ * @throws IllegalStateException if this type is not a {@code java.util.Collection} or array type
*/
public TypeDescriptor getElementTypeDescriptor() {
- assertCollectionOrArray();
- return this.elementTypeDescriptor;
+ if (this.resolvableType.isArray()) {
+ return new TypeDescriptor(this.resolvableType.getComponentType(), null, this.annotations);
+ }
+ return getRelatedIfResolvable(this, this.resolvableType.asCollection().getGeneric());
}
/**
- * 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.
+ * 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
+ * {@code java.util.List&lt;java.lang.Number&lt;} and the element argument is an
+ * {@code java.lang.Integer}, the returned TypeDescriptor will be {@code java.lang.Integer}.
+ * If this describes a {@code java.util.List&lt;?&gt;} and the element argument is an
+ * {@code java.lang.Integer}, the returned TypeDescriptor will be {@code 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
+ * @throws IllegalStateException if this type is not a {@code 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?
*/
@@ -476,27 +359,33 @@ public class TypeDescriptor implements Serializable {
}
/**
- * 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
+ * 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 {@code 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 {@code 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.
+ Assert.state(isMap(), "Not a java.util.Map");
+ return getRelatedIfResolvable(this, this.resolvableType.asMap().getGeneric(0));
+ }
+
+ /**
+ * 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
+ * {@code java.util.Map&lt;java.lang.Number, java.lang.String&lt;} and the key
+ * argument is a {@code java.lang.Integer}, the returned TypeDescriptor will be
+ * {@code java.lang.Integer}. If this describes a {@code java.util.Map&lt;?, ?&gt;}
+ * and the key argument is a {@code java.lang.Integer}, the returned
+ * TypeDescriptor will be {@code 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
+ * @throws IllegalStateException if this type is not a {@code java.util.Map}
* @see #narrow(Object)
*/
public TypeDescriptor getMapKeyTypeDescriptor(Object mapKey) {
@@ -504,151 +393,85 @@ public class TypeDescriptor implements Serializable {
}
/**
- * 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
+ * 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 {@code 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 {@code 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.
+ Assert.state(isMap(), "Not a java.util.Map");
+ return getRelatedIfResolvable(this, this.resolvableType.asMap().getGeneric(1));
+ }
+
+ /**
+ * 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
+ * {@code java.util.Map&lt;java.lang.String, java.lang.Number&lt;} and the value
+ * argument is a {@code java.lang.Integer}, the returned TypeDescriptor will be
+ * {@code java.lang.Integer}. If this describes a {@code java.util.Map&lt;?, ?&gt;}
+ * and the value argument is a {@code java.lang.Integer}, the returned
+ * TypeDescriptor will be {@code 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
+ * @throws IllegalStateException if this type is not a {@code java.util.Map}
+ * @see #narrow(Object)
*/
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
+ * @throws IllegalStateException if this type is not a {@code java.util.Collection} or array type
*/
@Deprecated
public Class<?> getElementType() {
- return getElementTypeDescriptor().getType();
+ return getType(getElementTypeDescriptor());
}
/**
* 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
+ * @throws IllegalStateException if this type is not a {@code java.util.Map}
*/
@Deprecated
public Class<?> getMapKeyType() {
- return getMapKeyTypeDescriptor().getType();
+ return getType(getMapKeyTypeDescriptor());
}
/**
* 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
+ * @throws IllegalStateException if this type is not a {@code 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);
+ return getType(getMapValueTypeDescriptor());
}
- 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 Class<?> getType(TypeDescriptor typeDescriptor) {
+ return (typeDescriptor != null ? typeDescriptor.getType() : null);
}
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() : "?");
+ return (value != null ? new TypeDescriptor(this.resolvableType, value.getClass(), this.annotations) : null);
}
-
+ @Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
@@ -660,44 +483,235 @@ public class TypeDescriptor implements Serializable {
if (!ObjectUtils.nullSafeEquals(this.type, other.type)) {
return false;
}
- if (this.annotations.length != other.annotations.length) {
+ if (getAnnotations().length != other.getAnnotations().length) {
return false;
}
- for (Annotation ann : this.annotations) {
+ for (Annotation ann : getAnnotations()) {
if (other.getAnnotation(ann.annotationType()) == null) {
return false;
}
}
if (isCollection() || isArray()) {
- return ObjectUtils.nullSafeEquals(this.elementTypeDescriptor, other.elementTypeDescriptor);
+ return ObjectUtils.nullSafeEquals(getElementTypeDescriptor(), other.getElementTypeDescriptor());
}
else if (isMap()) {
- return ObjectUtils.nullSafeEquals(this.mapKeyTypeDescriptor, other.mapKeyTypeDescriptor) &&
- ObjectUtils.nullSafeEquals(this.mapValueTypeDescriptor, other.mapValueTypeDescriptor);
+ return ObjectUtils.nullSafeEquals(getMapKeyTypeDescriptor(), other.getMapKeyTypeDescriptor()) &&
+ ObjectUtils.nullSafeEquals(getMapValueTypeDescriptor(), other.getMapValueTypeDescriptor());
}
else {
return true;
}
}
+ @Override
public int hashCode() {
return getType().hashCode();
}
+ @Override
public String toString() {
StringBuilder builder = new StringBuilder();
- for (Annotation ann : this.annotations) {
+ for (Annotation ann : getAnnotations()) {
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(">");
+ builder.append(this.resolvableType.toString());
+ return builder.toString();
+ }
+
+ /**
+ * 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 (may be {@code null} to indicate {@code Object.class})
+ * @return the corresponding type descriptor
+ */
+ public static TypeDescriptor valueOf(Class<?> type) {
+ if (type == null) {
+ type = Object.class;
}
- else if (isCollection()) {
- builder.append("<").append(wildcard(this.elementTypeDescriptor)).append(">");
+ TypeDescriptor desc = commonTypesCache.get(type);
+ return (desc != null ? desc : new TypeDescriptor(ResolvableType.forClass(type), null, null));
+ }
+
+ /**
+ * 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) {
+ Assert.notNull(collectionType, "collectionType must not be null");
+ if (!Collection.class.isAssignableFrom(collectionType)) {
+ throw new IllegalArgumentException("collectionType must be a java.util.Collection");
}
- return builder.toString();
+ ResolvableType element = (elementTypeDescriptor != null ? elementTypeDescriptor.resolvableType : null);
+ return new TypeDescriptor(ResolvableType.forClassWithGenerics(collectionType, element), null, null);
+ }
+
+ /**
+ * 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:
+ * <pre class="code">
+ * map(Map.class, TypeDescriptor.valueOf(Id.class), TypeDescriptor.valueOf(EmailAddress.class));
+ * </pre>
+ * @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");
+ }
+ ResolvableType key = (keyTypeDescriptor != null ? keyTypeDescriptor.resolvableType : null);
+ ResolvableType value = (valueTypeDescriptor != null ? valueTypeDescriptor.resolvableType : null);
+ return new TypeDescriptor(ResolvableType.forClassWithGenerics(mapType, key, value), null, null);
+ }
+
+ /**
+ * Create a new type descriptor as an array of the specified type.
+ * <p>For example to create a {@code Map<String,String>[]} use:
+ * <pre class="code">
+ * TypeDescriptor.array(TypeDescriptor.map(Map.class, TypeDescriptor.value(String.class), TypeDescriptor.value(String.class)));
+ * </pre>
+ * @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;
+ }
+ return new TypeDescriptor(ResolvableType.forArrayComponent(elementTypeDescriptor.resolvableType),
+ 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 {@code 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 TypeDescriptor(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 {@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(Field field, int nestingLevel) {
+ return nested(new TypeDescriptor(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 TypeDescriptor(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 {@code null}, returns {@code 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);
+ }
+
+ private static TypeDescriptor nested(TypeDescriptor typeDescriptor, int nestingLevel) {
+ ResolvableType nested = typeDescriptor.resolvableType;
+ for (int i = 0; i < nestingLevel; i++) {
+ if (Object.class.equals(nested.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...
+ }
+ else {
+ nested = nested.getNested(2);
+ }
+ }
+ if (nested == ResolvableType.NONE) {
+ return null;
+ }
+ return getRelatedIfResolvable(typeDescriptor, nested);
+ }
+
+ private static TypeDescriptor getRelatedIfResolvable(TypeDescriptor source, ResolvableType type) {
+ if (type.resolve() == null) {
+ return null;
+ }
+ return new TypeDescriptor(type, null, source.annotations);
}
}
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
index 3e2d7415..31c51128 100644
--- 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
@@ -77,6 +77,7 @@ public class ConvertingComparator<S, T> implements Comparator<S> {
}
+ @Override
public int compare(S o1, S o2) {
T c1 = this.converter.convert(o1);
T c2 = this.converter.convert(o2);
@@ -94,6 +95,7 @@ public class ConvertingComparator<S, T> implements Comparator<S> {
Comparator<K> comparator) {
return new ConvertingComparator<Map.Entry<K,V>, K>(comparator, new Converter<Map.Entry<K, V>, K>() {
+ @Override
public K convert(Map.Entry<K, V> source) {
return source.getKey();
}
@@ -111,6 +113,7 @@ public class ConvertingComparator<S, T> implements Comparator<S> {
Comparator<V> comparator) {
return new ConvertingComparator<Map.Entry<K,V>, V>(comparator, new Converter<Map.Entry<K, V>, V>() {
+ @Override
public V convert(Map.Entry<K, V> source) {
return source.getValue();
}
@@ -135,6 +138,7 @@ public class ConvertingComparator<S, T> implements Comparator<S> {
this.targetType = targetType;
}
+ @Override
public T convert(S source) {
return this.conversionService.convert(source, this.targetType);
}
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
index bb5c7331..df3b730d 100644
--- 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
@@ -48,14 +48,17 @@ final class ArrayToArrayConverter implements ConditionalGenericConverter {
}
+ @Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Object[].class, Object[].class));
}
+ @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.helperConverter.matches(sourceType, targetType);
}
+ @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (this.conversionService instanceof GenericConversionService &&
((GenericConversionService) this.conversionService).canBypassConvert(
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
index d034eddb..50c57870 100644
--- 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
@@ -48,16 +48,18 @@ final class ArrayToCollectionConverter implements ConditionalGenericConverter {
}
+ @Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Object[].class, Collection.class));
}
+ @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return ConversionUtils.canConvertElements(
sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor(), this.conversionService);
}
- @SuppressWarnings("unchecked")
+ @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
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
index f100458a..ef92f0bd 100644
--- 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
@@ -41,14 +41,17 @@ final class ArrayToObjectConverter implements ConditionalGenericConverter {
}
+ @Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Object[].class, Object.class));
}
+ @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType, this.conversionService);
}
+ @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
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
index 1307b991..80f986a6 100644
--- 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
@@ -43,14 +43,17 @@ final class ArrayToStringConverter implements ConditionalGenericConverter {
}
+ @Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Object[].class, String.class));
}
+ @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.helperConverter.matches(sourceType, targetType);
}
+ @Override
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/ByteBufferConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ByteBufferConverter.java
new file mode 100644
index 00000000..ec578fd6
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/ByteBufferConverter.java
@@ -0,0 +1,115 @@
+/*
+ * 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.support;
+
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.HashSet;
+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 {@link ByteBuffer} directly to and from {@code byte[]}s and indirectly to
+ * any type that the {@link ConversionService} support via {@code byte[]}.
+ *
+ * @author Phillip Webb
+ * @since 4.0
+ */
+final class ByteBufferConverter implements ConditionalGenericConverter {
+
+ private static final TypeDescriptor BYTE_BUFFER_TYPE = TypeDescriptor.valueOf(ByteBuffer.class);
+
+ private static final TypeDescriptor BYTE_ARRAY_TYPE = TypeDescriptor.valueOf(byte[].class);
+
+ private static final Set<ConvertiblePair> CONVERTIBLE_PAIRS;
+ static {
+ Set<ConvertiblePair> convertiblePairs = new HashSet<ConvertiblePair>();
+ convertiblePairs.add(new ConvertiblePair(ByteBuffer.class, Object.class));
+ convertiblePairs.add(new ConvertiblePair(Object.class, ByteBuffer.class));
+ CONVERTIBLE_PAIRS = Collections.unmodifiableSet(convertiblePairs);
+ }
+
+
+ private ConversionService conversionService;
+
+
+ public ByteBufferConverter(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+
+ @Override
+ public Set<ConvertiblePair> getConvertibleTypes() {
+ return CONVERTIBLE_PAIRS;
+ }
+
+ @Override
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ if (sourceType.isAssignableTo(BYTE_BUFFER_TYPE)) {
+ return matchesFromByteBuffer(targetType);
+ }
+ if (targetType.isAssignableTo(BYTE_BUFFER_TYPE)) {
+ return matchesToByteBuffer(sourceType);
+ }
+ return false;
+ }
+
+ private boolean matchesFromByteBuffer(TypeDescriptor targetType) {
+ return (targetType.isAssignableTo(BYTE_ARRAY_TYPE) || this.conversionService.canConvert(
+ BYTE_ARRAY_TYPE, targetType));
+ }
+
+ private boolean matchesToByteBuffer(TypeDescriptor sourceType) {
+ return (sourceType.isAssignableTo(BYTE_ARRAY_TYPE) || this.conversionService.canConvert(
+ sourceType, BYTE_ARRAY_TYPE));
+ }
+
+ @Override
+ public Object convert(Object source, TypeDescriptor sourceType,
+ TypeDescriptor targetType) {
+ if (sourceType.isAssignableTo(BYTE_BUFFER_TYPE)) {
+ return convertFromByteBuffer((ByteBuffer) source, targetType);
+ }
+ if (targetType.isAssignableTo(BYTE_BUFFER_TYPE)) {
+ return convertToByteBuffer(source, sourceType);
+ }
+ // Should not happen
+ throw new IllegalStateException("Unexpected source/target types");
+ }
+
+ private Object convertFromByteBuffer(ByteBuffer source, TypeDescriptor targetType) {
+ byte[] bytes = new byte[source.remaining()];
+ source.get(bytes);
+ if (targetType.isAssignableTo(BYTE_ARRAY_TYPE)) {
+ return bytes;
+ }
+ return this.conversionService.convert(bytes, BYTE_ARRAY_TYPE, targetType);
+ }
+
+ private Object convertToByteBuffer(Object source, TypeDescriptor sourceType) {
+ byte[] bytes = (byte[]) (source instanceof byte[] ? source
+ : this.conversionService.convert(source, sourceType, BYTE_ARRAY_TYPE));
+ ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length);
+ byteBuffer.put(bytes);
+ byteBuffer.rewind();
+ return byteBuffer;
+ }
+
+}
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
index 8f9642b0..179823f3 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * 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.
@@ -40,6 +40,7 @@ import org.springframework.util.NumberUtils;
*/
final class CharacterToNumberFactory implements ConverterFactory<Character, Number> {
+ @Override
public <T extends Number> Converter<Character, T> getConverter(Class<T> targetType) {
return new CharacterToNumber<T>(targetType);
}
@@ -52,6 +53,7 @@ final class CharacterToNumberFactory implements ConverterFactory<Character, Numb
this.targetType = targetType;
}
+ @Override
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
index d616eeb0..ea79a065 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * 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.
@@ -44,14 +44,17 @@ final class CollectionToArrayConverter implements ConditionalGenericConverter {
this.conversionService = conversionService;
}
+ @Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Collection.class, Object[].class));
}
+ @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor(), this.conversionService);
}
+ @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
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
index f0dfd3d6..a8071eae 100644
--- 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
@@ -47,16 +47,18 @@ final class CollectionToCollectionConverter implements ConditionalGenericConvert
}
+ @Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Collection.class, Collection.class));
}
+ @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return ConversionUtils.canConvertElements(
sourceType.getElementTypeDescriptor(), targetType.getElementTypeDescriptor(), this.conversionService);
}
- @SuppressWarnings("unchecked")
+ @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
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
index b9913ef6..bcc51e2f 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * 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.
@@ -38,14 +38,17 @@ final class CollectionToObjectConverter implements ConditionalGenericConverter {
this.conversionService = conversionService;
}
+ @Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Collection.class, Object.class));
}
+ @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType, this.conversionService);
}
+ @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
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
index 02b9d0eb..7537c1c3 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * 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.
@@ -40,14 +40,17 @@ final class CollectionToStringConverter implements ConditionalGenericConverter {
this.conversionService = conversionService;
}
+ @Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Collection.class, String.class));
}
+ @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return ConversionUtils.canConvertElements(sourceType.getElementTypeDescriptor(), targetType, this.conversionService);
}
+ @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
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
index 86e58098..b9538a7d 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * 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.
@@ -21,6 +21,7 @@ import java.util.UUID;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.ConverterRegistry;
+import org.springframework.util.ClassUtils;
/**
* A specialization of {@link GenericConversionService} configured by default with
@@ -31,10 +32,16 @@ import org.springframework.core.convert.converter.ConverterRegistry;
* {@code ConverterRegistry} instance.
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @since 3.1
*/
public class DefaultConversionService extends GenericConversionService {
+ /** Java 8's java.time package available? */
+ private static final boolean jsr310Available =
+ ClassUtils.isPresent("java.time.ZoneId", DefaultConversionService.class.getClassLoader());
+
+
/**
* Create a new {@code DefaultConversionService} with the set of
* {@linkplain DefaultConversionService#addDefaultConverters(ConverterRegistry) default converters}.
@@ -43,45 +50,55 @@ public class DefaultConversionService extends GenericConversionService {
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
+ * @param converterRegistry the registry of converters to add to (must also be castable to ConversionService,
+ * e.g. being a {@link ConfigurableConversionService})
+ * @throws ClassCastException if the given ConverterRegistry could not be cast to a ConversionService
*/
public static void addDefaultConverters(ConverterRegistry converterRegistry) {
addScalarConverters(converterRegistry);
addCollectionConverters(converterRegistry);
- addFallbackConverters(converterRegistry);
+
+ converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
+ if (jsr310Available) {
+ Jsr310ConverterRegistrar.registerZoneIdConverters(converterRegistry);
+ }
+
+ converterRegistry.addConverter(new ObjectToObjectConverter());
+ converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
+ converterRegistry.addConverter(new FallbackObjectToStringConverter());
}
// 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 NumberToNumberConverterFactory());
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.addConverter(new StringToBooleanConverter());
+ converterRegistry.addConverter(Boolean.class, String.class, new ObjectToStringConverter());
+
converterRegistry.addConverterFactory(new StringToEnumConverterFactory());
- converterRegistry.addConverter(Enum.class, String.class, new EnumToStringConverter(conversionService));
+ converterRegistry.addConverter(Enum.class, String.class,
+ new EnumToStringConverter((ConversionService) converterRegistry));
converterRegistry.addConverter(new StringToLocaleConverter());
converterRegistry.addConverter(Locale.class, String.class, new ObjectToStringConverter());
- converterRegistry.addConverter(new PropertiesToStringConverter());
converterRegistry.addConverter(new StringToPropertiesConverter());
+ converterRegistry.addConverter(new PropertiesToStringConverter());
converterRegistry.addConverter(new StringToUUIDConverter());
converterRegistry.addConverter(UUID.class, String.class, new ObjectToStringConverter());
@@ -89,6 +106,7 @@ public class DefaultConversionService extends GenericConversionService {
private static void addCollectionConverters(ConverterRegistry converterRegistry) {
ConversionService conversionService = (ConversionService) converterRegistry;
+
converterRegistry.addConverter(new ArrayToCollectionConverter(conversionService));
converterRegistry.addConverter(new CollectionToArrayConverter(conversionService));
@@ -109,11 +127,16 @@ public class DefaultConversionService extends GenericConversionService {
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());
+
+ /**
+ * Inner class to avoid a hard-coded dependency on Java 8's {@code java.time} package.
+ */
+ private static final class Jsr310ConverterRegistrar {
+
+ public static void registerZoneIdConverters(ConverterRegistry converterRegistry) {
+ converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
+ converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());
+ }
}
}
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
index e391b75b..bfd1762d 100644
--- 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
@@ -37,6 +37,7 @@ final class EnumToStringConverter implements Converter<Enum<?>, String>, Conditi
this.conversionService = conversionService;
}
+ @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
for (Class<?> interfaceType : ClassUtils.getAllInterfacesForClass(sourceType.getType())) {
if (conversionService.canConvert(TypeDescriptor.valueOf(interfaceType), targetType)) {
@@ -46,6 +47,7 @@ final class EnumToStringConverter implements Converter<Enum<?>, String>, Conditi
return true;
}
+ @Override
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
index 071ad3b6..90f15754 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * 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.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.springframework.core.convert.support;
import java.io.StringWriter;
@@ -21,10 +22,12 @@ import java.util.Set;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
+import org.springframework.util.ClassUtils;
/**
* 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.
+ * 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.
@@ -35,19 +38,24 @@ import org.springframework.core.convert.converter.ConditionalGenericConverter;
*/
final class FallbackObjectToStringConverter implements ConditionalGenericConverter {
+ @Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Object.class, String.class));
}
+ @Override
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);
+ return (CharSequence.class.isAssignableFrom(sourceClass) ||
+ StringWriter.class.isAssignableFrom(sourceClass) ||
+ ObjectToObjectConverter.getOfMethod(sourceClass, String.class) != null ||
+ ClassUtils.getConstructorIfAvailable(sourceClass, String.class) != null);
}
+ @Override
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
index 9f78f660..613729d3 100644
--- 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
@@ -28,7 +28,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-import org.springframework.core.GenericTypeResolver;
+import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
@@ -79,33 +79,35 @@ public class GenericConversionService implements ConfigurableConversionService {
// implementing ConverterRegistry
+ @Override
public void addConverter(Converter<?, ?> converter) {
- GenericConverter.ConvertiblePair typeInfo = getRequiredTypeInfo(converter, Converter.class);
+ ResolvableType[] 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));
+ addConverter(new ConverterAdapter(converter, typeInfo[0], typeInfo[1]));
}
+ @Override
public void addConverter(Class<?> sourceType, Class<?> targetType, Converter<?, ?> converter) {
- GenericConverter.ConvertiblePair typeInfo = new GenericConverter.ConvertiblePair(sourceType, targetType);
- addConverter(new ConverterAdapter(converter, typeInfo));
+ addConverter(new ConverterAdapter(converter, ResolvableType.forClass(sourceType), ResolvableType.forClass(targetType)));
}
+ @Override
public void addConverter(GenericConverter converter) {
this.converters.add(converter);
invalidateCache();
}
+ @Override
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));
+ ResolvableType[] typeInfo = getRequiredTypeInfo(converterFactory, ConverterFactory.class);
+ Assert.notNull("Unable to the determine sourceType <S> and targetRangeType R which your " +
+ "ConverterFactory<S, R> converts between; declare these generic types.");
+ addConverter(new ConverterFactoryAdapter(converterFactory,
+ new ConvertiblePair(typeInfo[0].resolve(), typeInfo[1].resolve())));
}
+ @Override
public void removeConvertible(Class<?> sourceType, Class<?> targetType) {
this.converters.remove(sourceType, targetType);
invalidateCache();
@@ -113,12 +115,14 @@ public class GenericConversionService implements ConfigurableConversionService {
// implementing ConversionService
+ @Override
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));
}
+ @Override
public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
Assert.notNull(targetType, "targetType to convert to cannot be null");
if (sourceType == null) {
@@ -147,12 +151,14 @@ public class GenericConversionService implements ConfigurableConversionService {
return (converter == NO_OP_CONVERTER);
}
+ @Override
@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));
}
+ @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
Assert.notNull(targetType,"The targetType to convert to cannot be null");
if (sourceType == null) {
@@ -256,9 +262,18 @@ public class GenericConversionService implements ConfigurableConversionService {
// 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 ResolvableType[] getRequiredTypeInfo(Object converter, Class<?> genericIfc) {
+ ResolvableType resolvableType = ResolvableType.forClass(converter.getClass()).as(genericIfc);
+ ResolvableType[] generics = resolvableType.getGenerics();
+ if (generics.length < 2) {
+ return null;
+ }
+ Class<?> sourceType = generics[0].resolve();
+ Class<?> targetType = generics[1].resolve();
+ if (sourceType == null || targetType == null) {
+ return null;
+ }
+ return generics;
}
private void invalidateCache() {
@@ -301,25 +316,36 @@ public class GenericConversionService implements ConfigurableConversionService {
private final ConvertiblePair typeInfo;
- public ConverterAdapter(Converter<?, ?> converter, ConvertiblePair typeInfo) {
+ private final ResolvableType targetType;
+
+ public ConverterAdapter(Converter<?, ?> converter, ResolvableType sourceType, ResolvableType targetType) {
this.converter = (Converter<Object, Object>) converter;
- this.typeInfo = typeInfo;
+ this.typeInfo = new ConvertiblePair(sourceType.resolve(Object.class), targetType.resolve(Object.class));
+ this.targetType = targetType;
}
+ @Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(this.typeInfo);
}
+ @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ // Check raw type first...
if (!this.typeInfo.getTargetType().equals(targetType.getObjectType())) {
return false;
}
- if (this.converter instanceof ConditionalConverter) {
- return ((ConditionalConverter) this.converter).matches(sourceType, targetType);
+ // Full check for complex generic type match required?
+ ResolvableType rt = targetType.getResolvableType();
+ if (!(rt.getType() instanceof Class) && !rt.isAssignableFrom(this.targetType) &&
+ !this.targetType.hasUnresolvableGenerics()) {
+ return false;
}
- return true;
+ return !(this.converter instanceof ConditionalConverter) ||
+ ((ConditionalConverter) this.converter).matches(sourceType, targetType);
}
+ @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return convertNullSource(sourceType, targetType);
@@ -349,10 +375,12 @@ public class GenericConversionService implements ConfigurableConversionService {
this.typeInfo = typeInfo;
}
+ @Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(this.typeInfo);
}
+ @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
boolean matches = true;
if (this.converterFactory instanceof ConditionalConverter) {
@@ -367,6 +395,7 @@ public class GenericConversionService implements ConfigurableConversionService {
return matches;
}
+ @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return convertNullSource(sourceType, targetType);
@@ -395,6 +424,7 @@ public class GenericConversionService implements ConfigurableConversionService {
this.targetType = targetType;
}
+ @Override
public boolean equals(Object other) {
if (this == other) {
return true;
@@ -518,7 +548,7 @@ public class GenericConversionService implements ConfigurableConversionService {
Class<?> candidate = hierarchy.get(i);
candidate = (array ? candidate.getComponentType() : ClassUtils.resolvePrimitiveIfNecessary(candidate));
Class<?> superclass = candidate.getSuperclass();
- if (candidate.getSuperclass() != null && superclass != Object.class) {
+ if (superclass != null && superclass != Object.class) {
addToClassHierarchy(i + 1, candidate.getSuperclass(), array, hierarchy, visited);
}
for (Class<?> implementedInterface : candidate.getInterfaces()) {
@@ -533,6 +563,7 @@ public class GenericConversionService implements ConfigurableConversionService {
private void addToClassHierarchy(int index, Class<?> type, boolean asArray,
List<Class<?>> hierarchy, Set<Class<?>> visited) {
+
if (asArray) {
type = Array.newInstance(type, 0).getClass();
}
@@ -601,10 +632,12 @@ public class GenericConversionService implements ConfigurableConversionService {
this.name = name;
}
+ @Override
public Set<ConvertiblePair> getConvertibleTypes() {
return null;
}
+ @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return source;
}
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
index da30e0d3..35f1d223 100644
--- 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
@@ -48,16 +48,19 @@ final class IdToEntityConverter implements ConditionalGenericConverter {
}
+ @Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Object.class, Object.class));
}
+ @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
Method finder = getFinder(targetType.getType());
return (finder != null &&
this.conversionService.canConvert(sourceType, TypeDescriptor.valueOf(finder.getParameterTypes()[0])));
}
+ @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
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
index 98b37dea..cd99976c 100644
--- 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
@@ -49,14 +49,17 @@ final class MapToMapConverter implements ConditionalGenericConverter {
}
+ @Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Map.class, Map.class));
}
+ @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return canConvertKey(sourceType, targetType) && canConvertValue(sourceType, targetType);
}
+ @Override
@SuppressWarnings("unchecked")
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
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
index b272abc6..d79435d0 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * 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.
@@ -34,6 +34,7 @@ import org.springframework.core.convert.converter.Converter;
*/
final class NumberToCharacterConverter implements Converter<Number, Character> {
+ @Override
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
index 240880d6..b98af70e 100644
--- 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
@@ -43,10 +43,12 @@ import org.springframework.util.NumberUtils;
final class NumberToNumberConverterFactory implements ConverterFactory<Number, Number>,
ConditionalConverter {
+ @Override
public <T extends Number> Converter<Number, T> getConverter(Class<T> targetType) {
return new NumberToNumber<T>(targetType);
}
+ @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return !sourceType.equals(targetType);
}
@@ -59,6 +61,7 @@ final class NumberToNumberConverterFactory implements ConverterFactory<Number, N
this.targetType = targetType;
}
+ @Override
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
index 9cdbb562..1531926a 100644
--- 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
@@ -41,14 +41,17 @@ final class ObjectToArrayConverter implements ConditionalGenericConverter {
}
+ @Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Object.class, Object[].class));
}
+ @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return ConversionUtils.canConvertElements(sourceType, targetType.getElementTypeDescriptor(), this.conversionService);
}
+ @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
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
index 7890cdb1..8900ba0c 100644
--- 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
@@ -43,15 +43,17 @@ final class ObjectToCollectionConverter implements ConditionalGenericConverter {
}
+ @Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Object.class, Collection.class));
}
+ @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return ConversionUtils.canConvertElements(sourceType, targetType.getElementTypeDescriptor(), this.conversionService);
}
- @SuppressWarnings("unchecked")
+ @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
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
index 0448e582..a7df4866 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * 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.
@@ -29,13 +29,14 @@ import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
- * Generic Converter that attempts to convert a source Object to a target type
+ * 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.
+ * <p>Calls a static {@code valueOf(sourceType)} or Java 8 style {@code of|from(sourceType)}
+ * method on the target type to perform the conversion, if such a method exists. Otherwise,
+ * it checks for a {@code to[targetType.simpleName]} method on the source type calls
+ * the target type's constructor that accepts a single {@code sourceType} argument, if such
+ * a constructor exists. If neither strategy works, it throws a ConversionFailedException.
*
* @author Keith Donald
* @author Juergen Hoeller
@@ -43,36 +44,46 @@ import org.springframework.util.ReflectionUtils;
*/
final class ObjectToObjectConverter implements ConditionalGenericConverter {
+ @Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(Object.class, Object.class));
}
+ @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
if (sourceType.getType().equals(targetType.getType())) {
// no conversion required
return false;
}
- return hasValueOfMethodOrConstructor(targetType.getType(), sourceType.getType());
+ return (String.class.equals(targetType.getType()) ?
+ (ClassUtils.getConstructorIfAvailable(String.class, sourceType.getType()) != null) :
+ hasToMethodOrOfMethodOrConstructor(targetType.getType(), sourceType.getType()));
}
+ @Override
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);
+ if (!String.class.equals(targetClass)) {
+ Method method = getToMethod(targetClass, sourceClass);
+ if (method != null) {
+ ReflectionUtils.makeAccessible(method);
+ return method.invoke(source);
+ }
+ method = getOfMethod(targetClass, sourceClass);
+ if (method != null) {
+ ReflectionUtils.makeAccessible(method);
+ return method.invoke(null, source);
}
}
+ Constructor<?> constructor = ClassUtils.getConstructorIfAvailable(targetClass, sourceClass);
+ if (constructor != null) {
+ return constructor.newInstance(source);
+ }
}
catch (InvocationTargetException ex) {
throw new ConversionFailedException(sourceType, targetType, source, ex.getTargetException());
@@ -80,20 +91,31 @@ final class ObjectToObjectConverter implements ConditionalGenericConverter {
catch (Throwable ex) {
throw new ConversionFailedException(sourceType, targetType, source, ex);
}
- throw new IllegalStateException("No static valueOf(" + sourceClass.getName() +
+ throw new IllegalStateException("No static valueOf/of/from(" + 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 boolean hasToMethodOrOfMethodOrConstructor(Class<?> targetClass, Class<?> sourceClass) {
+ return (getToMethod(targetClass, sourceClass) != null ||
+ getOfMethod(targetClass, sourceClass) != null ||
+ ClassUtils.getConstructorIfAvailable(targetClass, sourceClass) != null);
}
- private static Method getValueOfMethodOn(Class<?> clazz, Class<?> sourceParameterType) {
- return ClassUtils.getStaticMethod(clazz, "valueOf", sourceParameterType);
+ private static Method getToMethod(Class<?> targetClass, Class<?> sourceClass) {
+ Method method = ClassUtils.getMethodIfAvailable(sourceClass, "to" + targetClass.getSimpleName());
+ return (method != null && targetClass.equals(method.getReturnType()) ? method : null);
}
- private static Constructor<?> getConstructor(Class<?> clazz, Class<?> sourceParameterType) {
- return ClassUtils.getConstructorIfAvailable(clazz, sourceParameterType);
+ static Method getOfMethod(Class<?> targetClass, Class<?> sourceClass) {
+ Method method = ClassUtils.getStaticMethod(targetClass, "valueOf", sourceClass);
+ if (method == null) {
+ method = ClassUtils.getStaticMethod(targetClass, "of", sourceClass);
+ if (method == null) {
+ method = ClassUtils.getStaticMethod(targetClass, "from", sourceClass);
+ }
+ }
+ return method;
}
}
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
index 420183b3..e5fd8419 100644
--- 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
@@ -26,6 +26,7 @@ import org.springframework.core.convert.converter.Converter;
*/
final class ObjectToStringConverter implements Converter<Object, String> {
+ @Override
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
index f4962d01..4d2124ad 100644
--- 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
@@ -31,6 +31,7 @@ import org.springframework.core.convert.converter.Converter;
*/
final class PropertiesToStringConverter implements Converter<Properties, String> {
+ @Override
public String convert(Properties source) {
try {
ByteArrayOutputStream os = new ByteArrayOutputStream(256);
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
index 4bf32af3..af209148 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * 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.
@@ -40,14 +40,17 @@ final class StringToArrayConverter implements ConditionalGenericConverter {
this.conversionService = conversionService;
}
+ @Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(String.class, Object[].class));
}
+ @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor());
}
+ @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
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
index 1e23ee61..1d8bc61e 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * 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.
@@ -46,6 +46,7 @@ final class StringToBooleanConverter implements Converter<String, Boolean> {
falseValues.add("0");
}
+ @Override
public Boolean convert(String source) {
String value = source.trim();
if ("".equals(value)) {
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
index 3e379164..7a076d33 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * 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.
@@ -26,6 +26,7 @@ import org.springframework.core.convert.converter.Converter;
*/
final class StringToCharacterConverter implements Converter<String, Character> {
+ @Override
public Character convert(String source) {
if (source.length() == 0) {
return null;
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
index b16c2462..15d7df11 100644
--- 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
@@ -45,16 +45,18 @@ final class StringToCollectionConverter implements ConditionalGenericConverter {
}
+ @Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(String.class, Collection.class));
}
+ @Override
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return (targetType.getElementTypeDescriptor() == null ||
this.conversionService.canConvert(sourceType, targetType.getElementTypeDescriptor()));
}
- @SuppressWarnings("unchecked")
+ @Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (source == null) {
return null;
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
index e015afb3..923239b9 100644
--- 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
@@ -28,6 +28,7 @@ import org.springframework.core.convert.converter.ConverterFactory;
@SuppressWarnings({"unchecked", "rawtypes"})
final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
+ @Override
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
Class<?> enumType = targetType;
while (enumType != null && !enumType.isEnum()) {
@@ -49,6 +50,7 @@ final class StringToEnumConverterFactory implements ConverterFactory<String, Enu
this.enumType = enumType;
}
+ @Override
public T convert(String source) {
if (source.length() == 0) {
// It's an empty enum identifier: reset the enum value to null.
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
index 3ca91385..c6c22ea2 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * 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.
@@ -29,6 +29,7 @@ import org.springframework.util.StringUtils;
*/
final class StringToLocaleConverter implements Converter<String, Locale> {
+ @Override
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
index 8bb0d646..506dcec9 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * 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.
@@ -40,6 +40,7 @@ import org.springframework.util.NumberUtils;
*/
final class StringToNumberConverterFactory implements ConverterFactory<String, Number> {
+ @Override
public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToNumber<T>(targetType);
}
@@ -52,6 +53,7 @@ final class StringToNumberConverterFactory implements ConverterFactory<String, N
this.targetType = targetType;
}
+ @Override
public T convert(String source) {
if (source.length() == 0) {
return null;
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
index e97cae67..83c6c13f 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * 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.
@@ -30,6 +30,7 @@ import org.springframework.core.convert.converter.Converter;
*/
final class StringToPropertiesConverter implements Converter<String, Properties> {
+ @Override
public Properties convert(String source) {
try {
Properties props = new Properties();
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
index db5a3ebf..48e6a0ae 100644
--- 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
@@ -29,6 +29,7 @@ import org.springframework.util.StringUtils;
*/
final class StringToUUIDConverter implements Converter<String, UUID> {
+ @Override
public UUID convert(String source) {
if (StringUtils.hasLength(source)) {
return UUID.fromString(source.trim());
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ZoneIdToTimeZoneConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ZoneIdToTimeZoneConverter.java
new file mode 100644
index 00000000..e0416ea4
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/ZoneIdToTimeZoneConverter.java
@@ -0,0 +1,44 @@
+/*
+ * 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.support;
+
+import java.time.ZoneId;
+import java.util.TimeZone;
+
+import org.springframework.core.convert.converter.Converter;
+
+/**
+ * Simple converter from Java 8's {@link java.time.ZoneId} to {@link java.util.TimeZone}.
+ *
+ * <p>Note that Spring's default ConversionService setup understands the 'from'/'to' convention
+ * that the JSR-310 {@code java.time} package consistently uses. That convention is implemented
+ * reflectively in {@link ObjectToObjectConverter}, not in specific JSR-310 converters.
+ * It covers {@link java.util.TimeZone#toZoneId()} as well, and also
+ * {@link java.util.Date#from(java.time.Instant)} and {@link java.util.Date#toInstant()}.
+ *
+ * @author Juergen Hoeller
+ * @since 4.0
+ * @see TimeZone#getTimeZone(java.time.ZoneId)
+ */
+final class ZoneIdToTimeZoneConverter implements Converter<ZoneId, TimeZone> {
+
+ @Override
+ public TimeZone convert(ZoneId source) {
+ return TimeZone.getTimeZone(source);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ZonedDateTimeToCalendarConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ZonedDateTimeToCalendarConverter.java
new file mode 100644
index 00000000..02d6017d
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/ZonedDateTimeToCalendarConverter.java
@@ -0,0 +1,45 @@
+/*
+ * 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.support;
+
+import java.time.ZonedDateTime;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import org.springframework.core.convert.converter.Converter;
+
+/**
+ * Simple converter from Java 8's {@link java.time.ZonedDateTime} to {@link java.util.Calendar}.
+ *
+ * <p>Note that Spring's default ConversionService setup understands the 'from'/'to' convention
+ * that the JSR-310 {@code java.time} package consistently uses. That convention is implemented
+ * reflectively in {@link ObjectToObjectConverter}, not in specific JSR-310 converters.
+ * It covers {@link java.util.GregorianCalendar#toZonedDateTime()} as well, and also
+ * {@link java.util.Date#from(java.time.Instant)} and {@link java.util.Date#toInstant()}.
+ *
+ * @author Juergen Hoeller
+ * @since 4.0.1
+ * @see java.util.GregorianCalendar#from(java.time.ZonedDateTime)
+ */
+final class ZonedDateTimeToCalendarConverter implements Converter<ZonedDateTime, Calendar> {
+
+ @Override
+ public Calendar convert(ZonedDateTime source) {
+ return GregorianCalendar.from(source);
+ }
+
+}
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
deleted file mode 100644
index 4a278f92..00000000
--- a/spring-core/src/main/java/org/springframework/core/enums/AbstractCachingLabeledEnumResolver.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * 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
deleted file mode 100644
index cee9535c..00000000
--- a/spring-core/src/main/java/org/springframework/core/enums/AbstractGenericLabeledEnum.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 3c2860f7..00000000
--- a/spring-core/src/main/java/org/springframework/core/enums/AbstractLabeledEnum.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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
deleted file mode 100644
index dc2325bf..00000000
--- a/spring-core/src/main/java/org/springframework/core/enums/LabeledEnum.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 7b374a70..00000000
--- a/spring-core/src/main/java/org/springframework/core/enums/LabeledEnumResolver.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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
deleted file mode 100644
index cb70971a..00000000
--- a/spring-core/src/main/java/org/springframework/core/enums/LetterCodedLabeledEnum.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 0c5e3fae..00000000
--- a/spring-core/src/main/java/org/springframework/core/enums/ShortCodedLabeledEnum.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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
deleted file mode 100644
index e6440cc3..00000000
--- a/spring-core/src/main/java/org/springframework/core/enums/StaticLabeledEnum.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 3d9f78a4..00000000
--- a/spring-core/src/main/java/org/springframework/core/enums/StaticLabeledEnumResolver.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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
deleted file mode 100644
index d0a936ef..00000000
--- a/spring-core/src/main/java/org/springframework/core/enums/StringCodedLabeledEnum.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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
deleted file mode 100644
index ac720538..00000000
--- a/spring-core/src/main/java/org/springframework/core/enums/package-info.java
+++ /dev/null
@@ -1,9 +0,0 @@
-
-/**
- *
- * 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
index 786443be..b7918aee 100644
--- a/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java
+++ b/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java
@@ -223,6 +223,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
// Implementation of ConfigurableEnvironment interface
//---------------------------------------------------------------------
+ @Override
public String[] getActiveProfiles() {
return StringUtils.toStringArray(doGetActiveProfiles());
}
@@ -245,6 +246,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
return this.activeProfiles;
}
+ @Override
public void setActiveProfiles(String... profiles) {
Assert.notNull(profiles, "Profile array must not be null");
this.activeProfiles.clear();
@@ -254,6 +256,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
}
}
+ @Override
public void addActiveProfile(String profile) {
if (this.logger.isDebugEnabled()) {
this.logger.debug(format("Activating profile '%s'", profile));
@@ -264,6 +267,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
}
+ @Override
public String[] getDefaultProfiles() {
return StringUtils.toStringArray(doGetDefaultProfiles());
}
@@ -298,6 +302,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
* @see #AbstractEnvironment()
* @see #getReservedDefaultProfiles()
*/
+ @Override
public void setDefaultProfiles(String... profiles) {
Assert.notNull(profiles, "Profile array must not be null");
this.defaultProfiles.clear();
@@ -307,6 +312,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
}
}
+ @Override
public boolean acceptsProfiles(String... profiles) {
Assert.notEmpty(profiles, "Must specify at least one profile");
for (String profile : profiles) {
@@ -352,10 +358,12 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
}
}
+ @Override
public MutablePropertySources getPropertySources() {
return this.propertySources;
}
+ @Override
@SuppressWarnings("unchecked")
public Map<String, Object> getSystemEnvironment() {
if (suppressGetenvAccess()) {
@@ -399,6 +407,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
return SpringProperties.getFlag(IGNORE_GETENV_PROPERTY_NAME);
}
+ @Override
@SuppressWarnings("unchecked")
public Map<String, Object> getSystemProperties() {
try {
@@ -424,6 +433,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
}
}
+ @Override
public void merge(ConfigurableEnvironment parent) {
for (PropertySource<?> ps : parent.getPropertySources()) {
if (!this.propertySources.contains(ps.getName())) {
@@ -446,34 +456,42 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
// Implementation of ConfigurablePropertyResolver interface
//---------------------------------------------------------------------
+ @Override
public ConfigurableConversionService getConversionService() {
return this.propertyResolver.getConversionService();
}
+ @Override
public void setConversionService(ConfigurableConversionService conversionService) {
this.propertyResolver.setConversionService(conversionService);
}
+ @Override
public void setPlaceholderPrefix(String placeholderPrefix) {
this.propertyResolver.setPlaceholderPrefix(placeholderPrefix);
}
+ @Override
public void setPlaceholderSuffix(String placeholderSuffix) {
this.propertyResolver.setPlaceholderSuffix(placeholderSuffix);
}
+ @Override
public void setValueSeparator(String valueSeparator) {
this.propertyResolver.setValueSeparator(valueSeparator);
}
+ @Override
public void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders) {
this.propertyResolver.setIgnoreUnresolvableNestedPlaceholders(ignoreUnresolvableNestedPlaceholders);
}
+ @Override
public void setRequiredProperties(String... requiredProperties) {
this.propertyResolver.setRequiredProperties(requiredProperties);
}
+ @Override
public void validateRequiredProperties() throws MissingRequiredPropertiesException {
this.propertyResolver.validateRequiredProperties();
}
@@ -488,38 +506,47 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
return this.propertyResolver.containsProperty(key);
}
+ @Override
public String getProperty(String key) {
return this.propertyResolver.getProperty(key);
}
+ @Override
public String getProperty(String key, String defaultValue) {
return this.propertyResolver.getProperty(key, defaultValue);
}
+ @Override
public <T> T getProperty(String key, Class<T> targetType) {
return this.propertyResolver.getProperty(key, targetType);
}
+ @Override
public <T> T getProperty(String key, Class<T> targetType, T defaultValue) {
return this.propertyResolver.getProperty(key, targetType, defaultValue);
}
+ @Override
public <T> Class<T> getPropertyAsClass(String key, Class<T> targetType) {
return this.propertyResolver.getPropertyAsClass(key, targetType);
}
+ @Override
public String getRequiredProperty(String key) throws IllegalStateException {
return this.propertyResolver.getRequiredProperty(key);
}
+ @Override
public <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException {
return this.propertyResolver.getRequiredProperty(key, targetType);
}
+ @Override
public String resolvePlaceholders(String text) {
return this.propertyResolver.resolvePlaceholders(text);
}
+ @Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
return this.propertyResolver.resolveRequiredPlaceholders(text);
}
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
index 4b02991a..b5f18271 100644
--- a/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java
+++ b/spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java
@@ -55,10 +55,12 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
private final Set<String> requiredProperties = new LinkedHashSet<String>();
+ @Override
public ConfigurableConversionService getConversionService() {
return this.conversionService;
}
+ @Override
public void setConversionService(ConfigurableConversionService conversionService) {
this.conversionService = conversionService;
}
@@ -68,6 +70,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
* <p>The default is "${".
* @see org.springframework.util.SystemPropertyUtils#PLACEHOLDER_PREFIX
*/
+ @Override
public void setPlaceholderPrefix(String placeholderPrefix) {
this.placeholderPrefix = placeholderPrefix;
}
@@ -77,6 +80,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
* <p>The default is "}".
* @see org.springframework.util.SystemPropertyUtils#PLACEHOLDER_SUFFIX
*/
+ @Override
public void setPlaceholderSuffix(String placeholderSuffix) {
this.placeholderSuffix = placeholderSuffix;
}
@@ -88,6 +92,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
* <p>The default is ":".
* @see org.springframework.util.SystemPropertyUtils#VALUE_SEPARATOR
*/
+ @Override
public void setValueSeparator(String valueSeparator) {
this.valueSeparator = valueSeparator;
}
@@ -101,16 +106,19 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
* <p>The default is {@code false}.
* @since 3.2
*/
+ @Override
public void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders) {
this.ignoreUnresolvableNestedPlaceholders = ignoreUnresolvableNestedPlaceholders;
}
+ @Override
public void setRequiredProperties(String... requiredProperties) {
for (String key : requiredProperties) {
this.requiredProperties.add(key);
}
}
+ @Override
public void validateRequiredProperties() {
MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
for (String key : this.requiredProperties) {
@@ -124,16 +132,19 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
}
+ @Override
public String getProperty(String key, String defaultValue) {
String value = getProperty(key);
return (value != null ? value : defaultValue);
}
+ @Override
public <T> T getProperty(String key, Class<T> targetType, T defaultValue) {
T value = getProperty(key, targetType);
return (value != null ? value : defaultValue);
}
+ @Override
public String getRequiredProperty(String key) throws IllegalStateException {
String value = getProperty(key);
if (value == null) {
@@ -142,6 +153,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
return value;
}
+ @Override
public <T> T getRequiredProperty(String key, Class<T> valueType) throws IllegalStateException {
T value = getProperty(key, valueType);
if (value == null) {
@@ -150,6 +162,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
return value;
}
+ @Override
public String resolvePlaceholders(String text) {
if (this.nonStrictHelper == null) {
this.nonStrictHelper = createPlaceholderHelper(true);
@@ -157,6 +170,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
return doResolvePlaceholders(text, this.nonStrictHelper);
}
+ @Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
this.strictHelper = createPlaceholderHelper(false);
@@ -188,6 +202,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
+ @Override
public String resolvePlaceholder(String placeholderName) {
return getPropertyAsRawString(placeholderName);
}
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
index 575cd359..c71630fd 100644
--- a/spring-core/src/main/java/org/springframework/core/env/CommandLinePropertySource.java
+++ b/spring-core/src/main/java/org/springframework/core/env/CommandLinePropertySource.java
@@ -185,7 +185,7 @@ import org.springframework.util.StringUtils;
* @see SimpleCommandLinePropertySource
* @see JOptCommandLinePropertySource
*/
-public abstract class CommandLinePropertySource<T> extends PropertySource<T> {
+public abstract class CommandLinePropertySource<T> extends EnumerablePropertySource<T> {
/** The default name given to {@link CommandLinePropertySource} instances: {@value} */
public static final String COMMAND_LINE_PROPERTY_SOURCE_NAME = "commandLineArgs";
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
index c6742025..dde7a4ea 100644
--- a/spring-core/src/main/java/org/springframework/core/env/EnumerablePropertySource.java
+++ b/spring-core/src/main/java/org/springframework/core/env/EnumerablePropertySource.java
@@ -63,6 +63,7 @@ public abstract class EnumerablePropertySource<T> extends PropertySource<T> {
* {@link #getPropertyNames()} array.
* @param name the name of the property to find
*/
+ @Override
public boolean containsProperty(String name) {
Assert.notNull(name, "Property name must not be null");
for (String candidate : getPropertyNames()) {
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
index 61ed06a6..161cccd3 100644
--- a/spring-core/src/main/java/org/springframework/core/env/JOptCommandLinePropertySource.java
+++ b/spring-core/src/main/java/org/springframework/core/env/JOptCommandLinePropertySource.java
@@ -21,6 +21,7 @@ import java.util.Collections;
import java.util.List;
import joptsimple.OptionSet;
+import joptsimple.OptionSpec;
import org.springframework.util.Assert;
@@ -43,10 +44,11 @@ import org.springframework.util.Assert;
*
* See {@link CommandLinePropertySource} for complete general usage examples.
*
- * <p>Requires JOpt version 3.0 or higher. Tested against JOpt up until 4.6.
+ * <p>Requires JOpt version 4.3 or higher. Tested against JOpt up until 4.6.
*
* @author Chris Beams
* @author Juergen Hoeller
+ * @author Dave Syer
* @since 3.1
* @see CommandLinePropertySource
* @see joptsimple.OptionParser
@@ -79,6 +81,19 @@ public class JOptCommandLinePropertySource extends CommandLinePropertySource<Opt
}
@Override
+ public String[] getPropertyNames() {
+ List<String> names = new ArrayList<String>();
+ for (OptionSpec<?> spec : this.source.specs()) {
+ List<String> aliases = new ArrayList<String>(spec.options());
+ if (!aliases.isEmpty()) {
+ // Only the longest name is used for enumerating
+ names.add(aliases.get(aliases.size() - 1));
+ }
+ }
+ return names.toArray(new String[names.size()]);
+ }
+
+ @Override
public List<String> getOptionValues(String name) {
List<?> argValues = this.source.valuesOf(name);
List<String> stringArgValues = new ArrayList<String>();
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
index 507704bd..1587833c 100644
--- a/spring-core/src/main/java/org/springframework/core/env/MutablePropertySources.java
+++ b/spring-core/src/main/java/org/springframework/core/env/MutablePropertySources.java
@@ -17,12 +17,12 @@
package org.springframework.core.env;
import java.util.Iterator;
-import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@@ -35,24 +35,22 @@ import org.springframework.util.StringUtils;
* will be searched when resolving a given property with a {@link PropertyResolver}.
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @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<?>>();
+ private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<PropertySource<?>>();
/**
* Create a new {@link MutablePropertySources} object.
*/
public MutablePropertySources() {
- this.logger = LogFactory.getLog(this.getClass());
+ this.logger = LogFactory.getLog(getClass());
}
/**
@@ -62,7 +60,7 @@ public class MutablePropertySources implements PropertySources {
public MutablePropertySources(PropertySources propertySources) {
this();
for (PropertySource<?> propertySource : propertySources) {
- this.addLast(propertySource);
+ addLast(propertySource);
}
}
@@ -75,15 +73,18 @@ public class MutablePropertySources implements PropertySources {
}
+ @Override
public boolean contains(String name) {
return this.propertySourceList.contains(PropertySource.named(name));
}
+ @Override
public PropertySource<?> get(String name) {
int index = this.propertySourceList.indexOf(PropertySource.named(name));
- return index == -1 ? null : this.propertySourceList.get(index);
+ return (index != -1 ? this.propertySourceList.get(index) : null);
}
+ @Override
public Iterator<PropertySource<?>> iterator() {
return this.propertySourceList.iterator();
}
@@ -97,7 +98,7 @@ public class MutablePropertySources implements PropertySources {
propertySource.getName()));
}
removeIfPresent(propertySource);
- this.propertySourceList.addFirst(propertySource);
+ this.propertySourceList.add(0, propertySource);
}
/**
@@ -109,7 +110,7 @@ public class MutablePropertySources implements PropertySources {
propertySource.getName()));
}
removeIfPresent(propertySource);
- this.propertySourceList.addLast(propertySource);
+ this.propertySourceList.add(propertySource);
}
/**
@@ -158,7 +159,7 @@ public class MutablePropertySources implements PropertySources {
logger.debug(String.format("Removing [%s] PropertySource", name));
}
int index = this.propertySourceList.indexOf(PropertySource.named(name));
- return index == -1 ? null : this.propertySourceList.remove(index);
+ return (index != -1 ? this.propertySourceList.remove(index) : null);
}
/**
@@ -187,7 +188,7 @@ public class MutablePropertySources implements PropertySources {
@Override
public String toString() {
String[] names = new String[this.size()];
- for (int i=0; i < size(); i++) {
+ for (int i = 0; i < size(); i++) {
names[i] = this.propertySourceList.get(i).getName();
}
return String.format("[%s]", StringUtils.arrayToCommaDelimitedString(names));
@@ -198,17 +199,17 @@ public class MutablePropertySources implements PropertySources {
*/
protected void assertLegalRelativeAddition(String relativePropertySourceName, PropertySource<?> propertySource) {
String newPropertySourceName = propertySource.getName();
- Assert.isTrue(!relativePropertySourceName.equals(newPropertySourceName),
- String.format(ILLEGAL_RELATIVE_ADDITION_MESSAGE, newPropertySourceName));
+ if (relativePropertySourceName.equals(newPropertySourceName)) {
+ throw new IllegalArgumentException(
+ String.format("PropertySource named [%s] cannot be added relative to itself", newPropertySourceName));
+ }
}
/**
* Remove the given property source if it is present.
*/
protected void removeIfPresent(PropertySource<?> propertySource) {
- if (this.propertySourceList.contains(propertySource)) {
- this.propertySourceList.remove(propertySource);
- }
+ this.propertySourceList.remove(propertySource);
}
/**
@@ -227,7 +228,9 @@ public class MutablePropertySources implements PropertySources {
*/
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));
+ if (index == -1) {
+ throw new IllegalArgumentException(String.format("PropertySource named [%s] does not exist", 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
index 492d142d..062e25e9 100644
--- a/spring-core/src/main/java/org/springframework/core/env/PropertiesPropertySource.java
+++ b/spring-core/src/main/java/org/springframework/core/env/PropertiesPropertySource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * 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.
@@ -30,13 +30,18 @@ import java.util.Properties;
* {@link Properties#getProperty} and {@link Properties#setProperty}.
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @since 3.1
*/
public class PropertiesPropertySource extends MapPropertySource {
- @SuppressWarnings({ "unchecked", "rawtypes" })
+ @SuppressWarnings({"unchecked", "rawtypes"})
public PropertiesPropertySource(String name, Properties source) {
super(name, (Map) source);
}
+ protected PropertiesPropertySource(String name, Map<String, Object> source) {
+ super(name, source);
+ }
+
}
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
index 2f770277..b5826c51 100644
--- a/spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java
+++ b/spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java
@@ -39,6 +39,7 @@ import org.springframework.util.Assert;
*/
abstract class ReadOnlySystemAttributesMap implements Map<String, String> {
+ @Override
public boolean containsKey(Object key) {
return (get(key) != null);
}
@@ -47,12 +48,14 @@ abstract class ReadOnlySystemAttributesMap implements Map<String, String> {
* @param key the name of the system attribute to retrieve
* @throws IllegalArgumentException if given key is non-String
*/
+ @Override
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);
}
+ @Override
public boolean isEmpty() {
return false;
}
@@ -66,38 +69,47 @@ abstract class ReadOnlySystemAttributesMap implements Map<String, String> {
// Unsupported
+ @Override
public int size() {
throw new UnsupportedOperationException();
}
+ @Override
public String put(String key, String value) {
throw new UnsupportedOperationException();
}
+ @Override
public boolean containsValue(Object value) {
throw new UnsupportedOperationException();
}
+ @Override
public String remove(Object key) {
throw new UnsupportedOperationException();
}
+ @Override
public void clear() {
throw new UnsupportedOperationException();
}
+ @Override
public Set<String> keySet() {
return Collections.emptySet();
}
+ @Override
public void putAll(Map<? extends String, ? extends String> map) {
throw new UnsupportedOperationException();
}
+ @Override
public Collection<String> values() {
return Collections.emptySet();
}
+ @Override
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
index b249faf0..b7ad3c42 100644
--- a/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLineArgsParser.java
+++ b/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLineArgsParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -52,9 +52,9 @@ package org.springframework.core.env;
class SimpleCommandLineArgsParser {
/**
- * Parse the given {@code String} array based on the rules described
- * {@linkplain SimpleCommandLineArgsParser above}, returning a
- * fully-populated {@link CommandLineArgs} object.
+ * 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) {
@@ -65,13 +65,13 @@ class SimpleCommandLineArgsParser {
String optionName;
String optionValue = null;
if (optionText.contains("=")) {
- optionName = optionText.substring(0, optionText.indexOf('='));
- optionValue = optionText.substring(optionText.indexOf('=') + 1, optionText.length());
+ 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)) {
+ if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) {
throw new IllegalArgumentException("Invalid argument syntax: " + arg);
}
commandLineArgs.addOptionArg(optionName, optionValue);
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
index d2022317..f1b1f8d7 100644
--- a/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLinePropertySource.java
+++ b/spring-core/src/main/java/org/springframework/core/env/SimpleCommandLinePropertySource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * 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.
@@ -95,6 +95,14 @@ public class SimpleCommandLinePropertySource extends CommandLinePropertySource<C
super(name, new SimpleCommandLineArgsParser().parse(args));
}
+ /**
+ * Get the property names for the option arguments.
+ */
+ @Override
+ public String[] getPropertyNames() {
+ return source.getOptionNames().toArray(new String[source.getOptionNames().size()]);
+ }
+
@Override
protected boolean containsOption(String name) {
return this.source.containsOption(name);
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
index 91ea1288..cd001020 100644
--- a/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java
+++ b/spring-core/src/main/java/org/springframework/core/io/AbstractResource.java
@@ -46,6 +46,7 @@ public abstract class AbstractResource implements Resource {
* falling back to whether an InputStream can be opened.
* This will cover both directories and content resources.
*/
+ @Override
public boolean exists() {
// Try file existence: can we find the file in the file system?
try {
@@ -67,6 +68,7 @@ public abstract class AbstractResource implements Resource {
/**
* This implementation always returns {@code true}.
*/
+ @Override
public boolean isReadable() {
return true;
}
@@ -74,6 +76,7 @@ public abstract class AbstractResource implements Resource {
/**
* This implementation always returns {@code false}.
*/
+ @Override
public boolean isOpen() {
return false;
}
@@ -82,6 +85,7 @@ public abstract class AbstractResource implements Resource {
* This implementation throws a FileNotFoundException, assuming
* that the resource cannot be resolved to a URL.
*/
+ @Override
public URL getURL() throws IOException {
throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
}
@@ -90,6 +94,7 @@ public abstract class AbstractResource implements Resource {
* This implementation builds a URI based on the URL returned
* by {@link #getURL()}.
*/
+ @Override
public URI getURI() throws IOException {
URL url = getURL();
try {
@@ -104,6 +109,7 @@ public abstract class AbstractResource implements Resource {
* This implementation throws a FileNotFoundException, assuming
* that the resource cannot be resolved to an absolute file path.
*/
+ @Override
public File getFile() throws IOException {
throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
}
@@ -115,6 +121,7 @@ public abstract class AbstractResource implements Resource {
* @see #getInputStream()
* @throws IllegalStateException if {@link #getInputStream()} returns null.
*/
+ @Override
public long contentLength() throws IOException {
InputStream is = this.getInputStream();
Assert.state(is != null, "resource input stream must not be null");
@@ -141,6 +148,7 @@ public abstract class AbstractResource implements Resource {
* if available.
* @see #getFileForLastModifiedCheck()
*/
+ @Override
public long lastModified() throws IOException {
long lastModified = getFileForLastModifiedCheck().lastModified();
if (lastModified == 0L) {
@@ -165,6 +173,7 @@ public abstract class AbstractResource implements Resource {
* This implementation throws a FileNotFoundException, assuming
* that relative resources cannot be created for this resource.
*/
+ @Override
public Resource createRelative(String relativePath) throws IOException {
throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
}
@@ -173,6 +182,7 @@ public abstract class AbstractResource implements Resource {
* This implementation always returns {@code null},
* assuming that this resource type does not have a filename.
*/
+ @Override
public String getFilename() {
return null;
}
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
index d195def9..b9709e38 100644
--- a/spring-core/src/main/java/org/springframework/core/io/ByteArrayResource.java
+++ b/spring-core/src/main/java/org/springframework/core/io/ByteArrayResource.java
@@ -93,6 +93,7 @@ public class ByteArrayResource extends AbstractResource {
* underlying byte array.
* @see java.io.ByteArrayInputStream
*/
+ @Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(this.byteArray);
}
@@ -100,6 +101,7 @@ public class ByteArrayResource extends AbstractResource {
/**
* This implementation returns the passed-in description, if any.
*/
+ @Override
public String getDescription() {
return this.description;
}
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
index b1d2998e..dacfb011 100644
--- a/spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java
+++ b/spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java
@@ -156,6 +156,7 @@ public class ClassPathResource extends AbstractFileResolvingResource {
* @see java.lang.ClassLoader#getResourceAsStream(String)
* @see java.lang.Class#getResourceAsStream(String)
*/
+ @Override
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
@@ -212,6 +213,7 @@ public class ClassPathResource extends AbstractFileResolvingResource {
/**
* This implementation returns a description that includes the class path location.
*/
+ @Override
public String getDescription() {
StringBuilder builder = new StringBuilder("class path resource [");
String pathToUse = path;
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
index 00f267e4..234c073b 100644
--- a/spring-core/src/main/java/org/springframework/core/io/ClassRelativeResourceLoader.java
+++ b/spring-core/src/main/java/org/springframework/core/io/ClassRelativeResourceLoader.java
@@ -30,19 +30,20 @@ import org.springframework.util.StringUtils;
*/
public class ClassRelativeResourceLoader extends DefaultResourceLoader {
- private final Class clazz;
+ private final Class<?> clazz;
/**
* Create a new ClassRelativeResourceLoader for the given class.
* @param clazz the class to load resources through
*/
- public ClassRelativeResourceLoader(Class clazz) {
+ public ClassRelativeResourceLoader(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
this.clazz = clazz;
setClassLoader(clazz.getClassLoader());
}
+ @Override
protected Resource getResourceByPath(String path) {
return new ClassRelativeContextResource(path, this.clazz);
}
@@ -54,13 +55,14 @@ public class ClassRelativeResourceLoader extends DefaultResourceLoader {
*/
private static class ClassRelativeContextResource extends ClassPathResource implements ContextResource {
- private final Class clazz;
+ private final Class<?> clazz;
- public ClassRelativeContextResource(String path, Class clazz) {
+ public ClassRelativeContextResource(String path, Class<?> clazz) {
super(path, clazz);
this.clazz = clazz;
}
+ @Override
public String getPathWithinContext() {
return getPath();
}
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
index 1a390f04..2f22b14c 100644
--- a/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java
+++ b/spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * 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.
@@ -79,14 +79,19 @@ public class DefaultResourceLoader implements ResourceLoader {
* ClassPathResource objects created by this resource loader.
* @see ClassPathResource
*/
+ @Override
public ClassLoader getClassLoader() {
return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}
+ @Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
- if (location.startsWith(CLASSPATH_URL_PREFIX)) {
+ if (location.startsWith("/")) {
+ return getResourceByPath(location);
+ }
+ else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
@@ -122,12 +127,13 @@ public class DefaultResourceLoader implements ResourceLoader {
* ClassPathResource that explicitly expresses a context-relative path
* through implementing the ContextResource interface.
*/
- private static class ClassPathContextResource extends ClassPathResource implements ContextResource {
+ protected static class ClassPathContextResource extends ClassPathResource implements ContextResource {
public ClassPathContextResource(String path, ClassLoader classLoader) {
super(path, classLoader);
}
+ @Override
public String getPathWithinContext() {
return getPath();
}
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
index 8385891e..72021479 100644
--- a/spring-core/src/main/java/org/springframework/core/io/DescriptiveResource.java
+++ b/spring-core/src/main/java/org/springframework/core/io/DescriptiveResource.java
@@ -54,11 +54,13 @@ public class DescriptiveResource extends AbstractResource {
return false;
}
+ @Override
public InputStream getInputStream() throws IOException {
throw new FileNotFoundException(
getDescription() + " cannot be opened because it does not point to a readable resource");
}
+ @Override
public String getDescription() {
return this.description;
}
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
index 05202a3b..8f332385 100644
--- a/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java
+++ b/spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java
@@ -110,6 +110,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
* This implementation opens a FileInputStream for the underlying file.
* @see java.io.FileInputStream
*/
+ @Override
public InputStream getInputStream() throws IOException {
return new FileInputStream(this.file);
}
@@ -173,6 +174,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
* path of the file.
* @see java.io.File#getAbsolutePath()
*/
+ @Override
public String getDescription() {
return "file [" + this.file.getAbsolutePath() + "]";
}
@@ -186,6 +188,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
* @see java.io.File#canWrite()
* @see java.io.File#isDirectory()
*/
+ @Override
public boolean isWritable() {
return (this.file.canWrite() && !this.file.isDirectory());
}
@@ -194,6 +197,7 @@ public class FileSystemResource extends AbstractResource implements WritableReso
* This implementation opens a FileOutputStream for the underlying file.
* @see java.io.FileOutputStream
*/
+ @Override
public OutputStream getOutputStream() throws IOException {
return new FileOutputStream(this.file);
}
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
index d811d91f..7446b71f 100644
--- a/spring-core/src/main/java/org/springframework/core/io/FileSystemResourceLoader.java
+++ b/spring-core/src/main/java/org/springframework/core/io/FileSystemResourceLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2007 the original author or authors.
+ * 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.
@@ -65,6 +65,7 @@ public class FileSystemResourceLoader extends DefaultResourceLoader {
super(path);
}
+ @Override
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
index d9b2405c..3a37cac9 100644
--- a/spring-core/src/main/java/org/springframework/core/io/InputStreamResource.java
+++ b/spring-core/src/main/java/org/springframework/core/io/InputStreamResource.java
@@ -88,6 +88,7 @@ public class InputStreamResource extends AbstractResource {
* This implementation throws IllegalStateException if attempting to
* read the underlying stream multiple times.
*/
+ @Override
public InputStream getInputStream() throws IOException, IllegalStateException {
if (this.read) {
throw new IllegalStateException("InputStream has already been read - " +
@@ -100,6 +101,7 @@ public class InputStreamResource extends AbstractResource {
/**
* This implementation returns the passed-in description, if any.
*/
+ @Override
public String getDescription() {
return this.description;
}
diff --git a/spring-core/src/main/java/org/springframework/core/io/PathResource.java b/spring-core/src/main/java/org/springframework/core/io/PathResource.java
new file mode 100644
index 00000000..5a2c9f93
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/PathResource.java
@@ -0,0 +1,252 @@
+/*
+ * 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.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.springframework.util.Assert;
+
+/**
+ * {@link Resource} implementation for {@code java.nio.file.Path} handles.
+ * Supports resolution as File, and also as URL.
+ * Implements the extended {@link WritableResource} interface.
+ *
+ * @author Philippe Marschall
+ * @since 4.0
+ * @see java.nio.file.Path
+ */
+public class PathResource extends AbstractResource implements WritableResource {
+
+ private final Path path;
+
+
+ /**
+ * Create a new PathResource from a Path handle.
+ * <p>Note: Unlike {@link FileSystemResource}, when building relative resources
+ * via {@link #createRelative}, the relative path will be built <i>underneath</i> the
+ * given root:
+ * e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
+ * @param path a Path handle
+ */
+ public PathResource(Path path) {
+ Assert.notNull(path, "Path must not be null");
+ this.path = path.normalize();
+ }
+
+ /**
+ * Create a new PathResource from a Path handle.
+ * <p>Note: Unlike {@link FileSystemResource}, when building relative resources
+ * via {@link #createRelative}, the relative path will be built <i>underneath</i> the
+ * given root:
+ * e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
+ * @param path a path
+ * @see java.nio.file.Paths#get(String, String...)
+ */
+ public PathResource(String path) {
+ Assert.notNull(path, "Path must not be null");
+ this.path = Paths.get(path).normalize();
+ }
+
+ /**
+ * Create a new PathResource from a Path handle.
+ * <p>Note: Unlike {@link FileSystemResource}, when building relative resources
+ * via {@link #createRelative}, the relative path will be built <i>underneath</i> the
+ * given root:
+ * e.g. Paths.get("C:/dir1/"), relative path "dir2" -> "C:/dir1/dir2"!
+ * @see java.nio.file.Paths#get(URI)
+ * @param uri a path URI
+ */
+ public PathResource(URI uri) {
+ Assert.notNull(uri, "URI must not be null");
+ this.path = Paths.get(uri).normalize();
+ }
+
+
+ /**
+ * Return the file path for this resource.
+ */
+ public final String getPath() {
+ return this.path.toString();
+ }
+
+ /**
+ * This implementation returns whether the underlying file exists.
+ * @see org.springframework.core.io.PathResource#exists()
+ */
+ @Override
+ public boolean exists() {
+ return Files.exists(this.path);
+ }
+
+ /**
+ * 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.nio.file.Files#isReadable(Path)
+ * @see java.nio.file.Files#isDirectory(Path, java.nio.file.LinkOption...)
+ */
+ @Override
+ public boolean isReadable() {
+ return (Files.isReadable(this.path) && !Files.isDirectory(this.path));
+ }
+
+ /**
+ * This implementation opens a InputStream for the underlying file.
+ * @see java.nio.file.spi.FileSystemProvider#newInputStream(Path, OpenOption...)
+ */
+ @Override
+ public InputStream getInputStream() throws IOException {
+ if(!exists()) {
+ throw new FileNotFoundException(getPath() + " (No such file or directory)");
+ }
+ if(Files.isDirectory(this.path)) {
+ throw new FileNotFoundException(getPath() + " (Is a directory)");
+ }
+ return Files.newInputStream(this.path);
+ }
+
+ /**
+ * This implementation returns a URL for the underlying file.
+ * @see java.nio.file.Path#toUri()
+ * @see java.net.URI#toURL()
+ */
+ @Override
+ public URL getURL() throws IOException {
+ return this.path.toUri().toURL();
+ }
+
+ /**
+ * This implementation returns a URI for the underlying file.
+ * @see java.nio.file.Path#toUri()
+ */
+ @Override
+ public URI getURI() throws IOException {
+ return this.path.toUri();
+ }
+
+ /**
+ * This implementation returns the underlying File reference.
+ */
+ @Override
+ public File getFile() throws IOException {
+ try {
+ return this.path.toFile();
+ }
+ catch (UnsupportedOperationException ex) {
+ // only Paths on the default file system can be converted to a File
+ // do exception translation for cases where conversion is not possible
+ throw new FileNotFoundException(this.path + " cannot be resolved to "
+ + "absolute file path");
+ }
+ }
+
+ /**
+ * This implementation returns the underlying File's length.
+ */
+ @Override
+ public long contentLength() throws IOException {
+ return Files.size(this.path);
+ }
+
+ /**
+ * This implementation returns the underlying File's timestamp.
+ * @see java.nio.file.Files#getLastModifiedTime(Path, java.nio.file.LinkOption...)
+ */
+ @Override
+ public long lastModified() throws IOException {
+ // we can not use the super class method since it uses conversion to a File and
+ // only Paths on the default file system can be converted to a File
+ return Files.getLastModifiedTime(path).toMillis();
+ }
+
+ /**
+ * This implementation creates a FileResource, applying the given path
+ * relative to the path of the underlying file of this resource descriptor.
+ * @see java.nio.file.Path#resolve(String)
+ */
+ @Override
+ public Resource createRelative(String relativePath) throws IOException {
+ return new PathResource(this.path.resolve(relativePath));
+ }
+
+ /**
+ * This implementation returns the name of the file.
+ * @see java.nio.file.Path#getFileName()
+ */
+ @Override
+ public String getFilename() {
+ return this.path.getFileName().toString();
+ }
+
+ @Override
+ public String getDescription() {
+ return "path [" + this.path.toAbsolutePath() + "]";
+ }
+
+ // 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.nio.file.Files#isWritable(Path)
+ * @see java.nio.file.Files#isDirectory(Path, java.nio.file.LinkOption...)
+ */
+ @Override
+ public boolean isWritable() {
+ return Files.isWritable(this.path) && !Files.isDirectory(this.path);
+ }
+
+ /**
+ * This implementation opens a OutputStream for the underlying file.
+ * @see java.nio.file.spi.FileSystemProvider#newOutputStream(Path, OpenOption...)
+ */
+ @Override
+ public OutputStream getOutputStream() throws IOException {
+ if(Files.isDirectory(this.path)) {
+ throw new FileNotFoundException(getPath() + " (Is a directory)");
+ }
+ return Files.newOutputStream(this.path);
+ }
+
+
+ /**
+ * This implementation compares the underlying Path references.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return (obj == this ||
+ (obj instanceof PathResource && this.path.equals(((PathResource) obj).path)));
+ }
+
+ /**
+ * This implementation returns the hash code of the underlying Path reference.
+ */
+ @Override
+ public int hashCode() {
+ return this.path.hashCode();
+ }
+
+}
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
index cc6eef30..e3fc2a4e 100644
--- a/spring-core/src/main/java/org/springframework/core/io/Resource.java
+++ b/spring-core/src/main/java/org/springframework/core/io/Resource.java
@@ -42,6 +42,7 @@ import java.net.URL;
* @see UrlResource
* @see ByteArrayResource
* @see InputStreamResource
+ * @see PathResource
*/
public interface Resource extends InputStreamSource {
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
index 626f8f70..77f8b8bf 100644
--- a/spring-core/src/main/java/org/springframework/core/io/UrlResource.java
+++ b/spring-core/src/main/java/org/springframework/core/io/UrlResource.java
@@ -160,6 +160,7 @@ public class UrlResource extends AbstractFileResolvingResource {
* @see java.net.URLConnection#setUseCaches(boolean)
* @see java.net.URLConnection#getInputStream()
*/
+ @Override
public InputStream getInputStream() throws IOException {
URLConnection con = this.url.openConnection();
ResourceUtils.useCachesIfNecessary(con);
@@ -238,6 +239,7 @@ public class UrlResource extends AbstractFileResolvingResource {
/**
* This implementation returns a description that includes the URL.
*/
+ @Override
public String getDescription() {
return "URL [" + this.url + "]";
}
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
index 4877d0ad..a0ab31fe 100644
--- a/spring-core/src/main/java/org/springframework/core/io/VfsResource.java
+++ b/spring-core/src/main/java/org/springframework/core/io/VfsResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * 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.
@@ -26,8 +26,11 @@ 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.
+ * JBoss VFS based {@link Resource} implementation.
+ *
+ * <p>As of Spring 4.0, this class supports VFS 3.x on JBoss AS 6+ (package
+ * {@code org.jboss.vfs}) and is in particular compatible with JBoss AS 7 and
+ * WildFly 8.
*
* @author Ales Justin
* @author Juergen Hoeller
@@ -40,12 +43,13 @@ 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 VfsResource(Object resource) {
+ Assert.notNull(resource, "VirtualFile must not be null");
+ this.resource = resource;
}
+ @Override
public InputStream getInputStream() throws IOException {
return VfsUtils.getInputStream(this.resource);
}
@@ -114,6 +118,7 @@ public class VfsResource extends AbstractResource {
return VfsUtils.getName(this.resource);
}
+ @Override
public String getDescription() {
return this.resource.toString();
}
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
index 8c9a7aa1..9291cee6 100644
--- a/spring-core/src/main/java/org/springframework/core/io/VfsUtils.java
+++ b/spring-core/src/main/java/org/springframework/core/io/VfsUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * 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.
@@ -25,36 +25,27 @@ 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}).
+ * Utility for detecting and accessing JBoss VFS in the classpath.
*
- * <p>Thanks go to Marius Bogoevici for the initial patch.
+ * <p>As of Spring 4.0, this class supports VFS 3.x on JBoss AS 6+ (package
+ * {@code org.jboss.vfs}) and is in particular compatible with JBoss AS 7 and
+ * WildFly 8.
*
+ * <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
+ * @author Juergen Hoeller
* @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;
@@ -71,54 +62,17 @@ public abstract class VfsUtils {
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");
+ Class<?> vfsClass = loader.loadClass(VFS3_PKG + VFS_NAME);
+ VFS_METHOD_GET_ROOT_URL = ReflectionUtils.findMethod(vfsClass, "getChild", URL.class);
+ VFS_METHOD_GET_ROOT_URI = ReflectionUtils.findMethod(vfsClass, "getChild", URI.class);
+ Class<?> virtualFile = loader.loadClass(VFS3_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");
@@ -128,25 +82,16 @@ public abstract class VfsUtils {
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");
+ VIRTUAL_FILE_METHOD_GET_CHILD = ReflectionUtils.findMethod(virtualFile, "getChild", String.class);
- 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_VISITOR_INTERFACE = loader.loadClass(VFS3_PKG + "VirtualFileVisitor");
VIRTUAL_FILE_METHOD_VISIT = ReflectionUtils.findMethod(virtualFile, "visit", VIRTUAL_FILE_VISITOR_INTERFACE);
- Class<?> visitorAttributesClass = loader.loadClass(pkg + "VisitorAttributes");
+ Class<?> visitorAttributesClass = loader.loadClass(VFS3_PKG + "VisitorAttributes");
VISITOR_ATTRIBUTES_FIELD_RECURSE = ReflectionUtils.findField(visitorAttributesClass, "RECURSE");
}
catch (ClassNotFoundException ex) {
- throw new IllegalStateException("Could not detect the JBoss VFS infrastructure", ex);
+ throw new IllegalStateException("Could not detect JBoss VFS infrastructure", ex);
}
}
@@ -224,20 +169,7 @@ public abstract class VfsUtils {
}
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);
- }
+ return (File) invokeVfsMethod(GET_PHYSICAL_FILE, vfsResource);
}
static Object getRoot(URI url) throws IOException {
@@ -257,4 +189,5 @@ public abstract class VfsUtils {
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/support/EncodedResource.java b/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java
index 04a17383..59643dd6 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * 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.
@@ -31,7 +31,7 @@ import org.springframework.util.ObjectUtils;
* 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}.
+ * a specific encoding (usually through a {@code java.io.Reader}).
*
* @author Juergen Hoeller
* @since 1.2.6
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
index 72b0c481..1f54f638 100644
--- 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
@@ -233,6 +233,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
return this.resourceLoader;
}
+ @Override
public ClassLoader getClassLoader() {
return getResourceLoader().getClassLoader();
}
@@ -255,10 +256,12 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
}
+ @Override
public Resource getResource(String location) {
return getResourceLoader().getResource(location);
}
+ @Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
@@ -678,6 +681,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
this.rootPath = (rootPath.length() == 0 || rootPath.endsWith("/") ? rootPath : rootPath + "/");
}
+ @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (Object.class.equals(method.getDeclaringClass())) {
@@ -722,6 +726,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
return this.resources.size();
}
+ @Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("sub-pattern: ").append(this.subPattern);
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
index d0795545..cec785b3 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * 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.
@@ -17,6 +17,7 @@
package org.springframework.core.io.support;
import java.io.IOException;
+import java.util.Map;
import java.util.Properties;
import org.springframework.core.env.PropertiesPropertySource;
@@ -36,6 +37,8 @@ import org.springframework.util.StringUtils;
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.1
+ * @see org.springframework.core.io.Resource
+ * @see org.springframework.core.io.support.EncodedResource
*/
public class ResourcePropertySource extends PropertiesPropertySource {
@@ -112,10 +115,27 @@ public class ResourcePropertySource extends PropertiesPropertySource {
this(new DefaultResourceLoader().getResource(location));
}
+ private ResourcePropertySource(String name, Map<String, Object> source) {
+ super(name, source);
+ }
+
+
+ /**
+ * Return a potentially adapted variant of this {@link ResourcePropertySource},
+ * overriding the previously given (or derived) name with the specified name.
+ */
+ public ResourcePropertySource withName(String name) {
+ if (this.name.equals(name)) {
+ return this;
+ }
+ return new ResourcePropertySource(name, this.source);
+ }
+
/**
- * Return the description string for the resource, and if empty returns
- * the class name of the resource plus its identity hash code.
+ * Return the description String for the given Resource; it the description is
+ * empty, return the class name of the resource plus its identity hash code.
+ * @see org.springframework.core.io.Resource#getDescription()
*/
private static String getNameForResource(Resource resource) {
String name = resource.getDescription();
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
index f6e523ea..113970dc 100644
--- 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
@@ -45,8 +45,9 @@ abstract class VfsPatternUtils extends VfsUtils {
}
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);
+ 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/serializer/DefaultDeserializer.java b/spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java
index f61f697d..66a91b38 100644
--- a/spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java
+++ b/spring-core/src/main/java/org/springframework/core/serializer/DefaultDeserializer.java
@@ -34,6 +34,7 @@ public class DefaultDeserializer implements Deserializer<Object> {
/**
* Reads the input stream and deserializes into an object.
*/
+ @Override
public Object deserialize(InputStream inputStream) throws IOException {
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
try {
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
index 04fe8bed..6146ee01 100644
--- a/spring-core/src/main/java/org/springframework/core/serializer/DefaultSerializer.java
+++ b/spring-core/src/main/java/org/springframework/core/serializer/DefaultSerializer.java
@@ -34,6 +34,7 @@ public class DefaultSerializer implements Serializer<Object> {
* Writes the source object to an output stream using Java Serialization.
* The source object must implement {@link Serializable}.
*/
+ @Override
public void serialize(Object object, OutputStream outputStream) throws IOException {
if (!(object instanceof Serializable)) {
throw new IllegalArgumentException(getClass().getSimpleName() + " requires a Serializable payload " +
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
index 20020611..074acf7b 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * 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.
@@ -52,6 +52,7 @@ public class DeserializingConverter implements Converter<byte[], Object> {
}
+ @Override
public Object convert(byte[] source) {
ByteArrayInputStream byteStream = new ByteArrayInputStream(source);
try {
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
index b9909f61..1a35c6a2 100644
--- 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
@@ -55,6 +55,7 @@ public class SerializingConverter implements Converter<Object, byte[]> {
/**
* Serializes the source object and returns the byte array result.
*/
+ @Override
public byte[] convert(Object source) {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream(256);
try {
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
index f81cb4a8..97a6ce23 100644
--- a/spring-core/src/main/java/org/springframework/core/style/DefaultToStringStyler.java
+++ b/spring-core/src/main/java/org/springframework/core/style/DefaultToStringStyler.java
@@ -52,6 +52,7 @@ public class DefaultToStringStyler implements ToStringStyler {
}
+ @Override
public void styleStart(StringBuilder buffer, Object obj) {
if (!obj.getClass().isArray()) {
buffer.append('[').append(ClassUtils.getShortName(obj.getClass()));
@@ -70,10 +71,12 @@ public class DefaultToStringStyler implements ToStringStyler {
buffer.append(ObjectUtils.getIdentityHexString(obj));
}
+ @Override
public void styleEnd(StringBuilder buffer, Object o) {
buffer.append(']');
}
+ @Override
public void styleField(StringBuilder buffer, String fieldName, Object value) {
styleFieldStart(buffer, fieldName);
styleValue(buffer, value);
@@ -87,10 +90,12 @@ public class DefaultToStringStyler implements ToStringStyler {
protected void styleFieldEnd(StringBuilder buffer, String fieldName) {
}
+ @Override
public void styleValue(StringBuilder buffer, Object value) {
buffer.append(this.valueStyler.style(value));
}
+ @Override
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
index 2e6251c0..e32986f6 100644
--- a/spring-core/src/main/java/org/springframework/core/style/DefaultValueStyler.java
+++ b/spring-core/src/main/java/org/springframework/core/style/DefaultValueStyler.java
@@ -48,6 +48,7 @@ public class DefaultValueStyler implements ValueStyler {
private static final String ARRAY = "array";
+ @Override
public String style(Object value) {
if (value == null) {
return NULL;
@@ -56,20 +57,20 @@ public class DefaultValueStyler implements ValueStyler {
return "\'" + value + "\'";
}
else if (value instanceof Class) {
- return ClassUtils.getShortName((Class) value);
+ 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);
+ return style((Map<?, ?>) value);
}
else if (value instanceof Map.Entry) {
- return style((Map.Entry) value);
+ return style((Map.Entry<? ,?>) value);
}
else if (value instanceof Collection) {
- return style((Collection) value);
+ return style((Collection<?>) value);
}
else if (value.getClass().isArray()) {
return styleArray(ObjectUtils.toObjectArray(value));
@@ -79,11 +80,11 @@ public class DefaultValueStyler implements ValueStyler {
}
}
- private String style(Map value) {
+ private <K, V> String style(Map<K, V> 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();
+ for (Iterator<Map.Entry<K, V>> it = value.entrySet().iterator(); it.hasNext();) {
+ Map.Entry<K, V> entry = it.next();
result.append(style(entry));
if (it.hasNext()) {
result.append(',').append(' ');
@@ -96,14 +97,14 @@ public class DefaultValueStyler implements ValueStyler {
return result.toString();
}
- private String style(Map.Entry value) {
+ private String style(Map.Entry<?, ?> value) {
return style(value.getKey()) + " -> " + style(value.getValue());
}
- private String style(Collection value) {
+ 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();) {
+ for (Iterator<?> i = value.iterator(); i.hasNext();) {
result.append(style(i.next()));
if (i.hasNext()) {
result.append(',').append(' ');
@@ -116,7 +117,7 @@ public class DefaultValueStyler implements ValueStyler {
return result.toString();
}
- private String getCollectionTypeString(Collection value) {
+ private String getCollectionTypeString(Collection<?> value) {
if (value instanceof List) {
return LIST;
}
diff --git a/spring-core/src/main/java/org/springframework/core/task/AsyncListenableTaskExecutor.java b/spring-core/src/main/java/org/springframework/core/task/AsyncListenableTaskExecutor.java
new file mode 100644
index 00000000..6adaad38
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/task/AsyncListenableTaskExecutor.java
@@ -0,0 +1,52 @@
+/*
+ * 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 org.springframework.util.concurrent.ListenableFuture;
+
+/**
+ * Extension of the {@link AsyncTaskExecutor} interface, adding the capability to submit
+ * tasks for {@link ListenableFuture}s.
+ *
+ * @author Arjen Poutsma
+ * @since 4.0
+ * @see ListenableFuture
+ */
+public interface AsyncListenableTaskExecutor extends AsyncTaskExecutor {
+
+ /**
+ * Submit a {@code Runnable} task for execution, receiving a {@code ListenableFuture}
+ * 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 {@code ListenableFuture} representing pending completion of the task
+ * @throws TaskRejectedException if the given task was not accepted
+ */
+ ListenableFuture<?> submitListenable(Runnable task);
+
+ /**
+ * Submit a {@code Callable} task for execution, receiving a {@code ListenableFuture}
+ * 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 {@code ListenableFuture} representing pending completion of the task
+ * @throws TaskRejectedException if the given task was not accepted
+ */
+ <T> ListenableFuture<T> submitListenable(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
index f6b71400..05e759ab 100644
--- a/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java
+++ b/spring-core/src/main/java/org/springframework/core/task/SimpleAsyncTaskExecutor.java
@@ -25,6 +25,8 @@ import java.util.concurrent.ThreadFactory;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrencyThrottleSupport;
import org.springframework.util.CustomizableThreadCreator;
+import org.springframework.util.concurrent.ListenableFuture;
+import org.springframework.util.concurrent.ListenableFutureTask;
/**
* {@link TaskExecutor} implementation that fires up a new Thread for each task,
@@ -45,7 +47,7 @@ import org.springframework.util.CustomizableThreadCreator;
* @see org.springframework.scheduling.commonj.WorkManagerTaskExecutor
*/
@SuppressWarnings("serial")
-public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implements AsyncTaskExecutor, Serializable {
+public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implements AsyncListenableTaskExecutor, Serializable {
/**
* Permit any number of concurrent invocations: that is, don't throttle concurrency.
@@ -144,6 +146,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implement
* if configured (through the superclass's settings).
* @see #doExecute(Runnable)
*/
+ @Override
public void execute(Runnable task) {
execute(task, TIMEOUT_INDEFINITE);
}
@@ -157,6 +160,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implement
* @see #TIMEOUT_IMMEDIATE
* @see #doExecute(Runnable)
*/
+ @Override
public void execute(Runnable task, long startTimeout) {
Assert.notNull(task, "Runnable must not be null");
if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
@@ -168,18 +172,34 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implement
}
}
+ @Override
public Future<?> submit(Runnable task) {
FutureTask<Object> future = new FutureTask<Object>(task, null);
execute(future, TIMEOUT_INDEFINITE);
return future;
}
+ @Override
public <T> Future<T> submit(Callable<T> task) {
FutureTask<T> future = new FutureTask<T>(task);
execute(future, TIMEOUT_INDEFINITE);
return future;
}
+ @Override
+ public ListenableFuture<?> submitListenable(Runnable task) {
+ ListenableFutureTask<Object> future = new ListenableFutureTask<Object>(task, null);
+ execute(future, TIMEOUT_INDEFINITE);
+ return future;
+ }
+
+ @Override
+ public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
+ ListenableFutureTask<T> future = new ListenableFutureTask<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.
@@ -225,6 +245,7 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implement
this.target = target;
}
+ @Override
public void run() {
try {
this.target.run();
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
index 47bebf46..64f8c793 100644
--- a/spring-core/src/main/java/org/springframework/core/task/SyncTaskExecutor.java
+++ b/spring-core/src/main/java/org/springframework/core/task/SyncTaskExecutor.java
@@ -44,6 +44,7 @@ public class SyncTaskExecutor implements TaskExecutor, Serializable {
* invocation of it's {@link Runnable#run() run()} method.
* @throws IllegalArgumentException if the given {@code task} is {@code null}
*/
+ @Override
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
index fea960e3..bf8b9f91 100644
--- a/spring-core/src/main/java/org/springframework/core/task/TaskExecutor.java
+++ b/spring-core/src/main/java/org/springframework/core/task/TaskExecutor.java
@@ -45,6 +45,7 @@ public interface TaskExecutor extends Executor {
* @param task the {@code Runnable} to execute (never {@code null})
* @throws TaskRejectedException if the given task was not accepted
*/
+ @Override
void execute(Runnable 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
index 2acf1a28..8f9f09ed 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * 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.
@@ -50,6 +50,7 @@ public class ConcurrentExecutorAdapter implements Executor {
}
+ @Override
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
index 0c594b93..393ecee6 100644
--- 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
@@ -57,29 +57,35 @@ public class ExecutorServiceAdapter extends AbstractExecutorService {
}
+ @Override
public void execute(Runnable task) {
this.taskExecutor.execute(task);
}
+ @Override
public void shutdown() {
throw new IllegalStateException(
"Manual shutdown not supported - ExecutorServiceAdapter is dependent on an external lifecycle");
}
+ @Override
public List<Runnable> shutdownNow() {
throw new IllegalStateException(
"Manual shutdown not supported - ExecutorServiceAdapter is dependent on an external lifecycle");
}
+ @Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
throw new IllegalStateException(
"Manual shutdown not supported - ExecutorServiceAdapter is dependent on an external lifecycle");
}
+ @Override
public boolean isShutdown() {
return false;
}
+ @Override
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
index f980c4d4..fad9ae09 100644
--- 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
@@ -23,9 +23,11 @@ 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.AsyncListenableTaskExecutor;
import org.springframework.core.task.TaskRejectedException;
import org.springframework.util.Assert;
+import org.springframework.util.concurrent.ListenableFuture;
+import org.springframework.util.concurrent.ListenableFutureTask;
/**
* Adapter that takes a JDK {@code java.util.concurrent.Executor} and
@@ -39,7 +41,7 @@ import org.springframework.util.Assert;
* @see java.util.concurrent.ExecutorService
* @see java.util.concurrent.Executors
*/
-public class TaskExecutorAdapter implements AsyncTaskExecutor {
+public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {
private final Executor concurrentExecutor;
@@ -59,6 +61,7 @@ public class TaskExecutorAdapter implements AsyncTaskExecutor {
* Delegates to the specified JDK concurrent executor.
* @see java.util.concurrent.Executor#execute(Runnable)
*/
+ @Override
public void execute(Runnable task) {
try {
this.concurrentExecutor.execute(task);
@@ -69,10 +72,12 @@ public class TaskExecutorAdapter implements AsyncTaskExecutor {
}
}
+ @Override
public void execute(Runnable task, long startTimeout) {
execute(task);
}
+ @Override
public Future<?> submit(Runnable task) {
try {
if (this.concurrentExecutor instanceof ExecutorService) {
@@ -90,6 +95,7 @@ public class TaskExecutorAdapter implements AsyncTaskExecutor {
}
}
+ @Override
public <T> Future<T> submit(Callable<T> task) {
try {
if (this.concurrentExecutor instanceof ExecutorService) {
@@ -107,4 +113,30 @@ public class TaskExecutorAdapter implements AsyncTaskExecutor {
}
}
+ @Override
+ public ListenableFuture<?> submitListenable(Runnable task) {
+ try {
+ ListenableFutureTask<Object> future = new ListenableFutureTask<Object>(task, null);
+ this.concurrentExecutor.execute(future);
+ return future;
+ }
+ catch (RejectedExecutionException ex) {
+ throw new TaskRejectedException(
+ "Executor [" + this.concurrentExecutor + "] did not accept task: " + task, ex);
+ }
+ }
+
+ @Override
+ public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
+ try {
+ ListenableFutureTask<T> future = new ListenableFutureTask<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/type/AnnotatedTypeMetadata.java b/spring-core/src/main/java/org/springframework/core/type/AnnotatedTypeMetadata.java
new file mode 100644
index 00000000..be7e31d2
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/AnnotatedTypeMetadata.java
@@ -0,0 +1,100 @@
+/*
+ * 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.util.Map;
+
+import org.springframework.util.MultiValueMap;
+
+/**
+ * Defines access to the annotations of a specific type ({@link AnnotationMetadata class}
+ * or {@link MethodMetadata method}), in a form that does not necessarily require the
+ * class-loading.
+ *
+ * @author Juergen Hoeller
+ * @author Mark Fisher
+ * @author Mark Pollack
+ * @author Chris Beams
+ * @author Phillip Webb
+ * @author Sam Brannen
+ * @since 4.0
+ * @see AnnotationMetadata
+ * @see MethodMetadata
+ */
+public interface AnnotatedTypeMetadata {
+
+ /**
+ * Determine whether the underlying element has an annotation or meta-annotation
+ * of the given type defined.
+ * <p>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 element, as direct annotation or meta-annotation),
+ * also taking attribute overrides on composed annotations into account.
+ * @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 element, as direct annotation or meta-annotation),
+ * also taking attribute overrides on composed annotations into account.
+ * @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);
+
+ /**
+ * Retrieve all attributes of all annotations of the given type, if any (i.e. if
+ * defined on the underlying element, as direct annotation or meta-annotation).
+ * Note that this variant does <i>not</i> take attribute overrides into account.
+ * @param annotationType the annotation type to look for
+ * @return a MultiMap of attributes, with the attribute name as key (e.g. "value")
+ * and a list of the defined attribute values as Map value. This return value will
+ * be {@code null} if no matching annotation is defined.
+ * @see #getAllAnnotationAttributes(String, boolean)
+ */
+ MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType);
+
+ /**
+ * Retrieve all attributes of all annotations of the given type, if any (i.e. if
+ * defined on the underlying element, as direct annotation or meta-annotation).
+ * Note that this variant does <i>not</i> take attribute overrides into account.
+ * @param annotationType the annotation type to look for
+ * @param classValuesAsString whether to convert class references to String
+ * @return a MultiMap of attributes, with the attribute name as key (e.g. "value")
+ * and a list of the defined attribute values as Map value. This return value will
+ * be {@code null} if no matching annotation is defined.
+ * @see #getAllAnnotationAttributes(String)
+ */
+ MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString);
+
+}
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
index beeb7d93..8c2ef4ca 100644
--- a/spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java
+++ b/spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * 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.
@@ -16,7 +16,6 @@
package org.springframework.core.type;
-import java.util.Map;
import java.util.Set;
/**
@@ -25,79 +24,47 @@ import java.util.Set;
*
* @author Juergen Hoeller
* @author Mark Fisher
+ * @author Phillip Webb
+ * @author Sam Brannen
* @since 2.5
* @see StandardAnnotationMetadata
* @see org.springframework.core.type.classreading.MetadataReader#getAnnotationMetadata()
+ * @see AnnotatedTypeMetadata
*/
-public interface AnnotationMetadata extends ClassMetadata {
+public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata {
/**
- * Return the names of all annotation types defined on the underlying class.
+ * Return the names of all annotation types that are <em>present</em> 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.
+ * Return the names of all meta-annotation types <em>present</em> on the
+ * given annotation type on 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.
+ * Determine whether an annotation of the given type is <em>present</em> on
+ * the underlying class.
* @param annotationType the annotation type to look for
- * @return whether a matching annotation is defined
+ * @return whether a matching annotation is present
*/
boolean hasAnnotation(String annotationType);
/**
- * Determine whether the underlying class has an annotation that
- * is itself annotated with the meta-annotation of the given type.
+ * 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
+ * @return whether a matching meta-annotation is present
*/
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.
*/
@@ -109,7 +76,7 @@ public interface AnnotationMetadata extends ClassMetadata {
* <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
+ * @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.
*/
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
index 17d7bab2..585965ca 100644
--- a/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java
+++ b/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * 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.
@@ -16,8 +16,6 @@
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.
@@ -25,11 +23,13 @@ import java.util.Map;
* @author Juergen Hoeller
* @author Mark Pollack
* @author Chris Beams
+ * @author Phillip Webb
* @since 3.0
* @see StandardMethodMetadata
* @see AnnotationMetadata#getAnnotatedMethods
+ * @see AnnotatedTypeMetadata
*/
-public interface MethodMetadata {
+public interface MethodMetadata extends AnnotatedTypeMetadata {
/**
* Return the name of the method.
@@ -57,23 +57,4 @@ public interface MethodMetadata {
*/
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
index 1a4e5365..cc2a86ce 100644
--- a/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java
+++ b/spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java
@@ -22,8 +22,8 @@ import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
-import org.springframework.core.annotation.AnnotationAttributes;
-import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.util.MultiValueMap;
/**
* {@link AnnotationMetadata} implementation that uses standard reflection
@@ -32,6 +32,7 @@ import org.springframework.core.annotation.AnnotationUtils;
* @author Juergen Hoeller
* @author Mark Fisher
* @author Chris Beams
+ * @author Phillip Webb
* @since 2.5
*/
public class StandardAnnotationMetadata extends StandardClassMetadata implements AnnotationMetadata {
@@ -51,11 +52,12 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
/**
* 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
+ * form of {@link org.springframework.core.annotation.AnnotationAttributes} instead
+ * of actual {@link Annotation} instances.
+ * @param introspectedClass the Class to introspect
* @param nestedAnnotationsAsMap return nested annotations and annotation arrays as
- * {@link AnnotationAttributes} for compatibility with ASM-based
- * {@link AnnotationMetadata} implementations
+ * {@link org.springframework.core.annotation.AnnotationAttributes} for compatibility
+ * with ASM-based {@link AnnotationMetadata} implementations
* @since 3.1.1
*/
public StandardAnnotationMetadata(Class<?> introspectedClass, boolean nestedAnnotationsAsMap) {
@@ -64,6 +66,7 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
}
+ @Override
public Set<String> getAnnotationTypes() {
Set<String> types = new LinkedHashSet<String>();
Annotation[] anns = getIntrospectedClass().getAnnotations();
@@ -73,24 +76,12 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
return types;
}
+ @Override
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;
+ return AnnotatedElementUtils.getMetaAnnotationTypes(getIntrospectedClass(), annotationType);
}
+ @Override
public boolean hasAnnotation(String annotationType) {
Annotation[] anns = getIntrospectedClass().getAnnotations();
for (Annotation ann : anns) {
@@ -101,102 +92,56 @@ public class StandardAnnotationMetadata extends StandardClassMetadata implements
return false;
}
+ @Override
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;
+ return AnnotatedElementUtils.hasMetaAnnotationTypes(getIntrospectedClass(), annotationType);
}
+ @Override
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;
+ return AnnotatedElementUtils.isAnnotated(getIntrospectedClass(), annotationType);
}
+ @Override
public Map<String, Object> getAnnotationAttributes(String annotationType) {
return this.getAnnotationAttributes(annotationType, false);
}
+ @Override
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;
+ return AnnotatedElementUtils.getAnnotationAttributes(getIntrospectedClass(),
+ annotationType, classValuesAsString, this.nestedAnnotationsAsMap);
+ }
+
+ @Override
+ public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType) {
+ return getAllAnnotationAttributes(annotationType, false);
+ }
+
+ @Override
+ public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString) {
+ return AnnotatedElementUtils.getAllAnnotationAttributes(getIntrospectedClass(),
+ annotationType, classValuesAsString, this.nestedAnnotationsAsMap);
}
+ @Override
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;
- }
- }
- }
- }
+ if (!method.isBridge() && AnnotatedElementUtils.isAnnotated(method, annotationType)) {
+ return true;
}
}
return false;
}
+ @Override
public Set<MethodMetadata> getAnnotatedMethods(String annotationType) {
- Set<MethodMetadata> annotatedMethods = new LinkedHashSet<MethodMetadata>();
Method[] methods = getIntrospectedClass().getDeclaredMethods();
+ Set<MethodMetadata> annotatedMethods = new LinkedHashSet<MethodMetadata>();
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;
- }
- }
- }
- }
+ if (!method.isBridge() && AnnotatedElementUtils.isAnnotated(method, annotationType)) {
+ annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap));
}
}
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
index 4ac99d4d..1c56f4ff 100644
--- a/spring-core/src/main/java/org/springframework/core/type/StandardClassMetadata.java
+++ b/spring-core/src/main/java/org/springframework/core/type/StandardClassMetadata.java
@@ -30,14 +30,14 @@ import org.springframework.util.Assert;
*/
public class StandardClassMetadata implements ClassMetadata {
- private final Class introspectedClass;
+ private final Class<?> introspectedClass;
/**
* Create a new StandardClassMetadata wrapper for the given Class.
* @param introspectedClass the Class to introspect
*/
- public StandardClassMetadata(Class introspectedClass) {
+ public StandardClassMetadata(Class<?> introspectedClass) {
Assert.notNull(introspectedClass, "Class must not be null");
this.introspectedClass = introspectedClass;
}
@@ -45,57 +45,68 @@ public class StandardClassMetadata implements ClassMetadata {
/**
* Return the underlying Class.
*/
- public final Class getIntrospectedClass() {
+ public final Class<?> getIntrospectedClass() {
return this.introspectedClass;
}
+ @Override
public String getClassName() {
return this.introspectedClass.getName();
}
+ @Override
public boolean isInterface() {
return this.introspectedClass.isInterface();
}
+ @Override
public boolean isAbstract() {
return Modifier.isAbstract(this.introspectedClass.getModifiers());
}
+ @Override
public boolean isConcrete() {
return !(isInterface() || isAbstract());
}
+ @Override
public boolean isFinal() {
return Modifier.isFinal(this.introspectedClass.getModifiers());
}
+ @Override
public boolean isIndependent() {
return (!hasEnclosingClass() ||
(this.introspectedClass.getDeclaringClass() != null &&
Modifier.isStatic(this.introspectedClass.getModifiers())));
}
+ @Override
public boolean hasEnclosingClass() {
return (this.introspectedClass.getEnclosingClass() != null);
}
+ @Override
public String getEnclosingClassName() {
- Class enclosingClass = this.introspectedClass.getEnclosingClass();
+ Class<?> enclosingClass = this.introspectedClass.getEnclosingClass();
return (enclosingClass != null ? enclosingClass.getName() : null);
}
+ @Override
public boolean hasSuperClass() {
return (this.introspectedClass.getSuperclass() != null);
}
+ @Override
public String getSuperClassName() {
- Class superClass = this.introspectedClass.getSuperclass();
+ Class<?> superClass = this.introspectedClass.getSuperclass();
return (superClass != null ? superClass.getName() : null);
}
+ @Override
public String[] getInterfaceNames() {
- Class[] ifcs = this.introspectedClass.getInterfaces();
+ Class<?>[] ifcs = this.introspectedClass.getInterfaces();
String[] ifcNames = new String[ifcs.length];
for (int i = 0; i < ifcs.length; i++) {
ifcNames[i] = ifcs[i].getName();
@@ -103,6 +114,7 @@ public class StandardClassMetadata implements ClassMetadata {
return ifcNames;
}
+ @Override
public String[] getMemberClassNames() {
LinkedHashSet<String> memberClassNames = new LinkedHashSet<String>();
for (Class<?> nestedClass : this.introspectedClass.getDeclaredClasses()) {
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
index 8288debe..6899ed7b 100644
--- a/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java
+++ b/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java
@@ -16,13 +16,13 @@
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.core.annotation.AnnotatedElementUtils;
import org.springframework.util.Assert;
+import org.springframework.util.MultiValueMap;
/**
* {@link MethodMetadata} implementation that uses standard reflection
@@ -31,6 +31,7 @@ import org.springframework.util.Assert;
* @author Juergen Hoeller
* @author Mark Pollack
* @author Chris Beams
+ * @author Phillip Webb
* @since 3.0
*/
public class StandardMethodMetadata implements MethodMetadata {
@@ -73,56 +74,56 @@ public class StandardMethodMetadata implements MethodMetadata {
}
+ @Override
public String getMethodName() {
return this.introspectedMethod.getName();
}
+ @Override
public String getDeclaringClassName() {
return this.introspectedMethod.getDeclaringClass().getName();
}
+ @Override
public boolean isStatic() {
return Modifier.isStatic(this.introspectedMethod.getModifiers());
}
+ @Override
public boolean isFinal() {
return Modifier.isFinal(this.introspectedMethod.getModifiers());
}
+ @Override
public boolean isOverridable() {
return (!isStatic() && !isFinal() && !Modifier.isPrivate(this.introspectedMethod.getModifiers()));
}
+ @Override
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;
+ return AnnotatedElementUtils.isAnnotated(this.introspectedMethod, annotationType);
}
+ @Override
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;
+ return getAnnotationAttributes(annotationType, false);
+ }
+
+ @Override
+ public Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString) {
+ return AnnotatedElementUtils.getAnnotationAttributes(this.introspectedMethod,
+ annotationType, classValuesAsString, this.nestedAnnotationsAsMap);
+ }
+
+ @Override
+ public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType) {
+ return getAllAnnotationAttributes(annotationType, false);
+ }
+
+ @Override
+ public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString) {
+ return AnnotatedElementUtils.getAllAnnotationAttributes(this.introspectedMethod,
+ annotationType, classValuesAsString, this.nestedAnnotationsAsMap);
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AbstractRecursiveAnnotationVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AbstractRecursiveAnnotationVisitor.java
new file mode 100644
index 00000000..2cc12717
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AbstractRecursiveAnnotationVisitor.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.type.classreading;
+
+import java.lang.reflect.Field;
+
+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.util.ReflectionUtils;
+
+/**
+ * @author Chris Beams
+ * @author Juergen Hoeller
+ * @author Phillip Webb
+ * @author Sam Brannen
+ * @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;
+ }
+
+
+ @Override
+ public void visit(String attributeName, Object attributeValue) {
+ this.attributes.put(attributeName, attributeValue);
+ }
+
+ @Override
+ 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);
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String attributeName) {
+ return new RecursiveAnnotationArrayVisitor(attributeName, this.attributes, this.classLoader);
+ }
+
+ @Override
+ 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;
+ }
+
+}
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
index 4e9bb8fa..6937e811 100644
--- 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
@@ -17,202 +17,16 @@
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.MultiValueMap;
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
@@ -224,19 +38,22 @@ class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVi
*
* @author Juergen Hoeller
* @author Chris Beams
+ * @author Phillip Webb
+ * @author Sam Brannen
* @since 3.0
*/
final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor {
private final String annotationType;
- private final Map<String, AnnotationAttributes> attributesMap;
+ private final MultiValueMap<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) {
+
+ public AnnotationAttributesReadingVisitor(String annotationType,
+ MultiValueMap<String, AnnotationAttributes> attributesMap, Map<String, Set<String>> metaAnnotationMap,
+ ClassLoader classLoader) {
super(annotationType, new AnnotationAttributes(), classLoader);
this.annotationType = annotationType;
@@ -244,27 +61,23 @@ final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttrib
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.
+ List<AnnotationAttributes> attributes = this.attributesMap.get(this.annotationType);
+ if (attributes == null) {
+ this.attributesMap.add(this.annotationType, this.attributes);
+ }
+ else {
+ attributes.add(0, this.attributes);
+ }
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());
+ Annotation[] metaAnnotations = AnnotationUtils.getAnnotations(annotationClass);
+ if (!ObjectUtils.isEmpty(metaAnnotations)) {
+ for (Annotation metaAnnotation : metaAnnotations) {
+ if (!AnnotationUtils.isInJavaLangAnnotationPackage(metaAnnotation)) {
+ recursivelyCollectMetaAnnotations(metaAnnotationTypeNames, metaAnnotation);
}
}
}
@@ -273,4 +86,19 @@ final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttrib
}
}
+ private void recursivelyCollectMetaAnnotations(Set<String> visited, Annotation annotation) {
+ String annotationName = annotation.annotationType().getName();
+ if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation) && visited.add(annotationName)) {
+ // 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(annotation.annotationType().getModifiers())) {
+ this.attributesMap.add(annotationName, AnnotationUtils.getAnnotationAttributes(annotation, false, true));
+ for (Annotation metaMetaAnnotation : annotation.annotationType().getAnnotations()) {
+ recursivelyCollectMetaAnnotations(visited, metaMetaAnnotation);
+ }
+ }
+ }
+ }
+
}
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
index e1739846..62ceb04d 100644
--- 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
@@ -19,6 +19,7 @@ package org.springframework.core.type.classreading;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -29,6 +30,8 @@ import org.springframework.asm.Type;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
/**
* ASM class visitor which looks for the class name and implemented types as
@@ -38,19 +41,26 @@ import org.springframework.core.type.MethodMetadata;
* @author Juergen Hoeller
* @author Mark Fisher
* @author Costin Leau
+ * @author Phillip Webb
+ * @author Sam Brannen
* @since 2.5
*/
-final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata {
+public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata {
- private final ClassLoader classLoader;
+ protected final ClassLoader classLoader;
- private final Set<String> annotationSet = new LinkedHashSet<String>(4);
+ protected final Set<String> annotationSet = new LinkedHashSet<String>(4);
- private final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<String, Set<String>>(4);
+ protected final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<String, Set<String>>(4);
- private final Map<String, AnnotationAttributes> attributeMap = new LinkedHashMap<String, AnnotationAttributes>(4);
+ /**
+ * Declared as a {@link LinkedMultiValueMap} instead of a {@link MultiValueMap}
+ * to ensure that the hierarchical ordering of the entries is preserved.
+ * @see AnnotationReadingVisitorUtils#getMergedAnnotationAttributes
+ */
+ protected final LinkedMultiValueMap<String, AnnotationAttributes> attributesMap = new LinkedMultiValueMap<String, AnnotationAttributes>(4);
- private final Set<MethodMetadata> methodMetadataSet = new LinkedHashSet<MethodMetadata>(4);
+ protected final Set<MethodMetadata> methodMetadataSet = new LinkedHashSet<MethodMetadata>(4);
public AnnotationMetadataReadingVisitor(ClassLoader classLoader) {
@@ -72,22 +82,26 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor
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);
+ return new AnnotationAttributesReadingVisitor(className, this.attributesMap, this.metaAnnotationMap, this.classLoader);
}
+ @Override
public Set<String> getAnnotationTypes() {
return this.annotationSet;
}
+ @Override
public Set<String> getMetaAnnotationTypes(String annotationType) {
return this.metaAnnotationMap.get(annotationType);
}
+ @Override
public boolean hasAnnotation(String annotationType) {
return this.annotationSet.contains(annotationType);
}
+ @Override
public boolean hasMetaAnnotation(String metaAnnotationType) {
Collection<Set<String>> allMetaTypes = this.metaAnnotationMap.values();
for (Set<String> metaTypes : allMetaTypes) {
@@ -98,71 +112,45 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor
return false;
}
+ @Override
public boolean isAnnotated(String annotationType) {
- return this.attributeMap.containsKey(annotationType);
+ return this.attributesMap.containsKey(annotationType);
}
+ @Override
public AnnotationAttributes getAnnotationAttributes(String annotationType) {
return getAnnotationAttributes(annotationType, false);
}
+ @Override
public AnnotationAttributes getAnnotationAttributes(String annotationType, boolean classValuesAsString) {
- AnnotationAttributes raw = this.attributeMap.get(annotationType);
- return convertClassValues(raw, classValuesAsString);
+ AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes(
+ this.attributesMap, this.metaAnnotationMap, annotationType);
+ return AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, classValuesAsString);
}
- private AnnotationAttributes convertClassValues(AnnotationAttributes original, boolean classValuesAsString) {
- if (original == null) {
+ @Override
+ public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType) {
+ return getAllAnnotationAttributes(annotationType, false);
+ }
+
+ @Override
+ public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString) {
+ MultiValueMap<String, Object> allAttributes = new LinkedMultiValueMap<String, Object>();
+ List<AnnotationAttributes> attributes = this.attributesMap.get(annotationType);
+ if (attributes == 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.
+ for (AnnotationAttributes raw : attributes) {
+ for (Map.Entry<String, Object> entry :
+ AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, classValuesAsString).entrySet()) {
+ allAttributes.add(entry.getKey(), entry.getValue());
}
}
- return result;
+ return allAttributes;
}
+ @Override
public boolean hasAnnotatedMethods(String annotationType) {
for (MethodMetadata methodMetadata : this.methodMetadataSet) {
if (methodMetadata.isAnnotated(annotationType)) {
@@ -172,6 +160,7 @@ final class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor
return false;
}
+ @Override
public Set<MethodMetadata> getAnnotatedMethods(String annotationType) {
Set<MethodMetadata> annotatedMethods = new LinkedHashSet<MethodMetadata>(4);
for (MethodMetadata methodMetadata : this.methodMetadataSet) {
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java
new file mode 100644
index 00000000..2fe25085
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java
@@ -0,0 +1,164 @@
+/*
+ * 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.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.asm.Type;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.LinkedMultiValueMap;
+
+/**
+ * Internal utility class used when reading annotations.
+ *
+ * @author Juergen Hoeller
+ * @author Mark Fisher
+ * @author Costin Leau
+ * @author Phillip Webb
+ * @author Sam Brannen
+ * @since 4.0
+ */
+abstract class AnnotationReadingVisitorUtils {
+
+ public static AnnotationAttributes convertClassValues(ClassLoader classLoader, 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(classLoader, (AnnotationAttributes) value, classValuesAsString);
+ }
+ else if (value instanceof AnnotationAttributes[]) {
+ AnnotationAttributes[] values = (AnnotationAttributes[]) value;
+ for (int i = 0; i < values.length; i++) {
+ values[i] = convertClassValues(classLoader, values[i], classValuesAsString);
+ }
+ }
+ else if (value instanceof Type) {
+ value = (classValuesAsString ? ((Type) value).getClassName()
+ : 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()
+ : 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;
+ }
+
+ /**
+ * Retrieve the merged attributes of the annotation of the given type,
+ * if any, from the supplied {@code attributesMap}.
+ * <p>Annotation attribute values appearing <em>lower</em> in the annotation
+ * hierarchy (i.e., closer to the declaring class) will override those
+ * defined <em>higher</em> in the annotation hierarchy.
+ * @param attributesMap the map of annotation attribute lists,
+ * keyed by annotation type name
+ * @param metaAnnotationMap the map of meta annotation relationships,
+ * keyed by annotation type name
+ * @param annotationType the name of the annotation type to look for
+ * @return the merged annotation attributes, or {@code null} if no
+ * matching annotation is present in the {@code attributesMap}
+ * @since 4.0.3
+ */
+ public static AnnotationAttributes getMergedAnnotationAttributes(
+ LinkedMultiValueMap<String, AnnotationAttributes> attributesMap,
+ Map<String, Set<String>> metaAnnotationMap, String annotationType) {
+
+ // Get the unmerged list of attributes for the target annotation.
+ List<AnnotationAttributes> attributesList = attributesMap.get(annotationType);
+ if (attributesList == null || attributesList.isEmpty()) {
+ return null;
+ }
+
+ // To start with, we populate the results with a copy of all attribute
+ // values from the target annotation. A copy is necessary so that we do
+ // not inadvertently mutate the state of the metadata passed to this
+ // method.
+ AnnotationAttributes results = new AnnotationAttributes(attributesList.get(0));
+
+ Set<String> overridableAttributeNames = new HashSet<String>(results.keySet());
+ overridableAttributeNames.remove(AnnotationUtils.VALUE);
+
+ // Since the map is a LinkedMultiValueMap, we depend on the ordering of
+ // elements in the map and reverse the order of the keys in order to traverse
+ // "down" the annotation hierarchy.
+ List<String> annotationTypes = new ArrayList<String>(attributesMap.keySet());
+ Collections.reverse(annotationTypes);
+
+ // No need to revisit the target annotation type:
+ annotationTypes.remove(annotationType);
+
+ for (String currentAnnotationType : annotationTypes) {
+ List<AnnotationAttributes> currentAttributesList = attributesMap.get(currentAnnotationType);
+ if (currentAttributesList != null && !currentAttributesList.isEmpty()) {
+ Set<String> metaAnns = metaAnnotationMap.get(currentAnnotationType);
+ if (metaAnns != null && metaAnns.contains(annotationType)) {
+ AnnotationAttributes currentAttributes = currentAttributesList.get(0);
+ for (String overridableAttributeName : overridableAttributeNames) {
+ Object value = currentAttributes.get(overridableAttributeName);
+ if (value != null) {
+ // Store the value, potentially overriding a value from an
+ // attribute of the same name found higher in the annotation
+ // hierarchy.
+ results.put(overridableAttributeName, value);
+ }
+ }
+ }
+ }
+ }
+
+ return results;
+ }
+
+}
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
index 45785bc8..af3d616b 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * 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.
@@ -67,6 +67,7 @@ class ClassMetadataReadingVisitor extends ClassVisitor implements ClassMetadata
}
+ @Override
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);
@@ -81,10 +82,12 @@ class ClassMetadataReadingVisitor extends ClassVisitor implements ClassMetadata
}
}
+ @Override
public void visitOuterClass(String owner, String name, String desc) {
this.enclosingClassName = ClassUtils.convertResourcePathToClassName(owner);
}
+ @Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
if (outerName != null) {
String fqName = ClassUtils.convertResourcePathToClassName(name);
@@ -99,115 +102,132 @@ class ClassMetadataReadingVisitor extends ClassVisitor implements ClassMetadata
}
}
+ @Override
public void visitSource(String source, String debug) {
// no-op
}
+ @Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
// no-op
return new EmptyAnnotationVisitor();
}
+ @Override
public void visitAttribute(Attribute attr) {
// no-op
}
+ @Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
// no-op
return new EmptyFieldVisitor();
}
+ @Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
// no-op
return new EmptyMethodVisitor();
}
+ @Override
public void visitEnd() {
// no-op
}
+ @Override
public String getClassName() {
return this.className;
}
+ @Override
public boolean isInterface() {
return this.isInterface;
}
+ @Override
public boolean isAbstract() {
return this.isAbstract;
}
+ @Override
public boolean isConcrete() {
return !(this.isInterface || this.isAbstract);
}
+ @Override
public boolean isFinal() {
return this.isFinal;
}
+ @Override
public boolean isIndependent() {
return (this.enclosingClassName == null || this.independentInnerClass);
}
+ @Override
public boolean hasEnclosingClass() {
return (this.enclosingClassName != null);
}
+ @Override
public String getEnclosingClassName() {
return this.enclosingClassName;
}
+ @Override
public boolean hasSuperClass() {
return (this.superClassName != null);
}
+ @Override
public String getSuperClassName() {
return this.superClassName;
}
+ @Override
public String[] getInterfaceNames() {
return this.interfaces;
}
+ @Override
public String[] getMemberClassNames() {
return this.memberClassNames.toArray(new String[this.memberClassNames.size()]);
}
-}
+ private static class EmptyAnnotationVisitor extends AnnotationVisitor {
-class EmptyAnnotationVisitor extends AnnotationVisitor {
-
- public EmptyAnnotationVisitor() {
- super(SpringAsmInfo.ASM_VERSION);
- }
+ public EmptyAnnotationVisitor() {
+ super(SpringAsmInfo.ASM_VERSION);
+ }
- @Override
- public AnnotationVisitor visitAnnotation(String name, String desc) {
- return this;
- }
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String desc) {
+ return this;
+ }
- @Override
- public AnnotationVisitor visitArray(String name) {
- return this;
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ return this;
+ }
}
-}
-class EmptyMethodVisitor extends MethodVisitor {
+ private static class EmptyMethodVisitor extends MethodVisitor {
- public EmptyMethodVisitor() {
- super(SpringAsmInfo.ASM_VERSION);
+ public EmptyMethodVisitor() {
+ super(SpringAsmInfo.ASM_VERSION);
+ }
}
-}
-class EmptyFieldVisitor extends FieldVisitor {
+ private static class EmptyFieldVisitor extends FieldVisitor {
- public EmptyFieldVisitor() {
- super(SpringAsmInfo.ASM_VERSION);
+ 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/MethodMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java
index dde3790c..f88b57b0 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * 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.
@@ -16,7 +16,7 @@
package org.springframework.core.type.classreading;
-import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -27,6 +27,8 @@ import org.springframework.asm.SpringAsmInfo;
import org.springframework.asm.Type;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.MethodMetadata;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
/**
* ASM method visitor which looks for the annotations defined on the method,
@@ -37,21 +39,23 @@ import org.springframework.core.type.MethodMetadata;
* @author Mark Pollack
* @author Costin Leau
* @author Chris Beams
+ * @author Phillip Webb
* @since 3.0
*/
-final class MethodMetadataReadingVisitor extends MethodVisitor implements MethodMetadata {
+public class MethodMetadataReadingVisitor extends MethodVisitor implements MethodMetadata {
- private final String name;
+ protected final String name;
- private final int access;
+ protected final int access;
- private final String declaringClassName;
+ protected final String declaringClassName;
- private final ClassLoader classLoader;
+ protected final ClassLoader classLoader;
- private final Set<MethodMetadata> methodMetadataSet;
+ protected final Set<MethodMetadata> methodMetadataSet;
- private final Map<String, AnnotationAttributes> attributeMap = new LinkedHashMap<String, AnnotationAttributes>(2);
+ protected final MultiValueMap<String, AnnotationAttributes> attributeMap =
+ new LinkedMultiValueMap<String, AnnotationAttributes>(4);
public MethodMetadataReadingVisitor(String name, int access, String declaringClassName,
@@ -73,30 +77,64 @@ final class MethodMetadataReadingVisitor extends MethodVisitor implements Method
return new AnnotationAttributesReadingVisitor(className, this.attributeMap, null, this.classLoader);
}
+ @Override
public String getMethodName() {
return this.name;
}
+ @Override
public boolean isStatic() {
return ((this.access & Opcodes.ACC_STATIC) != 0);
}
+ @Override
public boolean isFinal() {
return ((this.access & Opcodes.ACC_FINAL) != 0);
}
+ @Override
public boolean isOverridable() {
return (!isStatic() && !isFinal() && ((this.access & Opcodes.ACC_PRIVATE) == 0));
}
+ @Override
public boolean isAnnotated(String annotationType) {
return this.attributeMap.containsKey(annotationType);
}
- public AnnotationAttributes getAnnotationAttributes(String annotationType) {
- return this.attributeMap.get(annotationType);
+ @Override
+ public Map<String, Object> getAnnotationAttributes(String annotationType) {
+ return getAnnotationAttributes(annotationType, false);
+ }
+
+ @Override
+ public Map<String, Object> getAnnotationAttributes(String annotationType, boolean classValuesAsString) {
+ List<AnnotationAttributes> attributes = this.attributeMap.get(annotationType);
+ return (attributes == null ? null : AnnotationReadingVisitorUtils.convertClassValues(
+ this.classLoader, attributes.get(0), classValuesAsString));
+ }
+
+ @Override
+ public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType) {
+ return getAllAnnotationAttributes(annotationType, false);
}
+ @Override
+ public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationType, boolean classValuesAsString) {
+ if (!this.attributeMap.containsKey(annotationType)) {
+ return null;
+ }
+ MultiValueMap<String, Object> allAttributes = new LinkedMultiValueMap<String, Object>();
+ for (AnnotationAttributes annotationAttributes : this.attributeMap.get(annotationType)) {
+ for (Map.Entry<String, Object> entry : AnnotationReadingVisitorUtils.convertClassValues(
+ this.classLoader, annotationAttributes, classValuesAsString).entrySet()) {
+ allAttributes.add(entry.getKey(), entry.getValue());
+ }
+ }
+ return allAttributes;
+ }
+
+ @Override
public String getDeclaringClassName() {
return this.declaringClassName;
}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationArrayVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationArrayVisitor.java
new file mode 100644
index 00000000..3c5bd9a4
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationArrayVisitor.java
@@ -0,0 +1,85 @@
+/*
+ * 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.reflect.Array;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.asm.AnnotationVisitor;
+import org.springframework.asm.Type;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.util.ObjectUtils;
+
+/**
+ * @author Chris Beams
+ * @author Juergen Hoeller
+ * @since 3.1.1
+ */
+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);
+ }
+
+ @Override
+ public void visitEnd() {
+ if (!this.allNestedAttributes.isEmpty()) {
+ this.attributes.put(this.attributeName,
+ this.allNestedAttributes.toArray(new AnnotationAttributes[this.allNestedAttributes.size()]));
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationAttributesVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationAttributesVisitor.java
new file mode 100644
index 00000000..2f1be81d
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationAttributesVisitor.java
@@ -0,0 +1,90 @@
+/*
+ * 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.Method;
+import java.lang.reflect.Modifier;
+
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.annotation.AnnotationUtils;
+
+/**
+ * @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;
+ }
+
+
+ @Override
+ 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);
+ }
+ }
+ }
+ }
+
+}
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
index 36c495e5..8ffe4c07 100644
--- 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
@@ -70,14 +70,17 @@ final class SimpleMetadataReader implements MetadataReader {
}
+ @Override
public Resource getResource() {
return this.resource;
}
+ @Override
public ClassMetadata getClassMetadata() {
return this.classMetadata;
}
+ @Override
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
index 9e1c736f..c4af4541 100644
--- 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
@@ -70,6 +70,7 @@ public class SimpleMetadataReaderFactory implements MetadataReaderFactory {
}
+ @Override
public MetadataReader getMetadataReader(String className) throws IOException {
String resourcePath = ResourceLoader.CLASSPATH_URL_PREFIX +
ClassUtils.convertClassNameToResourcePath(className) + ClassUtils.CLASS_FILE_SUFFIX;
@@ -92,6 +93,7 @@ public class SimpleMetadataReaderFactory implements MetadataReaderFactory {
return getMetadataReader(resource);
}
+ @Override
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/filter/AbstractClassTestingTypeFilter.java b/spring-core/src/main/java/org/springframework/core/type/filter/AbstractClassTestingTypeFilter.java
index e63571f6..e2e3d5c1 100644
--- 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
@@ -35,6 +35,7 @@ import org.springframework.core.type.classreading.MetadataReaderFactory;
*/
public abstract class AbstractClassTestingTypeFilter implements TypeFilter {
+ @Override
public final boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
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
index 6ee0c875..b10db84a 100644
--- 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
@@ -18,6 +18,9 @@ package org.springframework.core.type.filter;
import java.io.IOException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
@@ -36,6 +39,8 @@ import org.springframework.core.type.classreading.MetadataReaderFactory;
*/
public abstract class AbstractTypeHierarchyTraversingFilter implements TypeFilter {
+ protected final Log logger = LogFactory.getLog(getClass());
+
private final boolean considerInherited;
private final boolean considerInterfaces;
@@ -47,6 +52,7 @@ public abstract class AbstractTypeHierarchyTraversingFilter implements TypeFilte
}
+ @Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
@@ -60,40 +66,50 @@ public abstract class AbstractTypeHierarchyTraversingFilter implements TypeFilte
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.considerInherited) {
+ 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...
+ try {
+ if (match(metadata.getSuperClassName(), metadataReaderFactory)) {
+ return true;
+ }
+ }
+ catch (IOException ex) {
+ logger.debug("Could not read super class [" + metadata.getSuperClassName() +
+ "] of type-filtered class [" + metadata.getClassName() + "]");
+ }
+ }
}
}
- 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;
+ if (this.considerInterfaces) {
+ 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;
+ else {
+ // Need to read interface to determine a match...
+ try {
+ if (match(ifc, metadataReaderFactory)) {
+ return true;
+ }
+ }
+ catch (IOException ex) {
+ logger.debug("Could not read interface [" + ifc + "] for type-filtered class [" +
+ metadata.getClassName() + "]");
+ }
}
}
}
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
index 0e2a3a5d..b9b6abdc 100644
--- 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
@@ -60,6 +60,7 @@ public class AspectJTypeFilter implements TypeFilter {
}
+ @Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
diff --git a/spring-core/src/main/java/org/springframework/util/AlternativeJdkIdGenerator.java b/spring-core/src/main/java/org/springframework/util/AlternativeJdkIdGenerator.java
new file mode 100644
index 00000000..52ab29a1
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/AlternativeJdkIdGenerator.java
@@ -0,0 +1,64 @@
+/*
+ * 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.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.Random;
+import java.util.UUID;
+
+/**
+ * A variation of {@link UUID#randomUUID()} that uses {@link SecureRandom} only for the
+ * initial seed and {@link Random} thereafter. This provides better performance in
+ * exchange for less securely random id's.
+ *
+ * @author Rossen Stoyanchev
+ * @author Rob Winch
+ * @since 4.0
+ */
+public class AlternativeJdkIdGenerator implements IdGenerator {
+
+ private final Random random;
+
+
+ public AlternativeJdkIdGenerator() {
+ SecureRandom secureRandom = new SecureRandom();
+ byte[] seed = new byte[8];
+ secureRandom.nextBytes(seed);
+ this.random = new Random(new BigInteger(seed).longValue());
+ }
+
+
+ public UUID generateId() {
+
+ byte[] randomBytes = new byte[16];
+ this.random.nextBytes(randomBytes);
+
+ long mostSigBits = 0;
+ for (int i = 0; i < 8; i++) {
+ mostSigBits = (mostSigBits << 8) | (randomBytes[i] & 0xff);
+ }
+
+ long leastSigBits = 0;
+ for (int i = 8; i < 16; i++) {
+ leastSigBits = (leastSigBits << 8) | (randomBytes[i] & 0xff);
+ }
+
+ return new UUID(mostSigBits, leastSigBits);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java
index a4f74a4b..5108f474 100644
--- a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java
+++ b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java
@@ -53,6 +53,8 @@ public class AntPathMatcher implements PathMatcher {
/** Default path separator: "/" */
public static final String DEFAULT_PATH_SEPARATOR = "/";
+ private static final int CACHE_TURNOFF_THRESHOLD = 65536;
+
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?\\}");
@@ -60,8 +62,11 @@ public class AntPathMatcher implements PathMatcher {
private boolean trimTokens = true;
- private final Map<String, AntPathStringMatcher> stringMatcherCache =
- new ConcurrentHashMap<String, AntPathStringMatcher>(256);
+ private volatile Boolean cachePatterns;
+
+ private final Map<String, String[]> tokenizedPatternCache = new ConcurrentHashMap<String, String[]>(256);
+
+ final Map<String, AntPathStringMatcher> stringMatcherCache = new ConcurrentHashMap<String, AntPathStringMatcher>(256);
/**
@@ -80,20 +85,43 @@ public class AntPathMatcher implements PathMatcher {
this.trimTokens = trimTokens;
}
+ /**
+ * Specify whether to cache parsed pattern metadata for patterns passed
+ * into this matcher's {@link #match} method. A value of {@code true}
+ * activates an unlimited pattern cache; a value of {@code false} turns
+ * the pattern cache off completely.
+ * <p>Default is for the cache to be on, but with the variant to automatically
+ * turn it off when encountering too many patterns to cache at runtime
+ * (the threshold is 65536), assuming that arbitrary permutations of patterns
+ * are coming in, with little chance for encountering a reoccurring pattern.
+ * @see #getStringMatcher(String)
+ */
+ public void setCachePatterns(boolean cachePatterns) {
+ this.cachePatterns = cachePatterns;
+ }
+ private void deactivatePatternCache() {
+ this.cachePatterns = false;
+ this.tokenizedPatternCache.clear();
+ this.stringMatcherCache.clear();
+ }
+
+
+ @Override
public boolean isPattern(String path) {
return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
}
+ @Override
public boolean match(String pattern, String path) {
return doMatch(pattern, path, true, null);
}
+ @Override
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
@@ -107,7 +135,7 @@ public class AntPathMatcher implements PathMatcher {
return false;
}
- String[] pattDirs = tokenizePath(pattern);
+ String[] pattDirs = tokenizePattern(pattern);
String[] pathDirs = tokenizePath(path);
int pattIdxStart = 0;
@@ -228,6 +256,35 @@ public class AntPathMatcher implements PathMatcher {
}
/**
+ * Tokenize the given path pattern into parts, based on this matcher's settings.
+ * <p>Performs caching based on {@link #setCachePatterns}, delegating to
+ * {@link #tokenizePath(String)} for the actual tokenization algorithm.
+ * @param pattern the pattern to tokenize
+ * @return the tokenized pattern parts
+ */
+ protected String[] tokenizePattern(String pattern) {
+ String[] tokenized = null;
+ Boolean cachePatterns = this.cachePatterns;
+ if (cachePatterns == null || cachePatterns.booleanValue()) {
+ tokenized = this.tokenizedPatternCache.get(pattern);
+ }
+ if (tokenized == null) {
+ tokenized = tokenizePath(pattern);
+ if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) {
+ // Try to adapt to the runtime situation that we're encountering:
+ // There are obviously too many different patterns coming in here...
+ // So let's turn off the cache since the patterns are unlikely to be reoccurring.
+ deactivatePatternCache();
+ return tokenized;
+ }
+ if (cachePatterns == null || cachePatterns.booleanValue()) {
+ this.tokenizedPatternCache.put(pattern, tokenized);
+ }
+ }
+ return tokenized;
+ }
+
+ /**
* Tokenize the given path String into parts, based on this matcher's settings.
* @param path the path to tokenize
* @return the tokenized path parts
@@ -237,20 +294,48 @@ public class AntPathMatcher implements PathMatcher {
}
/**
- * 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.
+ * Tests whether or not a string matches against a pattern.
+ * @param pattern the pattern to match against (never {@code null})
+ * @param str the String which must be matched against the pattern (never {@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);
+ return getStringMatcher(pattern).matchStrings(str, uriTemplateVariables);
+ }
+
+ /**
+ * Build or retrieve an {@link AntPathStringMatcher} for the given pattern.
+ * <p>The default implementation checks this AntPathMatcher's internal cache
+ * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance
+ * if no cached copy is found.
+ * When encountering too many patterns to cache at runtime (the threshold is 65536),
+ * it turns the default cache off, assuming that arbitrary permutations of patterns
+ * are coming in, with little chance for encountering a reoccurring pattern.
+ * <p>This method may get overridden to implement a custom cache strategy.
+ * @param pattern the pattern to match against (never {@code null})
+ * @return a corresponding AntPathStringMatcher (never {@code null})
+ * @see #setCachePatterns
+ */
+ protected AntPathStringMatcher getStringMatcher(String pattern) {
+ AntPathStringMatcher matcher = null;
+ Boolean cachePatterns = this.cachePatterns;
+ if (cachePatterns == null || cachePatterns.booleanValue()) {
+ matcher = this.stringMatcherCache.get(pattern);
+ }
if (matcher == null) {
matcher = new AntPathStringMatcher(pattern);
- this.stringMatcherCache.put(pattern, matcher);
+ if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) {
+ // Try to adapt to the runtime situation that we're encountering:
+ // There are obviously too many different patterns coming in here...
+ // So let's turn off the cache since the patterns are unlikely to be reoccurring.
+ deactivatePatternCache();
+ return matcher;
+ }
+ if (cachePatterns == null || cachePatterns.booleanValue()) {
+ this.stringMatcherCache.put(pattern, matcher);
+ }
}
- return matcher.matchStrings(str, uriTemplateVariables);
+ return matcher;
}
/**
@@ -266,6 +351,7 @@ public class AntPathMatcher implements PathMatcher {
* <p>Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but
* does <strong>not</strong> enforce this.
*/
+ @Override
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);
@@ -296,6 +382,7 @@ public class AntPathMatcher implements PathMatcher {
return builder.toString();
}
+ @Override
public Map<String, String> extractUriTemplateVariables(String pattern, String path) {
Map<String, String> variables = new LinkedHashMap<String, String>();
boolean result = doMatch(pattern, path, true, variables);
@@ -322,14 +409,15 @@ public class AntPathMatcher implements PathMatcher {
* @return the combination of the two patterns
* @throws IllegalArgumentException when the two patterns cannot be combined
*/
+ @Override
public String combine(String pattern1, String pattern2) {
if (!StringUtils.hasText(pattern1) && !StringUtils.hasText(pattern2)) {
return "";
}
- else if (!StringUtils.hasText(pattern1)) {
+ if (!StringUtils.hasText(pattern1)) {
return pattern2;
}
- else if (!StringUtils.hasText(pattern2)) {
+ if (!StringUtils.hasText(pattern2)) {
return pattern1;
}
@@ -339,55 +427,37 @@ public class AntPathMatcher implements PathMatcher {
// 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;
- }
+
+ // /hotels/* + /booking -> /hotels/booking
+ // /hotels/* + booking -> /hotels/booking
+ if (pattern1.endsWith("/*")) {
+ return slashConcat(pattern1.substring(0, pattern1.length() - 2), pattern2);
}
- else if (pattern1.endsWith("/**")) {
- if (pattern2.startsWith("/")) {
- // /hotels/** + /booking -> /hotels/**/booking
- return pattern1 + pattern2;
- }
- else {
- // /hotels/** + booking -> /hotels/**/booking
- return pattern1 + "/" + pattern2;
- }
+
+ // /hotels/** + /booking -> /hotels/**/booking
+ // /hotels/** + booking -> /hotels/**/booking
+ if (pattern1.endsWith("/**")) {
+ return slashConcat(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;
+ int starDotPos1 = pattern1.indexOf("*.");
+ if (pattern1ContainsUriVar || starDotPos1 == -1) {
+ // simply concatenate the two patterns
+ return slashConcat(pattern1, pattern2);
}
+ String extension1 = pattern1.substring(starDotPos1 + 1);
+ int dotPos2 = pattern2.indexOf('.');
+ String fileName2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2));
+ String extension2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2));
+ String extension = extension1.startsWith("*") ? extension2 : extension1;
+ return fileName2 + extension;
+ }
+
+ private String slashConcat(String path1, String path2) {
+ if (path1.endsWith("/") || path2.startsWith("/")) {
+ return path1 + path2;
+ }
+ return path1 + "/" + path2;
}
/**
@@ -402,100 +472,18 @@ public class AntPathMatcher implements PathMatcher {
* @param path the full path to use for comparison
* @return a comparator capable of sorting patterns in order of explicitness
*/
+ @Override
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 {
+ protected static class AntPathStringMatcher {
private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}");
@@ -573,4 +561,97 @@ public class AntPathMatcher implements PathMatcher {
}
}
+
+ /**
+ * The default {@link Comparator} implementation returned by
+ * {@link #getPatternComparator(String)}.
+ */
+ protected static class AntPatternComparator implements Comparator<String> {
+
+ private final String path;
+
+ public AntPatternComparator(String path) {
+ this.path = path;
+ }
+
+ @Override
+ public int compare(String pattern1, String pattern2) {
+ if (isNullOrCaptureAllPattern(pattern1) && isNullOrCaptureAllPattern(pattern2)) {
+ return 0;
+ }
+ else if (isNullOrCaptureAllPattern(pattern1)) {
+ return 1;
+ }
+ else if (isNullOrCaptureAllPattern(pattern2)) {
+ 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 boolean isNullOrCaptureAllPattern(String pattern) {
+ return pattern == null || "/**".equals(pattern);
+ }
+
+ 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) {
+ return VARIABLE_PATTERN.matcher(pattern).replaceAll("#").length();
+ }
+ }
+
}
diff --git a/spring-core/src/main/java/org/springframework/util/Assert.java b/spring-core/src/main/java/org/springframework/util/Assert.java
index 55b2325d..0a28cac7 100644
--- a/spring-core/src/main/java/org/springframework/util/Assert.java
+++ b/spring-core/src/main/java/org/springframework/util/Assert.java
@@ -263,7 +263,7 @@ public abstract class Assert {
* @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) {
+ public static void notEmpty(Collection<?> collection, String message) {
if (CollectionUtils.isEmpty(collection)) {
throw new IllegalArgumentException(message);
}
@@ -276,7 +276,7 @@ public abstract class Assert {
* @param collection the collection to check
* @throws IllegalArgumentException if the collection is {@code null} or has no elements
*/
- public static void notEmpty(Collection collection) {
+ public static void notEmpty(Collection<?> collection) {
notEmpty(collection,
"[Assertion failed] - this collection must not be empty: it must contain at least 1 element");
}
@@ -289,7 +289,7 @@ public abstract class Assert {
* @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) {
+ public static void notEmpty(Map<?, ?> map, String message) {
if (CollectionUtils.isEmpty(map)) {
throw new IllegalArgumentException(message);
}
@@ -302,7 +302,7 @@ public abstract class Assert {
* @param map the map to check
* @throws IllegalArgumentException if the map is {@code null} or has no entries
*/
- public static void notEmpty(Map map) {
+ public static void notEmpty(Map<?, ?> map) {
notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry");
}
diff --git a/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java b/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java
index 7a2135fb..9cea1c99 100644
--- a/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java
+++ b/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java
@@ -92,31 +92,38 @@ public class AutoPopulatingList<E> implements List<E>, Serializable {
}
+ @Override
public void add(int index, E element) {
this.backingList.add(index, element);
}
+ @Override
public boolean add(E o) {
return this.backingList.add(o);
}
+ @Override
public boolean addAll(Collection<? extends E> c) {
return this.backingList.addAll(c);
}
+ @Override
public boolean addAll(int index, Collection<? extends E> c) {
return this.backingList.addAll(index, c);
}
+ @Override
public void clear() {
this.backingList.clear();
}
+ @Override
public boolean contains(Object o) {
return this.backingList.contains(o);
}
- public boolean containsAll(Collection c) {
+ @Override
+ public boolean containsAll(Collection<?> c) {
return this.backingList.containsAll(c);
}
@@ -124,6 +131,7 @@ public class AutoPopulatingList<E> implements List<E>, Serializable {
* Get the element at the supplied index, creating it if there is
* no element at that index.
*/
+ @Override
public E get(int index) {
int backingListSize = this.backingList.size();
E element = null;
@@ -144,62 +152,77 @@ public class AutoPopulatingList<E> implements List<E>, Serializable {
return element;
}
+ @Override
public int indexOf(Object o) {
return this.backingList.indexOf(o);
}
+ @Override
public boolean isEmpty() {
return this.backingList.isEmpty();
}
+ @Override
public Iterator<E> iterator() {
return this.backingList.iterator();
}
+ @Override
public int lastIndexOf(Object o) {
return this.backingList.lastIndexOf(o);
}
+ @Override
public ListIterator<E> listIterator() {
return this.backingList.listIterator();
}
+ @Override
public ListIterator<E> listIterator(int index) {
return this.backingList.listIterator(index);
}
+ @Override
public E remove(int index) {
return this.backingList.remove(index);
}
+ @Override
public boolean remove(Object o) {
return this.backingList.remove(o);
}
+ @Override
public boolean removeAll(Collection<?> c) {
return this.backingList.removeAll(c);
}
+ @Override
public boolean retainAll(Collection<?> c) {
return this.backingList.retainAll(c);
}
+ @Override
public E set(int index, E element) {
return this.backingList.set(index, element);
}
+ @Override
public int size() {
return this.backingList.size();
}
+ @Override
public List<E> subList(int fromIndex, int toIndex) {
return this.backingList.subList(fromIndex, toIndex);
}
+ @Override
public Object[] toArray() {
return this.backingList.toArray();
}
+ @Override
public <T> T[] toArray(T[] a) {
return this.backingList.toArray(a);
}
@@ -253,12 +276,13 @@ public class AutoPopulatingList<E> implements List<E>, Serializable {
private final Class<? extends E> elementClass;
public ReflectiveElementFactory(Class<? extends E> elementClass) {
- Assert.notNull(elementClass, "Element clas must not be null");
+ Assert.notNull(elementClass, "Element class 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;
}
+ @Override
public E createElement(int index) {
try {
return this.elementClass.newInstance();
diff --git a/spring-core/src/main/java/org/springframework/util/CachingMapDecorator.java b/spring-core/src/main/java/org/springframework/util/CachingMapDecorator.java
deleted file mode 100644
index d1290588..00000000
--- a/spring-core/src/main/java/org/springframework/util/CachingMapDecorator.java
+++ /dev/null
@@ -1,311 +0,0 @@
-/*
- * 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
index 4bac3488..282c020c 100644
--- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java
@@ -195,25 +195,6 @@ public abstract class ClassUtils {
/**
* 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").
@@ -332,19 +313,6 @@ public abstract class ClassUtils {
* 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
@@ -813,6 +781,26 @@ public abstract class ClassUtils {
}
/**
+ * Determine whether the given method is declared by the user or at least pointing to
+ * a user-declared method.
+ * <p>Checks {@link Method#isSynthetic()} (for implementation methods) as well as the
+ * {@code GroovyObject} interface (for interface methods; on an implementation class,
+ * implementations of the {@code GroovyObject} methods will be marked as synthetic anyway).
+ * Note that, despite being synthetic, bridge methods ({@link Method#isBridge()}) are considered
+ * as user-level methods since they are eventually pointing to a user-declared generic method.
+ * @param method the method to check
+ * @return {@code true} if the method can be considered as user-declared; [@code false} otherwise
+ */
+ public static boolean isUserLevelMethod(Method method) {
+ Assert.notNull(method, "Method must not be null");
+ return (method.isBridge() || (!method.isSynthetic() && !isGroovyObjectMethod(method)));
+ }
+
+ private static boolean isGroovyObjectMethod(Method method) {
+ return method.getDeclaringClass().getName().equals("groovy.lang.GroovyObject");
+ }
+
+ /**
* 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
@@ -1028,7 +1016,7 @@ public abstract class ClassUtils {
* @return a String of form "[com.foo.Bar, com.foo.Baz]"
* @see java.util.AbstractCollection#toString()
*/
- public static String classNamesToString(Class... classes) {
+ public static String classNamesToString(Class<?>... classes) {
return classNamesToString(Arrays.asList(classes));
}
@@ -1041,13 +1029,13 @@ public abstract class ClassUtils {
* @return a String of form "[com.foo.Bar, com.foo.Baz]"
* @see java.util.AbstractCollection#toString()
*/
- public static String classNamesToString(Collection<Class> classes) {
+ 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();
+ for (Iterator<Class<?>> it = classes.iterator(); it.hasNext(); ) {
+ Class<?> clazz = it.next();
sb.append(clazz.getName());
if (it.hasNext()) {
sb.append(", ");
@@ -1103,8 +1091,8 @@ public abstract class ClassUtils {
* @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()]);
+ Set<Class<?>> ifcs = getAllInterfacesForClassAsSet(clazz, classLoader);
+ return ifcs.toArray(new Class<?>[ifcs.size()]);
}
/**
@@ -1113,7 +1101,7 @@ public abstract class ClassUtils {
* @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) {
+ public static Set<Class<?>> getAllInterfacesAsSet(Object instance) {
Assert.notNull(instance, "Instance must not be null");
return getAllInterfacesForClassAsSet(instance.getClass());
}
@@ -1125,7 +1113,7 @@ public abstract class ClassUtils {
* @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) {
+ public static Set<Class<?>> getAllInterfacesForClassAsSet(Class<?> clazz) {
return getAllInterfacesForClassAsSet(clazz, null);
}
@@ -1138,12 +1126,12 @@ public abstract class ClassUtils {
* (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) {
+ 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);
+ return Collections.<Class<?>>singleton(clazz);
}
- Set<Class> interfaces = new LinkedHashSet<Class>();
+ Set<Class<?>> interfaces = new LinkedHashSet<Class<?>>();
while (clazz != null) {
Class<?>[] ifcs = clazz.getInterfaces();
for (Class<?> ifc : ifcs) {
diff --git a/spring-core/src/main/java/org/springframework/util/CollectionUtils.java b/spring-core/src/main/java/org/springframework/util/CollectionUtils.java
index 5e74473f..edadd3ab 100644
--- a/spring-core/src/main/java/org/springframework/util/CollectionUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/CollectionUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * 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.
@@ -42,34 +42,38 @@ import java.util.Set;
public abstract class CollectionUtils {
/**
- * Return {@code true} if the supplied Collection is {@code null}
- * or empty. Otherwise, return {@code false}.
+ * 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) {
+ 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}.
+ * 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) {
+ 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.
+ * Convert the supplied array into a List. A primitive array gets converted
+ * into a List of the appropriate wrapper type.
+ * <p><b>NOTE:</b> Generally prefer the standard {@link Arrays#asList} method.
+ * This {@code arrayToList} method is just meant to deal with an incoming Object
+ * value that might be an {@code Object[]} or a primitive array at runtime.
+ * <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)
+ * @see Arrays#asList(Object[])
*/
+ @SuppressWarnings("rawtypes")
public static List arrayToList(Object source) {
return Arrays.asList(ObjectUtils.toObjectArray(source));
}
@@ -80,13 +84,13 @@ public abstract class CollectionUtils {
* @param collection the target Collection to merge the array into
*/
@SuppressWarnings("unchecked")
- public static void mergeArrayIntoCollection(Object array, Collection collection) {
+ public static <E> void mergeArrayIntoCollection(Object array, Collection<E> collection) {
if (collection == null) {
throw new IllegalArgumentException("Collection must not be null");
}
Object[] arr = ObjectUtils.toObjectArray(array);
for (Object elem : arr) {
- collection.add(elem);
+ collection.add((E) elem);
}
}
@@ -99,19 +103,19 @@ public abstract class CollectionUtils {
* @param map the target Map to merge the properties into
*/
@SuppressWarnings("unchecked")
- public static void mergePropertiesIntoMap(Properties props, Map map) {
+ public static <K, V> void mergePropertiesIntoMap(Properties props, Map<K, V> map) {
if (map == null) {
throw new IllegalArgumentException("Map must not be null");
}
if (props != null) {
- for (Enumeration en = props.propertyNames(); en.hasMoreElements();) {
+ 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);
+ map.put((K) key, (V) value);
}
}
}
@@ -123,7 +127,7 @@ public abstract class CollectionUtils {
* @param element the element to look for
* @return {@code true} if found, {@code false} else
*/
- public static boolean contains(Iterator iterator, Object element) {
+ public static boolean contains(Iterator<?> iterator, Object element) {
if (iterator != null) {
while (iterator.hasNext()) {
Object candidate = iterator.next();
@@ -141,7 +145,7 @@ public abstract class CollectionUtils {
* @param element the element to look for
* @return {@code true} if found, {@code false} else
*/
- public static boolean contains(Enumeration enumeration, Object element) {
+ public static boolean contains(Enumeration<?> enumeration, Object element) {
if (enumeration != null) {
while (enumeration.hasMoreElements()) {
Object candidate = enumeration.nextElement();
@@ -161,7 +165,7 @@ public abstract class CollectionUtils {
* @param element the element to look for
* @return {@code true} if found, {@code false} else
*/
- public static boolean containsInstance(Collection collection, Object element) {
+ public static boolean containsInstance(Collection<?> collection, Object element) {
if (collection != null) {
for (Object candidate : collection) {
if (candidate == element) {
@@ -179,7 +183,7 @@ public abstract class CollectionUtils {
* @param candidates the candidates to search for
* @return whether any of the candidates has been found
*/
- public static boolean containsAny(Collection source, Collection candidates) {
+ public static boolean containsAny(Collection<?> source, Collection<?> candidates) {
if (isEmpty(source) || isEmpty(candidates)) {
return false;
}
@@ -200,13 +204,14 @@ public abstract class CollectionUtils {
* @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) {
+ @SuppressWarnings("unchecked")
+ public static <E> E findFirstMatch(Collection<?> source, Collection<E> candidates) {
if (isEmpty(source) || isEmpty(candidates)) {
return null;
}
for (Object candidate : candidates) {
if (source.contains(candidate)) {
- return candidate;
+ return (E) candidate;
}
}
return null;
@@ -265,7 +270,7 @@ public abstract class CollectionUtils {
* @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) {
+ public static boolean hasUniqueObject(Collection<?> collection) {
if (isEmpty(collection)) {
return false;
}
@@ -289,7 +294,7 @@ public abstract class CollectionUtils {
* @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) {
+ public static Class<?> findCommonElementType(Collection<?> collection) {
if (isEmpty(collection)) {
return null;
}
@@ -312,7 +317,7 @@ public abstract class CollectionUtils {
* 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) {
+ 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());
@@ -330,8 +335,7 @@ public abstract class CollectionUtils {
}
/**
- * Adapts a {@code Map<K, List<V>>} to an {@code MultiValueMap<K,V>}.
- *
+ * Adapt a {@code Map<K, List<V>>} to an {@code MultiValueMap<K,V>}.
* @param map the map
* @return the multi-value map
*/
@@ -341,8 +345,7 @@ public abstract class CollectionUtils {
}
/**
- * Returns an unmodifiable view of the specified multi-value map.
- *
+ * Return 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.
*/
@@ -358,7 +361,6 @@ public abstract class CollectionUtils {
}
-
/**
* Iterator wrapping an Enumeration.
*/
@@ -370,14 +372,17 @@ public abstract class CollectionUtils {
this.enumeration = enumeration;
}
+ @Override
public boolean hasNext() {
return this.enumeration.hasMoreElements();
}
+ @Override
public E next() {
return this.enumeration.nextElement();
}
+ @Override
public void remove() throws UnsupportedOperationException {
throw new UnsupportedOperationException("Not supported");
}
@@ -396,6 +401,7 @@ public abstract class CollectionUtils {
this.map = map;
}
+ @Override
public void add(K key, V value) {
List<V> values = this.map.get(key);
if (values == null) {
@@ -405,23 +411,27 @@ public abstract class CollectionUtils {
values.add(value);
}
+ @Override
public V getFirst(K key) {
List<V> values = this.map.get(key);
return (values != null ? values.get(0) : null);
}
+ @Override
public void set(K key, V value) {
List<V> values = new LinkedList<V>();
values.add(value);
this.map.put(key, values);
}
+ @Override
public void setAll(Map<K, V> values) {
for (Entry<K, V> entry : values.entrySet()) {
set(entry.getKey(), entry.getValue());
}
}
+ @Override
public Map<K, V> toSingleValueMap() {
LinkedHashMap<K, V> singleValueMap = new LinkedHashMap<K,V>(this.map.size());
for (Entry<K, List<V>> entry : map.entrySet()) {
@@ -430,50 +440,62 @@ public abstract class CollectionUtils {
return singleValueMap;
}
+ @Override
public int size() {
return this.map.size();
}
+ @Override
public boolean isEmpty() {
return this.map.isEmpty();
}
+ @Override
public boolean containsKey(Object key) {
return this.map.containsKey(key);
}
+ @Override
public boolean containsValue(Object value) {
return this.map.containsValue(value);
}
+ @Override
public List<V> get(Object key) {
return this.map.get(key);
}
+ @Override
public List<V> put(K key, List<V> value) {
return this.map.put(key, value);
}
+ @Override
public List<V> remove(Object key) {
return this.map.remove(key);
}
+ @Override
public void putAll(Map<? extends K, ? extends List<V>> m) {
this.map.putAll(m);
}
+ @Override
public void clear() {
this.map.clear();
}
+ @Override
public Set<K> keySet() {
return this.map.keySet();
}
+ @Override
public Collection<List<V>> values() {
return this.map.values();
}
+ @Override
public Set<Entry<K, List<V>>> entrySet() {
return this.map.entrySet();
}
diff --git a/spring-core/src/main/java/org/springframework/util/CompositeIterator.java b/spring-core/src/main/java/org/springframework/util/CompositeIterator.java
index 60b1571c..dea6f292 100644
--- a/spring-core/src/main/java/org/springframework/util/CompositeIterator.java
+++ b/spring-core/src/main/java/org/springframework/util/CompositeIterator.java
@@ -50,6 +50,7 @@ public class CompositeIterator<E> implements Iterator<E> {
this.iterators.add(iterator);
}
+ @Override
public boolean hasNext() {
this.inUse = true;
for (Iterator<E> iterator : this.iterators) {
@@ -60,6 +61,7 @@ public class CompositeIterator<E> implements Iterator<E> {
return false;
}
+ @Override
public E next() {
this.inUse = true;
for (Iterator<E> iterator : this.iterators) {
@@ -70,6 +72,7 @@ public class CompositeIterator<E> implements Iterator<E> {
throw new NoSuchElementException("All iterators exhausted");
}
+ @Override
public void remove() {
throw new UnsupportedOperationException("CompositeIterator does not support remove()");
}
diff --git a/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java b/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java
index c7324610..3bb6471f 100644
--- a/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java
+++ b/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java
@@ -252,6 +252,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
return put(key, value, true);
}
+ @Override
public V putIfAbsent(K key, V value) {
return put(key, value, false);
}
@@ -287,6 +288,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
});
}
+ @Override
public boolean remove(Object key, final Object value) {
return doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
@Override
@@ -300,6 +302,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
});
}
+ @Override
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
@@ -313,6 +316,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
});
}
+ @Override
public V replace(K key, final V value) {
return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
@Override
@@ -675,14 +679,17 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
this.value = value;
}
+ @Override
public K getKey() {
return this.key;
}
+ @Override
public V getValue() {
return this.value;
}
+ @Override
public V setValue(V value) {
V previous = this.value;
this.value = value;
@@ -842,11 +849,13 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
moveToNextSegment();
}
+ @Override
public boolean hasNext() {
getNextIfNecessary();
return (this.next != null);
}
+ @Override
public Entry<K, V> next() {
getNextIfNecessary();
if (this.next == null) {
@@ -892,6 +901,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
}
}
+ @Override
public void remove() {
Assert.state(this.last != null);
ConcurrentReferenceHashMap.this.remove(this.last.getKey());
@@ -959,14 +969,17 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
this.nextReference = next;
}
+ @Override
public int getHash() {
return this.hash;
}
+ @Override
public Reference<K, V> getNext() {
return this.nextReference;
}
+ @Override
public void release() {
enqueue();
clear();
@@ -989,14 +1002,17 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
this.nextReference = next;
}
+ @Override
public int getHash() {
return this.hash;
}
+ @Override
public Reference<K, V> getNext() {
return this.nextReference;
}
+ @Override
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
index 969b5e3d..d8338d3b 100644
--- a/spring-core/src/main/java/org/springframework/util/CustomizableThreadCreator.java
+++ b/spring-core/src/main/java/org/springframework/util/CustomizableThreadCreator.java
@@ -17,6 +17,7 @@
package org.springframework.util;
import java.io.Serializable;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Simple customizable helper class for creating new {@link Thread} instances.
@@ -40,9 +41,7 @@ public class CustomizableThreadCreator implements Serializable {
private ThreadGroup threadGroup;
- private int threadCount = 0;
-
- private final Object threadCountMonitor = new SerializableMonitor();
+ private final AtomicInteger threadCount = new AtomicInteger(0);
/**
@@ -160,12 +159,7 @@ public class CustomizableThreadCreator implements Serializable {
* @see #getThreadNamePrefix()
*/
protected String nextThreadName() {
- int threadNumber = 0;
- synchronized (this.threadCountMonitor) {
- this.threadCount++;
- threadNumber = this.threadCount;
- }
- return getThreadNamePrefix() + threadNumber;
+ return getThreadNamePrefix() + this.threadCount.incrementAndGet();
}
/**
@@ -176,11 +170,4 @@ public class CustomizableThreadCreator implements Serializable {
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
index 1337a5a5..b4be1bb2 100644
--- a/spring-core/src/main/java/org/springframework/util/DefaultPropertiesPersister.java
+++ b/spring-core/src/main/java/org/springframework/util/DefaultPropertiesPersister.java
@@ -16,15 +16,11 @@
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;
/**
@@ -38,7 +34,7 @@ import java.util.Properties;
*
* <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,
+ * the Unicode conversion as implemented by the JDK Properties class. As of JDK 1.6,
* {@code Properties.load/store} will also be used for readers/writers,
* effectively turning this class into a plain backwards compatibility adapter.
*
@@ -57,175 +53,37 @@ import java.util.Properties;
*/
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});
-
-
+ @Override
public void load(Properties props, InputStream is) throws IOException {
props.load(is);
}
+ @Override
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;
+ props.load(reader);
}
- 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();
- }
-
-
+ @Override
public void store(Properties props, OutputStream os, String header) throws IOException {
props.store(os, header);
}
+ @Override
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();
+ props.store(writer, header);
}
- 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();
- }
-
-
+ @Override
public void loadFromXml(Properties props, InputStream is) throws IOException {
props.loadFromXML(is);
}
+ @Override
public void storeToXml(Properties props, OutputStream os, String header) throws IOException {
props.storeToXML(os, header);
}
+ @Override
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/asm/util/package-info.java b/spring-core/src/main/java/org/springframework/util/IdGenerator.java
index f03a2e8d..475af0e1 100644
--- a/spring-core/src/main/java/org/springframework/asm/util/package-info.java
+++ b/spring-core/src/main/java/org/springframework/util/IdGenerator.java
@@ -1,11 +1,11 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * 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
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,22 @@
* limitations under the License.
*/
+package org.springframework.util;
+
+import java.util.UUID;
+
/**
- * Dummy implementations of asm-util classes (for internal use only).
+ * Contract for generating {@link UUID} identifiers.
*
- * @since 3.2
+ * @author Rossen Stoyanchev
+ * @since 4.0
*/
-package org.springframework.asm.util;
+public interface IdGenerator {
+
+ /**
+ * Generate a new identifier.
+ * @return the generated identifier
+ */
+ UUID generateId();
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/InvalidMimeTypeException.java b/spring-core/src/main/java/org/springframework/util/InvalidMimeTypeException.java
new file mode 100644
index 00000000..7eff6620
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/InvalidMimeTypeException.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+/**
+ * Exception thrown from {@link MimeTypeUtils#parseMimeType(String)} in case of
+ * encountering an invalid content type specification String.
+ *
+ * @author Juergen Hoeller
+ * @author Rossen Stoyanchev
+ * @since 4.0
+ */
+@SuppressWarnings("serial")
+public class InvalidMimeTypeException extends IllegalArgumentException {
+
+ private String mimeType;
+
+
+ /**
+ * Create a new InvalidContentTypeException for the given content type.
+ * @param mimeType the offending media type
+ * @param message a detail message indicating the invalid part
+ */
+ public InvalidMimeTypeException(String mimeType, String message) {
+ super("Invalid mime type \"" + mimeType + "\": " + message);
+ this.mimeType = mimeType;
+
+ }
+
+
+ /**
+ * Return the offending content type.
+ */
+ public String getMimeType() {
+ return this.mimeType;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java b/spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java
index 730047f7..f980b91c 100644
--- a/spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java
+++ b/spring-core/src/main/java/org/springframework/util/LinkedMultiValueMap.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * 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.
@@ -70,6 +70,7 @@ public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializa
// MultiValueMap implementation
+ @Override
public void add(K key, V value) {
List<V> values = this.targetMap.get(key);
if (values == null) {
@@ -79,23 +80,27 @@ public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializa
values.add(value);
}
+ @Override
public V getFirst(K key) {
List<V> values = this.targetMap.get(key);
return (values != null ? values.get(0) : null);
}
+ @Override
public void set(K key, V value) {
List<V> values = new LinkedList<V>();
values.add(value);
this.targetMap.put(key, values);
}
+ @Override
public void setAll(Map<K, V> values) {
for (Entry<K, V> entry : values.entrySet()) {
set(entry.getKey(), entry.getValue());
}
}
+ @Override
public Map<K, V> toSingleValueMap() {
LinkedHashMap<K, V> singleValueMap = new LinkedHashMap<K,V>(this.targetMap.size());
for (Entry<K, List<V>> entry : targetMap.entrySet()) {
@@ -107,50 +112,62 @@ public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializa
// Map implementation
+ @Override
public int size() {
return this.targetMap.size();
}
+ @Override
public boolean isEmpty() {
return this.targetMap.isEmpty();
}
+ @Override
public boolean containsKey(Object key) {
return this.targetMap.containsKey(key);
}
+ @Override
public boolean containsValue(Object value) {
return this.targetMap.containsValue(value);
}
+ @Override
public List<V> get(Object key) {
return this.targetMap.get(key);
}
+ @Override
public List<V> put(K key, List<V> value) {
return this.targetMap.put(key, value);
}
+ @Override
public List<V> remove(Object key) {
return this.targetMap.remove(key);
}
+ @Override
public void putAll(Map<? extends K, ? extends List<V>> m) {
this.targetMap.putAll(m);
}
+ @Override
public void clear() {
this.targetMap.clear();
}
+ @Override
public Set<K> keySet() {
return this.targetMap.keySet();
}
+ @Override
public Collection<List<V>> values() {
return this.targetMap.values();
}
+ @Override
public Set<Entry<K, List<V>>> entrySet() {
return this.targetMap.entrySet();
}
diff --git a/spring-core/src/main/java/org/springframework/util/MimeType.java b/spring-core/src/main/java/org/springframework/util/MimeType.java
new file mode 100644
index 00000000..894a0dc5
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/MimeType.java
@@ -0,0 +1,510 @@
+/*
+ * 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;
+import java.nio.charset.Charset;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeSet;
+
+/**
+ * Represents a MIME Type, as originally defined in RFC 2046 and subsequently used in
+ * other Internet protocols including HTTP. This class however does not contain support
+ * the q-parameters used in HTTP content negotiation. Those can be found in the sub-class
+ * {@code org.springframework.http.MediaType} in the {@code spring-web} module.
+ *
+ * <p>Consists of a {@linkplain #getType() type} and a {@linkplain #getSubtype() subtype}.
+ * Also has functionality to parse media types from a string using
+ * {@link #valueOf(String)}. For more parsing options see {@link MimeTypeUtils}.
+ *
+ * @author Arjen Poutsma
+ * @author Juergen Hoeller
+ * @author Rossen Stoyanchev
+ * @since 4.0
+ * @see MimeTypeUtils
+ */
+public class MimeType implements Comparable<MimeType>, Serializable {
+
+ private static final long serialVersionUID = 4085923477777865903L;
+
+ protected static final String WILDCARD_TYPE = "*";
+
+ private static final BitSet TOKEN;
+
+ private static final String PARAM_CHARSET = "charset";
+
+
+ private final String type;
+
+ private final String subtype;
+
+ private final Map<String, String> parameters;
+
+
+ static {
+ // variable names refer to RFC 2616, section 2.2
+ BitSet ctl = new BitSet(128);
+ for (int i = 0; i <= 31; i++) {
+ ctl.set(i);
+ }
+ ctl.set(127);
+
+ BitSet separators = new BitSet(128);
+ separators.set('(');
+ separators.set(')');
+ separators.set('<');
+ separators.set('>');
+ separators.set('@');
+ separators.set(',');
+ separators.set(';');
+ separators.set(':');
+ separators.set('\\');
+ separators.set('\"');
+ separators.set('/');
+ separators.set('[');
+ separators.set(']');
+ separators.set('?');
+ separators.set('=');
+ separators.set('{');
+ separators.set('}');
+ separators.set(' ');
+ separators.set('\t');
+
+ TOKEN = new BitSet(128);
+ TOKEN.set(0, 128);
+ TOKEN.andNot(ctl);
+ TOKEN.andNot(separators);
+ }
+
+
+ /**
+ * Create a new {@code MimeType} for the given primary type.
+ * <p>The {@linkplain #getSubtype() subtype} is set to "&#42;", parameters empty.
+ * @param type the primary type
+ * @throws IllegalArgumentException if any of the parameters contain illegal characters
+ */
+ public MimeType(String type) {
+ this(type, WILDCARD_TYPE);
+ }
+
+ /**
+ * Create a new {@code MimeType} for the given primary type and subtype.
+ * <p>The parameters are empty.
+ * @param type the primary type
+ * @param subtype the subtype
+ * @throws IllegalArgumentException if any of the parameters contain illegal characters
+ */
+ public MimeType(String type, String subtype) {
+ this(type, subtype, Collections.<String, String>emptyMap());
+ }
+
+ /**
+ * Create a new {@code MimeType} for the given type, subtype, and character set.
+ * @param type the primary type
+ * @param subtype the subtype
+ * @param charSet the character set
+ * @throws IllegalArgumentException if any of the parameters contain illegal characters
+ */
+ public MimeType(String type, String subtype, Charset charSet) {
+ this(type, subtype, Collections.singletonMap(PARAM_CHARSET, charSet.name()));
+ }
+
+ /**
+ * Copy-constructor that copies the type and subtype of the given {@code MimeType},
+ * and allows for different parameter.
+ * @param other the other media type
+ * @param parameters the parameters, may be {@code null}
+ * @throws IllegalArgumentException if any of the parameters contain illegal characters
+ */
+ public MimeType(MimeType other, Map<String, String> parameters) {
+ this(other.getType(), other.getSubtype(), parameters);
+ }
+
+ /**
+ * Create a new {@code MimeType} for the given type, subtype, and parameters.
+ * @param type the primary type
+ * @param subtype the subtype
+ * @param parameters the parameters, may be {@code null}
+ * @throws IllegalArgumentException if any of the parameters contain illegal characters
+ */
+ public MimeType(String type, String subtype, Map<String, String> parameters) {
+ Assert.hasLength(type, "type must not be empty");
+ Assert.hasLength(subtype, "subtype must not be empty");
+ checkToken(type);
+ checkToken(subtype);
+ this.type = type.toLowerCase(Locale.ENGLISH);
+ this.subtype = subtype.toLowerCase(Locale.ENGLISH);
+ if (!CollectionUtils.isEmpty(parameters)) {
+ Map<String, String> map = new LinkedCaseInsensitiveMap<String>(parameters.size(), Locale.ENGLISH);
+ for (Map.Entry<String, String> entry : parameters.entrySet()) {
+ String attribute = entry.getKey();
+ String value = entry.getValue();
+ checkParameters(attribute, value);
+ map.put(attribute, value);
+ }
+ this.parameters = Collections.unmodifiableMap(map);
+ }
+ else {
+ this.parameters = Collections.emptyMap();
+ }
+ }
+
+ /**
+ * Checks the given token string for illegal characters, as defined in RFC 2616,
+ * section 2.2.
+ * @throws IllegalArgumentException in case of illegal characters
+ * @see <a href="http://tools.ietf.org/html/rfc2616#section-2.2">HTTP 1.1, section 2.2</a>
+ */
+ private void checkToken(String token) {
+ for (int i=0; i < token.length(); i++ ) {
+ char ch = token.charAt(i);
+ if (!TOKEN.get(ch)) {
+ throw new IllegalArgumentException("Invalid token character '" + ch + "' in token \"" + token + "\"");
+ }
+ }
+ }
+
+ protected void checkParameters(String attribute, String value) {
+ Assert.hasLength(attribute, "parameter attribute must not be empty");
+ Assert.hasLength(value, "parameter value must not be empty");
+ checkToken(attribute);
+ if (PARAM_CHARSET.equals(attribute)) {
+ value = unquote(value);
+ Charset.forName(value);
+ }
+ else if (!isQuotedString(value)) {
+ checkToken(value);
+ }
+ }
+
+ private boolean isQuotedString(String s) {
+ if (s.length() < 2) {
+ return false;
+ }
+ else {
+ return ((s.startsWith("\"") && s.endsWith("\"")) || (s.startsWith("'") && s.endsWith("'")));
+ }
+ }
+
+ protected String unquote(String s) {
+ if (s == null) {
+ return null;
+ }
+ return isQuotedString(s) ? s.substring(1, s.length() - 1) : s;
+ }
+
+ /**
+ * Indicates whether the {@linkplain #getType() type} is the wildcard character
+ * {@code &#42;} or not.
+ */
+ public boolean isWildcardType() {
+ return WILDCARD_TYPE.equals(getType());
+ }
+
+ /**
+ * Indicates whether the {@linkplain #getSubtype() subtype} is the wildcard character
+ * {@code &#42;} or the wildcard character followed by a sufiix (e.g.
+ * {@code &#42;+xml}), or not.
+ * @return whether the subtype is {@code &#42;}
+ */
+ public boolean isWildcardSubtype() {
+ return WILDCARD_TYPE.equals(getSubtype()) || getSubtype().startsWith("*+");
+ }
+
+ /**
+ * Indicates whether this media type is concrete, i.e. whether neither the type or
+ * subtype is a wildcard character {@code &#42;}.
+ * @return whether this media type is concrete
+ */
+ public boolean isConcrete() {
+ return !isWildcardType() && !isWildcardSubtype();
+ }
+
+ /**
+ * Return the primary type.
+ */
+ public String getType() {
+ return this.type;
+ }
+
+ /**
+ * Return the subtype.
+ */
+ public String getSubtype() {
+ return this.subtype;
+ }
+
+ /**
+ * Return the character set, as indicated by a {@code charset} parameter, if any.
+ * @return the character set, or {@code null} if not available
+ */
+ public Charset getCharSet() {
+ String charSet = getParameter(PARAM_CHARSET);
+ return (charSet != null ? Charset.forName(unquote(charSet)) : null);
+ }
+
+ /**
+ * Return a generic parameter value, given a parameter name.
+ * @param name the parameter name
+ * @return the parameter value, or {@code null} if not present
+ */
+ public String getParameter(String name) {
+ return this.parameters.get(name);
+ }
+
+ /**
+ * Return all generic parameter values.
+ * @return a read-only map (possibly empty, never {@code null})
+ */
+ public Map<String, String> getParameters() {
+ return this.parameters;
+ }
+
+ /**
+ * Indicate whether this {@code MediaType} includes the given media type.
+ * <p>For instance, {@code text/*} includes {@code text/plain} and {@code text/html},
+ * and {@code application/*+xml} includes {@code application/soap+xml}, etc. This
+ * method is <b>not</b> symmetric.
+ * @param other the reference media type with which to compare
+ * @return {@code true} if this media type includes the given media type;
+ * {@code false} otherwise
+ */
+ public boolean includes(MimeType other) {
+ if (other == null) {
+ return false;
+ }
+ if (this.isWildcardType()) {
+ // */* includes anything
+ return true;
+ }
+ else if (getType().equals(other.getType())) {
+ if (getSubtype().equals(other.getSubtype())) {
+ return true;
+ }
+ if (this.isWildcardSubtype()) {
+ // wildcard with suffix, e.g. application/*+xml
+ int thisPlusIdx = getSubtype().indexOf('+');
+ if (thisPlusIdx == -1) {
+ return true;
+ }
+ else {
+ // application/*+xml includes application/soap+xml
+ int otherPlusIdx = other.getSubtype().indexOf('+');
+ if (otherPlusIdx != -1) {
+ String thisSubtypeNoSuffix = getSubtype().substring(0, thisPlusIdx);
+ String thisSubtypeSuffix = getSubtype().substring(thisPlusIdx + 1);
+ String otherSubtypeSuffix = other.getSubtype().substring(otherPlusIdx + 1);
+ if (thisSubtypeSuffix.equals(otherSubtypeSuffix) && WILDCARD_TYPE.equals(thisSubtypeNoSuffix)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Indicate whether this {@code MediaType} is compatible with the given media type.
+ * <p>For instance, {@code text/*} is compatible with {@code text/plain},
+ * {@code text/html}, and vice versa. In effect, this method is similar to
+ * {@link #includes}, except that it <b>is</b> symmetric.
+ * @param other the reference media type with which to compare
+ * @return {@code true} if this media type is compatible with the given media type;
+ * {@code false} otherwise
+ */
+ public boolean isCompatibleWith(MimeType other) {
+ if (other == null) {
+ return false;
+ }
+ if (isWildcardType() || other.isWildcardType()) {
+ return true;
+ }
+ else if (getType().equals(other.getType())) {
+ if (getSubtype().equals(other.getSubtype())) {
+ return true;
+ }
+ // wildcard with suffix? e.g. application/*+xml
+ if (this.isWildcardSubtype() || other.isWildcardSubtype()) {
+
+ int thisPlusIdx = getSubtype().indexOf('+');
+ int otherPlusIdx = other.getSubtype().indexOf('+');
+
+ if (thisPlusIdx == -1 && otherPlusIdx == -1) {
+ return true;
+ }
+ else if (thisPlusIdx != -1 && otherPlusIdx != -1) {
+ String thisSubtypeNoSuffix = getSubtype().substring(0, thisPlusIdx);
+ String otherSubtypeNoSuffix = other.getSubtype().substring(0, otherPlusIdx);
+
+ String thisSubtypeSuffix = getSubtype().substring(thisPlusIdx + 1);
+ String otherSubtypeSuffix = other.getSubtype().substring(otherPlusIdx + 1);
+
+ if (thisSubtypeSuffix.equals(otherSubtypeSuffix) &&
+ (WILDCARD_TYPE.equals(thisSubtypeNoSuffix) || WILDCARD_TYPE.equals(otherSubtypeNoSuffix))) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Compares this {@code MediaType} to another alphabetically.
+ * @param other media type to compare to
+ * @see MimeTypeUtils#sortBySpecificity(List)
+ */
+ @Override
+ public int compareTo(MimeType other) {
+ int comp = getType().compareToIgnoreCase(other.getType());
+ if (comp != 0) {
+ return comp;
+ }
+ comp = getSubtype().compareToIgnoreCase(other.getSubtype());
+ if (comp != 0) {
+ return comp;
+ }
+ comp = getParameters().size() - other.getParameters().size();
+ if (comp != 0) {
+ return comp;
+ }
+ TreeSet<String> thisAttributes = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
+ thisAttributes.addAll(getParameters().keySet());
+ TreeSet<String> otherAttributes = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
+ otherAttributes.addAll(other.getParameters().keySet());
+ Iterator<String> thisAttributesIterator = thisAttributes.iterator();
+ Iterator<String> otherAttributesIterator = otherAttributes.iterator();
+ while (thisAttributesIterator.hasNext()) {
+ String thisAttribute = thisAttributesIterator.next();
+ String otherAttribute = otherAttributesIterator.next();
+ comp = thisAttribute.compareToIgnoreCase(otherAttribute);
+ if (comp != 0) {
+ return comp;
+ }
+ String thisValue = getParameters().get(thisAttribute);
+ String otherValue = other.getParameters().get(otherAttribute);
+ if (otherValue == null) {
+ otherValue = "";
+ }
+ comp = thisValue.compareTo(otherValue);
+ if (comp != 0) {
+ return comp;
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof MimeType)) {
+ return false;
+ }
+ MimeType otherType = (MimeType) other;
+ return (this.type.equalsIgnoreCase(otherType.type) &&
+ this.subtype.equalsIgnoreCase(otherType.subtype) &&
+ this.parameters.equals(otherType.parameters));
+ }
+
+ @Override
+ public int hashCode() {
+ int result = this.type.hashCode();
+ result = 31 * result + this.subtype.hashCode();
+ result = 31 * result + this.parameters.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ appendTo(builder);
+ return builder.toString();
+ }
+
+ protected void appendTo(StringBuilder builder) {
+ builder.append(this.type);
+ builder.append('/');
+ builder.append(this.subtype);
+ appendTo(this.parameters, builder);
+ }
+
+ private void appendTo(Map<String, String> map, StringBuilder builder) {
+ for (Map.Entry<String, String> entry : map.entrySet()) {
+ builder.append(';');
+ builder.append(entry.getKey());
+ builder.append('=');
+ builder.append(entry.getValue());
+ }
+ }
+
+ /**
+ * Parse the given String value into a {@code MimeType} object,
+ * with this method name following the 'valueOf' naming convention
+ * (as supported by {@link org.springframework.core.convert.ConversionService}.
+ * @see MimeTypeUtils#parseMimeType(String)
+ */
+ public static MimeType valueOf(String value) {
+ return MimeTypeUtils.parseMimeType(value);
+ }
+
+
+ public static class SpecificityComparator<T extends MimeType> implements Comparator<T> {
+
+ @Override
+ public int compare(T mimeType1, T mimeType2) {
+ if (mimeType1.isWildcardType() && !mimeType2.isWildcardType()) { // */* < audio/*
+ return 1;
+ }
+ else if (mimeType2.isWildcardType() && !mimeType1.isWildcardType()) { // audio/* > */*
+ return -1;
+ }
+ else if (!mimeType1.getType().equals(mimeType2.getType())) { // audio/basic == text/html
+ return 0;
+ }
+ else { // mediaType1.getType().equals(mediaType2.getType())
+ if (mimeType1.isWildcardSubtype() && !mimeType2.isWildcardSubtype()) { // audio/* < audio/basic
+ return 1;
+ }
+ else if (mimeType2.isWildcardSubtype() && !mimeType1.isWildcardSubtype()) { // audio/basic > audio/*
+ return -1;
+ }
+ else if (!mimeType1.getSubtype().equals(mimeType2.getSubtype())) { // audio/basic == audio/wave
+ return 0;
+ }
+ else { // mediaType2.getSubtype().equals(mediaType2.getSubtype())
+ return compareParameters(mimeType1, mimeType2);
+ }
+ }
+ }
+
+ protected int compareParameters(T mimeType1, T mimeType2) {
+ int paramsSize1 = mimeType1.getParameters().size();
+ int paramsSize2 = mimeType2.getParameters().size();
+ return (paramsSize2 < paramsSize1 ? -1 : (paramsSize2 == paramsSize1 ? 0 : 1)); // audio/basic;level=1 < audio/basic
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java
new file mode 100644
index 00000000..2b2cd16f
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java
@@ -0,0 +1,328 @@
+/*
+ * 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.nio.charset.UnsupportedCharsetException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.util.MimeType.SpecificityComparator;
+
+/**
+ * Miscellaneous {@link MimeType} utility methods.
+ *
+ * @author Arjen Poutsma
+ * @author Rossen Stoyanchev
+ * @since 4.0
+ */
+public abstract class MimeTypeUtils {
+
+ /**
+ * Public constant mime type that includes all media ranges (i.e. "&#42;/&#42;").
+ */
+ public static final MimeType ALL;
+
+ /**
+ * A String equivalent of {@link MimeTypeUtils#ALL}.
+ */
+ public static final String ALL_VALUE = "*/*";
+
+ /**
+ * Public constant mime type for {@code application/atom+xml}.
+ */
+ public final static MimeType APPLICATION_ATOM_XML;
+
+ /**
+ * A String equivalent of {@link MimeTypeUtils#APPLICATION_ATOM_XML}.
+ */
+ public final static String APPLICATION_ATOM_XML_VALUE = "application/atom+xml";
+
+ /**
+ * Public constant mime type for {@code application/x-www-form-urlencoded}.
+ * */
+ public final static MimeType APPLICATION_FORM_URLENCODED;
+
+ /**
+ * A String equivalent of {@link MimeTypeUtils#APPLICATION_FORM_URLENCODED}.
+ */
+ public final static String APPLICATION_FORM_URLENCODED_VALUE = "application/x-www-form-urlencoded";
+
+ /**
+ * Public constant mime type for {@code application/json}.
+ * */
+ public final static MimeType APPLICATION_JSON;
+
+ /**
+ * A String equivalent of {@link MimeTypeUtils#APPLICATION_JSON}.
+ */
+ public final static String APPLICATION_JSON_VALUE = "application/json";
+
+ /**
+ * Public constant mime type for {@code application/octet-stream}.
+ * */
+ public final static MimeType APPLICATION_OCTET_STREAM;
+
+ /**
+ * A String equivalent of {@link MimeTypeUtils#APPLICATION_OCTET_STREAM}.
+ */
+ public final static String APPLICATION_OCTET_STREAM_VALUE = "application/octet-stream";
+
+ /**
+ * Public constant mime type for {@code application/xhtml+xml}.
+ * */
+ public final static MimeType APPLICATION_XHTML_XML;
+
+ /**
+ * A String equivalent of {@link MimeTypeUtils#APPLICATION_XHTML_XML}.
+ */
+ public final static String APPLICATION_XHTML_XML_VALUE = "application/xhtml+xml";
+
+ /**
+ * Public constant mime type for {@code application/xml}.
+ */
+ public final static MimeType APPLICATION_XML;
+
+ /**
+ * A String equivalent of {@link MimeTypeUtils#APPLICATION_XML}.
+ */
+ public final static String APPLICATION_XML_VALUE = "application/xml";
+
+ /**
+ * Public constant mime type for {@code image/gif}.
+ */
+ public final static MimeType IMAGE_GIF;
+
+ /**
+ * A String equivalent of {@link MimeTypeUtils#IMAGE_GIF}.
+ */
+ public final static String IMAGE_GIF_VALUE = "image/gif";
+
+ /**
+ * Public constant mime type for {@code image/jpeg}.
+ */
+ public final static MimeType IMAGE_JPEG;
+
+ /**
+ * A String equivalent of {@link MimeTypeUtils#IMAGE_JPEG}.
+ */
+ public final static String IMAGE_JPEG_VALUE = "image/jpeg";
+
+ /**
+ * Public constant mime type for {@code image/png}.
+ */
+ public final static MimeType IMAGE_PNG;
+
+ /**
+ * A String equivalent of {@link MimeTypeUtils#IMAGE_PNG}.
+ */
+ public final static String IMAGE_PNG_VALUE = "image/png";
+
+ /**
+ * Public constant mime type for {@code multipart/form-data}.
+ * */
+ public final static MimeType MULTIPART_FORM_DATA;
+
+ /**
+ * A String equivalent of {@link MimeTypeUtils#MULTIPART_FORM_DATA}.
+ */
+ public final static String MULTIPART_FORM_DATA_VALUE = "multipart/form-data";
+
+ /**
+ * Public constant mime type for {@code text/html}.
+ * */
+ public final static MimeType TEXT_HTML;
+
+ /**
+ * A String equivalent of {@link MimeTypeUtils#TEXT_HTML}.
+ */
+ public final static String TEXT_HTML_VALUE = "text/html";
+
+ /**
+ * Public constant mime type for {@code text/plain}.
+ * */
+ public final static MimeType TEXT_PLAIN;
+
+ /**
+ * A String equivalent of {@link MimeTypeUtils#TEXT_PLAIN}.
+ */
+ public final static String TEXT_PLAIN_VALUE = "text/plain";
+
+ /**
+ * Public constant mime type for {@code text/xml}.
+ * */
+ public final static MimeType TEXT_XML;
+
+ /**
+ * A String equivalent of {@link MimeTypeUtils#TEXT_XML}.
+ */
+ public final static String TEXT_XML_VALUE = "text/xml";
+
+
+ static {
+ ALL = MimeType.valueOf(ALL_VALUE);
+ APPLICATION_ATOM_XML = MimeType.valueOf(APPLICATION_ATOM_XML_VALUE);
+ APPLICATION_FORM_URLENCODED = MimeType.valueOf(APPLICATION_FORM_URLENCODED_VALUE);
+ APPLICATION_JSON = MimeType.valueOf(APPLICATION_JSON_VALUE);
+ APPLICATION_OCTET_STREAM = MimeType.valueOf(APPLICATION_OCTET_STREAM_VALUE);
+ APPLICATION_XHTML_XML = MimeType.valueOf(APPLICATION_XHTML_XML_VALUE);
+ APPLICATION_XML = MimeType.valueOf(APPLICATION_XML_VALUE);
+ IMAGE_GIF = MimeType.valueOf(IMAGE_GIF_VALUE);
+ IMAGE_JPEG = MimeType.valueOf(IMAGE_JPEG_VALUE);
+ IMAGE_PNG = MimeType.valueOf(IMAGE_PNG_VALUE);
+ MULTIPART_FORM_DATA = MimeType.valueOf(MULTIPART_FORM_DATA_VALUE);
+ TEXT_HTML = MimeType.valueOf(TEXT_HTML_VALUE);
+ TEXT_PLAIN = MimeType.valueOf(TEXT_PLAIN_VALUE);
+ TEXT_XML = MimeType.valueOf(TEXT_XML_VALUE);
+ }
+
+
+ /**
+ * Parse the given String into a single {@code MimeType}.
+ * @param mimeType the string to parse
+ * @return the mime type
+ * @throws InvalidMimeTypeException if the string cannot be parsed
+ */
+ public static MimeType parseMimeType(String mimeType) {
+ if (!StringUtils.hasLength(mimeType)) {
+ throw new InvalidMimeTypeException(mimeType, "'mimeType' must not be empty");
+ }
+ String[] parts = StringUtils.tokenizeToStringArray(mimeType, ";");
+
+ String fullType = parts[0].trim();
+ // java.net.HttpURLConnection returns a *; q=.2 Accept header
+ if (MimeType.WILDCARD_TYPE.equals(fullType)) {
+ fullType = "*/*";
+ }
+ int subIndex = fullType.indexOf('/');
+ if (subIndex == -1) {
+ throw new InvalidMimeTypeException(mimeType, "does not contain '/'");
+ }
+ if (subIndex == fullType.length() - 1) {
+ throw new InvalidMimeTypeException(mimeType, "does not contain subtype after '/'");
+ }
+ String type = fullType.substring(0, subIndex);
+ String subtype = fullType.substring(subIndex + 1, fullType.length());
+ if (MimeType.WILDCARD_TYPE.equals(type) && !MimeType.WILDCARD_TYPE.equals(subtype)) {
+ throw new InvalidMimeTypeException(mimeType, "wildcard type is legal only in '*/*' (all mime types)");
+ }
+
+ Map<String, String> parameters = null;
+ if (parts.length > 1) {
+ parameters = new LinkedHashMap<String, String>(parts.length - 1);
+ for (int i = 1; i < parts.length; i++) {
+ String parameter = parts[i];
+ int eqIndex = parameter.indexOf('=');
+ if (eqIndex != -1) {
+ String attribute = parameter.substring(0, eqIndex);
+ String value = parameter.substring(eqIndex + 1, parameter.length());
+ parameters.put(attribute, value);
+ }
+ }
+ }
+
+ try {
+ return new MimeType(type, subtype, parameters);
+ }
+ catch (UnsupportedCharsetException ex) {
+ throw new InvalidMimeTypeException(mimeType, "unsupported charset '" + ex.getCharsetName() + "'");
+ }
+ catch (IllegalArgumentException ex) {
+ throw new InvalidMimeTypeException(mimeType, ex.getMessage());
+ }
+ }
+
+ /**
+ * Parse the given, comma-separated string into a list of {@code MimeType} objects.
+ * @param mimeTypes the string to parse
+ * @return the list of mime types
+ * @throws IllegalArgumentException if the string cannot be parsed
+ */
+ public static List<MimeType> parseMimeTypes(String mimeTypes) {
+ if (!StringUtils.hasLength(mimeTypes)) {
+ return Collections.emptyList();
+ }
+ String[] tokens = mimeTypes.split(",\\s*");
+ List<MimeType> result = new ArrayList<MimeType>(tokens.length);
+ for (String token : tokens) {
+ result.add(parseMimeType(token));
+ }
+ return result;
+ }
+
+ /**
+ * Return a string representation of the given list of {@code MimeType} objects.
+ * @param mimeTypes the string to parse
+ * @return the list of mime types
+ * @throws IllegalArgumentException if the String cannot be parsed
+ */
+ public static String toString(Collection<? extends MimeType> mimeTypes) {
+ StringBuilder builder = new StringBuilder();
+ for (Iterator<? extends MimeType> iterator = mimeTypes.iterator(); iterator.hasNext();) {
+ MimeType mimeType = iterator.next();
+ mimeType.appendTo(builder);
+ if (iterator.hasNext()) {
+ builder.append(", ");
+ }
+ }
+ return builder.toString();
+ }
+
+
+ /**
+ * Sorts the given list of {@code MimeType} objects by specificity.
+ * <p>Given two mime types:
+ * <ol>
+ * <li>if either mime type has a {@linkplain MimeType#isWildcardType() wildcard type},
+ * then the mime type without the wildcard is ordered before the other.</li>
+ * <li>if the two mime types have different {@linkplain MimeType#getType() types},
+ * then they are considered equal and remain their current order.</li>
+ * <li>if either mime type has a {@linkplain MimeType#isWildcardSubtype() wildcard subtype}
+ * , then the mime type without the wildcard is sorted before the other.</li>
+ * <li>if the two mime types have different {@linkplain MimeType#getSubtype() subtypes},
+ * then they are considered equal and remain their current order.</li>
+ * <li>if the two mime types have a different amount of
+ * {@linkplain MimeType#getParameter(String) parameters}, then the mime type with the most
+ * parameters is ordered before the other.</li>
+ * </ol>
+ * <p>For example: <blockquote>audio/basic &lt; audio/* &lt; *&#047;*</blockquote>
+ * <blockquote>audio/basic;level=1 &lt; audio/basic</blockquote>
+ * <blockquote>audio/basic == text/html</blockquote> <blockquote>audio/basic ==
+ * audio/wave</blockquote>
+ * @param mimeTypes the list of mime types to be sorted
+ * @see <a href="http://tools.ietf.org/html/rfc2616#section-14.1">HTTP 1.1, section
+ * 14.1</a>
+ */
+ public static void sortBySpecificity(List<MimeType> mimeTypes) {
+ Assert.notNull(mimeTypes, "'mimeTypes' must not be null");
+ if (mimeTypes.size() > 1) {
+ Collections.sort(mimeTypes, SPECIFICITY_COMPARATOR);
+ }
+ }
+
+
+ /**
+ * Comparator used by {@link #sortBySpecificity(List)}.
+ */
+ public static final Comparator<MimeType> SPECIFICITY_COMPARATOR = new SpecificityComparator<MimeType>();
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/NumberUtils.java b/spring-core/src/main/java/org/springframework/util/NumberUtils.java
index 47f38a0b..8236c9ed 100644
--- a/spring-core/src/main/java/org/springframework/util/NumberUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/NumberUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * 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.
@@ -33,6 +33,11 @@ import java.text.ParseException;
*/
public abstract class NumberUtils {
+ private static final BigInteger LONG_MIN = BigInteger.valueOf(Long.MIN_VALUE);
+
+ private static final BigInteger LONG_MAX = BigInteger.valueOf(Long.MAX_VALUE);
+
+
/**
* Convert the given number into an instance of the given target class.
* @param number the number to convert
@@ -81,6 +86,17 @@ public abstract class NumberUtils {
return (T) new Integer(number.intValue());
}
else if (targetClass.equals(Long.class)) {
+ BigInteger bigInt = null;
+ if (number instanceof BigInteger) {
+ bigInt = (BigInteger) number;
+ }
+ else if (number instanceof BigDecimal) {
+ bigInt = ((BigDecimal) number).toBigInteger();
+ }
+ // Effectively analogous to JDK 8's BigInteger.longValueExact()
+ if (bigInt != null && (bigInt.compareTo(LONG_MIN) < 0 || bigInt.compareTo(LONG_MAX) > 0)) {
+ raiseOverflowException(number, targetClass);
+ }
return (T) new Long(number.longValue());
}
else if (targetClass.equals(BigInteger.class)) {
@@ -115,7 +131,7 @@ public abstract class NumberUtils {
* @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) {
+ 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");
}
diff --git a/spring-core/src/main/java/org/springframework/util/PropertyPlaceholderHelper.java b/spring-core/src/main/java/org/springframework/util/PropertyPlaceholderHelper.java
index 1ce878f8..b5c749fb 100644
--- a/spring-core/src/main/java/org/springframework/util/PropertyPlaceholderHelper.java
+++ b/spring-core/src/main/java/org/springframework/util/PropertyPlaceholderHelper.java
@@ -107,6 +107,7 @@ public class PropertyPlaceholderHelper {
public String replacePlaceholders(String value, final Properties properties) {
Assert.notNull(properties, "'properties' must not be null");
return replacePlaceholders(value, new PlaceholderResolver() {
+ @Override
public String resolvePlaceholder(String placeholderName) {
return properties.getProperty(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
index 502d6827..87b7c664 100644
--- a/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/ReflectionUtils.java
@@ -515,6 +515,7 @@ public abstract class ReflectionUtils {
public static Method[] getAllDeclaredMethods(Class<?> leafClass) throws IllegalArgumentException {
final List<Method> methods = new ArrayList<Method>(32);
doWithMethods(leafClass, new MethodCallback() {
+ @Override
public void doWith(Method method) {
methods.add(method);
}
@@ -530,6 +531,7 @@ public abstract class ReflectionUtils {
public static Method[] getUniqueDeclaredMethods(Class<?> leafClass) throws IllegalArgumentException {
final List<Method> methods = new ArrayList<Method>(32);
doWithMethods(leafClass, new MethodCallback() {
+ @Override
public void doWith(Method method) {
boolean knownSignature = false;
Method methodBeingOverriddenWithCovariantReturnType = null;
@@ -630,6 +632,7 @@ public abstract class ReflectionUtils {
"] must be same or subclass as source class [" + src.getClass().getName() + "]");
}
doWithFields(src.getClass(), new FieldCallback() {
+ @Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
makeAccessible(field);
Object srcValue = field.get(src);
@@ -696,6 +699,7 @@ public abstract class ReflectionUtils {
*/
public static FieldFilter COPYABLE_FIELDS = new FieldFilter() {
+ @Override
public boolean matches(Field field) {
return !(Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers()));
}
@@ -707,6 +711,7 @@ public abstract class ReflectionUtils {
*/
public static MethodFilter NON_BRIDGED_METHODS = new MethodFilter() {
+ @Override
public boolean matches(Method method) {
return !method.isBridge();
}
@@ -719,6 +724,7 @@ public abstract class ReflectionUtils {
*/
public static MethodFilter USER_DECLARED_METHODS = new MethodFilter() {
+ @Override
public boolean matches(Method method) {
return (!method.isBridge() && method.getDeclaringClass() != Object.class);
}
diff --git a/spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java b/spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java
new file mode 100644
index 00000000..78f29574
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java
@@ -0,0 +1,91 @@
+/*
+ * 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.ByteArrayOutputStream;
+
+/**
+ * An extension of {@link java.io.ByteArrayOutputStream} that:
+ * <ul>
+ * <li>has public {@link org.springframework.util.ResizableByteArrayOutputStream#grow(int)}
+ * and {@link org.springframework.util.ResizableByteArrayOutputStream#resize(int)} methods
+ * to get more control over the the size of the internal buffer</li>
+ * <li>has a higher initial capacity (256) by default</li>
+ * </ul>
+ *
+ * @author Brian Clozel
+ * @author Juergen Hoeller
+ * @since 4.0.3
+ */
+public class ResizableByteArrayOutputStream extends ByteArrayOutputStream {
+
+ private static final int DEFAULT_INITIAL_CAPACITY = 256;
+
+
+ /**
+ * Create a new <code>ResizableByteArrayOutputStream</code>
+ * with the default initial capacity of 128 bytes.
+ */
+ public ResizableByteArrayOutputStream() {
+ super(DEFAULT_INITIAL_CAPACITY);
+ }
+
+ /**
+ * Create a new <code>ResizableByteArrayOutputStream</code>
+ * with the specified initial capacity.
+ * @param initialCapacity the initial buffer size in bytes
+ */
+ public ResizableByteArrayOutputStream(int initialCapacity) {
+ super(initialCapacity);
+ }
+
+
+ /**
+ * Resize the internal buffer size to a specified capacity.
+ * @param targetCapacity the desired size of the buffer
+ * @throws IllegalArgumentException if the given capacity is smaller than
+ * the actual size of the content stored in the buffer already
+ * @see ResizableByteArrayOutputStream#size()
+ */
+ public synchronized void resize(int targetCapacity) {
+ Assert.isTrue(targetCapacity >= this.count, "New capacity must not be smaller than current size");
+ byte[] resizedBuffer = new byte[targetCapacity];
+ System.arraycopy(this.buf, 0, resizedBuffer, 0, this.count);
+ this.buf = resizedBuffer;
+ }
+
+ /**
+ * Grow the internal buffer size.
+ * @param additionalCapacity the number of bytes to add to the current buffer size
+ * @see ResizableByteArrayOutputStream#size()
+ */
+ public synchronized void grow(int additionalCapacity) {
+ Assert.isTrue(additionalCapacity >= 0, "Additional capacity must be 0 or higher");
+ if (this.count + additionalCapacity > this.buf.length) {
+ int newCapacity = Math.max(this.buf.length * 2, this.count + additionalCapacity);
+ resize(newCapacity);
+ }
+ }
+
+ /**
+ * Return the current size of this stream's internal buffer.
+ */
+ public synchronized int capacity() {
+ return this.buf.length;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/ResourceUtils.java b/spring-core/src/main/java/org/springframework/util/ResourceUtils.java
index ce552eb0..855a8ca5 100644
--- a/spring-core/src/main/java/org/springframework/util/ResourceUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/ResourceUtils.java
@@ -78,9 +78,6 @@ public abstract class ResourceUtils {
/** 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 = "!/";
@@ -266,18 +263,14 @@ public abstract class ResourceUtils {
/**
* 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.
+ * that is, has protocol "jar", "zip", "vfszip" or "wsjar".
* @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)));
+ URL_PROTOCOL_VFSZIP.equals(protocol) || URL_PROTOCOL_WSJAR.equals(protocol));
}
/**
diff --git a/spring-core/src/main/java/org/springframework/util/SocketUtils.java b/spring-core/src/main/java/org/springframework/util/SocketUtils.java
new file mode 100644
index 00000000..bd451250
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/SocketUtils.java
@@ -0,0 +1,304 @@
+/*
+ * 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.net.DatagramSocket;
+import java.net.ServerSocket;
+import java.util.Random;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import javax.net.ServerSocketFactory;
+
+/**
+ * Simple utility methods for working with network sockets &mdash; for example,
+ * for finding available ports on {@code localhost}.
+ *
+ * <p>Within this class, a TCP port refers to a port for a {@link ServerSocket};
+ * whereas, a UDP port refers to a port for a {@link DatagramSocket}.
+ *
+ * @author Sam Brannen
+ * @author Ben Hale
+ * @author Arjen Poutsma
+ * @author Gunnar Hillert
+ * @since 4.0
+ */
+public class SocketUtils {
+
+ /**
+ * The default minimum value for port ranges used when finding an available
+ * socket port.
+ */
+ public static final int PORT_RANGE_MIN = 1024;
+
+ /**
+ * The default maximum value for port ranges used when finding an available
+ * socket port.
+ */
+ public static final int PORT_RANGE_MAX = 65535;
+
+
+ private static final Random random = new Random(System.currentTimeMillis());
+
+
+ /**
+ * Although {@code SocketUtils} consists solely of static utility methods,
+ * this constructor is intentionally {@code public}.
+ * <h4>Rationale</h4>
+ * <p>Static methods from this class may be invoked from within XML
+ * configuration files using the Spring Expression Language (SpEL) and the
+ * following syntax.
+ * <pre><code>&lt;bean id="bean1" ... p:port="#{T(org.springframework.util.SocketUtils).findAvailableTcpPort(12000)}" /&gt;</code></pre>
+ * If this constructor were {@code private}, you would be required to supply
+ * the fully qualified class name to SpEL's {@code T()} function for each usage.
+ * Thus, the fact that this constructor is {@code public} allows you to reduce
+ * boilerplate configuration with SpEL as can be seen in the following example.
+ * <pre><code>&lt;bean id="socketUtils" class="org.springframework.util.SocketUtils" /&gt;
+ * &lt;bean id="bean1" ... p:port="#{socketUtils.findAvailableTcpPort(12000)}" /&gt;
+ * &lt;bean id="bean2" ... p:port="#{socketUtils.findAvailableTcpPort(30000)}" /&gt;</code></pre>
+ */
+ public SocketUtils() {
+ /* no-op */
+ }
+
+
+ /**
+ * Find an available TCP port randomly selected from the range
+ * [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}].
+ * @return an available TCP port number
+ * @throws IllegalStateException if no available port could be found
+ */
+ public static int findAvailableTcpPort() {
+ return findAvailableTcpPort(PORT_RANGE_MIN);
+ }
+
+ /**
+ * Find an available TCP port randomly selected from the range
+ * [{@code minPort}, {@value #PORT_RANGE_MAX}].
+ * @param minPort the minimum port number
+ * @return an available TCP port number
+ * @throws IllegalStateException if no available port could be found
+ */
+ public static int findAvailableTcpPort(int minPort) {
+ return findAvailableTcpPort(minPort, PORT_RANGE_MAX);
+ }
+
+ /**
+ * Find an available TCP port randomly selected from the range
+ * [{@code minPort}, {@code maxPort}].
+ * @param minPort the minimum port number
+ * @param maxPort the maximum port number
+ * @return an available TCP port number
+ * @throws IllegalStateException if no available port could be found
+ */
+ public static int findAvailableTcpPort(int minPort, int maxPort) {
+ return SocketType.TCP.findAvailablePort(minPort, maxPort);
+ }
+
+ /**
+ * Find the requested number of available TCP ports, each randomly selected
+ * from the range [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}].
+ * @param numRequested the number of available ports to find
+ * @return a sorted set of available TCP port numbers
+ * @throws IllegalStateException if the requested number of available ports could not be found
+ */
+ public static SortedSet<Integer> findAvailableTcpPorts(int numRequested) {
+ return findAvailableTcpPorts(numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX);
+ }
+
+ /**
+ * Find the requested number of available TCP ports, each randomly selected
+ * from the range [{@code minPort}, {@code maxPort}].
+ * @param numRequested the number of available ports to find
+ * @param minPort the minimum port number
+ * @param maxPort the maximum port number
+ * @return a sorted set of available TCP port numbers
+ * @throws IllegalStateException if the requested number of available ports could not be found
+ */
+ public static SortedSet<Integer> findAvailableTcpPorts(int numRequested, int minPort, int maxPort) {
+ return SocketType.TCP.findAvailablePorts(numRequested, minPort, maxPort);
+ }
+
+ /**
+ * Find an available UDP port randomly selected from the range
+ * [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}].
+ * @return an available UDP port number
+ * @throws IllegalStateException if no available port could be found
+ */
+ public static int findAvailableUdpPort() {
+ return findAvailableUdpPort(PORT_RANGE_MIN);
+ }
+
+ /**
+ * Find an available UDP port randomly selected from the range
+ * [{@code minPort}, {@value #PORT_RANGE_MAX}].
+ * @param minPort the minimum port number
+ * @return an available UDP port number
+ * @throws IllegalStateException if no available port could be found
+ */
+ public static int findAvailableUdpPort(int minPort) {
+ return findAvailableUdpPort(minPort, PORT_RANGE_MAX);
+ }
+
+ /**
+ * Find an available UDP port randomly selected from the range
+ * [{@code minPort}, {@code maxPort}].
+ * @param minPort the minimum port number
+ * @param maxPort the maximum port number
+ * @return an available UDP port number
+ * @throws IllegalStateException if no available port could be found
+ */
+ public static int findAvailableUdpPort(int minPort, int maxPort) {
+ return SocketType.UDP.findAvailablePort(minPort, maxPort);
+ }
+
+ /**
+ * Find the requested number of available UDP ports, each randomly selected
+ * from the range [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}].
+ * @param numRequested the number of available ports to find
+ * @return a sorted set of available UDP port numbers
+ * @throws IllegalStateException if the requested number of available ports could not be found
+ */
+ public static SortedSet<Integer> findAvailableUdpPorts(int numRequested) {
+ return findAvailableUdpPorts(numRequested, PORT_RANGE_MIN, PORT_RANGE_MAX);
+ }
+
+ /**
+ * Find the requested number of available UDP ports, each randomly selected
+ * from the range [{@code minPort}, {@code maxPort}].
+ * @param numRequested the number of available ports to find
+ * @param minPort the minimum port number
+ * @param maxPort the maximum port number
+ * @return a sorted set of available UDP port numbers
+ * @throws IllegalStateException if the requested number of available ports could not be found
+ */
+ public static SortedSet<Integer> findAvailableUdpPorts(int numRequested, int minPort, int maxPort) {
+ return SocketType.UDP.findAvailablePorts(numRequested, minPort, maxPort);
+ }
+
+
+ private static enum SocketType {
+
+ TCP {
+ @Override
+ protected boolean isPortAvailable(int port) {
+ try {
+ ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(port);
+ serverSocket.close();
+ return true;
+ }
+ catch (Exception ex) {
+ return false;
+ }
+ }
+ },
+
+ UDP {
+ @Override
+ protected boolean isPortAvailable(int port) {
+ try {
+ DatagramSocket socket = new DatagramSocket(port);
+ socket.close();
+ return true;
+ }
+ catch (Exception ex) {
+ return false;
+ }
+ }
+ };
+
+ /**
+ * Determine if the specified port for this {@code SocketType} is
+ * currently available on {@code localhost}.
+ */
+ protected abstract boolean isPortAvailable(int port);
+
+ /**
+ * Find a pseudo-random port number within the range
+ * [{@code minPort}, {@code maxPort}].
+ * @param minPort the minimum port number
+ * @param maxPort the maximum port number
+ * @return a random port number within the specified range
+ */
+ private int findRandomPort(int minPort, int maxPort) {
+ int portRange = maxPort - minPort;
+ return minPort + random.nextInt(portRange);
+ }
+
+ /**
+ * Find an available port for this {@code SocketType}, randomly selected
+ * from the range [{@code minPort}, {@code maxPort}].
+ * @param minPort the minimum port number
+ * @param maxPort the maximum port number
+ * @return an available port number for this socket type
+ * @throws IllegalStateException if no available port could be found
+ */
+ int findAvailablePort(int minPort, int maxPort) {
+ Assert.isTrue(minPort > 0, "'minPort' must be greater than 0");
+ Assert.isTrue(maxPort > minPort, "'maxPort' must be greater than 'minPort'");
+ Assert.isTrue(maxPort <= PORT_RANGE_MAX, "'maxPort' must be less than or equal to " + PORT_RANGE_MAX);
+
+ int portRange = maxPort - minPort;
+ int candidatePort;
+ int searchCounter = 0;
+ do {
+ if (++searchCounter > portRange) {
+ throw new IllegalStateException(String.format(
+ "Could not find an available %s port in the range [%d, %d] after %d attempts", name(), minPort,
+ maxPort, searchCounter));
+ }
+ candidatePort = findRandomPort(minPort, maxPort);
+ }
+ while (!isPortAvailable(candidatePort));
+
+ return candidatePort;
+ }
+
+ /**
+ * Find the requested number of available ports for this {@code SocketType},
+ * each randomly selected from the range [{@code minPort}, {@code maxPort}].
+ * @param numRequested the number of available ports to find
+ * @param minPort the minimum port number
+ * @param maxPort the maximum port number
+ * @return a sorted set of available port numbers for this socket type
+ * @throws IllegalStateException if the requested number of available ports could not be found
+ */
+ SortedSet<Integer> findAvailablePorts(int numRequested, int minPort, int maxPort) {
+ Assert.isTrue(minPort > 0, "'minPort' must be greater than 0");
+ Assert.isTrue(maxPort > minPort, "'maxPort' must be greater than 'minPort'");
+ Assert.isTrue(maxPort <= PORT_RANGE_MAX, "'maxPort' must be less than or equal to " + PORT_RANGE_MAX);
+ Assert.isTrue(numRequested > 0, "'numRequested' must be greater than 0");
+ Assert.isTrue((maxPort - minPort) >= numRequested,
+ "'numRequested' must not be greater than 'maxPort' - 'minPort'");
+
+ final SortedSet<Integer> availablePorts = new TreeSet<Integer>();
+ int attemptCount = 0;
+ while ((++attemptCount <= numRequested + 100) && (availablePorts.size() < numRequested)) {
+ availablePorts.add(findAvailablePort(minPort, maxPort));
+ }
+
+ if (availablePorts.size() != numRequested) {
+ throw new IllegalStateException(String.format(
+ "Could not find %d available %s ports in the range [%d, %d]", numRequested, name(), minPort,
+ maxPort));
+ }
+
+ return availablePorts;
+ }
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/StreamUtils.java b/spring-core/src/main/java/org/springframework/util/StreamUtils.java
index cc3107d8..0556c71e 100644
--- a/spring-core/src/main/java/org/springframework/util/StreamUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/StreamUtils.java
@@ -27,7 +27,6 @@ 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
diff --git a/spring-core/src/main/java/org/springframework/util/StringUtils.java b/spring-core/src/main/java/org/springframework/util/StringUtils.java
index 0ad561da..9ed12a1e 100644
--- a/spring-core/src/main/java/org/springframework/util/StringUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/StringUtils.java
@@ -28,6 +28,7 @@ import java.util.Locale;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
+import java.util.TimeZone;
import java.util.TreeSet;
/**
@@ -724,6 +725,22 @@ public abstract class StringUtils {
return locale.getLanguage() + (hasText(locale.getCountry()) ? "-" + locale.getCountry() : "");
}
+ /**
+ * Parse the given {@code timeZoneString} value into a {@link TimeZone}.
+ * @param timeZoneString the time zone String, following {@link TimeZone#getTimeZone(String)}
+ * but throwing {@link IllegalArgumentException} in case of an invalid time zone specification
+ * @return a corresponding {@link TimeZone} instance
+ * @throws IllegalArgumentException in case of an invalid time zone specification
+ */
+ public static TimeZone parseTimeZoneString(String timeZoneString) {
+ TimeZone timeZone = TimeZone.getTimeZone(timeZoneString);
+ if ("GMT".equals(timeZone.getID()) && !timeZoneString.startsWith("GMT")) {
+ // We don't want that GMT fallback...
+ throw new IllegalArgumentException("Invalid time zone specification '" + timeZoneString + "'");
+ }
+ return timeZone;
+ }
+
//---------------------------------------------------------------------
// Convenience methods for working with String arrays
diff --git a/spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java b/spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java
index 6c3acd7e..23ddd15c 100644
--- a/spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/SystemPropertyUtils.java
@@ -93,6 +93,7 @@ public abstract class SystemPropertyUtils {
this.text = text;
}
+ @Override
public String resolvePlaceholder(String placeholderName) {
try {
String propVal = System.getProperty(placeholderName);
diff --git a/spring-core/src/main/java/org/springframework/util/WeakReferenceMonitor.java b/spring-core/src/main/java/org/springframework/util/WeakReferenceMonitor.java
index ad2e01c5..1a486757 100644
--- a/spring-core/src/main/java/org/springframework/util/WeakReferenceMonitor.java
+++ b/spring-core/src/main/java/org/springframework/util/WeakReferenceMonitor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2010 the original author or authors.
+ * 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.
@@ -53,7 +53,7 @@ public class WeakReferenceMonitor {
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>();
+ private static final Map<Reference<?>, ReleaseListener> trackedEntries = new HashMap<Reference<?>, ReleaseListener>();
// Thread polling handleQueue, lazy initialized
private static Thread monitoringThread = null;
@@ -84,7 +84,7 @@ public class WeakReferenceMonitor {
* @param ref reference to tracked handle
* @param entry the associated entry
*/
- private static void addEntry(Reference ref, ReleaseListener entry) {
+ private static void addEntry(Reference<?> ref, ReleaseListener entry) {
synchronized (WeakReferenceMonitor.class) {
// Add entry, the key is given reference.
trackedEntries.put(ref, entry);
@@ -103,7 +103,7 @@ public class WeakReferenceMonitor {
* @param reference the reference that should be removed
* @return entry object associated with given reference
*/
- private static ReleaseListener removeEntry(Reference reference) {
+ private static ReleaseListener removeEntry(Reference<?> reference) {
synchronized (WeakReferenceMonitor.class) {
return trackedEntries.remove(reference);
}
@@ -132,12 +132,13 @@ public class WeakReferenceMonitor {
*/
private static class MonitoringProcess implements Runnable {
+ @Override
public void run() {
logger.debug("Starting reference monitor thread");
// Check if there are any tracked entries left.
while (keepMonitoringThreadAlive()) {
try {
- Reference reference = handleQueue.remove();
+ Reference<?> reference = handleQueue.remove();
// Stop tracking this reference.
ReleaseListener entry = removeEntry(reference);
if (entry != null) {
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
index 6fd0c229..ec68351e 100644
--- a/spring-core/src/main/java/org/springframework/util/comparator/BooleanComparator.java
+++ b/spring-core/src/main/java/org/springframework/util/comparator/BooleanComparator.java
@@ -59,6 +59,7 @@ public final class BooleanComparator implements Comparator<Boolean>, Serializabl
}
+ @Override
public int compare(Boolean v1, Boolean v2) {
return (v1 ^ v2) ? ((v1 ^ this.trueLow) ? 1 : -1) : 0;
}
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
index 40e4e7af..b8b27418 100644
--- a/spring-core/src/main/java/org/springframework/util/comparator/ComparableComparator.java
+++ b/spring-core/src/main/java/org/springframework/util/comparator/ComparableComparator.java
@@ -32,6 +32,7 @@ public class ComparableComparator<T extends Comparable<T>> implements Comparator
@SuppressWarnings("rawtypes")
public static final ComparableComparator INSTANCE = new ComparableComparator();
+ @Override
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
index ba56bd54..a20eab18 100644
--- a/spring-core/src/main/java/org/springframework/util/comparator/CompoundComparator.java
+++ b/spring-core/src/main/java/org/springframework/util/comparator/CompoundComparator.java
@@ -37,10 +37,10 @@ import org.springframework.util.Assert;
* @author Juergen Hoeller
* @since 1.2.2
*/
-@SuppressWarnings("serial")
+@SuppressWarnings({ "serial", "rawtypes" })
public class CompoundComparator<T> implements Comparator<T>, Serializable {
- private final List<InvertibleComparator<T>> comparators;
+ private final List<InvertibleComparator> comparators;
/**
@@ -49,7 +49,7 @@ public class CompoundComparator<T> implements Comparator<T>, Serializable {
* IllegalStateException is thrown.
*/
public CompoundComparator() {
- this.comparators = new ArrayList<InvertibleComparator<T>>();
+ this.comparators = new ArrayList<InvertibleComparator>();
}
/**
@@ -62,7 +62,7 @@ public class CompoundComparator<T> implements Comparator<T>, Serializable {
@SuppressWarnings({ "unchecked", "rawtypes" })
public CompoundComparator(Comparator... comparators) {
Assert.notNull(comparators, "Comparators must not be null");
- this.comparators = new ArrayList<InvertibleComparator<T>>(comparators.length);
+ this.comparators = new ArrayList<InvertibleComparator>(comparators.length);
for (Comparator comparator : comparators) {
this.addComparator(comparator);
}
@@ -76,12 +76,13 @@ public class CompoundComparator<T> implements Comparator<T>, Serializable {
* @param comparator the Comparator to add to the end of the chain
* @see InvertibleComparator
*/
- public void addComparator(Comparator<T> comparator) {
+ @SuppressWarnings("unchecked")
+ public void addComparator(Comparator<? extends T> comparator) {
if (comparator instanceof InvertibleComparator) {
- this.comparators.add((InvertibleComparator<T>) comparator);
+ this.comparators.add((InvertibleComparator) comparator);
}
else {
- this.comparators.add(new InvertibleComparator<T>(comparator));
+ this.comparators.add(new InvertibleComparator(comparator));
}
}
@@ -90,8 +91,9 @@ public class CompoundComparator<T> implements Comparator<T>, Serializable {
* @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));
+ @SuppressWarnings("unchecked")
+ public void addComparator(Comparator<? extends T> comparator, boolean ascending) {
+ this.comparators.add(new InvertibleComparator(comparator, ascending));
}
/**
@@ -102,12 +104,13 @@ public class CompoundComparator<T> implements Comparator<T>, Serializable {
* @param comparator the Comparator to place at the given index
* @see InvertibleComparator
*/
- public void setComparator(int index, Comparator<T> comparator) {
+ @SuppressWarnings("unchecked")
+ public void setComparator(int index, Comparator<? extends T> comparator) {
if (comparator instanceof InvertibleComparator) {
- this.comparators.set(index, (InvertibleComparator<T>) comparator);
+ this.comparators.set(index, (InvertibleComparator) comparator);
}
else {
- this.comparators.set(index, new InvertibleComparator<T>(comparator));
+ this.comparators.set(index, new InvertibleComparator(comparator));
}
}
@@ -126,7 +129,7 @@ public class CompoundComparator<T> implements Comparator<T>, Serializable {
* comparator.
*/
public void invertOrder() {
- for (InvertibleComparator<T> comparator : this.comparators) {
+ for (InvertibleComparator comparator : this.comparators) {
comparator.invertOrder();
}
}
@@ -162,10 +165,12 @@ public class CompoundComparator<T> implements Comparator<T>, Serializable {
return this.comparators.size();
}
+ @Override
+ @SuppressWarnings("unchecked")
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) {
+ for (InvertibleComparator comparator : this.comparators) {
int result = comparator.compare(o1, o2);
if (result != 0) {
return result;
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
index f29310e2..35d324ee 100644
--- a/spring-core/src/main/java/org/springframework/util/comparator/InstanceComparator.java
+++ b/spring-core/src/main/java/org/springframework/util/comparator/InstanceComparator.java
@@ -51,6 +51,7 @@ public class InstanceComparator<T> implements Comparator<T> {
}
+ @Override
public int compare(T o1, T o2) {
int i1 = getOrder(o1);
int i2 = getOrder(o2);
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
index f78e56ff..9a9f5f9c 100644
--- a/spring-core/src/main/java/org/springframework/util/comparator/InvertibleComparator.java
+++ b/spring-core/src/main/java/org/springframework/util/comparator/InvertibleComparator.java
@@ -84,6 +84,7 @@ public class InvertibleComparator<T> implements Comparator<T>, Serializable {
}
+ @Override
public int compare(T o1, T o2) {
int result = this.comparator.compare(o1, o2);
if (result != 0) {
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
index 1171bdc1..f18b374d 100644
--- a/spring-core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java
+++ b/spring-core/src/main/java/org/springframework/util/comparator/NullSafeComparator.java
@@ -64,7 +64,7 @@ public class NullSafeComparator<T> implements Comparator<T> {
* @see #NULLS_LOW
* @see #NULLS_HIGH
*/
- @SuppressWarnings({ "unchecked"})
+ @SuppressWarnings({ "unchecked", "rawtypes"})
private NullSafeComparator(boolean nullsLow) {
this.nonNullComparator = new ComparableComparator();
this.nullsLow = nullsLow;
@@ -86,6 +86,7 @@ public class NullSafeComparator<T> implements Comparator<T> {
}
+ @Override
public int compare(T o1, T o2) {
if (o1 == o2) {
return 0;
diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/FutureAdapter.java b/spring-core/src/main/java/org/springframework/util/concurrent/FutureAdapter.java
new file mode 100644
index 00000000..b0c6e182
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/FutureAdapter.java
@@ -0,0 +1,125 @@
+/*
+ * 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.concurrent;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.springframework.util.Assert;
+
+/**
+ * Abstract class that adapts a {@link Future} parameterized over S into a {@code
+ * Future} parameterized over T. All methods are delegated to the adaptee, where {@link
+ * #get()} and {@link #get(long, TimeUnit)} call {@link #adapt(Object)} on the adaptee's
+ * result.
+ *
+ * @author Arjen Poutsma
+ * @since 4.0
+ * @param <T> the type of this {@code Future}
+ * @param <S> the type of the adaptee's {@code Future}
+ */
+public abstract class FutureAdapter<T, S> implements Future<T> {
+
+ private final Future<S> adaptee;
+
+ private Object result = null;
+
+ private State state = State.NEW;
+
+ private final Object mutex = new Object();
+
+
+ /**
+ * Constructs a new {@code FutureAdapter} with the given adaptee.
+ * @param adaptee the future to delegate to
+ */
+ protected FutureAdapter(Future<S> adaptee) {
+ Assert.notNull(adaptee, "'delegate' must not be null");
+ this.adaptee = adaptee;
+ }
+
+
+ /**
+ * Returns the adaptee.
+ */
+ protected Future<S> getAdaptee() {
+ return adaptee;
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ return adaptee.cancel(mayInterruptIfRunning);
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return adaptee.isCancelled();
+ }
+
+ @Override
+ public boolean isDone() {
+ return adaptee.isDone();
+ }
+
+ @Override
+ public T get() throws InterruptedException, ExecutionException {
+ return adaptInternal(adaptee.get());
+ }
+
+ @Override
+ public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+ return adaptInternal(this.adaptee.get(timeout, unit));
+ }
+
+ @SuppressWarnings("unchecked")
+ final T adaptInternal(S adapteeResult) throws ExecutionException {
+ synchronized (this.mutex) {
+ switch (this.state) {
+ case SUCCESS:
+ return (T) this.result;
+ case FAILURE:
+ throw (ExecutionException) this.result;
+ case NEW:
+ try {
+ T adapted = adapt(adapteeResult);
+ this.result = adapted;
+ this.state = State.SUCCESS;
+ return adapted;
+ }
+ catch (ExecutionException ex) {
+ this.result = ex;
+ this.state = State.FAILURE;
+ throw ex;
+ }
+ default:
+ throw new IllegalStateException();
+ }
+ }
+ }
+
+ /**
+ * Adapts the given adaptee's result into T.
+ * @return the adapted result
+ */
+ protected abstract T adapt(S adapteeResult) throws ExecutionException;
+
+
+ private enum State {NEW, SUCCESS, FAILURE}
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java
new file mode 100644
index 00000000..f95b48c0
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java
@@ -0,0 +1,40 @@
+/*
+ * 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.concurrent;
+
+import java.util.concurrent.Future;
+
+/**
+ * Extends the {@link Future} interface with the capability to accept completion
+ * callbacks. If the future has already completed when the callback is added, the
+ * callback will be triggered immediately.
+ * <p>Inspired by {@code com.google.common.util.concurrent.ListenableFuture}.
+
+ * @author Arjen Poutsma
+ * @since 4.0
+ */
+public interface ListenableFuture<T> extends Future<T> {
+
+ /**
+ * Registers the given callback to this {@code ListenableFuture}. The callback will
+ * be triggered when this {@code Future} is complete or, if it is already complete,
+ * immediately.
+ * @param callback the callback to register
+ */
+ void addCallback(ListenableFutureCallback<? super T> callback);
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureAdapter.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureAdapter.java
new file mode 100644
index 00000000..804a2a90
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureAdapter.java
@@ -0,0 +1,67 @@
+/*
+ * 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.concurrent;
+
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Abstract class that adapts a {@link ListenableFuture} parameterized over S into a
+ * {@code ListenableFuture} parameterized over T. All methods are delegated to the
+ * adaptee, where {@link #get()}, {@link #get(long, java.util.concurrent.TimeUnit)},
+ * and {@link ListenableFutureCallback#onSuccess(Object)} call {@link #adapt(Object)}
+ * on the adaptee's result.
+ *
+ * @param <T> the type of this {@code Future}
+ * @param <S> the type of the adaptee's {@code Future}
+ * @author Arjen Poutsma
+ * @since 4.0
+ */
+public abstract class ListenableFutureAdapter<T, S> extends FutureAdapter<T, S> implements ListenableFuture<T> {
+
+ /**
+ * Construct a new {@code ListenableFutureAdapter} with the given adaptee.
+ * @param adaptee the future to adapt to
+ */
+ protected ListenableFutureAdapter(ListenableFuture<S> adaptee) {
+ super(adaptee);
+ }
+
+ @Override
+ public void addCallback(final ListenableFutureCallback<? super T> callback) {
+ ListenableFuture<S> listenableAdaptee = (ListenableFuture<S>) getAdaptee();
+ listenableAdaptee.addCallback(new ListenableFutureCallback<S>() {
+ @Override
+ public void onSuccess(S result) {
+ try {
+ callback.onSuccess(adaptInternal(result));
+ }
+ catch (ExecutionException ex) {
+ Throwable cause = ex.getCause();
+ onFailure(cause != null ? cause : ex);
+ }
+ catch (Throwable ex) {
+ onFailure(ex);
+ }
+ }
+ @Override
+ public void onFailure(Throwable ex) {
+ callback.onFailure(ex);
+ }
+ });
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/asm/util/TraceClassVisitor.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java
index bf5900fb..924c4439 100644
--- a/spring-core/src/main/java/org/springframework/asm/util/TraceClassVisitor.java
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * 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.
@@ -14,24 +14,27 @@
* limitations under the License.
*/
-package org.springframework.asm.util;
-
-import java.io.PrintWriter;
-
-import org.springframework.asm.ClassVisitor;
-import org.springframework.asm.SpringAsmInfo;
+package org.springframework.util.concurrent;
/**
- * Dummy implementation of missing TraceClassVisitor from cglib-nodep's internally
- * repackaged ASM library, added to avoid NoClassDefFoundErrors.
+ * Defines the contract for callbacks that accept the result of a
+ * {@link ListenableFuture}.
*
- * @author Chris Beams
- * @since 3.2
+ * @author Arjen Poutsma
+ * @since 4.0
*/
-public class TraceClassVisitor extends ClassVisitor {
+public interface ListenableFutureCallback<T> {
+
+ /**
+ * Called when the {@link ListenableFuture} successfully completes.
+ * @param result the result
+ */
+ void onSuccess(T result);
- public TraceClassVisitor(Object object, PrintWriter pw) {
- super(SpringAsmInfo.ASM_VERSION);
- }
+ /**
+ * Called when the {@link ListenableFuture} fails to complete.
+ * @param ex the exception that triggered the failure
+ */
+ void onFailure(Throwable ex);
}
diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.java
new file mode 100644
index 00000000..ec328f14
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.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.util.concurrent;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+import org.springframework.util.Assert;
+
+/**
+ * Registry for {@link ListenableFutureCallback} instances.
+ *
+ * <p>Inspired by {@code com.google.common.util.concurrent.ExecutionList}.
+ *
+ * @author Arjen Poutsma
+ * @since 4.0
+ */
+public class ListenableFutureCallbackRegistry<T> {
+
+ private final Queue<ListenableFutureCallback<? super T>> callbacks =
+ new LinkedList<ListenableFutureCallback<? super T>>();
+
+ private State state = State.NEW;
+
+ private Object result = null;
+
+ private final Object mutex = new Object();
+
+
+ /**
+ * Add the given callback to this registry.
+ * @param callback the callback to add
+ */
+ @SuppressWarnings("unchecked")
+ public void addCallback(ListenableFutureCallback<? super T> callback) {
+ Assert.notNull(callback, "'callback' must not be null");
+ synchronized (this.mutex) {
+ switch (this.state) {
+ case NEW:
+ this.callbacks.add(callback);
+ break;
+ case SUCCESS:
+ callback.onSuccess((T) this.result);
+ break;
+ case FAILURE:
+ callback.onFailure((Throwable) this.result);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Trigger a {@link ListenableFutureCallback#onSuccess(Object)} call on all
+ * added callbacks with the given result.
+ * @param result the result to trigger the callbacks with
+ */
+ public void success(T result) {
+ synchronized (this.mutex) {
+ this.state = State.SUCCESS;
+ this.result = result;
+ while (!this.callbacks.isEmpty()) {
+ this.callbacks.poll().onSuccess(result);
+ }
+ }
+ }
+
+ /**
+ * Trigger a {@link ListenableFutureCallback#onFailure(Throwable)} call on all
+ * added callbacks with the given {@code Throwable}.
+ * @param ex the exception to trigger the callbacks with
+ */
+ public void failure(Throwable ex) {
+ synchronized (this.mutex) {
+ this.state = State.FAILURE;
+ this.result = ex;
+ while (!this.callbacks.isEmpty()) {
+ this.callbacks.poll().onFailure(ex);
+ }
+ }
+ }
+
+
+ private enum State {NEW, SUCCESS, FAILURE}
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureTask.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureTask.java
new file mode 100644
index 00000000..b61f885f
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureTask.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.util.concurrent;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+
+/**
+ * Extension of {@link FutureTask} that implements {@link ListenableFuture}.
+ *
+ * @author Arjen Poutsma
+ * @since 4.0
+ */
+public class ListenableFutureTask<T> extends FutureTask<T> implements ListenableFuture<T> {
+
+ private final ListenableFutureCallbackRegistry<T> callbacks = new ListenableFutureCallbackRegistry<T>();
+
+
+ /**
+ * Create a new {@code ListenableFutureTask} that will, upon running,
+ * execute the given {@link Callable}.
+ * @param callable the callable task
+ */
+ public ListenableFutureTask(Callable<T> callable) {
+ super(callable);
+ }
+
+ /**
+ * Create a {@code ListenableFutureTask} that will, upon running,
+ * execute the given {@link Runnable}, and arrange that {@link #get()}
+ * will return the given result on successful completion.
+ * @param runnable the runnable task
+ * @param result the result to return on successful completion
+ */
+ public ListenableFutureTask(Runnable runnable, T result) {
+ super(runnable, result);
+ }
+
+
+ @Override
+ public void addCallback(ListenableFutureCallback<? super T> callback) {
+ this.callbacks.addCallback(callback);
+ }
+
+ @Override
+ protected final void done() {
+ Throwable cause;
+ try {
+ T result = get();
+ this.callbacks.success(result);
+ return;
+ }
+ catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ return;
+ }
+ catch (ExecutionException ex) {
+ cause = ex.getCause();
+ if (cause == null) {
+ cause = ex;
+ }
+ }
+ catch (Throwable ex) {
+ cause = ex;
+ }
+ this.callbacks.failure(cause);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/package-info.java b/spring-core/src/main/java/org/springframework/util/concurrent/package-info.java
new file mode 100644
index 00000000..ce15f2eb
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/package-info.java
@@ -0,0 +1,8 @@
+
+/**
+ *
+ * Useful generic {@code java.util.concurrent.Future} extension.
+ *
+ */
+package org.springframework.util.concurrent;
+
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
deleted file mode 100644
index 46f4d98e..00000000
--- a/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxContentHandler.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * 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/AbstractStaxHandler.java b/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxHandler.java
new file mode 100644
index 00000000..afbdf9a0
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxHandler.java
@@ -0,0 +1,273 @@
+/*
+ * 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.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.xml.XMLConstants;
+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;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * Abstract base class for SAX {@code ContentHandler} and {@code LexicalHandler}
+ * implementations that use StAX as a basis. All methods delegate to internal template
+ * methods, capable of throwing a {@code XMLStreamException}. Additionally, an namespace
+ * context stack is used to keep track of declared namespaces.
+ *
+ * @author Arjen Poutsma
+ * @since 4.0.3
+ */
+abstract class AbstractStaxHandler implements ContentHandler, LexicalHandler {
+
+ private final List<Map<String, String>> namespaceMappings = new ArrayList<Map<String, String>>();
+
+ private boolean inCData;
+
+
+ @Override
+ public final void startDocument() throws SAXException {
+ removeAllNamespaceMappings();
+ newNamespaceMapping();
+ try {
+ startDocumentInternal();
+ }
+ catch (XMLStreamException ex) {
+ throw new SAXException("Could not handle startDocument: " + ex.getMessage(), ex);
+ }
+ }
+
+ @Override
+ public final void endDocument() throws SAXException {
+ removeAllNamespaceMappings();
+ try {
+ endDocumentInternal();
+ }
+ catch (XMLStreamException ex) {
+ throw new SAXException("Could not handle endDocument: " + ex.getMessage(), ex);
+ }
+ }
+
+ @Override
+ public final void startPrefixMapping(String prefix, String uri) {
+ currentNamespaceMapping().put(prefix, uri);
+ }
+
+ @Override
+ public final void endPrefixMapping(String prefix) {
+ }
+
+ @Override
+ public final void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
+ try {
+ startElementInternal(toQName(uri, qName), atts, currentNamespaceMapping());
+ newNamespaceMapping();
+ }
+ catch (XMLStreamException ex) {
+ throw new SAXException("Could not handle startElement: " + ex.getMessage(), ex);
+ }
+ }
+
+ @Override
+ public final void endElement(String uri, String localName, String qName) throws SAXException {
+ try {
+ endElementInternal(toQName(uri, qName), currentNamespaceMapping());
+ removeNamespaceMapping();
+ }
+ catch (XMLStreamException ex) {
+ throw new SAXException("Could not handle endElement: " + ex.getMessage(), ex);
+ }
+ }
+
+ @Override
+ public final void characters(char ch[], int start, int length) throws SAXException {
+ try {
+ String data = new String(ch, start, length);
+ if (!this.inCData) {
+ charactersInternal(data);
+ }
+ else {
+ cDataInternal(data);
+ }
+ }
+ catch (XMLStreamException ex) {
+ throw new SAXException("Could not handle characters: " + ex.getMessage(), ex);
+ }
+ }
+
+ @Override
+ public final void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
+ try {
+ ignorableWhitespaceInternal(new String(ch, start, length));
+ }
+ catch (XMLStreamException ex) {
+ throw new SAXException(
+ "Could not handle ignorableWhitespace:" + ex.getMessage(), ex);
+ }
+ }
+
+ @Override
+ 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);
+ }
+ }
+
+ @Override
+ public final void skippedEntity(String name) throws SAXException {
+ try {
+ skippedEntityInternal(name);
+ }
+ catch (XMLStreamException ex) {
+ throw new SAXException("Could not handle skippedEntity: " + ex.getMessage(), ex);
+ }
+ }
+
+ @Override
+ public final void startDTD(String name, String publicId, String systemId) throws SAXException {
+ try {
+ StringBuilder builder = new StringBuilder("<!DOCTYPE ");
+ builder.append(name);
+ if (publicId != null) {
+ builder.append(" PUBLIC \"");
+ builder.append(publicId);
+ builder.append("\" \"");
+ }
+ else {
+ builder.append(" SYSTEM \"");
+ }
+ builder.append(systemId);
+ builder.append("\">");
+
+ dtdInternal(builder.toString());
+ }
+ catch (XMLStreamException ex) {
+ throw new SAXException("Could not handle startDTD: " + ex.getMessage(), ex);
+ }
+ }
+
+ @Override
+ public final void endDTD() throws SAXException {
+ }
+
+ @Override
+ public final void startCDATA() throws SAXException {
+ this.inCData = true;
+ }
+
+ @Override
+ public final void endCDATA() throws SAXException {
+ this.inCData = false;
+ }
+
+ @Override
+ public final void comment(char[] ch, int start, int length) throws SAXException {
+ try {
+ commentInternal(new String(ch, start, length));
+ }
+ catch (XMLStreamException ex) {
+ throw new SAXException("Could not handle comment: " + ex.getMessage(), ex);
+ }
+ }
+
+ @Override
+ public void startEntity(String name) throws SAXException {
+ }
+
+ @Override
+ public void endEntity(String name) throws SAXException {
+ }
+
+ /**
+ * 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 boolean isNamespaceDeclaration(QName qName) {
+ String prefix = qName.getPrefix();
+ String localPart = qName.getLocalPart();
+ return (XMLConstants.XMLNS_ATTRIBUTE.equals(localPart) && prefix.length() == 0) ||
+ (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix) && localPart.length() != 0);
+ }
+
+
+ private Map<String, String> currentNamespaceMapping() {
+ return this.namespaceMappings.get(this.namespaceMappings.size() - 1);
+ }
+
+ private void newNamespaceMapping() {
+ this.namespaceMappings.add(new HashMap<String, String>());
+ }
+
+ private void removeNamespaceMapping() {
+ this.namespaceMappings.remove(this.namespaceMappings.size() - 1);
+ }
+
+ private void removeAllNamespaceMappings() {
+ this.namespaceMappings.clear();
+ }
+
+
+ protected abstract void startDocumentInternal() throws XMLStreamException;
+
+ protected abstract void endDocumentInternal() throws XMLStreamException;
+
+ protected abstract void startElementInternal(QName name, Attributes attributes,
+ Map<String, String> namespaceMapping) throws XMLStreamException;
+
+ protected abstract void endElementInternal(QName name, Map<String, String> namespaceMapping)
+ throws XMLStreamException;
+
+ protected abstract void charactersInternal(String data) throws XMLStreamException;
+
+ protected abstract void cDataInternal(String data) throws XMLStreamException;
+
+ protected abstract void ignorableWhitespaceInternal(String data) throws XMLStreamException;
+
+ protected abstract void processingInstructionInternal(String target, String data)
+ throws XMLStreamException;
+
+ protected abstract void skippedEntityInternal(String name) throws XMLStreamException;
+
+ protected abstract void dtdInternal(String dtd) throws XMLStreamException;
+
+ protected abstract void commentInternal(String comment) 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
index feeefd6e..25dc97b2 100644
--- a/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxXMLReader.java
+++ b/spring-core/src/main/java/org/springframework/util/xml/AbstractStaxXMLReader.java
@@ -134,6 +134,7 @@ abstract class AbstractStaxXMLReader extends AbstractXMLReader {
* @param ignored is ignored
* @throws SAXException a SAX exception, possibly wrapping a {@code XMLStreamException}
*/
+ @Override
public final void parse(InputSource ignored) throws SAXException {
parse();
}
@@ -144,6 +145,7 @@ abstract class AbstractStaxXMLReader extends AbstractXMLReader {
* @param ignored is ignored
* @throws SAXException A SAX exception, possibly wrapping a {@code XMLStreamException}
*/
+ @Override
public final void parse(String ignored) throws SAXException {
parse();
}
@@ -217,18 +219,22 @@ abstract class AbstractStaxXMLReader extends AbstractXMLReader {
this.location = location;
}
+ @Override
public String getPublicId() {
return location.getPublicId();
}
+ @Override
public String getSystemId() {
return location.getSystemId();
}
+ @Override
public int getLineNumber() {
return location.getLineNumber();
}
+ @Override
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
index 300f9ff2..7b24a3c1 100644
--- a/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLReader.java
+++ b/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLReader.java
@@ -48,34 +48,42 @@ abstract class AbstractXMLReader implements XMLReader {
private LexicalHandler lexicalHandler;
+ @Override
public ContentHandler getContentHandler() {
return contentHandler;
}
+ @Override
public void setContentHandler(ContentHandler contentHandler) {
this.contentHandler = contentHandler;
}
+ @Override
public void setDTDHandler(DTDHandler dtdHandler) {
this.dtdHandler = dtdHandler;
}
+ @Override
public DTDHandler getDTDHandler() {
return dtdHandler;
}
+ @Override
public EntityResolver getEntityResolver() {
return entityResolver;
}
+ @Override
public void setEntityResolver(EntityResolver entityResolver) {
this.entityResolver = entityResolver;
}
+ @Override
public ErrorHandler getErrorHandler() {
return errorHandler;
}
+ @Override
public void setErrorHandler(ErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
@@ -90,6 +98,7 @@ abstract class AbstractXMLReader implements XMLReader {
* @throws org.xml.sax.SAXNotRecognizedException
* always
*/
+ @Override
public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
throw new SAXNotRecognizedException(name);
}
@@ -99,6 +108,7 @@ abstract class AbstractXMLReader implements XMLReader {
*
* @throws SAXNotRecognizedException always
*/
+ @Override
public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {
throw new SAXNotRecognizedException(name);
}
@@ -107,6 +117,7 @@ abstract class AbstractXMLReader implements XMLReader {
* 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}.
*/
+ @Override
public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
if ("http://xml.org/sax/properties/lexical-handler".equals(name)) {
return lexicalHandler;
@@ -120,6 +131,7 @@ abstract class AbstractXMLReader implements XMLReader {
* 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}.
*/
+ @Override
public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
if ("http://xml.org/sax/properties/lexical-handler".equals(name)) {
lexicalHandler = (LexicalHandler) value;
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
index e8c8a643..6335ea15 100644
--- a/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLStreamReader.java
+++ b/spring-core/src/main/java/org/springframework/util/xml/AbstractXMLStreamReader.java
@@ -31,6 +31,7 @@ import org.springframework.util.Assert;
*/
abstract class AbstractXMLStreamReader implements XMLStreamReader {
+ @Override
public String getElementText() throws XMLStreamException {
if (getEventType() != XMLStreamConstants.START_ELEMENT) {
throw new XMLStreamException("parser must be on START_ELEMENT to read next text", getLocation());
@@ -61,18 +62,22 @@ abstract class AbstractXMLStreamReader implements XMLStreamReader {
return builder.toString();
}
+ @Override
public String getAttributeLocalName(int index) {
return getAttributeName(index).getLocalPart();
}
+ @Override
public String getAttributeNamespace(int index) {
return getAttributeName(index).getNamespaceURI();
}
+ @Override
public String getAttributePrefix(int index) {
return getAttributeName(index).getPrefix();
}
+ @Override
public String getNamespaceURI() {
int eventType = getEventType();
if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) {
@@ -83,11 +88,13 @@ abstract class AbstractXMLStreamReader implements XMLStreamReader {
}
}
+ @Override
public String getNamespaceURI(String prefix) {
Assert.notNull(prefix, "No prefix given");
return getNamespaceContext().getNamespaceURI(prefix);
}
+ @Override
public boolean hasText() {
int eventType = getEventType();
return eventType == XMLStreamConstants.SPACE || eventType == XMLStreamConstants.CHARACTERS ||
@@ -95,6 +102,7 @@ abstract class AbstractXMLStreamReader implements XMLStreamReader {
eventType == XMLStreamConstants.ENTITY_REFERENCE;
}
+ @Override
public String getPrefix() {
int eventType = getEventType();
if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) {
@@ -105,27 +113,33 @@ abstract class AbstractXMLStreamReader implements XMLStreamReader {
}
}
+ @Override
public boolean hasName() {
int eventType = getEventType();
return eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT;
}
+ @Override
public boolean isWhiteSpace() {
return getEventType() == XMLStreamConstants.SPACE;
}
+ @Override
public boolean isStartElement() {
return getEventType() == XMLStreamConstants.START_ELEMENT;
}
+ @Override
public boolean isEndElement() {
return getEventType() == XMLStreamConstants.END_ELEMENT;
}
+ @Override
public boolean isCharacters() {
return getEventType() == XMLStreamConstants.CHARACTERS;
}
+ @Override
public int nextTag() throws XMLStreamException {
int eventType = next();
while (eventType == XMLStreamConstants.CHARACTERS && isWhiteSpace() ||
@@ -139,6 +153,7 @@ abstract class AbstractXMLStreamReader implements XMLStreamReader {
return eventType;
}
+ @Override
public void require(int expectedType, String namespaceURI, String localName) throws XMLStreamException {
int eventType = getEventType();
if (eventType != expectedType) {
@@ -146,6 +161,7 @@ abstract class AbstractXMLStreamReader implements XMLStreamReader {
}
}
+ @Override
public String getAttributeValue(String namespaceURI, String localName) {
for (int i = 0; i < getAttributeCount(); i++) {
QName name = getAttributeName(i);
@@ -157,18 +173,22 @@ abstract class AbstractXMLStreamReader implements XMLStreamReader {
return null;
}
+ @Override
public boolean hasNext() throws XMLStreamException {
return getEventType() != END_DOCUMENT;
}
+ @Override
public String getLocalName() {
return getName().getLocalPart();
}
+ @Override
public char[] getTextCharacters() {
return getText().toCharArray();
}
+ @Override
public int getTextCharacters(int sourceStart, char[] target, int targetStart, int length)
throws XMLStreamException {
char[] source = getTextCharacters();
@@ -177,6 +197,7 @@ abstract class AbstractXMLStreamReader implements XMLStreamReader {
return length;
}
+ @Override
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
index 534cb007..5a8d92aa 100644
--- a/spring-core/src/main/java/org/springframework/util/xml/DomContentHandler.java
+++ b/spring-core/src/main/java/org/springframework/util/xml/DomContentHandler.java
@@ -72,6 +72,7 @@ class DomContentHandler implements ContentHandler {
}
}
+ @Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
Node parent = getParent();
Element element = document.createElementNS(uri, qName);
@@ -87,10 +88,12 @@ class DomContentHandler implements ContentHandler {
elements.add(element);
}
+ @Override
public void endElement(String uri, String localName, String qName) throws SAXException {
elements.remove(elements.size() - 1);
}
+ @Override
public void characters(char ch[], int start, int length) throws SAXException {
String data = new String(ch, start, length);
Node parent = getParent();
@@ -104,6 +107,7 @@ class DomContentHandler implements ContentHandler {
}
}
+ @Override
public void processingInstruction(String target, String data) throws SAXException {
Node parent = getParent();
ProcessingInstruction pi = document.createProcessingInstruction(target, data);
@@ -114,24 +118,31 @@ class DomContentHandler implements ContentHandler {
* Unsupported
*/
+ @Override
public void setDocumentLocator(Locator locator) {
}
+ @Override
public void startDocument() throws SAXException {
}
+ @Override
public void endDocument() throws SAXException {
}
+ @Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
}
+ @Override
public void endPrefixMapping(String prefix) throws SAXException {
}
+ @Override
public void ignorableWhitespace(char ch[], int start, int length) throws SAXException {
}
+ @Override
public void skippedEntity(String name) throws SAXException {
}
}
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
index c0d966d6..f865c956 100644
--- a/spring-core/src/main/java/org/springframework/util/xml/SimpleNamespaceContext.java
+++ b/spring-core/src/main/java/org/springframework/util/xml/SimpleNamespaceContext.java
@@ -43,6 +43,7 @@ public class SimpleNamespaceContext implements NamespaceContext {
private String defaultNamespaceUri = "";
+ @Override
public String getNamespaceURI(String prefix) {
Assert.notNull(prefix, "prefix is null");
if (XMLConstants.XML_NS_PREFIX.equals(prefix)) {
@@ -60,12 +61,14 @@ public class SimpleNamespaceContext implements NamespaceContext {
return "";
}
+ @Override
public String getPrefix(String namespaceUri) {
- List prefixes = getPrefixesInternal(namespaceUri);
+ List<?> prefixes = getPrefixesInternal(namespaceUri);
return prefixes.isEmpty() ? null : (String) prefixes.get(0);
}
- public Iterator getPrefixes(String namespaceUri) {
+ @Override
+ public Iterator<String> getPrefixes(String namespaceUri) {
return getPrefixesInternal(namespaceUri).iterator();
}
@@ -152,7 +155,7 @@ public class SimpleNamespaceContext implements NamespaceContext {
}
else {
String namespaceUri = prefixToNamespaceUri.remove(prefix);
- List prefixes = getPrefixesInternal(namespaceUri);
+ List<String> 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
index ed06ed8c..3d0227db 100644
--- a/spring-core/src/main/java/org/springframework/util/xml/SimpleSaxErrorHandler.java
+++ b/spring-core/src/main/java/org/springframework/util/xml/SimpleSaxErrorHandler.java
@@ -43,14 +43,17 @@ public class SimpleSaxErrorHandler implements ErrorHandler {
}
+ @Override
public void warning(SAXParseException ex) throws SAXException {
logger.warn("Ignored XML validation warning", ex);
}
+ @Override
public void error(SAXParseException ex) throws SAXException {
throw ex;
}
+ @Override
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
index 7fd5dffa..cab7670a 100644
--- a/spring-core/src/main/java/org/springframework/util/xml/SimpleTransformErrorListener.java
+++ b/spring-core/src/main/java/org/springframework/util/xml/SimpleTransformErrorListener.java
@@ -43,14 +43,17 @@ public class SimpleTransformErrorListener implements ErrorListener {
}
+ @Override
public void warning(TransformerException ex) throws TransformerException {
logger.warn("XSLT transformation warning", ex);
}
+ @Override
public void error(TransformerException ex) throws TransformerException {
logger.error("XSLT transformation error", ex);
}
+ @Override
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
deleted file mode 100644
index 686ff1a6..00000000
--- a/spring-core/src/main/java/org/springframework/util/xml/StaxEventContentHandler.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * 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/StaxEventHandler.java b/spring-core/src/main/java/org/springframework/util/xml/StaxEventHandler.java
new file mode 100644
index 00000000..7a4151dc
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/StaxEventHandler.java
@@ -0,0 +1,196 @@
+/*
+ * 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.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javax.xml.namespace.QName;
+import javax.xml.stream.Location;
+import javax.xml.stream.XMLEventFactory;
+import javax.xml.stream.XMLEventWriter;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Attribute;
+import javax.xml.stream.events.Namespace;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+import org.xml.sax.ext.LexicalHandler;
+
+/**
+ * SAX {@link org.xml.sax.ContentHandler} and {@link LexicalHandler}
+ * that writes to a {@link javax.xml.stream.util.XMLEventConsumer}.
+ *
+ * @author Arjen Poutsma
+ * @since 4.0.3
+ */
+class StaxEventHandler extends AbstractStaxHandler {
+
+ private final XMLEventFactory eventFactory;
+
+ private final XMLEventWriter eventWriter;
+
+
+ /**
+ * Construct a new instance of the {@code StaxEventContentHandler} that writes to the
+ * given {@code XMLEventWriter}. A default {@code XMLEventFactory} will be created.
+ * @param eventWriter the writer to write events to
+ */
+ public StaxEventHandler(XMLEventWriter eventWriter) {
+ this.eventFactory = XMLEventFactory.newInstance();
+ this.eventWriter = eventWriter;
+ }
+
+ /**
+ * 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 eventWriter the writer to write events to
+ * @param factory the factory used to create events
+ */
+ public StaxEventHandler(XMLEventWriter eventWriter, XMLEventFactory factory) {
+ this.eventFactory = factory;
+ this.eventWriter = eventWriter;
+ }
+
+
+ @Override
+ public void setDocumentLocator(Locator locator) {
+ if (locator != null) {
+ this.eventFactory.setLocation(new LocatorLocationAdapter(locator));
+ }
+ }
+
+ @Override
+ protected void startDocumentInternal() throws XMLStreamException {
+ this.eventWriter.add(this.eventFactory.createStartDocument());
+ }
+
+ @Override
+ protected void endDocumentInternal() throws XMLStreamException {
+ this.eventWriter.add(this.eventFactory.createEndDocument());
+ }
+
+ @Override
+ protected void startElementInternal(QName name, Attributes atts,
+ Map<String, String> namespaceMapping) throws XMLStreamException {
+
+ List<Attribute> attributes = getAttributes(atts);
+ List<Namespace> namespaces = getNamespaces(namespaceMapping);
+ this.eventWriter.add(
+ this.eventFactory.createStartElement(name, attributes.iterator(), namespaces.iterator()));
+
+ }
+
+ private List<Namespace> getNamespaces(Map<String, String> namespaceMapping) {
+ List<Namespace> result = new ArrayList<Namespace>();
+ for (Map.Entry<String, String> entry : namespaceMapping.entrySet()) {
+ String prefix = entry.getKey();
+ String namespaceUri = entry.getValue();
+ result.add(this.eventFactory.createNamespace(prefix, namespaceUri));
+ }
+ return result;
+ }
+
+ private List<Attribute> getAttributes(Attributes attributes) {
+ List<Attribute> result = new ArrayList<Attribute>();
+ for (int i = 0; i < attributes.getLength(); i++) {
+ QName attrName = toQName(attributes.getURI(i), attributes.getQName(i));
+ if (!isNamespaceDeclaration(attrName)) {
+ result.add(this.eventFactory.createAttribute(attrName, attributes.getValue(i)));
+ }
+ }
+ return result;
+ }
+
+ @Override
+ protected void endElementInternal(QName name, Map<String, String> namespaceMapping) throws XMLStreamException {
+ List<Namespace> namespaces = getNamespaces(namespaceMapping);
+ this.eventWriter.add(this.eventFactory.createEndElement(name, namespaces.iterator()));
+ }
+
+ @Override
+ protected void charactersInternal(String data) throws XMLStreamException {
+ this.eventWriter.add(this.eventFactory.createCharacters(data));
+ }
+
+ @Override
+ protected void cDataInternal(String data) throws XMLStreamException {
+ this.eventWriter.add(this.eventFactory.createCData(data));
+ }
+
+ @Override
+ protected void ignorableWhitespaceInternal(String data) throws XMLStreamException {
+ this.eventWriter.add(this.eventFactory.createIgnorableSpace(data));
+ }
+
+ @Override
+ protected void processingInstructionInternal(String target, String data) throws XMLStreamException {
+ this.eventWriter.add(this.eventFactory.createProcessingInstruction(target, data));
+ }
+
+ @Override
+ protected void dtdInternal(String dtd) throws XMLStreamException {
+ this.eventWriter.add(this.eventFactory.createDTD(dtd));
+ }
+
+ @Override
+ protected void commentInternal(String comment) throws XMLStreamException {
+ this.eventWriter.add(this.eventFactory.createComment(comment));
+ }
+
+ // Ignored
+
+ @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;
+ }
+
+ @Override
+ public int getLineNumber() {
+ return this.locator.getLineNumber();
+ }
+
+ @Override
+ public int getColumnNumber() {
+ return this.locator.getColumnNumber();
+ }
+
+ @Override
+ public int getCharacterOffset() {
+ return -1;
+ }
+
+ @Override
+ public String getPublicId() {
+ return this.locator.getPublicId();
+ }
+
+ @Override
+ 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
index fd43354a..490b9de8 100644
--- a/spring-core/src/main/java/org/springframework/util/xml/StaxEventXMLReader.java
+++ b/spring-core/src/main/java/org/springframework/util/xml/StaxEventXMLReader.java
@@ -17,8 +17,7 @@
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;
@@ -38,14 +37,13 @@ import javax.xml.stream.events.StartDocument;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
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.
@@ -58,14 +56,13 @@ import org.springframework.util.StringUtils;
* @see #setEntityResolver(org.xml.sax.EntityResolver)
* @see #setErrorHandler(org.xml.sax.ErrorHandler)
*/
+@SuppressWarnings("rawtypes")
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;
@@ -168,21 +165,27 @@ class StaxEventXMLReader extends AbstractStaxXMLReader {
if (getContentHandler() != null) {
final Location location = event.getLocation();
getContentHandler().setDocumentLocator(new Locator2() {
+ @Override
public int getColumnNumber() {
return (location != null ? location.getColumnNumber() : -1);
}
+ @Override
public int getLineNumber() {
return (location != null ? location.getLineNumber() : -1);
}
+ @Override
public String getPublicId() {
return (location != null ? location.getPublicId() : null);
}
+ @Override
public String getSystemId() {
return (location != null ? location.getSystemId() : null);
}
+ @Override
public String getXMLVersion() {
return xmlVersion;
}
+ @Override
public String getEncoding() {
return encoding;
}
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
index 801769cd..ef91ceae 100644
--- a/spring-core/src/main/java/org/springframework/util/xml/StaxResult.java
+++ b/spring-core/src/main/java/org/springframework/util/xml/StaxResult.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * 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.
@@ -22,6 +22,7 @@ import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.sax.SAXResult;
import org.xml.sax.ContentHandler;
+import org.xml.sax.ext.LexicalHandler;
/**
* Implementation of the {@code Result} tagging interface for StAX writers. Can be constructed with
@@ -35,7 +36,8 @@ import org.xml.sax.ContentHandler;
* {@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.
+ * or {@link #setLexicalHandler(org.xml.sax.ext.LexicalHandler)} will result in
+ * {@code UnsupportedOperationException}s.
*
* @author Arjen Poutsma
* @since 3.0
@@ -54,8 +56,10 @@ class StaxResult extends SAXResult {
* 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));
+ public StaxResult(XMLStreamWriter streamWriter) {
+ StaxStreamHandler handler = new StaxStreamHandler(streamWriter);
+ super.setHandler(handler);
+ super.setLexicalHandler(handler);
this.streamWriter = streamWriter;
}
@@ -63,19 +67,10 @@ class StaxResult extends SAXResult {
* 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));
+ public StaxResult(XMLEventWriter eventWriter) {
+ StaxEventHandler handler = new StaxEventHandler(eventWriter);
+ super.setHandler(handler);
+ super.setLexicalHandler(handler);
this.eventWriter = eventWriter;
}
@@ -86,7 +81,7 @@ class StaxResult extends SAXResult {
* @return the StAX event writer used by this result
* @see #StaxResult(javax.xml.stream.XMLEventWriter)
*/
- XMLEventWriter getXMLEventWriter() {
+ public XMLEventWriter getXMLEventWriter() {
return this.eventWriter;
}
@@ -96,7 +91,7 @@ class StaxResult extends SAXResult {
* @return the StAX stream writer used by this result
* @see #StaxResult(javax.xml.stream.XMLStreamWriter)
*/
- XMLStreamWriter getXMLStreamWriter() {
+ public XMLStreamWriter getXMLStreamWriter() {
return this.streamWriter;
}
@@ -110,4 +105,13 @@ class StaxResult extends SAXResult {
throw new UnsupportedOperationException("setHandler is not supported");
}
+ /**
+ * Throws an {@code UnsupportedOperationException}.
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public void setLexicalHandler(LexicalHandler handler) {
+ throw new UnsupportedOperationException("setLexicalHandler 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
deleted file mode 100644
index 2241e2f2..00000000
--- a/spring-core/src/main/java/org/springframework/util/xml/StaxStreamContentHandler.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * 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/StaxStreamHandler.java b/spring-core/src/main/java/org/springframework/util/xml/StaxStreamHandler.java
new file mode 100644
index 00000000..6323b446
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/util/xml/StaxStreamHandler.java
@@ -0,0 +1,139 @@
+/*
+ * 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.util.Map;
+import javax.xml.XMLConstants;
+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.xml.sax.SAXException;
+import org.xml.sax.ext.LexicalHandler;
+
+import org.springframework.util.Assert;
+
+/**
+ * SAX {@link org.xml.sax.ContentHandler} and {@link LexicalHandler}
+ * that writes to an {@link XMLStreamWriter}.
+ *
+ * @author Arjen Poutsma
+ * @since 4.0.3
+ */
+class StaxStreamHandler extends AbstractStaxHandler {
+
+ private final XMLStreamWriter streamWriter;
+
+
+ public StaxStreamHandler(XMLStreamWriter streamWriter) {
+ Assert.notNull(streamWriter, "XMLStreamWriter must not be null");
+ this.streamWriter = streamWriter;
+ }
+
+
+ @Override
+ protected void startDocumentInternal() throws XMLStreamException {
+ this.streamWriter.writeStartDocument();
+ }
+
+ @Override
+ protected void endDocumentInternal() throws XMLStreamException {
+ this.streamWriter.writeEndDocument();
+ }
+
+ @Override
+ protected void startElementInternal(QName name, Attributes attributes,
+ Map<String, String> namespaceMapping) throws XMLStreamException {
+
+ this.streamWriter.writeStartElement(name.getPrefix(), name.getLocalPart(), name.getNamespaceURI());
+
+ for (Map.Entry<String, String> entry : namespaceMapping.entrySet()) {
+ String prefix = entry.getKey();
+ String namespaceUri = entry.getValue();
+ this.streamWriter.writeNamespace(prefix, namespaceUri);
+ if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) {
+ this.streamWriter.setDefaultNamespace(namespaceUri);
+ }
+ else {
+ this.streamWriter.setPrefix(prefix, namespaceUri);
+ }
+ }
+ for (int i = 0; i < attributes.getLength(); i++) {
+ QName attrName = toQName(attributes.getURI(i), attributes.getQName(i));
+ if (!isNamespaceDeclaration(attrName)) {
+ this.streamWriter.writeAttribute(attrName.getPrefix(), attrName.getNamespaceURI(),
+ attrName.getLocalPart(), attributes.getValue(i));
+ }
+ }
+ }
+
+ @Override
+ protected void endElementInternal(QName name, Map<String, String> namespaceMapping) throws XMLStreamException {
+ this.streamWriter.writeEndElement();
+ }
+
+ @Override
+ protected void charactersInternal(String data) throws XMLStreamException {
+ this.streamWriter.writeCharacters(data);
+ }
+
+ @Override
+ protected void cDataInternal(String data) throws XMLStreamException {
+ this.streamWriter.writeCData(data);
+ }
+
+ @Override
+ protected void ignorableWhitespaceInternal(String data) throws XMLStreamException {
+ this.streamWriter.writeCharacters(data);
+ }
+
+ @Override
+ protected void processingInstructionInternal(String target, String data) throws XMLStreamException {
+ this.streamWriter.writeProcessingInstruction(target, data);
+ }
+
+ @Override
+ protected void dtdInternal(String dtd) throws XMLStreamException {
+ this.streamWriter.writeDTD(dtd);
+ }
+
+ @Override
+ protected void commentInternal(String comment) throws XMLStreamException {
+ this.streamWriter.writeComment(comment);
+ }
+
+ // Ignored
+
+ @Override
+ public void setDocumentLocator(Locator locator) {
+ }
+
+ @Override
+ public void startEntity(String name) throws SAXException {
+ }
+
+ @Override
+ public void endEntity(String name) throws SAXException {
+ }
+
+ @Override
+ protected void skippedEntityInternal(String name) throws XMLStreamException {
+ }
+
+}
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
index 359514b5..ce186157 100644
--- a/spring-core/src/main/java/org/springframework/util/xml/StaxStreamXMLReader.java
+++ b/spring-core/src/main/java/org/springframework/util/xml/StaxStreamXMLReader.java
@@ -142,21 +142,27 @@ class StaxStreamXMLReader extends AbstractStaxXMLReader {
if (getContentHandler() != null) {
final Location location = this.reader.getLocation();
getContentHandler().setDocumentLocator(new Locator2() {
+ @Override
public int getColumnNumber() {
return (location != null ? location.getColumnNumber() : -1);
}
+ @Override
public int getLineNumber() {
return (location != null ? location.getLineNumber() : -1);
}
+ @Override
public String getPublicId() {
return (location != null ? location.getPublicId() : null);
}
+ @Override
public String getSystemId() {
return (location != null ? location.getSystemId() : null);
}
+ @Override
public String getXMLVersion() {
return xmlVersion;
}
+ @Override
public String getEncoding() {
return encoding;
}
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
index 41fd3f63..eb98be07 100644
--- a/spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * 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.
@@ -30,11 +30,9 @@ 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.
+ * Convenience methods for working with the StAX API. Partly historic due to JAXP 1.3 compatibility;
+ * as of Spring 4.0, relying on JAXP 1.4 as included in JDK 1.6 and higher.
*
* <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.
@@ -45,37 +43,31 @@ import org.springframework.util.ClassUtils;
*/
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}.
+ * Create a JAXP 1.4 {@link StAXSource} 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);
+ public static Source createStaxSource(XMLStreamReader streamReader) {
+ return new StAXSource(streamReader);
+ }
+
+ /**
+ * Create a JAXP 1.4 a {@link StAXSource} for the given {@link XMLEventReader}.
+ * @param eventReader the StAX event reader
+ * @return a source wrapping the {@code eventReader}
+ */
+ public static Source createStaxSource(XMLEventReader eventReader) throws XMLStreamException {
+ return new StAXSource(eventReader);
}
/**
- * 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.
+ * 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}
- * @see #createCustomStaxSource(XMLStreamReader)
*/
- public static Source createStaxSource(XMLStreamReader streamReader) {
- if (jaxp14Available) {
- return Jaxp14StaxHandler.createStaxSource(streamReader);
- }
- else {
- return createCustomStaxSource(streamReader);
- }
+ public static Source createCustomStaxSource(XMLStreamReader streamReader) {
+ return new StaxSource(streamReader);
}
/**
@@ -88,43 +80,70 @@ public abstract class StaxUtils {
}
/**
- * 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)
+ * Indicate whether the given {@link Source} is a JAXP 1.4 StAX Source or
+ * custom StAX Source.
+ * @return {@code true} if {@code source} is a JAXP 1.4 {@link StAXSource} or
+ * custom StAX Source; {@code false} otherwise
*/
- public static Source createStaxSource(XMLEventReader eventReader) throws XMLStreamException {
- if (jaxp14Available) {
- return Jaxp14StaxHandler.createStaxSource(eventReader);
+ public static boolean isStaxSource(Source source) {
+ return (source instanceof StAXSource || source instanceof StaxSource);
+ }
+
+ /**
+ * Return the {@link XMLStreamReader} for the given StAX Source.
+ * @param source a JAXP 1.4 {@link StAXSource}
+ * @return the {@link XMLStreamReader}
+ * @throws IllegalArgumentException if {@code source} isn't a JAXP 1.4 {@link StAXSource}
+ * or custom StAX Source
+ */
+ public static XMLStreamReader getXMLStreamReader(Source source) {
+ if (source instanceof StAXSource) {
+ return ((StAXSource) source).getXMLStreamReader();
+ }
+ else if (source instanceof StaxSource) {
+ return ((StaxSource) source).getXMLStreamReader();
}
else {
- return createCustomStaxSource(eventReader);
+ throw new IllegalArgumentException("Source '" + source + "' is neither StaxSource nor StAXSource");
}
}
/**
- * 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.
+ * Return the {@link XMLEventReader} for the given StAX Source.
+ * @param source a JAXP 1.4 {@link StAXSource}
+ * @return the {@link XMLEventReader}
+ * @throws IllegalArgumentException if {@code source} isn't a JAXP 1.4 {@link StAXSource}
+ * or custom StAX Source
*/
- public static boolean isStaxSource(Source source) {
- return ((source instanceof StaxSource) || (jaxp14Available && Jaxp14StaxHandler.isStaxSource(source)));
+ public static XMLEventReader getXMLEventReader(Source source) {
+ if (source instanceof StAXSource) {
+ return ((StAXSource) source).getXMLEventReader();
+ }
+ else if (source instanceof StaxSource) {
+ return ((StaxSource) source).getXMLEventReader();
+ }
+ else {
+ throw new IllegalArgumentException("Source '" + source + "' is neither StaxSource nor StAXSource");
+ }
}
/**
- * 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.
+ * Create a JAXP 1.4 {@link StAXResult} for the given {@link XMLStreamWriter}.
+ * @param streamWriter the StAX stream writer
+ * @return a result wrapping the {@code streamWriter}
*/
- public static boolean isStaxSourceClass(Class<? extends Source> clazz) {
- return (StaxSource.class.equals(clazz) || (jaxp14Available && Jaxp14StaxHandler.isStaxSourceClass(clazz)));
+ public static Result createStaxResult(XMLStreamWriter streamWriter) {
+ return new StAXResult(streamWriter);
}
-
- // Stax Result
+ /**
+ * Create a JAXP 1.4 {@link StAXResult} for the given {@link XMLEventWriter}.
+ * @param eventWriter the StAX event writer
+ * @return a result wrapping {@code streamReader}
+ */
+ public static Result createStaxResult(XMLEventWriter eventWriter) {
+ return new StAXResult(eventWriter);
+ }
/**
* Create a custom, non-JAXP 1.4 StAX {@link Result} for the given {@link XMLStreamWriter}.
@@ -136,23 +155,6 @@ public abstract class StaxUtils {
}
/**
- * 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}
@@ -162,86 +164,28 @@ public abstract class StaxUtils {
}
/**
- * 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
+ * Indicate whether the given {@link Result} is a JAXP 1.4 StAX Result or
* 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.
+ * @return {@code true} if {@code result} is a JAXP 1.4 {@link StAXResult} or
+ * custom StAX Result; {@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 (result instanceof StAXResult || result instanceof StaxResult);
}
/**
* Return the {@link XMLStreamWriter} for the given StAX Result.
- * @param result a {@linkplain #createCustomStaxResult(XMLStreamWriter) custom StAX Result} or
- * JAXP 1.4 {@link StAXResult}
+ * @param result a 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}
+ * @throws IllegalArgumentException if {@code source} isn't a JAXP 1.4 {@link StAXResult}
+ * or custom StAX Result
*/
public static XMLStreamWriter getXMLStreamWriter(Result result) {
- if (result instanceof StaxResult) {
- return ((StaxResult) result).getXMLStreamWriter();
+ if (result instanceof StAXResult) {
+ return ((StAXResult) result).getXMLStreamWriter();
}
- else if (jaxp14Available) {
- return Jaxp14StaxHandler.getXMLStreamWriter(result);
+ else if (result instanceof StaxResult) {
+ return ((StaxResult) result).getXMLStreamWriter();
}
else {
throw new IllegalArgumentException("Result '" + result + "' is neither StaxResult nor StAXResult");
@@ -250,18 +194,17 @@ public abstract class StaxUtils {
/**
* Return the {@link XMLEventWriter} for the given StAX Result.
- * @param result a {@linkplain #createCustomStaxResult(XMLEventWriter) custom StAX Result} or
- * JAXP 1.4 {@link StAXResult}
+ * @param result a 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}
+ * @throws IllegalArgumentException if {@code source} isn't a JAXP 1.4 {@link StAXResult}
+ * or custom StAX Result
*/
public static XMLEventWriter getXMLEventWriter(Result result) {
- if (result instanceof StaxResult) {
- return ((StaxResult) result).getXMLEventWriter();
+ if (result instanceof StAXResult) {
+ return ((StAXResult) result).getXMLEventWriter();
}
- else if (jaxp14Available) {
- return Jaxp14StaxHandler.getXMLEventWriter(result);
+ else if (result instanceof StaxResult) {
+ return ((StaxResult) result).getXMLEventWriter();
}
else {
throw new IllegalArgumentException("Result '" + result + "' is neither StaxResult nor StAXResult");
@@ -274,7 +217,7 @@ public abstract class StaxUtils {
* @return a content handler writing to the {@code streamWriter}
*/
public static ContentHandler createContentHandler(XMLStreamWriter streamWriter) {
- return new StaxStreamContentHandler(streamWriter);
+ return new StaxStreamHandler(streamWriter);
}
/**
@@ -283,7 +226,7 @@ public abstract class StaxUtils {
* @return a content handler writing to the {@code eventWriter}
*/
public static ContentHandler createContentHandler(XMLEventWriter eventWriter) {
- return new StaxEventContentHandler(eventWriter);
+ return new StaxEventHandler(eventWriter);
}
/**
@@ -305,8 +248,9 @@ public abstract class StaxUtils {
}
/**
- * 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 {@link XMLStreamReader} that reads from a {@link XMLEventReader}.
+ * Useful because the StAX {@code XMLInputFactory} allows one to create an
+ * 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 {
@@ -331,59 +275,4 @@ public abstract class StaxUtils {
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/XMLEventStreamReader.java b/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java
index b8321a05..134cbdc6 100644
--- a/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java
+++ b/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamReader.java
@@ -51,6 +51,7 @@ class XMLEventStreamReader extends AbstractXMLStreamReader {
}
+ @Override
public QName getName() {
if (this.event.isStartElement()) {
return this.event.asStartElement().getName();
@@ -63,14 +64,17 @@ class XMLEventStreamReader extends AbstractXMLStreamReader {
}
}
+ @Override
public Location getLocation() {
return this.event.getLocation();
}
+ @Override
public int getEventType() {
return this.event.getEventType();
}
+ @Override
public String getVersion() {
if (this.event.isStartDocument()) {
return ((StartDocument) this.event).getVersion();
@@ -80,10 +84,12 @@ class XMLEventStreamReader extends AbstractXMLStreamReader {
}
}
+ @Override
public Object getProperty(String name) throws IllegalArgumentException {
return this.eventReader.getProperty(name);
}
+ @Override
public boolean isStandalone() {
if (this.event.isStartDocument()) {
return ((StartDocument) event).isStandalone();
@@ -93,6 +99,7 @@ class XMLEventStreamReader extends AbstractXMLStreamReader {
}
}
+ @Override
public boolean standaloneSet() {
if (this.event.isStartDocument()) {
return ((StartDocument) this.event).standaloneSet();
@@ -102,14 +109,17 @@ class XMLEventStreamReader extends AbstractXMLStreamReader {
}
}
+ @Override
public String getEncoding() {
return null;
}
+ @Override
public String getCharacterEncodingScheme() {
return null;
}
+ @Override
public String getPITarget() {
if (this.event.isProcessingInstruction()) {
return ((ProcessingInstruction) this.event).getTarget();
@@ -119,6 +129,7 @@ class XMLEventStreamReader extends AbstractXMLStreamReader {
}
}
+ @Override
public String getPIData() {
if (this.event.isProcessingInstruction()) {
return ((ProcessingInstruction) this.event).getData();
@@ -128,10 +139,12 @@ class XMLEventStreamReader extends AbstractXMLStreamReader {
}
}
+ @Override
public int getTextStart() {
return 0;
}
+ @Override
public String getText() {
if (this.event.isCharacters()) {
return event.asCharacters().getData();
@@ -144,6 +157,7 @@ class XMLEventStreamReader extends AbstractXMLStreamReader {
}
}
+ @Override
@SuppressWarnings("rawtypes")
public int getAttributeCount() {
if (!this.event.isStartElement()) {
@@ -153,18 +167,22 @@ class XMLEventStreamReader extends AbstractXMLStreamReader {
return countIterator(attributes);
}
+ @Override
public boolean isAttributeSpecified(int index) {
return getAttribute(index).isSpecified();
}
+ @Override
public QName getAttributeName(int index) {
return getAttribute(index).getName();
}
+ @Override
public String getAttributeType(int index) {
return getAttribute(index).getDTDType();
}
+ @Override
public String getAttributeValue(int index) {
return getAttribute(index).getValue();
}
@@ -188,6 +206,7 @@ class XMLEventStreamReader extends AbstractXMLStreamReader {
throw new IllegalArgumentException();
}
+ @Override
public NamespaceContext getNamespaceContext() {
if (this.event.isStartElement()) {
return this.event.asStartElement().getNamespaceContext();
@@ -197,6 +216,7 @@ class XMLEventStreamReader extends AbstractXMLStreamReader {
}
}
+ @Override
@SuppressWarnings("rawtypes")
public int getNamespaceCount() {
Iterator namespaces;
@@ -212,10 +232,12 @@ class XMLEventStreamReader extends AbstractXMLStreamReader {
return countIterator(namespaces);
}
+ @Override
public String getNamespacePrefix(int index) {
return getNamespace(index).getPrefix();
}
+ @Override
public String getNamespaceURI(int index) {
return getNamespace(index).getNamespaceURI();
}
@@ -245,11 +267,13 @@ class XMLEventStreamReader extends AbstractXMLStreamReader {
throw new IllegalArgumentException();
}
+ @Override
public int next() throws XMLStreamException {
this.event = this.eventReader.nextEvent();
return this.event.getEventType();
}
+ @Override
public void close() throws XMLStreamException {
this.eventReader.close();
}
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
index d65c4a36..f910377d 100644
--- a/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamWriter.java
+++ b/spring-core/src/main/java/org/springframework/util/xml/XMLEventStreamWriter.java
@@ -60,56 +60,68 @@ class XMLEventStreamWriter implements XMLStreamWriter {
}
+ @Override
public void setNamespaceContext(NamespaceContext context) throws XMLStreamException {
this.eventWriter.setNamespaceContext(context);
}
+ @Override
public NamespaceContext getNamespaceContext() {
return this.eventWriter.getNamespaceContext();
}
+ @Override
public void setPrefix(String prefix, String uri) throws XMLStreamException {
this.eventWriter.setPrefix(prefix, uri);
}
+ @Override
public String getPrefix(String uri) throws XMLStreamException {
return this.eventWriter.getPrefix(uri);
}
+ @Override
public void setDefaultNamespace(String uri) throws XMLStreamException {
this.eventWriter.setDefaultNamespace(uri);
}
+ @Override
public Object getProperty(String name) throws IllegalArgumentException {
throw new IllegalArgumentException();
}
+ @Override
public void writeStartDocument() throws XMLStreamException {
closeEmptyElementIfNecessary();
this.eventWriter.add(this.eventFactory.createStartDocument());
}
+ @Override
public void writeStartDocument(String version) throws XMLStreamException {
closeEmptyElementIfNecessary();
this.eventWriter.add(this.eventFactory.createStartDocument(DEFAULT_ENCODING, version));
}
+ @Override
public void writeStartDocument(String encoding, String version) throws XMLStreamException {
closeEmptyElementIfNecessary();
this.eventWriter.add(this.eventFactory.createStartDocument(encoding, version));
}
+ @Override
public void writeStartElement(String localName) throws XMLStreamException {
closeEmptyElementIfNecessary();
doWriteStartElement(this.eventFactory.createStartElement(new QName(localName), null, null));
}
+ @Override
public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException {
closeEmptyElementIfNecessary();
doWriteStartElement(this.eventFactory.createStartElement(new QName(namespaceURI, localName), null, null));
}
+ @Override
public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
closeEmptyElementIfNecessary();
doWriteStartElement(this.eventFactory.createStartElement(new QName(namespaceURI, localName, prefix), null, null));
@@ -120,18 +132,21 @@ class XMLEventStreamWriter implements XMLStreamWriter {
this.endElements.add(this.eventFactory.createEndElement(startElement.getName(), startElement.getNamespaces()));
}
+ @Override
public void writeEmptyElement(String localName) throws XMLStreamException {
closeEmptyElementIfNecessary();
writeStartElement(localName);
this.emptyElement = true;
}
+ @Override
public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException {
closeEmptyElementIfNecessary();
writeStartElement(namespaceURI, localName);
this.emptyElement = true;
}
+ @Override
public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
closeEmptyElementIfNecessary();
writeStartElement(prefix, localName, namespaceURI);
@@ -145,6 +160,7 @@ class XMLEventStreamWriter implements XMLStreamWriter {
}
}
+ @Override
public void writeEndElement() throws XMLStreamException {
closeEmptyElementIfNecessary();
int last = this.endElements.size() - 1;
@@ -153,24 +169,29 @@ class XMLEventStreamWriter implements XMLStreamWriter {
this.endElements.remove(last);
}
+ @Override
public void writeAttribute(String localName, String value) throws XMLStreamException {
this.eventWriter.add(this.eventFactory.createAttribute(localName, value));
}
+ @Override
public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException {
this.eventWriter.add(this.eventFactory.createAttribute(new QName(namespaceURI, localName), value));
}
+ @Override
public void writeAttribute(String prefix, String namespaceURI, String localName, String value)
throws XMLStreamException {
this.eventWriter.add(this.eventFactory.createAttribute(prefix, namespaceURI, localName, value));
}
+ @Override
public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException {
doWriteNamespace(this.eventFactory.createNamespace(prefix, namespaceURI));
}
+ @Override
public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException {
doWriteNamespace(this.eventFactory.createNamespace(namespaceURI));
}
@@ -191,55 +212,66 @@ class XMLEventStreamWriter implements XMLStreamWriter {
this.endElements.set(last, newEndElement);
}
+ @Override
public void writeCharacters(String text) throws XMLStreamException {
closeEmptyElementIfNecessary();
this.eventWriter.add(this.eventFactory.createCharacters(text));
}
+ @Override
public void writeCharacters(char[] text, int start, int len) throws XMLStreamException {
closeEmptyElementIfNecessary();
this.eventWriter.add(this.eventFactory.createCharacters(new String(text, start, len)));
}
+ @Override
public void writeCData(String data) throws XMLStreamException {
closeEmptyElementIfNecessary();
this.eventWriter.add(this.eventFactory.createCData(data));
}
+ @Override
public void writeComment(String data) throws XMLStreamException {
closeEmptyElementIfNecessary();
this.eventWriter.add(this.eventFactory.createComment(data));
}
+ @Override
public void writeProcessingInstruction(String target) throws XMLStreamException {
closeEmptyElementIfNecessary();
this.eventWriter.add(this.eventFactory.createProcessingInstruction(target, ""));
}
+ @Override
public void writeProcessingInstruction(String target, String data) throws XMLStreamException {
closeEmptyElementIfNecessary();
this.eventWriter.add(this.eventFactory.createProcessingInstruction(target, data));
}
+ @Override
public void writeDTD(String dtd) throws XMLStreamException {
closeEmptyElementIfNecessary();
this.eventWriter.add(this.eventFactory.createDTD(dtd));
}
+ @Override
public void writeEntityRef(String name) throws XMLStreamException {
closeEmptyElementIfNecessary();
this.eventWriter.add(this.eventFactory.createEntityReference(name, null));
}
+ @Override
public void writeEndDocument() throws XMLStreamException {
closeEmptyElementIfNecessary();
this.eventWriter.add(this.eventFactory.createEndDocument());
}
+ @Override
public void flush() throws XMLStreamException {
this.eventWriter.flush();
}
+ @Override
public void close() throws XMLStreamException {
closeEmptyElementIfNecessary();
this.eventWriter.close();