summaryrefslogtreecommitdiff
path: root/spring-core/src/main
diff options
context:
space:
mode:
authorEmmanuel Bourg <ebourg@apache.org>2016-08-03 19:55:01 +0200
committerEmmanuel Bourg <ebourg@apache.org>2016-08-03 19:55:01 +0200
commit75a721d1019da2a2fa86e24ff439df4a224e5b19 (patch)
tree2c44c00ce2c8641cccad177177e5682e187a17ea /spring-core/src/main
parent9eaca6a06af3cbceb3754de19d477be770614265 (diff)
Imported Upstream version 4.3.2
Diffstat (limited to 'spring-core/src/main')
-rw-r--r--spring-core/src/main/java/org/springframework/asm/ClassReader.java3
-rw-r--r--spring-core/src/main/java/org/springframework/asm/ClassWriter.java47
-rw-r--r--spring-core/src/main/java/org/springframework/asm/Frame.java2
-rw-r--r--spring-core/src/main/java/org/springframework/asm/Handle.java60
-rw-r--r--spring-core/src/main/java/org/springframework/core/CollectionFactory.java6
-rw-r--r--spring-core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java12
-rw-r--r--spring-core/src/main/java/org/springframework/core/DecoratingClassLoader.java33
-rw-r--r--spring-core/src/main/java/org/springframework/core/DecoratingProxy.java47
-rw-r--r--spring-core/src/main/java/org/springframework/core/MethodClassKey.java85
-rw-r--r--spring-core/src/main/java/org/springframework/core/MethodIntrospector.java15
-rw-r--r--spring-core/src/main/java/org/springframework/core/MethodParameter.java78
-rw-r--r--spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java37
-rw-r--r--spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java7
-rw-r--r--spring-core/src/main/java/org/springframework/core/SpringProperties.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AbstractAliasAwareAnnotationAttributeExtractor.java17
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java1135
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributeExtractor.java5
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java100
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java20
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java293
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/DefaultAnnotationAttributeExtractor.java7
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/MapAnnotationAttributeExtractor.java36
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java31
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java57
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java6
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/converter/ConverterRegistry.java13
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/AbstractConditionalEnumConverter.java50
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java15
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/DefaultConversionService.java8
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/EnumToIntegerConverter.java40
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/EnumToStringConverter.java21
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java17
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/IntegerToEnumConverterFactory.java52
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java7
-rw-r--r--spring-core/src/main/java/org/springframework/core/convert/support/StringToEnumConverterFactory.java13
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java3
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/AbstractPropertyResolver.java17
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java20
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java24
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java67
-rw-r--r--spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java58
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java4
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/DefaultResourceLoader.java37
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/FileSystemResource.java5
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/PathResource.java6
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/ProtocolResolver.java42
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/Resource.java23
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/UrlResource.java7
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/DefaultPropertySourceFactory.java39
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java132
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/PropertySourceFactory.java41
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/ResourcePatternUtils.java6
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/ResourceRegion.java77
-rw-r--r--spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java10
-rw-r--r--spring-core/src/main/java/org/springframework/core/serializer/support/SerializationDelegate.java78
-rw-r--r--spring-core/src/main/java/org/springframework/core/style/ToStringCreator.java14
-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/TaskDecorator.java45
-rw-r--r--spring-core/src/main/java/org/springframework/core/task/support/TaskExecutorAdapter.java50
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/AbstractRecursiveAnnotationVisitor.java4
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationAttributesReadingVisitor.java51
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java5
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java43
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/MethodMetadataReadingVisitor.java8
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationArrayVisitor.java2
-rw-r--r--spring-core/src/main/java/org/springframework/core/type/classreading/RecursiveAnnotationAttributesVisitor.java51
-rw-r--r--spring-core/src/main/java/org/springframework/lang/UsesSunMisc.java36
-rw-r--r--spring-core/src/main/java/org/springframework/util/AntPathMatcher.java65
-rw-r--r--spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java15
-rw-r--r--spring-core/src/main/java/org/springframework/util/Base64Utils.java8
-rw-r--r--spring-core/src/main/java/org/springframework/util/ClassUtils.java11
-rw-r--r--spring-core/src/main/java/org/springframework/util/DigestUtils.java26
-rw-r--r--spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java10
-rw-r--r--spring-core/src/main/java/org/springframework/util/MimeType.java157
-rw-r--r--spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java5
-rw-r--r--spring-core/src/main/java/org/springframework/util/NumberUtils.java48
-rw-r--r--spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java2
-rw-r--r--spring-core/src/main/java/org/springframework/util/ResourceUtils.java4
-rw-r--r--spring-core/src/main/java/org/springframework/util/StopWatch.java4
-rw-r--r--spring-core/src/main/java/org/springframework/util/StreamUtils.java59
-rw-r--r--spring-core/src/main/java/org/springframework/util/UpdateMessageDigestInputStream.java4
-rw-r--r--spring-core/src/main/java/org/springframework/util/backoff/BackOff.java2
-rw-r--r--spring-core/src/main/java/org/springframework/util/comparator/CompoundComparator.java2
-rw-r--r--spring-core/src/main/java/org/springframework/util/comparator/InstanceComparator.java2
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/CompletableToListenableFutureAdapter.java5
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/FailureCallback.java10
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/FutureAdapter.java9
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java23
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureAdapter.java8
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java4
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.java38
-rw-r--r--spring-core/src/main/java/org/springframework/util/concurrent/SuccessCallback.java8
-rw-r--r--spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java2
93 files changed, 2970 insertions, 967 deletions
diff --git a/spring-core/src/main/java/org/springframework/asm/ClassReader.java b/spring-core/src/main/java/org/springframework/asm/ClassReader.java
index 25ad9aeb..316c14ec 100644
--- a/spring-core/src/main/java/org/springframework/asm/ClassReader.java
+++ b/spring-core/src/main/java/org/springframework/asm/ClassReader.java
@@ -2502,11 +2502,12 @@ public class ClassReader {
int tag = readByte(index);
int[] items = this.items;
int cpIndex = items[readUnsignedShort(index + 1)];
+ boolean itf = b[cpIndex - 1] == ClassWriter.IMETH;
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);
+ return new Handle(tag, owner, name, desc, itf);
}
}
}
diff --git a/spring-core/src/main/java/org/springframework/asm/ClassWriter.java b/spring-core/src/main/java/org/springframework/asm/ClassWriter.java
index 4ec8c53a..72add9d6 100644
--- a/spring-core/src/main/java/org/springframework/asm/ClassWriter.java
+++ b/spring-core/src/main/java/org/springframework/asm/ClassWriter.java
@@ -1052,7 +1052,7 @@ public class ClassWriter extends ClassVisitor {
}
} else if (cst instanceof Handle) {
Handle h = (Handle) cst;
- return newHandleItem(h.tag, h.owner, h.name, h.desc);
+ return newHandleItem(h.tag, h.owner, h.name, h.desc, h.itf);
} else {
throw new IllegalArgumentException("value " + cst);
}
@@ -1187,10 +1187,12 @@ public class ClassWriter extends ClassVisitor {
* the name of the field or method.
* @param desc
* the descriptor of the field or method.
+ * @param itf
+ * true if the owner is an interface.
* @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) {
+ final String desc, final boolean itf) {
key4.set(HANDLE_BASE + tag, owner, name, desc);
Item result = get(key4);
if (result == null) {
@@ -1199,8 +1201,7 @@ public class ClassWriter extends ClassVisitor {
} else {
put112(HANDLE,
tag,
- newMethod(owner, name, desc,
- tag == Opcodes.H_INVOKEINTERFACE));
+ newMethod(owner, name, desc, itf));
}
result = new Item(index++, key4);
put(result);
@@ -1230,10 +1231,44 @@ public class ClassWriter extends ClassVisitor {
* the descriptor of the field or method.
* @return the index of a new or already existing method type reference
* item.
+ *
+ * @deprecated this method is superseded by
+ * {@link #newHandle(int, String, String, String, boolean)}.
*/
+ @Deprecated
public int newHandle(final int tag, final String owner, final String name,
final String desc) {
- return newHandleItem(tag, owner, name, desc).index;
+ return newHandle(tag, owner, name, desc, tag == Opcodes.H_INVOKEINTERFACE);
+ }
+
+ /**
+ * 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.
+ * @param itf
+ * true if the owner is an interface.
+ * @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, final boolean itf) {
+ return newHandleItem(tag, owner, name, desc, itf).index;
}
/**
@@ -1265,7 +1300,7 @@ public class ClassWriter extends ClassVisitor {
int hashCode = bsm.hashCode();
bootstrapMethods.putShort(newHandle(bsm.tag, bsm.owner, bsm.name,
- bsm.desc));
+ bsm.desc, bsm.isInterface()));
int argsLength = bsmArgs.length;
bootstrapMethods.putShort(argsLength);
diff --git a/spring-core/src/main/java/org/springframework/asm/Frame.java b/spring-core/src/main/java/org/springframework/asm/Frame.java
index 29e71c5a..389e416b 100644
--- a/spring-core/src/main/java/org/springframework/asm/Frame.java
+++ b/spring-core/src/main/java/org/springframework/asm/Frame.java
@@ -163,7 +163,7 @@ final class Frame {
private static final int LOCAL = 0x2000000;
/**
- * Kind of the the types that are relative to the stack of an input stack
+ * Kind of 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.
*/
diff --git a/spring-core/src/main/java/org/springframework/asm/Handle.java b/spring-core/src/main/java/org/springframework/asm/Handle.java
index adc6f097..02d4a58c 100644
--- a/spring-core/src/main/java/org/springframework/asm/Handle.java
+++ b/spring-core/src/main/java/org/springframework/asm/Handle.java
@@ -64,6 +64,12 @@ public final class Handle {
*/
final String desc;
+
+ /**
+ * Indicate if the owner is an interface or not.
+ */
+ final boolean itf;
+
/**
* Constructs a new field or method handle.
*
@@ -84,12 +90,44 @@ public final class Handle {
* @param desc
* the descriptor of the field or method designated by this
* handle.
+ *
+ * @deprecated this constructor has been superseded
+ * by {@link #Handle(int, String, String, String, boolean)}.
*/
+ @Deprecated
public Handle(int tag, String owner, String name, String desc) {
+ this(tag, owner, name, desc, tag == Opcodes.H_INVOKEINTERFACE);
+ }
+
+ /**
+ * 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.
+ * @param itf
+ * true if the owner is an interface.
+ */
+ public Handle(int tag, String owner, String name, String desc, boolean itf) {
this.tag = tag;
this.owner = owner;
this.name = name;
this.desc = desc;
+ this.itf = itf;
}
/**
@@ -135,6 +173,17 @@ public final class Handle {
return desc;
}
+ /**
+ * Returns true if the owner of the field or method designated
+ * by this handle is an interface.
+ *
+ * @return true if the owner of the field or method designated
+ * by this handle is an interface.
+ */
+ public boolean isInterface() {
+ return itf;
+ }
+
@Override
public boolean equals(Object obj) {
if (obj == this) {
@@ -144,13 +193,13 @@ public final class Handle {
return false;
}
Handle h = (Handle) obj;
- return tag == h.tag && owner.equals(h.owner) && name.equals(h.name)
- && desc.equals(h.desc);
+ return tag == h.tag && itf == h.itf && owner.equals(h.owner)
+ && name.equals(h.name) && desc.equals(h.desc);
}
@Override
public int hashCode() {
- return tag + owner.hashCode() * name.hashCode() * desc.hashCode();
+ return tag + (itf? 64: 0) + owner.hashCode() * name.hashCode() * desc.hashCode();
}
/**
@@ -158,13 +207,16 @@ public final class Handle {
* representation is:
*
* <pre>
+ * for a reference to a class:
* owner '.' name desc ' ' '(' tag ')'
+ * for a reference to an interface:
+ * owner '.' name desc ' ' '(' tag ' ' itf ')'
* </pre>
*
* . As this format is unambiguous, it can be parsed if necessary.
*/
@Override
public String toString() {
- return owner + '.' + name + desc + " (" + tag + ')';
+ return owner + '.' + name + desc + " (" + tag + (itf? " itf": "") + ')';
}
}
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 e9af0617..0a83068c 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-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -203,7 +203,7 @@ public abstract class CollectionFactory {
try {
return (Collection<E>) collectionType.newInstance();
}
- catch (Exception ex) {
+ catch (Throwable ex) {
throw new IllegalArgumentException(
"Could not instantiate Collection type: " + collectionType.getName(), ex);
}
@@ -318,7 +318,7 @@ public abstract class CollectionFactory {
try {
return (Map<K, V>) mapType.newInstance();
}
- catch (Exception ex) {
+ catch (Throwable ex) {
throw new IllegalArgumentException("Could not instantiate Map type: " + mapType.getName(), ex);
}
}
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 5af282f6..70a0a87c 100644
--- a/spring-core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java
+++ b/spring-core/src/main/java/org/springframework/core/ConfigurableObjectInputStream.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@ import java.io.InputStream;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
-import java.lang.reflect.Proxy;
import org.springframework.util.ClassUtils;
@@ -101,7 +100,7 @@ public class ConfigurableObjectInputStream extends ObjectInputStream {
}
}
try {
- return Proxy.getProxyClass(this.classLoader, resolvedInterfaces);
+ return ClassUtils.createCompositeInterface(resolvedInterfaces, this.classLoader);
}
catch (IllegalArgumentException ex) {
throw new ClassNotFoundException(null, ex);
@@ -117,7 +116,7 @@ public class ConfigurableObjectInputStream extends ObjectInputStream {
for (int i = 0; i < interfaces.length; i++) {
resolvedInterfaces[i] = resolveFallbackIfPossible(interfaces[i], ex);
}
- return Proxy.getProxyClass(getFallbackClassLoader(), resolvedInterfaces);
+ return ClassUtils.createCompositeInterface(resolvedInterfaces, getFallbackClassLoader());
}
}
}
@@ -139,8 +138,9 @@ public class ConfigurableObjectInputStream extends ObjectInputStream {
/**
* Return the fallback ClassLoader to use when no ClassLoader was specified
- * and ObjectInputStream's own default ClassLoader failed.
- * <p>The default implementation simply returns {@code null}.
+ * and ObjectInputStream's own default class loader failed.
+ * <p>The default implementation simply returns {@code null}, indicating
+ * that no specific fallback is available.
*/
protected ClassLoader getFallbackClassLoader() throws IOException {
return null;
diff --git a/spring-core/src/main/java/org/springframework/core/DecoratingClassLoader.java b/spring-core/src/main/java/org/springframework/core/DecoratingClassLoader.java
index 350df5cf..b29ac071 100644
--- a/spring-core/src/main/java/org/springframework/core/DecoratingClassLoader.java
+++ b/spring-core/src/main/java/org/springframework/core/DecoratingClassLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,8 +16,9 @@
package org.springframework.core;
-import java.util.HashSet;
+import java.util.Collections;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import org.springframework.lang.UsesJava7;
import org.springframework.util.Assert;
@@ -49,11 +50,11 @@ public abstract class DecoratingClassLoader extends ClassLoader {
}
- private final Set<String> excludedPackages = new HashSet<String>();
+ private final Set<String> excludedPackages =
+ Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(8));
- private final Set<String> excludedClasses = new HashSet<String>();
-
- private final Object exclusionMonitor = new Object();
+ private final Set<String> excludedClasses =
+ Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(8));
/**
@@ -79,9 +80,7 @@ public abstract class DecoratingClassLoader extends ClassLoader {
*/
public void excludePackage(String packageName) {
Assert.notNull(packageName, "Package name must not be null");
- synchronized (this.exclusionMonitor) {
- this.excludedPackages.add(packageName);
- }
+ this.excludedPackages.add(packageName);
}
/**
@@ -92,9 +91,7 @@ public abstract class DecoratingClassLoader extends ClassLoader {
*/
public void excludeClass(String className) {
Assert.notNull(className, "Class name must not be null");
- synchronized (this.exclusionMonitor) {
- this.excludedClasses.add(className);
- }
+ this.excludedClasses.add(className);
}
/**
@@ -107,15 +104,13 @@ public abstract class DecoratingClassLoader extends ClassLoader {
* @see #excludeClass
*/
protected boolean isExcluded(String className) {
- synchronized (this.exclusionMonitor) {
- if (this.excludedClasses.contains(className)) {
+ if (this.excludedClasses.contains(className)) {
+ return true;
+ }
+ for (String packageName : this.excludedPackages) {
+ if (className.startsWith(packageName)) {
return true;
}
- for (String packageName : this.excludedPackages) {
- if (className.startsWith(packageName)) {
- return true;
- }
- }
}
return false;
}
diff --git a/spring-core/src/main/java/org/springframework/core/DecoratingProxy.java b/spring-core/src/main/java/org/springframework/core/DecoratingProxy.java
new file mode 100644
index 00000000..b3ae9fce
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/DecoratingProxy.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core;
+
+/**
+ * Interface to be implemented by decorating proxies, in particular Spring AOP
+ * proxies but potentially also custom proxies with decorator semantics.
+ *
+ * <p>Note that this interface should just be implemented if the decorated class
+ * is not within the hierarchy of the proxy class to begin with. In particular,
+ * a "target-class" proxy such as a Spring AOP CGLIB proxy should not implement
+ * it since any lookup on the target class can simply be performed on the proxy
+ * class there anyway.
+ *
+ * <p>Defined in the core module in order to allow
+ * #{@link org.springframework.core.annotation.AnnotationAwareOrderComparator}
+ * (and potential other candidates without spring-aop dependencies) to use it
+ * for introspection purposes, in particular annotation lookups.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ */
+public interface DecoratingProxy {
+
+ /**
+ * Return the (ultimate) decorated class behind this proxy.
+ * <p>In case of an AOP proxy, this will be the ultimate target class,
+ * not just the immediate target (in case of multiple nested proxies).
+ * @return the decorated class (never {@code null})
+ */
+ Class<?> getDecoratedClass();
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/MethodClassKey.java b/spring-core/src/main/java/org/springframework/core/MethodClassKey.java
new file mode 100644
index 00000000..b837fc64
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/MethodClassKey.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core;
+
+import java.lang.reflect.Method;
+
+import org.springframework.util.ObjectUtils;
+
+/**
+ * A common key class for a method against a specific target class,
+ * including {@link #toString()} representation and {@link Comparable}
+ * support (as suggested for custom {@code HashMap} keys as of Java 8).
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ */
+public final class MethodClassKey implements Comparable<MethodClassKey> {
+
+ private final Method method;
+
+ private final Class<?> targetClass;
+
+
+ /**
+ * Create a key object for the given method and target class.
+ * @param method the method to wrap (must not be {@code null})
+ * @param targetClass the target class that the method will be invoked
+ * on (may be {@code null} if identical to the declaring class)
+ */
+ public MethodClassKey(Method method, Class<?> targetClass) {
+ this.method = method;
+ this.targetClass = targetClass;
+ }
+
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof MethodClassKey)) {
+ return false;
+ }
+ MethodClassKey otherKey = (MethodClassKey) other;
+ return (this.method.equals(otherKey.method) &&
+ ObjectUtils.nullSafeEquals(this.targetClass, otherKey.targetClass));
+ }
+
+ @Override
+ public int hashCode() {
+ return this.method.hashCode() + (this.targetClass != null ? this.targetClass.hashCode() * 29 : 0);
+ }
+
+ @Override
+ public String toString() {
+ return this.method + (this.targetClass != null ? " on " + this.targetClass : "");
+ }
+
+ @Override
+ public int compareTo(MethodClassKey other) {
+ int result = this.method.getName().compareTo(other.method.getName());
+ if (result == 0) {
+ result = this.method.toString().compareTo(other.method.toString());
+ if (result == 0 && this.targetClass != null) {
+ result = this.targetClass.getName().compareTo(other.targetClass.getName());
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/MethodIntrospector.java b/spring-core/src/main/java/org/springframework/core/MethodIntrospector.java
index d402c10b..804c26e7 100644
--- a/spring-core/src/main/java/org/springframework/core/MethodIntrospector.java
+++ b/spring-core/src/main/java/org/springframework/core/MethodIntrospector.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -85,10 +85,9 @@ public abstract class MethodIntrospector {
/**
* Select methods on the given target type based on a filter.
- * <p>Callers define methods of interest through the
- * {@link ReflectionUtils.MethodFilter} parameter.
+ * <p>Callers define methods of interest through the {@code MethodFilter} parameter.
* @param targetType the target type to search methods on
- * @param methodFilter a {@link ReflectionUtils.MethodFilter} to help
+ * @param methodFilter a {@code MethodFilter} to help
* recognize handler methods of interest
* @return the selected methods, or an empty set in case of no match
*/
@@ -111,22 +110,26 @@ public abstract class MethodIntrospector {
* @param targetType the target type to search methods on
* (typically an interface-based JDK proxy)
* @return a corresponding invocable method on the target type
+ * @throws IllegalStateException if the given method is not invocable on the given
+ * target type (typically due to a proxy mismatch)
*/
public static Method selectInvocableMethod(Method method, Class<?> targetType) {
if (method.getDeclaringClass().isAssignableFrom(targetType)) {
return method;
}
try {
+ String methodName = method.getName();
+ Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> ifc : targetType.getInterfaces()) {
try {
- return ifc.getMethod(method.getName(), method.getParameterTypes());
+ return ifc.getMethod(methodName, parameterTypes);
}
catch (NoSuchMethodException ex) {
// Alright, not on this interface then...
}
}
// A final desperate attempt on the proxy class itself...
- return targetType.getMethod(method.getName(), method.getParameterTypes());
+ return targetType.getMethod(methodName, parameterTypes);
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException(String.format(
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 e34b8d00..48344a91 100644
--- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java
+++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java
@@ -27,6 +27,7 @@ import java.util.HashMap;
import java.util.Map;
import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
/**
* Helper class that encapsulates the specification of a method parameter, i.e. a {@link Method}
@@ -47,6 +48,21 @@ import org.springframework.util.Assert;
*/
public class MethodParameter {
+ private static final Class<?> javaUtilOptionalClass;
+
+ static {
+ Class<?> clazz;
+ try {
+ clazz = ClassUtils.forName("java.util.Optional", MethodParameter.class.getClassLoader());
+ }
+ catch (ClassNotFoundException ex) {
+ // Java 8 not available - Optional references simply not supported then.
+ clazz = null;
+ }
+ javaUtilOptionalClass = clazz;
+ }
+
+
private final Method method;
private final Constructor<?> constructor;
@@ -70,6 +86,8 @@ public class MethodParameter {
private volatile String parameterName;
+ private volatile MethodParameter nestedMethodParameter;
+
/**
* Create a new {@code MethodParameter} for the given method, with nesting level 1.
@@ -279,6 +297,44 @@ public class MethodParameter {
return this.typeIndexesPerLevel;
}
+ /**
+ * Return a variant of this {@code MethodParameter} which points to the
+ * same parameter but one nesting level deeper. This is effectively the
+ * same as {@link #increaseNestingLevel()}, just with an independent
+ * {@code MethodParameter} object (e.g. in case of the original being cached).
+ * @since 4.3
+ */
+ public MethodParameter nested() {
+ if (this.nestedMethodParameter != null) {
+ return this.nestedMethodParameter;
+ }
+ MethodParameter nestedParam = clone();
+ nestedParam.nestingLevel = this.nestingLevel + 1;
+ this.nestedMethodParameter = nestedParam;
+ return nestedParam;
+ }
+
+ /**
+ * Return whether this method parameter is declared as optional
+ * in the form of Java 8's {@link java.util.Optional}.
+ * @since 4.3
+ */
+ public boolean isOptional() {
+ return (getParameterType() == javaUtilOptionalClass);
+ }
+
+ /**
+ * Return a variant of this {@code MethodParameter} which points to
+ * the same parameter but one nesting level deeper in case of a
+ * {@link java.util.Optional} declaration.
+ * @since 4.3
+ * @see #isOptional()
+ * @see #nested()
+ */
+ public MethodParameter nestedIfOptional() {
+ return (isOptional() ? nested() : this);
+ }
+
/**
* Set a containing class to resolve the parameter type against.
@@ -350,6 +406,7 @@ public class MethodParameter {
Integer index = getTypeIndexForLevel(i);
type = args[index != null ? index : args.length - 1];
}
+ // TODO: Object.class if unresolvable
}
if (type instanceof Class) {
return (Class<?>) type;
@@ -407,6 +464,16 @@ public class MethodParameter {
}
/**
+ * Return whether the method/constructor is annotated with the given type.
+ * @param annotationType the annotation type to look for
+ * @since 4.3
+ * @see #getMethodAnnotation(Class)
+ */
+ public <A extends Annotation> boolean hasMethodAnnotation(Class<A> annotationType) {
+ return getAnnotatedElement().isAnnotationPresent(annotationType);
+ }
+
+ /**
* Return the annotations associated with the specific method/constructor parameter.
*/
public Annotation[] getParameterAnnotations() {
@@ -530,6 +597,17 @@ public class MethodParameter {
return (getMember().hashCode() * 31 + this.parameterIndex);
}
+ @Override
+ public String toString() {
+ return (this.method != null ? "method '" + this.method.getName() + "'" : "constructor") +
+ " parameter " + this.parameterIndex;
+ }
+
+ @Override
+ public MethodParameter clone() {
+ return new MethodParameter(this);
+ }
+
/**
* Create a new MethodParameter for the given method or constructor.
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 684d8561..c0bc9f32 100644
--- a/spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java
+++ b/spring-core/src/main/java/org/springframework/core/OverridingClassLoader.java
@@ -23,12 +23,12 @@ import org.springframework.lang.UsesJava7;
import org.springframework.util.FileCopyUtils;
/**
- * {@code ClassLoader} that does <i>not</i> always delegate to the
- * parent loader, as normal class loaders do. This enables, for example,
- * instrumentation to be forced in the overriding ClassLoader, or a
- * "throwaway" class loading behavior, where selected classes are
- * temporarily loaded in the overriding ClassLoader, in order to load
- * an instrumented version of the class in the parent ClassLoader later on.
+ * {@code ClassLoader} that does <i>not</i> always delegate to the parent loader
+ * as normal class loaders do. This enables, for example, instrumentation to be
+ * forced in the overriding ClassLoader, or a "throwaway" class loading behavior
+ * where selected application classes are temporarily loaded in the overriding
+ * {@code ClassLoader} for introspection purposes before eventually loading an
+ * instrumented version of the class in the given parent {@code ClassLoader}.
*
* @author Rod Johnson
* @author Juergen Hoeller
@@ -38,7 +38,8 @@ import org.springframework.util.FileCopyUtils;
public class OverridingClassLoader extends DecoratingClassLoader {
/** Packages that are excluded by default */
- public static final String[] DEFAULT_EXCLUDED_PACKAGES = new String[] {"java.", "javax.", "sun.", "oracle."};
+ public static final String[] DEFAULT_EXCLUDED_PACKAGES = new String[]
+ {"java.", "javax.", "sun.", "oracle.", "javassist.", "org.aspectj.", "net.sf.cglib."};
private static final String CLASS_FILE_SUFFIX = ".class";
@@ -49,12 +50,26 @@ public class OverridingClassLoader extends DecoratingClassLoader {
}
+ private final ClassLoader overrideDelegate;
+
+
/**
* Create a new OverridingClassLoader for the given ClassLoader.
* @param parent the ClassLoader to build an overriding ClassLoader for
*/
public OverridingClassLoader(ClassLoader parent) {
+ this(parent, null);
+ }
+
+ /**
+ * Create a new OverridingClassLoader for the given ClassLoader.
+ * @param parent the ClassLoader to build an overriding ClassLoader for
+ * @param overrideDelegate the ClassLoader to delegate to for overriding
+ * @since 4.3
+ */
+ public OverridingClassLoader(ClassLoader parent, ClassLoader overrideDelegate) {
super(parent);
+ this.overrideDelegate = overrideDelegate;
for (String packageName : DEFAULT_EXCLUDED_PACKAGES) {
excludePackage(packageName);
}
@@ -62,6 +77,14 @@ public class OverridingClassLoader extends DecoratingClassLoader {
@Override
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ if (this.overrideDelegate != null && isEligibleForOverriding(name)) {
+ return this.overrideDelegate.loadClass(name);
+ }
+ return super.loadClass(name);
+ }
+
+ @Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (isEligibleForOverriding(name)) {
Class<?> result = loadClassForOverriding(name);
diff --git a/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java
index 8f1fd9d9..eaae658a 100644
--- a/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java
+++ b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -372,6 +372,8 @@ abstract class SerializableTypeWrapper {
private final String methodName;
+ private final Class<?> declaringClass;
+
private final int index;
private transient Method method;
@@ -381,6 +383,7 @@ abstract class SerializableTypeWrapper {
public MethodInvokeTypeProvider(TypeProvider provider, Method method, int index) {
this.provider = provider;
this.methodName = method.getName();
+ this.declaringClass = method.getDeclaringClass();
this.index = index;
this.method = method;
}
@@ -404,7 +407,7 @@ abstract class SerializableTypeWrapper {
private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
inputStream.defaultReadObject();
- this.method = ReflectionUtils.findMethod(this.provider.getType().getClass(), this.methodName);
+ this.method = ReflectionUtils.findMethod(this.declaringClass, this.methodName);
Assert.state(Type.class == this.method.getReturnType() || Type[].class == this.method.getReturnType());
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/SpringProperties.java b/spring-core/src/main/java/org/springframework/core/SpringProperties.java
index f0b25d6d..f5c34994 100644
--- a/spring-core/src/main/java/org/springframework/core/SpringProperties.java
+++ b/spring-core/src/main/java/org/springframework/core/SpringProperties.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -42,6 +42,7 @@ import org.apache.commons.logging.LogFactory;
* @see org.springframework.core.env.AbstractEnvironment#IGNORE_GETENV_PROPERTY_NAME
* @see org.springframework.beans.CachedIntrospectionResults#IGNORE_BEANINFO_PROPERTY_NAME
* @see org.springframework.jdbc.core.StatementCreatorUtils#IGNORE_GETPARAMETERTYPE_PROPERTY_NAME
+ * @see org.springframework.test.context.cache.ContextCache#MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME
*/
public abstract class SpringProperties {
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AbstractAliasAwareAnnotationAttributeExtractor.java b/spring-core/src/main/java/org/springframework/core/annotation/AbstractAliasAwareAnnotationAttributeExtractor.java
index 0febd916..32b8725c 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AbstractAliasAwareAnnotationAttributeExtractor.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AbstractAliasAwareAnnotationAttributeExtractor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
-import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
@@ -35,13 +34,13 @@ import org.springframework.util.ObjectUtils;
* @param <S> the type of source supported by this extractor
* @see Annotation
* @see AliasFor
- * @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement)
+ * @see AnnotationUtils#synthesizeAnnotation(Annotation, Object)
*/
abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements AnnotationAttributeExtractor<S> {
private final Class<? extends Annotation> annotationType;
- private final AnnotatedElement annotatedElement;
+ private final Object annotatedElement;
private final S source;
@@ -56,7 +55,7 @@ abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements Anno
* @param source the underlying source of annotation attributes; never {@code null}
*/
AbstractAliasAwareAnnotationAttributeExtractor(
- Class<? extends Annotation> annotationType, AnnotatedElement annotatedElement, S source) {
+ Class<? extends Annotation> annotationType, Object annotatedElement, S source) {
Assert.notNull(annotationType, "annotationType must not be null");
Assert.notNull(source, "source must not be null");
@@ -73,7 +72,7 @@ abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements Anno
}
@Override
- public final AnnotatedElement getAnnotatedElement() {
+ public final Object getAnnotatedElement() {
return this.annotatedElement;
}
@@ -89,18 +88,18 @@ abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements Anno
List<String> aliasNames = this.attributeAliasMap.get(attributeName);
if (aliasNames != null) {
- Object defaultValue = AnnotationUtils.getDefaultValue(getAnnotationType(), attributeName);
+ Object defaultValue = AnnotationUtils.getDefaultValue(this.annotationType, attributeName);
for (String aliasName : aliasNames) {
Object aliasValue = getRawAttributeValue(aliasName);
if (!ObjectUtils.nullSafeEquals(attributeValue, aliasValue) &&
!ObjectUtils.nullSafeEquals(attributeValue, defaultValue) &&
!ObjectUtils.nullSafeEquals(aliasValue, defaultValue)) {
- String elementName = (getAnnotatedElement() != null ? getAnnotatedElement().toString() : "unknown element");
+ String elementName = (this.annotatedElement != null ? this.annotatedElement.toString() : "unknown element");
throw new AnnotationConfigurationException(String.format(
"In annotation [%s] declared on %s and synthesized from [%s], attribute '%s' and its " +
"alias '%s' are present with values of [%s] and [%s], but only one is permitted.",
- getAnnotationType().getName(), elementName, getSource(), attributeName, aliasName,
+ this.annotationType.getName(), elementName, this.source, attributeName, aliasName,
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue)));
}
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
index 74549cf2..6021ab16 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,8 +33,8 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
- * General utility methods for finding annotations and meta-annotations on
- * {@link AnnotatedElement AnnotatedElements}.
+ * General utility methods for finding annotations, meta-annotations, and
+ * repeatable annotations on {@link AnnotatedElement AnnotatedElements}.
*
* <p>{@code AnnotatedElementUtils} defines the public API for Spring's
* meta-annotation programming model with support for <em>annotation attribute
@@ -48,7 +48,9 @@ import org.springframework.util.MultiValueMap;
* <p>Support for meta-annotations with <em>attribute overrides</em> in
* <em>composed annotations</em> is provided by all variants of the
* {@code getMergedAnnotationAttributes()}, {@code getMergedAnnotation()},
- * {@code findMergedAnnotationAttributes()}, and {@code findMergedAnnotation()}
+ * {@code getAllMergedAnnotations()}, {@code getMergedRepeatableAnnotations()},
+ * {@code findMergedAnnotationAttributes()}, {@code findMergedAnnotation()},
+ * {@code findAllMergedAnnotations()}, and {@code findMergedRepeatableAnnotations()}
* methods.
*
* <h3>Find vs. Get Semantics</h3>
@@ -94,8 +96,44 @@ import org.springframework.util.MultiValueMap;
*/
public class AnnotatedElementUtils {
+ /**
+ * {@code null} constant used to denote that the search algorithm should continue.
+ */
private static final Boolean CONTINUE = null;
+ private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
+
+ private static final Processor<Boolean> alwaysTrueAnnotationProcessor = new AlwaysTrueBooleanAnnotationProcessor();
+
+
+ /**
+ * Build an adapted {@link AnnotatedElement} for the given annotations,
+ * typically for use with other methods on {@link AnnotatedElementUtils}.
+ * @param annotations the annotations to expose through the {@code AnnotatedElement}
+ * @since 4.3
+ */
+ public static AnnotatedElement forAnnotations(final Annotation... annotations) {
+ return new AnnotatedElement() {
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
+ for (Annotation ann : annotations) {
+ if (ann.annotationType() == annotationClass) {
+ return (T) ann;
+ }
+ }
+ return null;
+ }
+ @Override
+ public Annotation[] getAnnotations() {
+ return annotations;
+ }
+ @Override
+ public Annotation[] getDeclaredAnnotations() {
+ return annotations;
+ }
+ };
+ }
/**
* Get the fully qualified class names of all meta-annotation types
@@ -114,26 +152,8 @@ public class AnnotatedElementUtils {
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, Class<? extends Annotation> annotationType) {
Assert.notNull(element, "AnnotatedElement must not be null");
Assert.notNull(annotationType, "annotationType must not be null");
- final Set<String> types = new LinkedHashSet<String>();
-
- try {
- Annotation annotation = element.getAnnotation(annotationType);
- if (annotation != null) {
- searchWithGetSemantics(annotation.annotationType(), annotationType, null, new SimpleAnnotationProcessor<Object>() {
- @Override
- public Object process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
- types.add(annotation.annotationType().getName());
- return CONTINUE;
- }
- }, new HashSet<AnnotatedElement>(), 1);
- }
- }
- catch (Throwable ex) {
- AnnotationUtils.rethrowAnnotationConfigurationException(ex);
- throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
- }
- return (!types.isEmpty() ? types : null);
+ return getMetaAnnotationTypes(element, element.getAnnotation(annotationType));
}
/**
@@ -153,51 +173,49 @@ public class AnnotatedElementUtils {
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, String annotationName) {
Assert.notNull(element, "AnnotatedElement must not be null");
Assert.hasLength(annotationName, "annotationName must not be null or empty");
- final Set<String> types = new LinkedHashSet<String>();
+
+ return getMetaAnnotationTypes(element, AnnotationUtils.getAnnotation(element, annotationName));
+ }
+
+ private static Set<String> getMetaAnnotationTypes(AnnotatedElement element, Annotation composed) {
+ if (composed == null) {
+ return null;
+ }
try {
- Annotation annotation = AnnotationUtils.getAnnotation(element, annotationName);
- if (annotation != null) {
- searchWithGetSemantics(annotation.annotationType(), null, annotationName, new SimpleAnnotationProcessor<Object>() {
+ final Set<String> types = new LinkedHashSet<String>();
+ searchWithGetSemantics(composed.annotationType(), null, null, null, new SimpleAnnotationProcessor<Object>(true) {
@Override
public Object process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
types.add(annotation.annotationType().getName());
return CONTINUE;
}
}, new HashSet<AnnotatedElement>(), 1);
- }
+ return (!types.isEmpty() ? types : null);
}
catch (Throwable ex) {
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
}
-
- return (!types.isEmpty() ? types : null);
}
/**
* Determine if the supplied {@link AnnotatedElement} is annotated with
* a <em>composed annotation</em> that is meta-annotated with an
- * annotation of the specified {@code annotationName}.
+ * annotation of the specified {@code annotationType}.
* <p>This method follows <em>get semantics</em> as described in the
* {@linkplain AnnotatedElementUtils class-level javadoc}.
* @param element the annotated element
- * @param annotationType the annotation type on which to find meta-annotations
+ * @param annotationType the meta-annotation type to find
* @return {@code true} if a matching meta-annotation is present
* @since 4.2.3
* @see #getMetaAnnotationTypes
*/
- public static boolean hasMetaAnnotationTypes(AnnotatedElement element, final Class<? extends Annotation> annotationType) {
+ public static boolean hasMetaAnnotationTypes(AnnotatedElement element, Class<? extends Annotation> annotationType) {
Assert.notNull(element, "AnnotatedElement must not be null");
Assert.notNull(annotationType, "annotationType must not be null");
- return Boolean.TRUE.equals(searchWithGetSemantics(element, annotationType, null, new SimpleAnnotationProcessor<Boolean>() {
- @Override
- public Boolean process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
- boolean found = (annotation.annotationType() == annotationType);
- return (found && metaDepth > 0 ? Boolean.TRUE : CONTINUE);
- }
- }));
+ return hasMetaAnnotationTypes(element, annotationType, null);
}
/**
@@ -212,21 +230,28 @@ public class AnnotatedElementUtils {
* @return {@code true} if a matching meta-annotation is present
* @see #getMetaAnnotationTypes
*/
- public static boolean hasMetaAnnotationTypes(AnnotatedElement element, final String annotationName) {
+ public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationName) {
Assert.notNull(element, "AnnotatedElement must not be null");
Assert.hasLength(annotationName, "annotationName must not be null or empty");
- return Boolean.TRUE.equals(searchWithGetSemantics(element, null, annotationName, new SimpleAnnotationProcessor<Boolean>() {
- @Override
- public Boolean process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
- boolean found = annotation.annotationType().getName().equals(annotationName);
- return (found && metaDepth > 0 ? Boolean.TRUE : CONTINUE);
- }
- }));
+ return hasMetaAnnotationTypes(element, null, annotationName);
+ }
+
+ private static boolean hasMetaAnnotationTypes(AnnotatedElement element, Class<? extends Annotation> annotationType,
+ String annotationName) {
+
+ return Boolean.TRUE.equals(
+ searchWithGetSemantics(element, annotationType, annotationName, new SimpleAnnotationProcessor<Boolean>() {
+
+ @Override
+ public Boolean process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
+ return (metaDepth > 0 ? Boolean.TRUE : CONTINUE);
+ }
+ }));
}
/**
- * Determine if an annotation of the specified {@code annotationName}
+ * Determine if an annotation of the specified {@code annotationType}
* is <em>present</em> on the supplied {@link AnnotatedElement} or
* within the annotation hierarchy <em>above</em> the specified element.
* <p>If this method returns {@code true}, then {@link #getMergedAnnotationAttributes}
@@ -234,21 +259,21 @@ public class AnnotatedElementUtils {
* <p>This method follows <em>get semantics</em> as described in the
* {@linkplain AnnotatedElementUtils class-level javadoc}.
* @param element the annotated element
- * @param annotationType the annotation type on which to find meta-annotations
+ * @param annotationType the annotation type to find
* @return {@code true} if a matching annotation is present
* @since 4.2.3
+ * @see #hasAnnotation(AnnotatedElement, Class)
*/
- public static boolean isAnnotated(AnnotatedElement element, final Class<? extends Annotation> annotationType) {
+ public static boolean isAnnotated(AnnotatedElement element, Class<? extends Annotation> annotationType) {
Assert.notNull(element, "AnnotatedElement must not be null");
Assert.notNull(annotationType, "annotationType must not be null");
- return Boolean.TRUE.equals(searchWithGetSemantics(element, annotationType, null, new SimpleAnnotationProcessor<Boolean>() {
- @Override
- public Boolean process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
- boolean found = annotation.annotationType() == annotationType;
- return (found ? Boolean.TRUE : CONTINUE);
- }
- }));
+ // Shortcut: directly present on the element, with no processing needed?
+ if (element.isAnnotationPresent(annotationType)) {
+ return true;
+ }
+
+ return Boolean.TRUE.equals(searchWithGetSemantics(element, annotationType, null, alwaysTrueAnnotationProcessor));
}
/**
@@ -263,40 +288,29 @@ public class AnnotatedElementUtils {
* @param annotationName the fully qualified class name of the annotation type to find
* @return {@code true} if a matching annotation is present
*/
- public static boolean isAnnotated(AnnotatedElement element, final String annotationName) {
+ public static boolean isAnnotated(AnnotatedElement element, String annotationName) {
Assert.notNull(element, "AnnotatedElement must not be null");
Assert.hasLength(annotationName, "annotationName must not be null or empty");
- return Boolean.TRUE.equals(searchWithGetSemantics(element, null, annotationName, new SimpleAnnotationProcessor<Boolean>() {
- @Override
- public Boolean process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
- boolean found = annotation.annotationType().getName().equals(annotationName);
- return (found ? Boolean.TRUE : CONTINUE);
- }
- }));
+ return Boolean.TRUE.equals(searchWithGetSemantics(element, null, annotationName, alwaysTrueAnnotationProcessor));
}
/**
- * Get the first annotation of the specified {@code annotationType} within
- * the annotation hierarchy <em>above</em> the supplied {@code element},
- * merge that annotation's attributes with <em>matching</em> attributes from
- * annotations in lower levels of the annotation hierarchy, and synthesize
- * the result back into an annotation of the specified {@code annotationType}.
- * <p>{@link AliasFor @AliasFor} semantics are fully supported, both
- * within a single annotation and within the annotation hierarchy.
- * <p>This method delegates to {@link #getMergedAnnotationAttributes(AnnotatedElement, Class)}
- * and {@link AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)}.
- * @param element the annotated element
- * @param annotationType the annotation type to find
- * @return the merged, synthesized {@code Annotation}, or {@code null} if not found
- * @since 4.2
- * @see #getMergedAnnotationAttributes(AnnotatedElement, Class)
- * @see #findMergedAnnotation(AnnotatedElement, Class)
- * @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)
+ * @deprecated As of Spring Framework 4.2, use {@link #getMergedAnnotationAttributes(AnnotatedElement, String)} instead.
*/
- public static <A extends Annotation> A getMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
- AnnotationAttributes attributes = getMergedAnnotationAttributes(element, annotationType);
- return AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element);
+ @Deprecated
+ public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationName) {
+ return getMergedAnnotationAttributes(element, annotationName);
+ }
+
+ /**
+ * @deprecated As of Spring Framework 4.2, use {@link #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)} instead.
+ */
+ @Deprecated
+ public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationName,
+ boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+
+ return getMergedAnnotationAttributes(element, annotationName, classValuesAsString, nestedAnnotationsAsMap);
}
/**
@@ -321,7 +335,7 @@ public class AnnotatedElementUtils {
Assert.notNull(annotationType, "annotationType must not be null");
AnnotationAttributes attributes = searchWithGetSemantics(element, annotationType, null,
- new MergedAnnotationAttributesProcessor(annotationType, null, false, false));
+ new MergedAnnotationAttributesProcessor());
AnnotationUtils.postProcessAnnotationAttributes(element, attributes, false, false);
return attributes;
}
@@ -377,62 +391,238 @@ public class AnnotatedElementUtils {
public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element,
String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+ Assert.hasLength(annotationName, "annotationName must not be null or empty");
AnnotationAttributes attributes = searchWithGetSemantics(element, null, annotationName,
- new MergedAnnotationAttributesProcessor(null, annotationName, classValuesAsString, nestedAnnotationsAsMap));
+ new MergedAnnotationAttributesProcessor(classValuesAsString, nestedAnnotationsAsMap));
AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString, nestedAnnotationsAsMap);
return attributes;
}
/**
- * Find the first annotation of the specified {@code annotationType} within
+ * Get the first annotation of the specified {@code annotationType} within
* the annotation hierarchy <em>above</em> the supplied {@code element},
* merge that annotation's attributes with <em>matching</em> attributes from
* annotations in lower levels of the annotation hierarchy, and synthesize
* the result back into an annotation of the specified {@code annotationType}.
* <p>{@link AliasFor @AliasFor} semantics are fully supported, both
* within a single annotation and within the annotation hierarchy.
+ * <p>This method delegates to {@link #getMergedAnnotationAttributes(AnnotatedElement, Class)}
+ * and {@link AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)}.
* @param element the annotated element
* @param annotationType the annotation type to find
* @return the merged, synthesized {@code Annotation}, or {@code null} if not found
* @since 4.2
- * @see #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
* @see #getMergedAnnotationAttributes(AnnotatedElement, Class)
+ * @see #findMergedAnnotation(AnnotatedElement, Class)
+ * @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)
*/
- public static <A extends Annotation> A findMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
+ public static <A extends Annotation> A getMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
Assert.notNull(annotationType, "annotationType must not be null");
- AnnotationAttributes attributes = findMergedAnnotationAttributes(element, annotationType, false, false);
+
+ // Shortcut: directly present on the element, with no merging needed?
+ if (!(element instanceof Class)) {
+ // Do not use this shortcut against a Class: Inherited annotations
+ // would get preferred over locally declared composed annotations.
+ A annotation = element.getAnnotation(annotationType);
+ if (annotation != null) {
+ return AnnotationUtils.synthesizeAnnotation(annotation, element);
+ }
+ }
+
+ // Exhaustive retrieval of merged annotation attributes...
+ AnnotationAttributes attributes = getMergedAnnotationAttributes(element, annotationType);
return AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element);
}
/**
- * Find the first annotation of the specified {@code annotationName} within
- * the annotation hierarchy <em>above</em> the supplied {@code element},
- * merge that annotation's attributes with <em>matching</em> attributes from
- * annotations in lower levels of the annotation hierarchy, and synthesize
- * the result back into an annotation of the specified {@code annotationName}.
- * <p>{@link AliasFor @AliasFor} semantics are fully supported, both
- * within a single annotation and within the annotation hierarchy.
- * <p>This method delegates to {@link #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)}
- * (supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap})
- * and {@link AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)}.
+ * Get <strong>all</strong> annotations of the specified {@code annotationType}
+ * within the annotation hierarchy <em>above</em> the supplied {@code element};
+ * and for each annotation found, merge that annotation's attributes with
+ * <em>matching</em> attributes from annotations in lower levels of the annotation
+ * hierarchy and synthesize the results back into an annotation of the specified
+ * {@code annotationType}.
+ * <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
+ * single annotation and within annotation hierarchies.
+ * <p>This method follows <em>get semantics</em> as described in the
+ * {@linkplain AnnotatedElementUtils class-level javadoc}.
+ * @param element the annotated element; never {@code null}
+ * @param annotationType the annotation type to find; never {@code null}
+ * @return the set of all merged, synthesized {@code Annotations} found, or an empty
+ * set if none were found
+ * @since 4.3
+ * @see #getMergedAnnotation(AnnotatedElement, Class)
+ * @see #getAllAnnotationAttributes(AnnotatedElement, String)
+ * @see #findAllMergedAnnotations(AnnotatedElement, Class)
+ */
+ public static <A extends Annotation> Set<A> getAllMergedAnnotations(AnnotatedElement element,
+ Class<A> annotationType) {
+
+ Assert.notNull(element, "AnnotatedElement must not be null");
+ Assert.notNull(annotationType, "annotationType must not be null");
+
+ MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true);
+ searchWithGetSemantics(element, annotationType, null, processor);
+ return postProcessAndSynthesizeAggregatedResults(element, annotationType, processor.getAggregatedResults());
+ }
+
+ /**
+ * Get all <em>repeatable annotations</em> of the specified {@code annotationType}
+ * within the annotation hierarchy <em>above</em> the supplied {@code element};
+ * and for each annotation found, merge that annotation's attributes with
+ * <em>matching</em> attributes from annotations in lower levels of the annotation
+ * hierarchy and synthesize the results back into an annotation of the specified
+ * {@code annotationType}.
+ * <p>The container type that holds the repeatable annotations will be looked up
+ * via {@link java.lang.annotation.Repeatable}.
+ * <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
+ * single annotation and within annotation hierarchies.
+ * <p>This method follows <em>get semantics</em> as described in the
+ * {@linkplain AnnotatedElementUtils class-level javadoc}.
+ * @param element the annotated element; never {@code null}
+ * @param annotationType the annotation type to find; never {@code null}
+ * @return the set of all merged repeatable {@code Annotations} found, or an empty
+ * set if none were found
+ * @since 4.3
+ * @see #getMergedAnnotation(AnnotatedElement, Class)
+ * @see #getAllMergedAnnotations(AnnotatedElement, Class)
+ * @see #getMergedRepeatableAnnotations(AnnotatedElement, Class, Class)
+ * @throws IllegalArgumentException if the {@code element} or {@code annotationType}
+ * is {@code null}, or if the container type cannot be resolved
+ */
+ public static <A extends Annotation> Set<A> getMergedRepeatableAnnotations(AnnotatedElement element,
+ Class<A> annotationType) {
+
+ return getMergedRepeatableAnnotations(element, annotationType, null);
+ }
+
+ /**
+ * Get all <em>repeatable annotations</em> of the specified {@code annotationType}
+ * within the annotation hierarchy <em>above</em> the supplied {@code element};
+ * and for each annotation found, merge that annotation's attributes with
+ * <em>matching</em> attributes from annotations in lower levels of the annotation
+ * hierarchy and synthesize the results back into an annotation of the specified
+ * {@code annotationType}.
+ * <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
+ * single annotation and within annotation hierarchies.
+ * <p>This method follows <em>get semantics</em> as described in the
+ * {@linkplain AnnotatedElementUtils class-level javadoc}.
+ * @param element the annotated element; never {@code null}
+ * @param annotationType the annotation type to find; never {@code null}
+ * @param containerType the type of the container that holds the annotations;
+ * may be {@code null} if the container type should be looked up via
+ * {@link java.lang.annotation.Repeatable}
+ * @return the set of all merged repeatable {@code Annotations} found, or an empty
+ * set if none were found
+ * @since 4.3
+ * @see #getMergedAnnotation(AnnotatedElement, Class)
+ * @see #getAllMergedAnnotations(AnnotatedElement, Class)
+ * @throws IllegalArgumentException if the {@code element} or {@code annotationType}
+ * is {@code null}, or if the container type cannot be resolved
+ * @throws AnnotationConfigurationException if the supplied {@code containerType}
+ * is not a valid container annotation for the supplied {@code annotationType}
+ */
+ public static <A extends Annotation> Set<A> getMergedRepeatableAnnotations(AnnotatedElement element,
+ Class<A> annotationType, Class<? extends Annotation> containerType) {
+
+ Assert.notNull(element, "AnnotatedElement must not be null");
+ Assert.notNull(annotationType, "annotationType must not be null");
+
+ if (containerType == null) {
+ containerType = resolveContainerType(annotationType);
+ }
+ else {
+ validateContainerType(annotationType, containerType);
+ }
+
+ MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true);
+ searchWithGetSemantics(element, annotationType, null, containerType, processor);
+ return postProcessAndSynthesizeAggregatedResults(element, annotationType, processor.getAggregatedResults());
+ }
+
+ /**
+ * Get the annotation attributes of <strong>all</strong> annotations of the specified
+ * {@code annotationName} in the annotation hierarchy above the supplied
+ * {@link AnnotatedElement} and store the results in a {@link MultiValueMap}.
+ * <p>Note: in contrast to {@link #getMergedAnnotationAttributes(AnnotatedElement, String)},
+ * this method does <em>not</em> support attribute overrides.
+ * <p>This method follows <em>get semantics</em> as described in the
+ * {@linkplain AnnotatedElementUtils class-level javadoc}.
* @param element the annotated element
* @param annotationName the fully qualified class name of the annotation type to find
- * @return the merged, synthesized {@code Annotation}, or {@code null} if not found
- * @since 4.2
- * @see #findMergedAnnotation(AnnotatedElement, Class)
- * @see #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
- * @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)
- * @deprecated As of Spring Framework 4.2.3, use {@link #findMergedAnnotation(AnnotatedElement, Class)} instead.
+ * @return a {@link MultiValueMap} keyed by attribute name, containing the annotation
+ * attributes from all annotations found, or {@code null} if not found
+ * @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
*/
- @Deprecated
- @SuppressWarnings("unchecked")
- public static <A extends Annotation> A findMergedAnnotation(AnnotatedElement element, String annotationName) {
- AnnotationAttributes attributes = findMergedAnnotationAttributes(element, annotationName, false, false);
- return AnnotationUtils.synthesizeAnnotation(attributes, (Class<A>) attributes.annotationType(), element);
+ public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element, String annotationName) {
+ return getAllAnnotationAttributes(element, annotationName, false, false);
}
/**
- * Find the first annotation of the specified {@code annotationName} within
+ * Get the annotation attributes of <strong>all</strong> annotations of
+ * the specified {@code annotationName} in the annotation hierarchy above
+ * the supplied {@link AnnotatedElement} and store the results in a
+ * {@link MultiValueMap}.
+ * <p>Note: in contrast to {@link #getMergedAnnotationAttributes(AnnotatedElement, String)},
+ * this method does <em>not</em> support attribute overrides.
+ * <p>This method follows <em>get semantics</em> as described in the
+ * {@linkplain AnnotatedElementUtils class-level javadoc}.
+ * @param element the annotated element
+ * @param annotationName the fully qualified class name of the annotation type to find
+ * @param classValuesAsString whether to convert Class references into Strings or to
+ * preserve them as Class references
+ * @param nestedAnnotationsAsMap whether to convert nested Annotation instances into
+ * {@code AnnotationAttributes} maps or to preserve them as Annotation instances
+ * @return a {@link MultiValueMap} keyed by attribute name, containing the annotation
+ * attributes from all annotations found, or {@code null} if not found
+ */
+ public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element,
+ String annotationName, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
+
+ final MultiValueMap<String, Object> attributesMap = new LinkedMultiValueMap<String, Object>();
+
+ searchWithGetSemantics(element, null, annotationName, new SimpleAnnotationProcessor<Object>() {
+ @Override
+ public Object process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
+ AnnotationAttributes annotationAttributes = AnnotationUtils.getAnnotationAttributes(
+ annotation, classValuesAsString, nestedAnnotationsAsMap);
+ for (Map.Entry<String, Object> entry : annotationAttributes.entrySet()) {
+ attributesMap.add(entry.getKey(), entry.getValue());
+ }
+ return CONTINUE;
+ }
+ });
+
+ return (!attributesMap.isEmpty() ? attributesMap : null);
+ }
+
+ /**
+ * Determine if an annotation of the specified {@code annotationType}
+ * is <em>available</em> on the supplied {@link AnnotatedElement} or
+ * within the annotation hierarchy <em>above</em> the specified element.
+ * <p>If this method returns {@code true}, then {@link #findMergedAnnotationAttributes}
+ * will return a non-null value.
+ * <p>This method follows <em>find semantics</em> as described in the
+ * {@linkplain AnnotatedElementUtils class-level javadoc}.
+ * @param element the annotated element
+ * @param annotationType the annotation type to find
+ * @return {@code true} if a matching annotation is present
+ * @since 4.3
+ * @see #isAnnotated(AnnotatedElement, Class)
+ */
+ public static boolean hasAnnotation(AnnotatedElement element, Class<? extends Annotation> annotationType) {
+ Assert.notNull(element, "AnnotatedElement must not be null");
+ Assert.notNull(annotationType, "annotationType must not be null");
+
+ // Shortcut: directly present on the element, with no processing needed?
+ if (element.isAnnotationPresent(annotationType)) {
+ return true;
+ }
+
+ return Boolean.TRUE.equals(searchWithFindSemantics(element, annotationType, null, alwaysTrueAnnotationProcessor));
+ }
+
+ /**
+ * Find the first annotation of the specified {@code annotationType} within
* the annotation hierarchy <em>above</em> the supplied {@code element} and
* merge that annotation's attributes with <em>matching</em> attributes from
* annotations in lower levels of the annotation hierarchy.
@@ -443,8 +633,8 @@ public class AnnotatedElementUtils {
* <p>In contrast to {@link #getAllAnnotationAttributes}, the search
* algorithm used by this method will stop searching the annotation
* hierarchy once the first annotation of the specified
- * {@code annotationName} has been found. As a consequence, additional
- * annotations of the specified {@code annotationName} will be ignored.
+ * {@code annotationType} has been found. As a consequence, additional
+ * annotations of the specified {@code annotationType} will be ignored.
* <p>This method follows <em>find semantics</em> as described in the
* {@linkplain AnnotatedElementUtils class-level javadoc}.
* @param element the annotated element
@@ -463,8 +653,8 @@ public class AnnotatedElementUtils {
public static AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element,
Class<? extends Annotation> annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
- AnnotationAttributes attributes = searchWithFindSemantics(element, annotationType, annotationType.getName(),
- new MergedAnnotationAttributesProcessor(annotationType, null, classValuesAsString, nestedAnnotationsAsMap));
+ AnnotationAttributes attributes = searchWithFindSemantics(element, annotationType, null,
+ new MergedAnnotationAttributesProcessor(classValuesAsString, nestedAnnotationsAsMap));
AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString, nestedAnnotationsAsMap);
return attributes;
}
@@ -500,105 +690,217 @@ public class AnnotatedElementUtils {
String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
AnnotationAttributes attributes = searchWithFindSemantics(element, null, annotationName,
- new MergedAnnotationAttributesProcessor(null, annotationName, classValuesAsString, nestedAnnotationsAsMap));
+ new MergedAnnotationAttributesProcessor(classValuesAsString, nestedAnnotationsAsMap));
AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString, nestedAnnotationsAsMap);
return attributes;
}
/**
- * @deprecated As of Spring Framework 4.2, use {@link #getMergedAnnotationAttributes(AnnotatedElement, String)} instead.
+ * Find the first annotation of the specified {@code annotationType} within
+ * the annotation hierarchy <em>above</em> the supplied {@code element},
+ * merge that annotation's attributes with <em>matching</em> attributes from
+ * annotations in lower levels of the annotation hierarchy, and synthesize
+ * the result back into an annotation of the specified {@code annotationType}.
+ * <p>{@link AliasFor @AliasFor} semantics are fully supported, both
+ * within a single annotation and within the annotation hierarchy.
+ * <p>This method follows <em>find semantics</em> as described in the
+ * {@linkplain AnnotatedElementUtils class-level javadoc}.
+ * @param element the annotated element
+ * @param annotationType the annotation type to find
+ * @return the merged, synthesized {@code Annotation}, or {@code null} if not found
+ * @since 4.2
+ * @see #findAllMergedAnnotations(AnnotatedElement, Class)
+ * @see #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
+ * @see #getMergedAnnotationAttributes(AnnotatedElement, Class)
*/
- @Deprecated
- public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationName) {
- return getMergedAnnotationAttributes(element, annotationName);
+ public static <A extends Annotation> A findMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
+ Assert.notNull(annotationType, "annotationType must not be null");
+
+ // Shortcut: directly present on the element, with no merging needed?
+ if (!(element instanceof Class)) {
+ // Do not use this shortcut against a Class: Inherited annotations
+ // would get preferred over locally declared composed annotations.
+ A annotation = element.getAnnotation(annotationType);
+ if (annotation != null) {
+ return AnnotationUtils.synthesizeAnnotation(annotation, element);
+ }
+ }
+
+ // Exhaustive retrieval of merged annotation attributes...
+ AnnotationAttributes attributes = findMergedAnnotationAttributes(element, annotationType, false, false);
+ return AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element);
}
/**
- * @deprecated As of Spring Framework 4.2, use {@link #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)} instead.
+ * Find the first annotation of the specified {@code annotationName} within
+ * the annotation hierarchy <em>above</em> the supplied {@code element},
+ * merge that annotation's attributes with <em>matching</em> attributes from
+ * annotations in lower levels of the annotation hierarchy, and synthesize
+ * the result back into an annotation of the specified {@code annotationName}.
+ * <p>{@link AliasFor @AliasFor} semantics are fully supported, both
+ * within a single annotation and within the annotation hierarchy.
+ * <p>This method delegates to {@link #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)}
+ * (supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap})
+ * and {@link AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)}.
+ * <p>This method follows <em>find semantics</em> as described in the
+ * {@linkplain AnnotatedElementUtils class-level javadoc}.
+ * @param element the annotated element
+ * @param annotationName the fully qualified class name of the annotation type to find
+ * @return the merged, synthesized {@code Annotation}, or {@code null} if not found
+ * @since 4.2
+ * @see #findMergedAnnotation(AnnotatedElement, Class)
+ * @see #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
+ * @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)
+ * @deprecated As of Spring Framework 4.2.3, use {@link #findMergedAnnotation(AnnotatedElement, Class)} instead.
*/
@Deprecated
- public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationName,
- boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+ @SuppressWarnings("unchecked")
+ public static <A extends Annotation> A findMergedAnnotation(AnnotatedElement element, String annotationName) {
+ AnnotationAttributes attributes = findMergedAnnotationAttributes(element, annotationName, false, false);
+ return AnnotationUtils.synthesizeAnnotation(attributes, (Class<A>) attributes.annotationType(), element);
+ }
- return getMergedAnnotationAttributes(element, annotationName, classValuesAsString, nestedAnnotationsAsMap);
+ /**
+ * Find <strong>all</strong> annotations of the specified {@code annotationType}
+ * within the annotation hierarchy <em>above</em> the supplied {@code element};
+ * and for each annotation found, merge that annotation's attributes with
+ * <em>matching</em> attributes from annotations in lower levels of the annotation
+ * hierarchy and synthesize the results back into an annotation of the specified
+ * {@code annotationType}.
+ * <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
+ * single annotation and within annotation hierarchies.
+ * <p>This method follows <em>find semantics</em> as described in the
+ * {@linkplain AnnotatedElementUtils class-level javadoc}.
+ * @param element the annotated element; never {@code null}
+ * @param annotationType the annotation type to find; never {@code null}
+ * @return the set of all merged, synthesized {@code Annotations} found, or an empty
+ * set if none were found
+ * @since 4.3
+ * @see #findMergedAnnotation(AnnotatedElement, Class)
+ * @see #getAllMergedAnnotations(AnnotatedElement, Class)
+ */
+ public static <A extends Annotation> Set<A> findAllMergedAnnotations(AnnotatedElement element,
+ Class<A> annotationType) {
+
+ Assert.notNull(element, "AnnotatedElement must not be null");
+ Assert.notNull(annotationType, "annotationType must not be null");
+
+ MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true);
+ searchWithFindSemantics(element, annotationType, null, processor);
+ return postProcessAndSynthesizeAggregatedResults(element, annotationType, processor.getAggregatedResults());
}
/**
- * Get the annotation attributes of <strong>all</strong> annotations of the specified
- * {@code annotationName} in the annotation hierarchy above the supplied
- * {@link AnnotatedElement} and store the results in a {@link MultiValueMap}.
- * <p>Note: in contrast to {@link #getMergedAnnotationAttributes(AnnotatedElement, String)},
- * this method does <em>not</em> support attribute overrides.
- * <p>This method follows <em>get semantics</em> as described in the
+ * Find all <em>repeatable annotations</em> of the specified {@code annotationType}
+ * within the annotation hierarchy <em>above</em> the supplied {@code element};
+ * and for each annotation found, merge that annotation's attributes with
+ * <em>matching</em> attributes from annotations in lower levels of the annotation
+ * hierarchy and synthesize the results back into an annotation of the specified
+ * {@code annotationType}.
+ * <p>The container type that holds the repeatable annotations will be looked up
+ * via {@link java.lang.annotation.Repeatable}.
+ * <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
+ * single annotation and within annotation hierarchies.
+ * <p>This method follows <em>find semantics</em> as described in the
* {@linkplain AnnotatedElementUtils class-level javadoc}.
- * @param element the annotated element
- * @param annotationName the fully qualified class name of the annotation type to find
- * @return a {@link MultiValueMap} keyed by attribute name, containing the annotation
- * attributes from all annotations found, or {@code null} if not found
- * @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
+ * @param element the annotated element; never {@code null}
+ * @param annotationType the annotation type to find; never {@code null}
+ * @return the set of all merged repeatable {@code Annotations} found, or an empty
+ * set if none were found
+ * @since 4.3
+ * @see #findMergedAnnotation(AnnotatedElement, Class)
+ * @see #findAllMergedAnnotations(AnnotatedElement, Class)
+ * @see #findMergedRepeatableAnnotations(AnnotatedElement, Class, Class)
+ * @throws IllegalArgumentException if the {@code element} or {@code annotationType}
+ * is {@code null}, or if the container type cannot be resolved
*/
- public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element, String annotationName) {
- return getAllAnnotationAttributes(element, annotationName, false, false);
+ public static <A extends Annotation> Set<A> findMergedRepeatableAnnotations(AnnotatedElement element,
+ Class<A> annotationType) {
+
+ return findMergedRepeatableAnnotations(element, annotationType, null);
}
/**
- * Get the annotation attributes of <strong>all</strong> annotations of
- * the specified {@code annotationName} in the annotation hierarchy above
- * the supplied {@link AnnotatedElement} and store the results in a
- * {@link MultiValueMap}.
- * <p>Note: in contrast to {@link #getMergedAnnotationAttributes(AnnotatedElement, String)},
- * this method does <em>not</em> support attribute overrides.
- * <p>This method follows <em>get semantics</em> as described in the
+ * Find all <em>repeatable annotations</em> of the specified {@code annotationType}
+ * within the annotation hierarchy <em>above</em> the supplied {@code element};
+ * and for each annotation found, merge that annotation's attributes with
+ * <em>matching</em> attributes from annotations in lower levels of the annotation
+ * hierarchy and synthesize the results back into an annotation of the specified
+ * {@code annotationType}.
+ * <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
+ * single annotation and within annotation hierarchies.
+ * <p>This method follows <em>find semantics</em> as described in the
* {@linkplain AnnotatedElementUtils class-level javadoc}.
- * @param element the annotated element
- * @param annotationName the fully qualified class name of the annotation type to find
- * @param classValuesAsString whether to convert Class references into Strings or to
- * preserve them as Class references
- * @param nestedAnnotationsAsMap whether to convert nested Annotation instances into
- * {@code AnnotationAttributes} maps or to preserve them as Annotation instances
- * @return a {@link MultiValueMap} keyed by attribute name, containing the annotation
- * attributes from all annotations found, or {@code null} if not found
+ * @param element the annotated element; never {@code null}
+ * @param annotationType the annotation type to find; never {@code null}
+ * @param containerType the type of the container that holds the annotations;
+ * may be {@code null} if the container type should be looked up via
+ * {@link java.lang.annotation.Repeatable}
+ * @return the set of all merged repeatable {@code Annotations} found, or an empty
+ * set if none were found
+ * @since 4.3
+ * @see #findMergedAnnotation(AnnotatedElement, Class)
+ * @see #findAllMergedAnnotations(AnnotatedElement, Class)
+ * @throws IllegalArgumentException if the {@code element} or {@code annotationType}
+ * is {@code null}, or if the container type cannot be resolved
+ * @throws AnnotationConfigurationException if the supplied {@code containerType}
+ * is not a valid container annotation for the supplied {@code annotationType}
*/
- public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element,
- final String annotationName, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
+ public static <A extends Annotation> Set<A> findMergedRepeatableAnnotations(AnnotatedElement element,
+ Class<A> annotationType, Class<? extends Annotation> containerType) {
- final MultiValueMap<String, Object> attributesMap = new LinkedMultiValueMap<String, Object>();
+ Assert.notNull(element, "AnnotatedElement must not be null");
+ Assert.notNull(annotationType, "annotationType must not be null");
- searchWithGetSemantics(element, null, annotationName, new SimpleAnnotationProcessor<Void>() {
- @Override
- public Void process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
- boolean found = annotation.annotationType().getName().equals(annotationName);
- if (found) {
- AnnotationAttributes annotationAttributes = AnnotationUtils.getAnnotationAttributes(
- annotation, classValuesAsString, nestedAnnotationsAsMap);
- for (Map.Entry<String, Object> entry : annotationAttributes.entrySet()) {
- attributesMap.add(entry.getKey(), entry.getValue());
- }
- }
- // Continue searching...
- return null;
- }
- });
+ if (containerType == null) {
+ containerType = resolveContainerType(annotationType);
+ }
+ else {
+ validateContainerType(annotationType, containerType);
+ }
- return (!attributesMap.isEmpty() ? attributesMap : null);
+ MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true);
+ searchWithFindSemantics(element, annotationType, null, containerType, processor);
+ return postProcessAndSynthesizeAggregatedResults(element, annotationType, processor.getAggregatedResults());
}
/**
- * Search for annotations of the specified {@code annotationName} on
- * the specified {@code element}, following <em>get semantics</em>.
+ * Search for annotations of the specified {@code annotationName} or
+ * {@code annotationType} on the specified {@code element}, following
+ * <em>get semantics</em>.
* @param element the annotated element
- * @param annotationType the annotation type on which to find meta-annotations
+ * @param annotationType the annotation type to find
+ * @param annotationName the fully qualified class name of the annotation
+ * type to find (as an alternative to {@code annotationType})
+ * @param processor the processor to delegate to
+ * @return the result of the processor, potentially {@code null}
+ */
+ private static <T> T searchWithGetSemantics(AnnotatedElement element, Class<? extends Annotation> annotationType,
+ String annotationName, Processor<T> processor) {
+
+ return searchWithGetSemantics(element, annotationType, annotationName, null, processor);
+ }
+
+ /**
+ * Search for annotations of the specified {@code annotationName} or
+ * {@code annotationType} on the specified {@code element}, following
+ * <em>get semantics</em>.
+ * @param element the annotated element
+ * @param annotationType the annotation type to find
* @param annotationName the fully qualified class name of the annotation
* type to find (as an alternative to {@code annotationType})
+ * @param containerType the type of the container that holds repeatable
+ * annotations, or {@code null} if the annotation is not repeatable
* @param processor the processor to delegate to
* @return the result of the processor, potentially {@code null}
+ * @since 4.3
*/
- private static <T> T searchWithGetSemantics(AnnotatedElement element,
- Class<? extends Annotation> annotationType, String annotationName, Processor<T> processor) {
+ private static <T> T searchWithGetSemantics(AnnotatedElement element, Class<? extends Annotation> annotationType,
+ String annotationName, Class<? extends Annotation> containerType, Processor<T> processor) {
try {
- return searchWithGetSemantics(
- element, annotationType, annotationName, processor, new HashSet<AnnotatedElement>(), 0);
+ return searchWithGetSemantics(element, annotationType, annotationName, containerType, processor,
+ new HashSet<AnnotatedElement>(), 0);
}
catch (Throwable ex) {
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
@@ -613,17 +915,19 @@ public class AnnotatedElementUtils {
* <p>The {@code metaDepth} parameter is explained in the
* {@link Processor#process process()} method of the {@link Processor} API.
* @param element the annotated element
- * @param annotationType the annotation type on which to find meta-annotations
+ * @param annotationType the annotation type to find
* @param annotationName the fully qualified class name of the annotation
* type to find (as an alternative to {@code annotationType})
+ * @param containerType the type of the container that holds repeatable
+ * annotations, or {@code null} if the annotation is not repeatable
* @param processor the processor to delegate to
* @param visited the set of annotated elements that have already been visited
* @param metaDepth the meta-depth of the annotation
* @return the result of the processor, potentially {@code null}
*/
- private static <T> T searchWithGetSemantics(AnnotatedElement element,
- Class<? extends Annotation> annotationType, String annotationName,
- Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
+ private static <T> T searchWithGetSemantics(AnnotatedElement element, Class<? extends Annotation> annotationType,
+ String annotationName, Class<? extends Annotation> containerType, Processor<T> processor,
+ Set<AnnotatedElement> visited, int metaDepth) {
Assert.notNull(element, "AnnotatedElement must not be null");
@@ -632,12 +936,12 @@ public class AnnotatedElementUtils {
// Start searching within locally declared annotations
List<Annotation> declaredAnnotations = Arrays.asList(element.getDeclaredAnnotations());
T result = searchWithGetSemanticsInAnnotations(element, declaredAnnotations,
- annotationType, annotationName, processor, visited, metaDepth);
+ annotationType, annotationName, containerType, processor, visited, metaDepth);
if (result != null) {
return result;
}
- if (element instanceof Class) { // otherwise getAnnotations doesn't return anything new
+ if (element instanceof Class) { // otherwise getAnnotations doesn't return anything new
List<Annotation> inheritedAnnotations = new ArrayList<Annotation>();
for (Annotation annotation : element.getAnnotations()) {
if (!declaredAnnotations.contains(annotation)) {
@@ -647,7 +951,7 @@ public class AnnotatedElementUtils {
// Continue searching within inherited annotations
result = searchWithGetSemanticsInAnnotations(element, inheritedAnnotations,
- annotationType, annotationName, processor, visited, metaDepth);
+ annotationType, annotationName, containerType, processor, visited, metaDepth);
if (result != null) {
return result;
}
@@ -662,39 +966,58 @@ public class AnnotatedElementUtils {
}
/**
- * This method is invoked by
- * {@link #searchWithGetSemantics(AnnotatedElement, Class, String, Processor, Set, int)}
- * to perform the actual search within the supplied list of annotations.
+ * This method is invoked by {@link #searchWithGetSemantics} to perform
+ * the actual search within the supplied list of annotations.
* <p>This method should be invoked first with locally declared annotations
* and then subsequently with inherited annotations, thereby allowing
* local annotations to take precedence over inherited annotations.
* <p>The {@code metaDepth} parameter is explained in the
* {@link Processor#process process()} method of the {@link Processor} API.
- * @param annotatedElement the element that is annotated with the supplied
+ * @param element the element that is annotated with the supplied
* annotations, used for contextual logging; may be {@code null} if unknown
* @param annotations the annotations to search in
- * @param annotationType the annotation type on which to find meta-annotations
+ * @param annotationType the annotation type to find
* @param annotationName the fully qualified class name of the annotation
* type to find (as an alternative to {@code annotationType})
+ * @param containerType the type of the container that holds repeatable
+ * annotations, or {@code null} if the annotation is not repeatable
* @param processor the processor to delegate to
* @param visited the set of annotated elements that have already been visited
* @param metaDepth the meta-depth of the annotation
* @return the result of the processor, potentially {@code null}
* @since 4.2
*/
- private static <T> T searchWithGetSemanticsInAnnotations(AnnotatedElement annotatedElement,
+ private static <T> T searchWithGetSemanticsInAnnotations(AnnotatedElement element,
List<Annotation> annotations, Class<? extends Annotation> annotationType, String annotationName,
- Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
+ Class<? extends Annotation> containerType, Processor<T> processor, Set<AnnotatedElement> visited,
+ int metaDepth) {
// Search in annotations
for (Annotation annotation : annotations) {
- if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation) &&
- ((annotationType != null ? annotation.annotationType() == annotationType :
- annotation.annotationType().getName().equals(annotationName)) ||
- metaDepth > 0)) {
- T result = processor.process(annotatedElement, annotation, metaDepth);
- if (result != null) {
- return result;
+ if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
+ if (annotation.annotationType() == annotationType ||
+ annotation.annotationType().getName().equals(annotationName) ||
+ processor.alwaysProcesses()) {
+ T result = processor.process(element, annotation, metaDepth);
+ if (result != null) {
+ if (processor.aggregates() && metaDepth == 0) {
+ processor.getAggregatedResults().add(result);
+ }
+ else {
+ return result;
+ }
+ }
+ }
+ // Repeatable annotations in container?
+ else if (annotation.annotationType() == containerType) {
+ for (Annotation contained : getRawAnnotationsFromContainer(element, annotation)) {
+ T result = processor.process(element, contained, metaDepth);
+ if (result != null) {
+ // No need to post-process since repeatable annotations within a
+ // container cannot be composed annotations.
+ processor.getAggregatedResults().add(result);
+ }
+ }
}
}
}
@@ -703,10 +1026,15 @@ public class AnnotatedElementUtils {
for (Annotation annotation : annotations) {
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
T result = searchWithGetSemantics(annotation.annotationType(), annotationType,
- annotationName, processor, visited, metaDepth + 1);
+ annotationName, containerType, processor, visited, metaDepth + 1);
if (result != null) {
- processor.postProcess(annotatedElement, annotation, result);
- return result;
+ processor.postProcess(element, annotation, result);
+ if (processor.aggregates() && metaDepth == 0) {
+ processor.getAggregatedResults().add(result);
+ }
+ else {
+ return result;
+ }
}
}
}
@@ -715,22 +1043,48 @@ public class AnnotatedElementUtils {
}
/**
- * Search for annotations of the specified {@code annotationName} on
- * the specified {@code element}, following <em>find semantics</em>.
+ * Search for annotations of the specified {@code annotationName} or
+ * {@code annotationType} on the specified {@code element}, following
+ * <em>find semantics</em>.
* @param element the annotated element
- * @param annotationType the annotation type on which to find meta-annotations
+ * @param annotationType the annotation type to find
* @param annotationName the fully qualified class name of the annotation
* type to find (as an alternative to {@code annotationType})
* @param processor the processor to delegate to
* @return the result of the processor, potentially {@code null}
* @since 4.2
*/
- private static <T> T searchWithFindSemantics(
- AnnotatedElement element, Class<? extends Annotation> annotationType, String annotationName, Processor<T> processor) {
+ private static <T> T searchWithFindSemantics(AnnotatedElement element, Class<? extends Annotation> annotationType,
+ String annotationName, Processor<T> processor) {
+
+ return searchWithFindSemantics(element, annotationType, annotationName, null, processor);
+ }
+
+ /**
+ * Search for annotations of the specified {@code annotationName} or
+ * {@code annotationType} on the specified {@code element}, following
+ * <em>find semantics</em>.
+ * @param element the annotated element
+ * @param annotationType the annotation type to find
+ * @param annotationName the fully qualified class name of the annotation
+ * type to find (as an alternative to {@code annotationType})
+ * @param containerType the type of the container that holds repeatable
+ * annotations, or {@code null} if the annotation is not repeatable
+ * @param processor the processor to delegate to
+ * @return the result of the processor, potentially {@code null}
+ * @since 4.3
+ */
+ private static <T> T searchWithFindSemantics(AnnotatedElement element, Class<? extends Annotation> annotationType,
+ String annotationName, Class<? extends Annotation> containerType, Processor<T> processor) {
+
+ if (containerType != null && !processor.aggregates()) {
+ throw new IllegalArgumentException(
+ "Searches for repeatable annotations must supply an aggregating Processor");
+ }
try {
return searchWithFindSemantics(
- element, annotationType, annotationName, processor, new HashSet<AnnotatedElement>(), 0);
+ element, annotationType, annotationName, containerType, processor, new HashSet<AnnotatedElement>(), 0);
}
catch (Throwable ex) {
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
@@ -744,10 +1098,12 @@ public class AnnotatedElementUtils {
* have already been <em>visited</em>.
* <p>The {@code metaDepth} parameter is explained in the
* {@link Processor#process process()} method of the {@link Processor} API.
- * @param element the annotated element
- * @param annotationType the annotation type on which to find meta-annotations
+ * @param element the annotated element; never {@code null}
+ * @param annotationType the annotation type to find
* @param annotationName the fully qualified class name of the annotation
* type to find (as an alternative to {@code annotationType})
+ * @param containerType the type of the container that holds repeatable
+ * annotations, or {@code null} if the annotation is not repeatable
* @param processor the processor to delegate to
* @param visited the set of annotated elements that have already been visited
* @param metaDepth the meta-depth of the annotation
@@ -755,25 +1111,44 @@ public class AnnotatedElementUtils {
* @since 4.2
*/
private static <T> T searchWithFindSemantics(AnnotatedElement element, Class<? extends Annotation> annotationType,
- String annotationName, Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
+ String annotationName, Class<? extends Annotation> containerType, Processor<T> processor,
+ Set<AnnotatedElement> visited, int metaDepth) {
Assert.notNull(element, "AnnotatedElement must not be null");
- Assert.hasLength(annotationName, "annotationName must not be null or empty");
if (visited.add(element)) {
try {
// Locally declared annotations (ignoring @Inherited)
Annotation[] annotations = element.getDeclaredAnnotations();
+ List<T> aggregatedResults = (processor.aggregates() ? new ArrayList<T>() : null);
// Search in local annotations
for (Annotation annotation : annotations) {
- if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation) &&
- ((annotationType != null ? annotation.annotationType() == annotationType :
- annotation.annotationType().getName().equals(annotationName)) ||
- metaDepth > 0)) {
- T result = processor.process(element, annotation, metaDepth);
- if (result != null) {
- return result;
+ if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
+ if (annotation.annotationType() == annotationType
+ || annotation.annotationType().getName().equals(annotationName)
+ || processor.alwaysProcesses()) {
+
+ T result = processor.process(element, annotation, metaDepth);
+ if (result != null) {
+ if (processor.aggregates() && metaDepth == 0) {
+ aggregatedResults.add(result);
+ }
+ else {
+ return result;
+ }
+ }
+ }
+ // Repeatable annotations in container?
+ else if (annotation.annotationType() == containerType) {
+ for (Annotation contained : getRawAnnotationsFromContainer(element, annotation)) {
+ T result = processor.process(element, contained, metaDepth);
+ if (result != null) {
+ // No need to post-process since repeatable annotations within a
+ // container cannot be composed annotations.
+ aggregatedResults.add(result);
+ }
+ }
}
}
}
@@ -781,21 +1156,31 @@ public class AnnotatedElementUtils {
// Search in meta annotations on local annotations
for (Annotation annotation : annotations) {
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
- T result = searchWithFindSemantics(
- annotation.annotationType(), annotationType, annotationName, processor, visited, metaDepth + 1);
+ T result = searchWithFindSemantics(annotation.annotationType(), annotationType, annotationName,
+ containerType, processor, visited, metaDepth + 1);
if (result != null) {
processor.postProcess(annotation.annotationType(), annotation, result);
- return result;
+ if (processor.aggregates() && metaDepth == 0) {
+ aggregatedResults.add(result);
+ }
+ else {
+ return result;
+ }
}
}
}
+ if (processor.aggregates()) {
+ // Prepend to support top-down ordering within class hierarchies
+ processor.getAggregatedResults().addAll(0, aggregatedResults);
+ }
+
if (element instanceof Method) {
Method method = (Method) element;
// Search on possibly bridged method
Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
- T result = searchWithFindSemantics(resolvedMethod, annotationType, annotationName,
+ T result = searchWithFindSemantics(resolvedMethod, annotationType, annotationName, containerType,
processor, visited, metaDepth);
if (result != null) {
return result;
@@ -803,8 +1188,8 @@ public class AnnotatedElementUtils {
// Search on methods in interfaces declared locally
Class<?>[] ifcs = method.getDeclaringClass().getInterfaces();
- result = searchOnInterfaces(
- method, annotationType, annotationName, processor, visited, metaDepth, ifcs);
+ result = searchOnInterfaces(method, annotationType, annotationName, containerType, processor,
+ visited, metaDepth, ifcs);
if (result != null) {
return result;
}
@@ -821,7 +1206,7 @@ public class AnnotatedElementUtils {
Method equivalentMethod = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
Method resolvedEquivalentMethod = BridgeMethodResolver.findBridgedMethod(equivalentMethod);
result = searchWithFindSemantics(resolvedEquivalentMethod, annotationType, annotationName,
- processor, visited, metaDepth);
+ containerType, processor, visited, metaDepth);
if (result != null) {
return result;
}
@@ -831,21 +1216,20 @@ public class AnnotatedElementUtils {
}
// Search on interfaces declared on superclass
- result = searchOnInterfaces(method, annotationType, annotationName, processor, visited,
- metaDepth, clazz.getInterfaces());
+ result = searchOnInterfaces(method, annotationType, annotationName, containerType, processor,
+ visited, metaDepth, clazz.getInterfaces());
if (result != null) {
return result;
}
}
}
-
- if (element instanceof Class) {
+ else if (element instanceof Class) {
Class<?> clazz = (Class<?>) element;
// Search on interfaces
for (Class<?> ifc : clazz.getInterfaces()) {
- T result = searchWithFindSemantics(
- ifc, annotationType, annotationName, processor, visited, metaDepth);
+ T result = searchWithFindSemantics(ifc, annotationType, annotationName, containerType,
+ processor, visited, metaDepth);
if (result != null) {
return result;
}
@@ -854,8 +1238,8 @@ public class AnnotatedElementUtils {
// Search on superclass
Class<?> superclass = clazz.getSuperclass();
if (superclass != null && Object.class != superclass) {
- T result = searchWithFindSemantics(
- superclass, annotationType, annotationName, processor, visited, metaDepth);
+ T result = searchWithFindSemantics(superclass, annotationType, annotationName, containerType,
+ processor, visited, metaDepth);
if (result != null) {
return result;
}
@@ -869,14 +1253,15 @@ public class AnnotatedElementUtils {
return null;
}
- private static <T> T searchOnInterfaces(Method method, Class<? extends Annotation> annotationType, String annotationName,
- Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth, Class<?>[] ifcs) {
+ private static <T> T searchOnInterfaces(Method method, Class<? extends Annotation> annotationType,
+ String annotationName, Class<? extends Annotation> containerType, Processor<T> processor,
+ Set<AnnotatedElement> visited, int metaDepth, Class<?>[] ifcs) {
for (Class<?> iface : ifcs) {
if (AnnotationUtils.isInterfaceWithAnnotatedMethods(iface)) {
try {
Method equivalentMethod = iface.getMethod(method.getName(), method.getParameterTypes());
- T result = searchWithFindSemantics(equivalentMethod, annotationType, annotationName,
+ T result = searchWithFindSemantics(equivalentMethod, annotationType, annotationName, containerType,
processor, visited, metaDepth);
if (result != null) {
return result;
@@ -891,6 +1276,87 @@ public class AnnotatedElementUtils {
return null;
}
+ /**
+ * Get the array of raw (unsynthesized) annotations from the {@code value}
+ * attribute of the supplied repeatable annotation {@code container}.
+ * @since 4.3
+ */
+ @SuppressWarnings("unchecked")
+ private static <A extends Annotation> A[] getRawAnnotationsFromContainer(AnnotatedElement element,
+ Annotation container) {
+
+ try {
+ return (A[]) AnnotationUtils.getValue(container);
+ }
+ catch (Exception ex) {
+ AnnotationUtils.handleIntrospectionFailure(element, ex);
+ }
+ // Unable to read value from repeating annotation container -> ignore it.
+ return (A[]) EMPTY_ANNOTATION_ARRAY;
+ }
+
+ /**
+ * Resolve the container type for the supplied repeatable {@code annotationType}.
+ * <p>Delegates to {@link AnnotationUtils#resolveContainerAnnotationType(Class)}.
+ * @param annotationType the annotation type to resolve the container for
+ * @return the container type; never {@code null}
+ * @throws IllegalArgumentException if the container type cannot be resolved
+ * @since 4.3
+ */
+ private static Class<? extends Annotation> resolveContainerType(Class<? extends Annotation> annotationType) {
+ Class<? extends Annotation> containerType = AnnotationUtils.resolveContainerAnnotationType(annotationType);
+ if (containerType == null) {
+ throw new IllegalArgumentException(
+ "annotationType must be a repeatable annotation: failed to resolve container type for "
+ + annotationType.getName());
+ }
+ return containerType;
+ }
+
+ /**
+ * Validate that the supplied {@code containerType} is a proper container
+ * annotation for the supplied repeatable {@code annotationType} (i.e.,
+ * that it declares a {@code value} attribute that holds an array of the
+ * {@code annotationType}).
+ * @since 4.3
+ * @throws AnnotationConfigurationException if the supplied {@code containerType}
+ * is not a valid container annotation for the supplied {@code annotationType}
+ */
+ private static void validateContainerType(Class<? extends Annotation> annotationType,
+ Class<? extends Annotation> containerType) {
+
+ try {
+ Method method = containerType.getDeclaredMethod(AnnotationUtils.VALUE);
+ Class<?> returnType = method.getReturnType();
+ if (!returnType.isArray() || returnType.getComponentType() != annotationType) {
+ String msg = String.format(
+ "Container type [%s] must declare a 'value' attribute for an array of type [%s]",
+ containerType.getName(), annotationType.getName());
+ throw new AnnotationConfigurationException(msg);
+ }
+ }
+ catch (Exception ex) {
+ AnnotationUtils.rethrowAnnotationConfigurationException(ex);
+ String msg = String.format("Invalid declaration of container type [%s] for repeatable annotation [%s]",
+ containerType.getName(), annotationType.getName());
+ throw new AnnotationConfigurationException(msg, ex);
+ }
+ }
+
+ /**
+ * @since 4.3
+ */
+ private static <A extends Annotation> Set<A> postProcessAndSynthesizeAggregatedResults(AnnotatedElement element,
+ Class<A> annotationType, List<AnnotationAttributes> aggregatedResults) {
+
+ Set<A> annotations = new LinkedHashSet<A>();
+ for (AnnotationAttributes attributes : aggregatedResults) {
+ AnnotationUtils.postProcessAnnotationAttributes(element, attributes, false, false);
+ annotations.add(AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element));
+ }
+ return annotations;
+ }
+
/**
* Callback interface that is used to process annotations during a search.
@@ -899,11 +1365,16 @@ public class AnnotatedElementUtils {
* annotations, or all annotations discovered by the currently executing
* search. The term "target" in this context refers to a matching
* annotation (i.e., a specific annotation type that was found during
- * the search). Returning a non-null value from the {@link #process}
+ * the search).
+ * <p>Returning a non-null value from the {@link #process}
* method instructs the search algorithm to stop searching further;
* whereas, returning {@code null} from the {@link #process} method
* instructs the search algorithm to continue searching for additional
- * annotations.
+ * annotations. One exception to this rule applies to processors
+ * that {@linkplain #aggregates aggregate} results. If an aggregating
+ * processor returns a non-null value, that value will be added to the
+ * list of {@linkplain #getAggregatedResults aggregated results}
+ * and the search algorithm will continue.
* <p>Processors can optionally {@linkplain #postProcess post-process}
* the result of the {@link #process} method as the search algorithm
* goes back down the annotation hierarchy from an invocation of
@@ -916,11 +1387,12 @@ public class AnnotatedElementUtils {
/**
* Process the supplied annotation.
- * <p>Depending on the use case, the supplied annotation may be an
- * actual target annotation that has been found by the search
- * algorithm, or it may be some other annotation within the
+ * <p>The supplied annotation will be an actual target annotation
+ * that has been found by the search algorithm, unless this processor
+ * is configured to {@linkplain #alwaysProcesses always process}
+ * annotations in which case it may be some other annotation within an
* annotation hierarchy. In the latter case, the {@code metaDepth}
- * should have a value greater than {@code 0}. In any case, it is
+ * will have a value greater than {@code 0}. In any case, it is
* up to concrete implementations of this method to decide what to
* do with the supplied annotation.
* <p>The {@code metaDepth} parameter represents the depth of the
@@ -952,20 +1424,93 @@ public class AnnotatedElementUtils {
* @param result the result to post-process
*/
void postProcess(AnnotatedElement annotatedElement, Annotation annotation, T result);
+
+ /**
+ * Determine if this processor always processes annotations regardless of
+ * whether or not the target annotation has been found.
+ * @return {@code true} if this processor always processes annotations
+ * @since 4.3
+ */
+ boolean alwaysProcesses();
+
+ /**
+ * Determine if this processor aggregates the results returned by {@link #process}.
+ * <p>If this method returns {@code true}, then {@link #getAggregatedResults()}
+ * must return a non-null value.
+ * @return {@code true} if this processor supports aggregated results
+ * @see #getAggregatedResults
+ * @since 4.3
+ */
+ boolean aggregates();
+
+ /**
+ * Get the list of results aggregated by this processor.
+ * <p>NOTE: the processor does <strong>not</strong> aggregate the results
+ * itself. Rather, the search algorithm that uses this processor is
+ * responsible for asking this processor if it {@link #aggregates} results
+ * and then adding the post-processed results to the list returned by this
+ * method.
+ * @return the list of results aggregated by this processor; never
+ * {@code null} unless {@link #aggregates} returns {@code false}
+ * @see #aggregates
+ * @since 4.3
+ */
+ List<T> getAggregatedResults();
}
/**
- * {@link Processor} that {@linkplain #process processes} annotations
- * but does not {@linkplain #postProcess post-process} results.
+ * {@link Processor} that {@linkplain #process(AnnotatedElement, Annotation, int)
+ * processes} annotations but does not {@linkplain #postProcess post-process} or
+ * {@linkplain #aggregates aggregate} results.
* @since 4.2
*/
private abstract static class SimpleAnnotationProcessor<T> implements Processor<T> {
+ private final boolean alwaysProcesses;
+
+ public SimpleAnnotationProcessor() {
+ this(false);
+ }
+
+ public SimpleAnnotationProcessor(boolean alwaysProcesses) {
+ this.alwaysProcesses = alwaysProcesses;
+ }
+
+ @Override
+ public final boolean alwaysProcesses() {
+ return this.alwaysProcesses;
+ }
+
@Override
public final void postProcess(AnnotatedElement annotatedElement, Annotation annotation, T result) {
// no-op
}
+
+ @Override
+ public final boolean aggregates() {
+ return false;
+ }
+
+ @Override
+ public final List<T> getAggregatedResults() {
+ throw new UnsupportedOperationException("SimpleAnnotationProcessor does not support aggregated results");
+ }
+ }
+
+
+ /**
+ * {@link SimpleAnnotationProcessor} that always returns {@link Boolean#TRUE} when
+ * asked to {@linkplain #process(AnnotatedElement, Annotation, int) process} an
+ * annotation.
+ * @since 4.3
+ */
+ static class AlwaysTrueBooleanAnnotationProcessor extends SimpleAnnotationProcessor<Boolean> {
+
+ @Override
+ public final Boolean process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
+ return Boolean.TRUE;
+ }
}
@@ -974,35 +1519,58 @@ public class AnnotatedElementUtils {
* target annotation during the {@link #process} phase and then merges
* annotation attributes from lower levels in the annotation hierarchy
* during the {@link #postProcess} phase.
+ * <p>A {@code MergedAnnotationAttributesProcessor} may optionally be
+ * configured to {@linkplain #aggregates aggregate} results.
* @since 4.2
- * @see AnnotationUtils#retrieveAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)
+ * @see AnnotationUtils#retrieveAnnotationAttributes
* @see AnnotationUtils#postProcessAnnotationAttributes
*/
private static class MergedAnnotationAttributesProcessor implements Processor<AnnotationAttributes> {
- private final Class<? extends Annotation> annotationType;
-
- private final String annotationName;
-
private final boolean classValuesAsString;
private final boolean nestedAnnotationsAsMap;
- MergedAnnotationAttributesProcessor(Class<? extends Annotation> annotationType, String annotationName,
- boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+ private final boolean aggregates;
+
+ private final List<AnnotationAttributes> aggregatedResults;
+
+ MergedAnnotationAttributesProcessor() {
+ this(false, false, false);
+ }
+
+ MergedAnnotationAttributesProcessor(boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+ this(classValuesAsString, nestedAnnotationsAsMap, false);
+ }
+
+ MergedAnnotationAttributesProcessor(boolean classValuesAsString, boolean nestedAnnotationsAsMap,
+ boolean aggregates) {
- this.annotationType = annotationType;
- this.annotationName = annotationName;
this.classValuesAsString = classValuesAsString;
this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
+ this.aggregates = aggregates;
+ this.aggregatedResults = (aggregates ? new ArrayList<AnnotationAttributes>() : null);
+ }
+
+ @Override
+ public boolean alwaysProcesses() {
+ return false;
+ }
+
+ @Override
+ public boolean aggregates() {
+ return this.aggregates;
+ }
+
+ @Override
+ public List<AnnotationAttributes> getAggregatedResults() {
+ return this.aggregatedResults;
}
@Override
public AnnotationAttributes process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
- boolean found = (this.annotationType != null ? annotation.annotationType() == this.annotationType :
- annotation.annotationType().getName().equals(this.annotationName));
- return (found ? AnnotationUtils.retrieveAnnotationAttributes(annotatedElement, annotation,
- this.classValuesAsString, this.nestedAnnotationsAsMap) : null);
+ return AnnotationUtils.retrieveAnnotationAttributes(annotatedElement, annotation,
+ this.classValuesAsString, this.nestedAnnotationsAsMap);
}
@Override
@@ -1010,15 +1578,36 @@ public class AnnotatedElementUtils {
annotation = AnnotationUtils.synthesizeAnnotation(annotation, element);
Class<? extends Annotation> targetAnnotationType = attributes.annotationType();
+ // Track which attribute values have already been replaced so that we can short
+ // circuit the search algorithms.
+ Set<String> valuesAlreadyReplaced = new HashSet<String>();
+
for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotation.annotationType())) {
String attributeName = attributeMethod.getName();
String attributeOverrideName = AnnotationUtils.getAttributeOverrideName(attributeMethod, targetAnnotationType);
// Explicit annotation attribute override declared via @AliasFor
if (attributeOverrideName != null) {
- if (attributes.containsKey(attributeOverrideName)) {
- overrideAttribute(element, annotation, attributes, attributeName, attributeOverrideName);
+ if (valuesAlreadyReplaced.contains(attributeOverrideName)) {
+ continue;
+ }
+
+ List<String> targetAttributeNames = new ArrayList<String>();
+ targetAttributeNames.add(attributeOverrideName);
+ valuesAlreadyReplaced.add(attributeOverrideName);
+
+ // Ensure all aliased attributes in the target annotation are overridden. (SPR-14069)
+ List<String> aliases = AnnotationUtils.getAttributeAliasMap(targetAnnotationType).get(attributeOverrideName);
+ if (aliases != null) {
+ for (String alias : aliases) {
+ if (!valuesAlreadyReplaced.contains(alias)) {
+ targetAttributeNames.add(alias);
+ valuesAlreadyReplaced.add(alias);
+ }
+ }
}
+
+ overrideAttributes(element, annotation, attributes, attributeName, targetAttributeNames);
}
// Implicit annotation attribute override based on convention
else if (!AnnotationUtils.VALUE.equals(attributeName) && attributes.containsKey(attributeName)) {
@@ -1027,13 +1616,25 @@ public class AnnotatedElementUtils {
}
}
- private void overrideAttribute(AnnotatedElement element, Annotation annotation,
- AnnotationAttributes attributes, String sourceAttributeName, String targetAttributeName) {
+ private void overrideAttributes(AnnotatedElement element, Annotation annotation,
+ AnnotationAttributes attributes, String sourceAttributeName, List<String> targetAttributeNames) {
+
+ Object adaptedValue = getAdaptedValue(element, annotation, sourceAttributeName);
+
+ for (String targetAttributeName : targetAttributeNames) {
+ attributes.put(targetAttributeName, adaptedValue);
+ }
+ }
+
+ private void overrideAttribute(AnnotatedElement element, Annotation annotation, AnnotationAttributes attributes,
+ String sourceAttributeName, String targetAttributeName) {
+
+ attributes.put(targetAttributeName, getAdaptedValue(element, annotation, sourceAttributeName));
+ }
+ private Object getAdaptedValue(AnnotatedElement element, Annotation annotation, String sourceAttributeName) {
Object value = AnnotationUtils.getValue(annotation, sourceAttributeName);
- Object adaptedValue = AnnotationUtils.adaptValue(
- element, value, this.classValuesAsString, this.nestedAnnotationsAsMap);
- attributes.put(targetAttributeName, adaptedValue);
+ return AnnotationUtils.adaptValue(element, value, this.classValuesAsString, this.nestedAnnotationsAsMap);
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributeExtractor.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributeExtractor.java
index 86ca194b..7fd8dd42 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributeExtractor.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributeExtractor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
-import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
/**
@@ -44,7 +43,7 @@ interface AnnotationAttributeExtractor<S> {
* type supported by this extractor.
* @return the annotated element, or {@code null} if unknown
*/
- AnnotatedElement getAnnotatedElement();
+ Object getAnnotatedElement();
/**
* Get the underlying source of annotation attributes.
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 395d2dba..0cf257d6 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,8 +36,7 @@ import org.springframework.util.StringUtils;
*
* <p>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, including support for attribute aliases configured
- * via {@link AliasFor @AliasFor}.
+ * in a type-safe fashion.
*
* @author Chris Beams
* @author Sam Brannen
@@ -45,22 +44,36 @@ import org.springframework.util.StringUtils;
* @since 3.1.1
* @see AnnotationUtils#getAnnotationAttributes
* @see AnnotatedElementUtils
- * @see AliasFor
*/
@SuppressWarnings("serial")
public class AnnotationAttributes extends LinkedHashMap<String, Object> {
+ private static final String UNKNOWN = "unknown";
+
private final Class<? extends Annotation> annotationType;
private final String displayName;
+ boolean validated = false;
+
/**
* Create a new, empty {@link AnnotationAttributes} instance.
*/
public AnnotationAttributes() {
this.annotationType = null;
- this.displayName = "unknown";
+ this.displayName = UNKNOWN;
+ }
+
+ /**
+ * Create a new, empty {@link AnnotationAttributes} instance with the
+ * given initial capacity to optimize performance.
+ * @param initialCapacity initial size of the underlying map
+ */
+ public AnnotationAttributes(int initialCapacity) {
+ super(initialCapacity);
+ this.annotationType = null;
+ this.displayName = UNKNOWN;
}
/**
@@ -71,33 +84,62 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
* @since 4.2
*/
public AnnotationAttributes(Class<? extends Annotation> annotationType) {
- Assert.notNull(annotationType, "annotationType must not be null");
+ Assert.notNull(annotationType, "'annotationType' must not be null");
this.annotationType = annotationType;
this.displayName = annotationType.getName();
}
/**
- * Create a new, empty {@link AnnotationAttributes} instance with the
- * given initial capacity to optimize performance.
- * @param initialCapacity initial size of the underlying map
+ * Create a new, empty {@link AnnotationAttributes} instance for the
+ * specified {@code annotationType}.
+ * @param annotationType the annotation type name represented by this
+ * {@code AnnotationAttributes} instance; never {@code null}
+ * @param classLoader the ClassLoader to try to load the annotation type on,
+ * or {@code null} to just store the annotation type name
+ * @since 4.3.2
*/
- public AnnotationAttributes(int initialCapacity) {
- super(initialCapacity);
- this.annotationType = null;
- this.displayName = "unknown";
+ public AnnotationAttributes(String annotationType, ClassLoader classLoader) {
+ Assert.notNull(annotationType, "'annotationType' must not be null");
+ this.annotationType = getAnnotationType(annotationType, classLoader);
+ this.displayName = annotationType;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Class<? extends Annotation> getAnnotationType(String annotationType, ClassLoader classLoader) {
+ if (classLoader != null) {
+ try {
+ return (Class<? extends Annotation>) classLoader.loadClass(annotationType);
+ }
+ catch (ClassNotFoundException ex) {
+ // Annotation Class not resolvable
+ }
+ }
+ return null;
}
/**
- * Create a new {@link AnnotationAttributes} instance, wrapping the
- * provided map and all its <em>key-value</em> pairs.
- * @param map original source of annotation attribute <em>key-value</em>
- * pairs
+ * Create a new {@link AnnotationAttributes} instance, wrapping the provided
+ * map and all its <em>key-value</em> pairs.
+ * @param map original source of annotation attribute <em>key-value</em> pairs
* @see #fromMap(Map)
*/
public AnnotationAttributes(Map<String, Object> map) {
super(map);
this.annotationType = null;
- this.displayName = "unknown";
+ this.displayName = UNKNOWN;
+ }
+
+ /**
+ * Create a new {@link AnnotationAttributes} instance, wrapping the provided
+ * map and all its <em>key-value</em> pairs.
+ * @param other original source of annotation attribute <em>key-value</em> pairs
+ * @see #fromMap(Map)
+ */
+ public AnnotationAttributes(AnnotationAttributes other) {
+ super(other);
+ this.annotationType = other.annotationType;
+ this.displayName = other.displayName;
+ this.validated = other.validated;
}
@@ -144,8 +186,10 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
* @throws AnnotationConfigurationException if the attribute and its
* alias are both present with different non-empty values
* @since 4.2
- * @see ObjectUtils#isEmpty(Object)
+ * @deprecated as of Spring 4.3.2, in favor of built-in alias resolution
+ * in {@link #getString} itself
*/
+ @Deprecated
public String getAliasedString(String attributeName, Class<? extends Annotation> annotationType,
Object annotationSource) {
@@ -188,7 +232,10 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
* @throws AnnotationConfigurationException if the attribute and its
* alias are both present with different non-empty values
* @since 4.2
+ * @deprecated as of Spring 4.3.2, in favor of built-in alias resolution
+ * in {@link #getStringArray} itself
*/
+ @Deprecated
public String[] getAliasedStringArray(String attributeName, Class<? extends Annotation> annotationType,
Object annotationSource) {
@@ -286,7 +333,10 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
* @throws AnnotationConfigurationException if the attribute and its
* alias are both present with different non-empty values
* @since 4.2
+ * @deprecated as of Spring 4.3.2, in favor of built-in alias resolution
+ * in {@link #getClassArray} itself
*/
+ @Deprecated
public Class<?>[] getAliasedClassArray(String attributeName, Class<? extends Annotation> annotationType,
Object annotationSource) {
@@ -378,7 +428,7 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
*/
@SuppressWarnings("unchecked")
private <T> T getRequiredAttribute(String attributeName, Class<T> expectedType) {
- Assert.hasText(attributeName, "attributeName must not be null or empty");
+ Assert.hasText(attributeName, "'attributeName' must not be null or empty");
Object value = get(attributeName);
assertAttributePresence(attributeName, value);
assertNotException(attributeName, value);
@@ -418,9 +468,9 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
private <T> T getRequiredAttributeWithAlias(String attributeName, Class<? extends Annotation> annotationType,
Object annotationSource, Class<T> expectedType) {
- Assert.hasText(attributeName, "attributeName must not be null or empty");
- Assert.notNull(annotationType, "annotationType must not be null");
- Assert.notNull(expectedType, "expectedType must not be null");
+ Assert.hasText(attributeName, "'attributeName' must not be null or empty");
+ Assert.notNull(annotationType, "'annotationType' must not be null");
+ Assert.notNull(expectedType, "'expectedType' must not be null");
T attributeValue = getAttribute(attributeName, expectedType);
@@ -433,8 +483,8 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
if (!attributeEmpty && !aliasEmpty && !ObjectUtils.nullSafeEquals(attributeValue, aliasValue)) {
String elementName = (annotationSource == null ? "unknown element" : annotationSource.toString());
- String msg = String.format("In annotation [%s] declared on [%s], attribute [%s] and its alias [%s] " +
- "are present with values of [%s] and [%s], but only one is permitted.",
+ String msg = String.format("In annotation [%s] declared on [%s], attribute [%s] and its " +
+ "alias [%s] are present with values of [%s] and [%s], but only one is permitted.",
annotationType.getName(), elementName, attributeName, aliasName,
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue));
throw new AnnotationConfigurationException(msg);
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 beecd919..01ddd5be 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import org.springframework.core.DecoratingProxy;
import org.springframework.core.OrderComparator;
/**
@@ -81,10 +82,13 @@ public class AnnotationAwareOrderComparator extends OrderComparator {
}
}
else if (obj != null) {
- return OrderUtils.getOrder(obj.getClass());
+ order = OrderUtils.getOrder(obj.getClass());
+ if (order == null && obj instanceof DecoratingProxy) {
+ order = OrderUtils.getOrder(((DecoratingProxy) obj).getDecoratedClass());
+ }
}
- return null;
+ return order;
}
/**
@@ -94,13 +98,17 @@ public class AnnotationAwareOrderComparator extends OrderComparator {
* multiple matches but only one object to be returned.
*/
public Integer getPriority(Object obj) {
+ Integer priority = null;
if (obj instanceof Class) {
- return OrderUtils.getPriority((Class<?>) obj);
+ priority = OrderUtils.getPriority((Class<?>) obj);
}
else if (obj != null) {
- return OrderUtils.getPriority(obj.getClass());
+ priority = OrderUtils.getPriority(obj.getClass());
+ if (priority == null && obj instanceof DecoratingProxy) {
+ priority = OrderUtils.getOrder(((DecoratingProxy) obj).getDecoratedClass());
+ }
}
- return null;
+ return priority;
}
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 09eb167e..2fe059a5 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
@@ -22,6 +22,7 @@ import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
@@ -111,6 +112,7 @@ public abstract class AnnotationUtils {
*/
public static final String VALUE = "value";
+ private static final String REPEATABLE_CLASS_NAME = "java.lang.annotation.Repeatable";
private static final Map<AnnotationCacheKey, Annotation> findAnnotationCache =
new ConcurrentReferenceHashMap<AnnotationCacheKey, Annotation>(256);
@@ -308,6 +310,7 @@ public abstract class AnnotationUtils {
* @since 4.2
* @see #getRepeatableAnnotations(AnnotatedElement, Class, Class)
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class)
+ * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class)
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getAnnotationsByType
@@ -343,6 +346,7 @@ public abstract class AnnotationUtils {
* @see #getRepeatableAnnotations(AnnotatedElement, Class)
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class)
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class)
+ * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class, Class)
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getAnnotationsByType
@@ -388,6 +392,7 @@ public abstract class AnnotationUtils {
* @see #getRepeatableAnnotations(AnnotatedElement, Class)
* @see #getRepeatableAnnotations(AnnotatedElement, Class, Class)
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class)
+ * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class)
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType
@@ -423,6 +428,7 @@ public abstract class AnnotationUtils {
* @see #getRepeatableAnnotations(AnnotatedElement, Class)
* @see #getRepeatableAnnotations(AnnotatedElement, Class, Class)
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class)
+ * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class, Class)
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType
@@ -616,7 +622,7 @@ public abstract class AnnotationUtils {
static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) {
Boolean found = annotatedInterfaceCache.get(iface);
if (found != null) {
- return found.booleanValue();
+ return found;
}
found = Boolean.FALSE;
for (Method ifcMethod : iface.getMethods()) {
@@ -631,7 +637,7 @@ public abstract class AnnotationUtils {
}
}
annotatedInterfaceCache.put(iface, found);
- return found.booleanValue();
+ return found;
}
/**
@@ -883,14 +889,14 @@ public abstract class AnnotationUtils {
AnnotationCacheKey cacheKey = new AnnotationCacheKey(annotationType, metaAnnotationType);
Boolean metaPresent = metaPresentCache.get(cacheKey);
if (metaPresent != null) {
- return metaPresent.booleanValue();
+ return metaPresent;
}
metaPresent = Boolean.FALSE;
if (findAnnotation(annotationType, metaAnnotationType, false) != null) {
metaPresent = Boolean.TRUE;
}
metaPresentCache.put(cacheKey, metaPresent);
- return metaPresent.booleanValue();
+ return metaPresent;
}
/**
@@ -1013,6 +1019,13 @@ public abstract class AnnotationUtils {
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement,
Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+ return getAnnotationAttributes(
+ (Object) annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap);
+ }
+
+ private static AnnotationAttributes getAnnotationAttributes(Object annotatedElement,
+ Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+
AnnotationAttributes attributes =
retrieveAnnotationAttributes(annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap);
postProcessAnnotationAttributes(annotatedElement, attributes, classValuesAsString, nestedAnnotationsAsMap);
@@ -1047,7 +1060,7 @@ public abstract class AnnotationUtils {
* @since 4.2
* @see #postProcessAnnotationAttributes
*/
- static AnnotationAttributes retrieveAnnotationAttributes(AnnotatedElement annotatedElement, Annotation annotation,
+ static AnnotationAttributes retrieveAnnotationAttributes(Object annotatedElement, Annotation annotation,
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
Class<? extends Annotation> annotationType = annotation.annotationType();
@@ -1091,14 +1104,14 @@ public abstract class AnnotationUtils {
* {@code Annotation} instances
* @return the adapted value, or the original value if no adaptation is needed
*/
- static Object adaptValue(AnnotatedElement annotatedElement, Object value, boolean classValuesAsString,
+ static Object adaptValue(Object annotatedElement, Object value, boolean classValuesAsString,
boolean nestedAnnotationsAsMap) {
if (classValuesAsString) {
- if (value instanceof Class) {
+ if (value instanceof Class<?>) {
return ((Class<?>) value).getName();
}
- else if (value instanceof Class[]) {
+ else if (value instanceof Class<?>[]) {
Class<?>[] clazzArray = (Class<?>[]) value;
String[] classNames = new String[clazzArray.length];
for (int i = 0; i < clazzArray.length; i++) {
@@ -1138,6 +1151,63 @@ public abstract class AnnotationUtils {
}
/**
+ * Register the annotation-declared default values for the given attributes,
+ * if available.
+ * @param attributes the annotation attributes to process
+ * @since 4.3.2
+ */
+ public static void registerDefaultValues(AnnotationAttributes attributes) {
+ // Only do defaults scanning for public annotations; we'd run into
+ // IllegalAccessExceptions otherwise, and we don't want to mess with
+ // accessibility in a SecurityManager environment.
+ Class<? extends Annotation> annotationType = attributes.annotationType();
+ if (annotationType != null && Modifier.isPublic(annotationType.getModifiers())) {
+ // Check declared default values of attributes in the annotation type.
+ for (Method annotationAttribute : getAttributeMethods(annotationType)) {
+ String attributeName = annotationAttribute.getName();
+ Object defaultValue = annotationAttribute.getDefaultValue();
+ if (defaultValue != null && !attributes.containsKey(attributeName)) {
+ if (defaultValue instanceof Annotation) {
+ defaultValue = 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] = getAnnotationAttributes(realAnnotations[i], false, true);
+ }
+ defaultValue = mappedAnnotations;
+ }
+ attributes.put(attributeName, new DefaultValueHolder(defaultValue));
+ }
+ }
+ }
+ }
+
+ /**
+ * Post-process the supplied {@link AnnotationAttributes}, preserving nested
+ * annotations as {@code Annotation} instances.
+ * <p>Specifically, this method enforces <em>attribute alias</em> semantics
+ * for annotation attributes that are annotated with {@link AliasFor @AliasFor}
+ * and replaces default value placeholders with their original default values.
+ * @param annotatedElement the element that is annotated with an annotation or
+ * annotation hierarchy from which the supplied attributes were created;
+ * may be {@code null} if unknown
+ * @param attributes the annotation attributes to post-process
+ * @param classValuesAsString whether to convert Class references into Strings (for
+ * compatibility with {@link org.springframework.core.type.AnnotationMetadata})
+ * or to preserve them as Class references
+ * @since 4.3.2
+ * @see #postProcessAnnotationAttributes(Object, AnnotationAttributes, boolean, boolean)
+ * @see #getDefaultValue(Class, String)
+ */
+ public static void postProcessAnnotationAttributes(Object annotatedElement,
+ AnnotationAttributes attributes, boolean classValuesAsString) {
+
+ postProcessAnnotationAttributes(annotatedElement, attributes, classValuesAsString, false);
+ }
+
+ /**
* Post-process the supplied {@link AnnotationAttributes}.
* <p>Specifically, this method enforces <em>attribute alias</em> semantics
* for annotation attributes that are annotated with {@link AliasFor @AliasFor}
@@ -1154,10 +1224,10 @@ public abstract class AnnotationUtils {
* {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as
* {@code Annotation} instances
* @since 4.2
- * @see #retrieveAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)
+ * @see #retrieveAnnotationAttributes(Object, Annotation, boolean, boolean)
* @see #getDefaultValue(Class, String)
*/
- static void postProcessAnnotationAttributes(AnnotatedElement annotatedElement,
+ static void postProcessAnnotationAttributes(Object annotatedElement,
AnnotationAttributes attributes, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
// Abort?
@@ -1171,51 +1241,55 @@ public abstract class AnnotationUtils {
// circuit the search algorithms.
Set<String> valuesAlreadyReplaced = new HashSet<String>();
- // Validate @AliasFor configuration
- Map<String, List<String>> aliasMap = getAttributeAliasMap(annotationType);
- for (String attributeName : aliasMap.keySet()) {
- if (valuesAlreadyReplaced.contains(attributeName)) {
- continue;
- }
- Object value = attributes.get(attributeName);
- boolean valuePresent = (value != null && !(value instanceof DefaultValueHolder));
-
- for (String aliasedAttributeName : aliasMap.get(attributeName)) {
- if (valuesAlreadyReplaced.contains(aliasedAttributeName)) {
+ if (!attributes.validated) {
+ // Validate @AliasFor configuration
+ Map<String, List<String>> aliasMap = getAttributeAliasMap(annotationType);
+ for (String attributeName : aliasMap.keySet()) {
+ if (valuesAlreadyReplaced.contains(attributeName)) {
continue;
}
+ Object value = attributes.get(attributeName);
+ boolean valuePresent = (value != null && !(value instanceof DefaultValueHolder));
- Object aliasedValue = attributes.get(aliasedAttributeName);
- boolean aliasPresent = (aliasedValue != null && !(aliasedValue instanceof DefaultValueHolder));
-
- // Something to validate or replace with an alias?
- if (valuePresent || aliasPresent) {
- if (valuePresent && aliasPresent) {
- // Since annotation attributes can be arrays, we must use ObjectUtils.nullSafeEquals().
- if (!ObjectUtils.nullSafeEquals(value, aliasedValue)) {
- String elementAsString = (annotatedElement != null ? annotatedElement.toString() : "unknown element");
- throw new AnnotationConfigurationException(String.format(
- "In AnnotationAttributes for annotation [%s] declared on %s, " +
- "attribute '%s' and its alias '%s' are declared with values of [%s] and [%s], " +
- "but only one is permitted.", annotationType.getName(), elementAsString,
- attributeName, aliasedAttributeName, ObjectUtils.nullSafeToString(value),
- ObjectUtils.nullSafeToString(aliasedValue)));
- }
+ for (String aliasedAttributeName : aliasMap.get(attributeName)) {
+ if (valuesAlreadyReplaced.contains(aliasedAttributeName)) {
+ continue;
}
- else if (aliasPresent) {
- // Replace value with aliasedValue
- attributes.put(attributeName,
- adaptValue(annotatedElement, aliasedValue, classValuesAsString, nestedAnnotationsAsMap));
- valuesAlreadyReplaced.add(attributeName);
- }
- else {
- // Replace aliasedValue with value
- attributes.put(aliasedAttributeName,
- adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap));
- valuesAlreadyReplaced.add(aliasedAttributeName);
+
+ Object aliasedValue = attributes.get(aliasedAttributeName);
+ boolean aliasPresent = (aliasedValue != null && !(aliasedValue instanceof DefaultValueHolder));
+
+ // Something to validate or replace with an alias?
+ if (valuePresent || aliasPresent) {
+ if (valuePresent && aliasPresent) {
+ // Since annotation attributes can be arrays, we must use ObjectUtils.nullSafeEquals().
+ if (!ObjectUtils.nullSafeEquals(value, aliasedValue)) {
+ String elementAsString =
+ (annotatedElement != null ? annotatedElement.toString() : "unknown element");
+ throw new AnnotationConfigurationException(String.format(
+ "In AnnotationAttributes for annotation [%s] declared on %s, " +
+ "attribute '%s' and its alias '%s' are declared with values of [%s] and [%s], " +
+ "but only one is permitted.", annotationType.getName(), elementAsString,
+ attributeName, aliasedAttributeName, ObjectUtils.nullSafeToString(value),
+ ObjectUtils.nullSafeToString(aliasedValue)));
+ }
+ }
+ else if (aliasPresent) {
+ // Replace value with aliasedValue
+ attributes.put(attributeName,
+ adaptValue(annotatedElement, aliasedValue, classValuesAsString, nestedAnnotationsAsMap));
+ valuesAlreadyReplaced.add(attributeName);
+ }
+ else {
+ // Replace aliasedValue with value
+ attributes.put(aliasedAttributeName,
+ adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap));
+ valuesAlreadyReplaced.add(aliasedAttributeName);
+ }
}
}
}
+ attributes.validated = true;
}
// Replace any remaining placeholders with actual default values
@@ -1355,8 +1429,12 @@ public abstract class AnnotationUtils {
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
* @see #synthesizeAnnotation(Class)
*/
- @SuppressWarnings("unchecked")
public static <A extends Annotation> A synthesizeAnnotation(A annotation, AnnotatedElement annotatedElement) {
+ return synthesizeAnnotation(annotation, (Object) annotatedElement);
+ }
+
+ @SuppressWarnings("unchecked")
+ static <A extends Annotation> A synthesizeAnnotation(A annotation, Object annotatedElement) {
if (annotation == null) {
return null;
}
@@ -1461,7 +1539,7 @@ public abstract class AnnotationUtils {
* @see #synthesizeAnnotation(Annotation, AnnotatedElement)
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
*/
- public static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, AnnotatedElement annotatedElement) {
+ static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, Object annotatedElement) {
if (annotations == null) {
return null;
}
@@ -1489,7 +1567,7 @@ public abstract class AnnotationUtils {
* {@code @AliasFor} is detected
* @since 4.2.1
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
- * @see #synthesizeAnnotationArray(Annotation[], AnnotatedElement)
+ * @see #synthesizeAnnotationArray(Annotation[], Object)
*/
@SuppressWarnings("unchecked")
static <A extends Annotation> A[] synthesizeAnnotationArray(Map<String, Object>[] maps, Class<A> annotationType) {
@@ -1577,7 +1655,7 @@ public abstract class AnnotationUtils {
private static boolean isSynthesizable(Class<? extends Annotation> annotationType) {
Boolean synthesizable = synthesizableCache.get(annotationType);
if (synthesizable != null) {
- return synthesizable.booleanValue();
+ return synthesizable;
}
synthesizable = Boolean.FALSE;
@@ -1605,7 +1683,7 @@ public abstract class AnnotationUtils {
}
synthesizableCache.put(annotationType, synthesizable);
- return synthesizable.booleanValue();
+ return synthesizable;
}
/**
@@ -1721,6 +1799,29 @@ public abstract class AnnotationUtils {
}
/**
+ * Resolve the container type for the supplied repeatable {@code annotationType}.
+ * <p>Automatically detects a <em>container annotation</em> declared via
+ * {@link java.lang.annotation.Repeatable}. If the supplied annotation type
+ * is not annotated with {@code @Repeatable}, this method simply returns
+ * {@code null}.
+ * @since 4.2
+ */
+ @SuppressWarnings("unchecked")
+ static Class<? extends Annotation> resolveContainerAnnotationType(Class<? extends Annotation> annotationType) {
+ try {
+ Annotation repeatable = getAnnotation(annotationType, REPEATABLE_CLASS_NAME);
+ if (repeatable != null) {
+ Object value = getValue(repeatable);
+ return (Class<? extends Annotation>) value;
+ }
+ }
+ catch (Exception ex) {
+ handleIntrospectionFailure(annotationType, ex);
+ }
+ return null;
+ }
+
+ /**
* If the supplied throwable is an {@link AnnotationConfigurationException},
* it will be cast to an {@code AnnotationConfigurationException} and thrown,
* allowing it to propagate to the caller.
@@ -1774,7 +1875,7 @@ public abstract class AnnotationUtils {
/**
* Cache key for the AnnotatedElement cache.
*/
- private static class AnnotationCacheKey {
+ private static final class AnnotationCacheKey implements Comparable<AnnotationCacheKey> {
private final AnnotatedElement element;
@@ -1801,13 +1902,25 @@ public abstract class AnnotationUtils {
public int hashCode() {
return (this.element.hashCode() * 29 + this.annotationType.hashCode());
}
+
+ @Override
+ public String toString() {
+ return "@" + this.annotationType + " on " + this.element;
+ }
+
+ @Override
+ public int compareTo(AnnotationCacheKey other) {
+ int result = this.element.toString().compareTo(other.element.toString());
+ if (result == 0) {
+ result = this.annotationType.getName().compareTo(other.annotationType.getName());
+ }
+ return result;
+ }
}
private static class AnnotationCollector<A extends Annotation> {
- private static final String REPEATABLE_CLASS_NAME = "java.lang.annotation.Repeatable";
-
private final Class<A> annotationType;
private final Class<? extends Annotation> containerAnnotationType;
@@ -1825,21 +1938,6 @@ public abstract class AnnotationUtils {
this.declaredMode = declaredMode;
}
- @SuppressWarnings("unchecked")
- static Class<? extends Annotation> resolveContainerAnnotationType(Class<? extends Annotation> annotationType) {
- try {
- Annotation repeatable = getAnnotation(annotationType, REPEATABLE_CLASS_NAME);
- if (repeatable != null) {
- Object value = AnnotationUtils.getValue(repeatable);
- return (Class<? extends Annotation>) value;
- }
- }
- catch (Exception ex) {
- handleIntrospectionFailure(annotationType, ex);
- }
- return null;
- }
-
Set<A> getResult(AnnotatedElement element) {
process(element);
return Collections.unmodifiableSet(this.result);
@@ -1951,12 +2049,19 @@ public abstract class AnnotationUtils {
this.aliasedAnnotationType = (Annotation.class == aliasFor.annotation() ?
this.sourceAnnotationType : aliasFor.annotation());
this.aliasedAttributeName = getAliasedAttributeName(aliasFor, sourceAttribute);
+ if (this.aliasedAnnotationType == this.sourceAnnotationType &&
+ this.aliasedAttributeName.equals(this.sourceAttributeName)) {
+ String msg = String.format("@AliasFor declaration on attribute '%s' in annotation [%s] points to " +
+ "itself. Specify 'annotation' to point to a same-named attribute on a meta-annotation.",
+ sourceAttribute.getName(), declaringClass.getName());
+ throw new AnnotationConfigurationException(msg);
+ }
try {
this.aliasedAttribute = this.aliasedAnnotationType.getDeclaredMethod(this.aliasedAttributeName);
}
catch (NoSuchMethodException ex) {
String msg = String.format(
- "Attribute [%s] in annotation [%s] is declared as an @AliasFor nonexistent attribute [%s] in annotation [%s].",
+ "Attribute '%s' in annotation [%s] is declared as an @AliasFor nonexistent attribute '%s' in annotation [%s].",
this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
this.aliasedAnnotationType.getName());
throw new AnnotationConfigurationException(msg, ex);
@@ -1968,8 +2073,8 @@ public abstract class AnnotationUtils {
private void validate() {
// Target annotation is not meta-present?
if (!this.isAliasPair && !isAnnotationMetaPresent(this.sourceAnnotationType, this.aliasedAnnotationType)) {
- String msg = String.format("@AliasFor declaration on attribute [%s] in annotation [%s] declares " +
- "an alias for attribute [%s] in meta-annotation [%s] which is not meta-present.",
+ String msg = String.format("@AliasFor declaration on attribute '%s' in annotation [%s] declares " +
+ "an alias for attribute '%s' in meta-annotation [%s] which is not meta-present.",
this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
this.aliasedAnnotationType.getName());
throw new AnnotationConfigurationException(msg);
@@ -1978,14 +2083,14 @@ public abstract class AnnotationUtils {
if (this.isAliasPair) {
AliasFor mirrorAliasFor = this.aliasedAttribute.getAnnotation(AliasFor.class);
if (mirrorAliasFor == null) {
- String msg = String.format("Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s].",
+ String msg = String.format("Attribute '%s' in annotation [%s] must be declared as an @AliasFor [%s].",
this.aliasedAttributeName, this.sourceAnnotationType.getName(), this.sourceAttributeName);
throw new AnnotationConfigurationException(msg);
}
String mirrorAliasedAttributeName = getAliasedAttributeName(mirrorAliasFor, this.aliasedAttribute);
if (!this.sourceAttributeName.equals(mirrorAliasedAttributeName)) {
- String msg = String.format("Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s], not [%s].",
+ String msg = String.format("Attribute '%s' in annotation [%s] must be declared as an @AliasFor [%s], not [%s].",
this.aliasedAttributeName, this.sourceAnnotationType.getName(), this.sourceAttributeName,
mirrorAliasedAttributeName);
throw new AnnotationConfigurationException(msg);
@@ -1994,9 +2099,10 @@ public abstract class AnnotationUtils {
Class<?> returnType = this.sourceAttribute.getReturnType();
Class<?> aliasedReturnType = this.aliasedAttribute.getReturnType();
- if (returnType != aliasedReturnType) {
- String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " +
- "and attribute [%s] in annotation [%s] must declare the same return type.",
+ if (returnType != aliasedReturnType &&
+ (!aliasedReturnType.isArray() || returnType != aliasedReturnType.getComponentType())) {
+ String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " +
+ "and attribute '%s' in annotation [%s] must declare the same return type.",
this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
this.aliasedAnnotationType.getName());
throw new AnnotationConfigurationException(msg);
@@ -2013,16 +2119,16 @@ public abstract class AnnotationUtils {
Object aliasedDefaultValue = aliasedAttribute.getDefaultValue();
if (defaultValue == null || aliasedDefaultValue == null) {
- String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " +
- "and attribute [%s] in annotation [%s] must declare default values.",
+ String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " +
+ "and attribute '%s' in annotation [%s] must declare default values.",
this.sourceAttributeName, this.sourceAnnotationType.getName(), aliasedAttribute.getName(),
aliasedAttribute.getDeclaringClass().getName());
throw new AnnotationConfigurationException(msg);
}
if (!ObjectUtils.nullSafeEquals(defaultValue, aliasedDefaultValue)) {
- String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " +
- "and attribute [%s] in annotation [%s] must declare the same default value.",
+ String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " +
+ "and attribute '%s' in annotation [%s] must declare the same default value.",
this.sourceAttributeName, this.sourceAnnotationType.getName(), aliasedAttribute.getName(),
aliasedAttribute.getDeclaringClass().getName());
throw new AnnotationConfigurationException(msg);
@@ -2125,15 +2231,16 @@ public abstract class AnnotationUtils {
/**
* Get the name of the aliased attribute configured via the supplied
- * {@link AliasFor @AliasFor} annotation on the supplied {@code attribute}.
+ * {@link AliasFor @AliasFor} annotation on the supplied {@code attribute},
+ * or the original attribute if no aliased one specified (indicating that
+ * the reference goes to a same-named attribute on a meta-annotation).
* <p>This method returns the value of either the {@code attribute}
* or {@code value} attribute of {@code @AliasFor}, ensuring that only
* one of the attributes has been declared while simultaneously ensuring
* that at least one of the attributes has been declared.
* @param aliasFor the {@code @AliasFor} annotation from which to retrieve
* the aliased attribute name
- * @param attribute the attribute that is annotated with {@code @AliasFor},
- * used solely for building an exception message
+ * @param attribute the attribute that is annotated with {@code @AliasFor}
* @return the name of the aliased attribute (never {@code null} or empty)
* @throws AnnotationConfigurationException if invalid configuration of
* {@code @AliasFor} is detected
@@ -2146,23 +2253,15 @@ public abstract class AnnotationUtils {
// Ensure user did not declare both 'value' and 'attribute' in @AliasFor
if (attributeDeclared && valueDeclared) {
- String msg = String.format("In @AliasFor declared on attribute [%s] in annotation [%s], attribute 'attribute' " +
+ String msg = String.format("In @AliasFor declared on attribute '%s' in annotation [%s], attribute 'attribute' " +
"and its alias 'value' are present with values of [%s] and [%s], but only one is permitted.",
attribute.getName(), attribute.getDeclaringClass().getName(), attributeName, value);
throw new AnnotationConfigurationException(msg);
}
+ // Either explicit attribute name or pointing to same-named attribute by default
attributeName = (attributeDeclared ? attributeName : value);
-
- // Ensure user declared either 'value' or 'attribute' in @AliasFor
- if (!StringUtils.hasText(attributeName)) {
- String msg = String.format(
- "@AliasFor declaration on attribute [%s] in annotation [%s] is missing required 'attribute' value.",
- attribute.getName(), attribute.getDeclaringClass().getName());
- throw new AnnotationConfigurationException(msg);
- }
-
- return attributeName.trim();
+ return (StringUtils.hasText(attributeName) ? attributeName.trim() : attribute.getName());
}
@Override
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/DefaultAnnotationAttributeExtractor.java b/spring-core/src/main/java/org/springframework/core/annotation/DefaultAnnotationAttributeExtractor.java
index 805c769b..0ea2cf00 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/DefaultAnnotationAttributeExtractor.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/DefaultAnnotationAttributeExtractor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
-import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import org.springframework.util.ReflectionUtils;
@@ -32,7 +31,7 @@ import org.springframework.util.ReflectionUtils;
* @see AliasFor
* @see AbstractAliasAwareAnnotationAttributeExtractor
* @see MapAnnotationAttributeExtractor
- * @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement)
+ * @see AnnotationUtils#synthesizeAnnotation
*/
class DefaultAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttributeExtractor<Annotation> {
@@ -42,7 +41,7 @@ class DefaultAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAt
* @param annotatedElement the element that is annotated with the supplied
* annotation; may be {@code null} if unknown
*/
- DefaultAnnotationAttributeExtractor(Annotation annotation, AnnotatedElement annotatedElement) {
+ DefaultAnnotationAttributeExtractor(Annotation annotation, Object annotatedElement) {
super(annotation.annotationType(), annotatedElement, annotation);
}
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/MapAnnotationAttributeExtractor.java b/spring-core/src/main/java/org/springframework/core/annotation/MapAnnotationAttributeExtractor.java
index 005ae431..a2491fe5 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/MapAnnotationAttributeExtractor.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/MapAnnotationAttributeExtractor.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,15 +18,15 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Array;
import java.lang.reflect.Method;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
-import static org.springframework.core.annotation.AnnotationUtils.*;
-
/**
* Implementation of the {@link AnnotationAttributeExtractor} strategy that
* is backed by a {@link Map}.
@@ -87,10 +87,10 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
private static Map<String, Object> enrichAndValidateAttributes(
Map<String, Object> originalAttributes, Class<? extends Annotation> annotationType) {
- Map<String, Object> attributes = new HashMap<String, Object>(originalAttributes);
- Map<String, List<String>> attributeAliasMap = getAttributeAliasMap(annotationType);
+ Map<String, Object> attributes = new LinkedHashMap<String, Object>(originalAttributes);
+ Map<String, List<String>> attributeAliasMap = AnnotationUtils.getAttributeAliasMap(annotationType);
- for (Method attributeMethod : getAttributeMethods(annotationType)) {
+ for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotationType)) {
String attributeName = attributeMethod.getName();
Object attributeValue = attributes.get(attributeName);
@@ -111,7 +111,7 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
// if aliases not present, check default
if (attributeValue == null) {
- Object defaultValue = getDefaultValue(annotationType, attributeName);
+ Object defaultValue = AnnotationUtils.getDefaultValue(annotationType, attributeName);
if (defaultValue != null) {
attributeValue = defaultValue;
attributes.put(attributeName, attributeValue);
@@ -121,7 +121,7 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
// if still null
if (attributeValue == null) {
throw new IllegalArgumentException(String.format(
- "Attributes map [%s] returned null for required attribute [%s] defined by annotation type [%s].",
+ "Attributes map %s returned null for required attribute '%s' defined by annotation type [%s].",
attributes, attributeName, annotationType.getName()));
}
@@ -132,13 +132,21 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
if (!ClassUtils.isAssignable(requiredReturnType, actualReturnType)) {
boolean converted = false;
+ // Single element overriding an array of the same type?
+ if (requiredReturnType.isArray() && requiredReturnType.getComponentType() == actualReturnType) {
+ Object array = Array.newInstance(requiredReturnType.getComponentType(), 1);
+ Array.set(array, 0, attributeValue);
+ attributes.put(attributeName, array);
+ converted = true;
+ }
+
// Nested map representing a single annotation?
- if (Annotation.class.isAssignableFrom(requiredReturnType) &&
+ else if (Annotation.class.isAssignableFrom(requiredReturnType) &&
Map.class.isAssignableFrom(actualReturnType)) {
Class<? extends Annotation> nestedAnnotationType =
(Class<? extends Annotation>) requiredReturnType;
Map<String, Object> map = (Map<String, Object>) attributeValue;
- attributes.put(attributeName, synthesizeAnnotation(map, nestedAnnotationType, null));
+ attributes.put(attributeName, AnnotationUtils.synthesizeAnnotation(map, nestedAnnotationType, null));
converted = true;
}
@@ -149,14 +157,14 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
Class<? extends Annotation> nestedAnnotationType =
(Class<? extends Annotation>) requiredReturnType.getComponentType();
Map<String, Object>[] maps = (Map<String, Object>[]) attributeValue;
- attributes.put(attributeName, synthesizeAnnotationArray(maps, nestedAnnotationType));
+ attributes.put(attributeName, AnnotationUtils.synthesizeAnnotationArray(maps, nestedAnnotationType));
converted = true;
}
if (!converted) {
throw new IllegalArgumentException(String.format(
- "Attributes map [%s] returned a value of type [%s] for attribute [%s], "
- + "but a value of type [%s] is required as defined by annotation type [%s].",
+ "Attributes map %s returned a value of type [%s] for attribute '%s', " +
+ "but a value of type [%s] is required as defined by annotation type [%s].",
attributes, actualReturnType.getName(), attributeName, requiredReturnType.getName(),
annotationType.getName()));
}
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java
index c8614eb5..27e14c43 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java
@@ -27,11 +27,9 @@ import java.util.concurrent.ConcurrentHashMap;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
+import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
-import static org.springframework.core.annotation.AnnotationUtils.*;
-import static org.springframework.util.ReflectionUtils.*;
-
/**
* {@link InvocationHandler} for an {@link Annotation} that Spring has
* <em>synthesized</em> (i.e., wrapped in a dynamic proxy) with additional
@@ -63,22 +61,21 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- if (isEqualsMethod(method)) {
+ if (ReflectionUtils.isEqualsMethod(method)) {
return annotationEquals(args[0]);
}
- if (isHashCodeMethod(method)) {
+ if (ReflectionUtils.isHashCodeMethod(method)) {
return annotationHashCode();
}
- if (isToStringMethod(method)) {
+ if (ReflectionUtils.isToStringMethod(method)) {
return annotationToString();
}
- if (isAnnotationTypeMethod(method)) {
+ if (AnnotationUtils.isAnnotationTypeMethod(method)) {
return annotationType();
}
- if (!isAttributeMethod(method)) {
- String msg = String.format("Method [%s] is unsupported for synthesized annotation type [%s]",
- method, annotationType());
- throw new AnnotationConfigurationException(msg);
+ if (!AnnotationUtils.isAttributeMethod(method)) {
+ throw new AnnotationConfigurationException(String.format(
+ "Method [%s] is unsupported for synthesized annotation type [%s]", method, annotationType()));
}
return getAttributeValue(method);
}
@@ -100,10 +97,10 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
// Synthesize nested annotations before returning them.
if (value instanceof Annotation) {
- value = synthesizeAnnotation((Annotation) value, this.attributeExtractor.getAnnotatedElement());
+ value = AnnotationUtils.synthesizeAnnotation((Annotation) value, this.attributeExtractor.getAnnotatedElement());
}
else if (value instanceof Annotation[]) {
- value = synthesizeAnnotationArray((Annotation[]) value, this.attributeExtractor.getAnnotatedElement());
+ value = AnnotationUtils.synthesizeAnnotationArray((Annotation[]) value, this.attributeExtractor.getAnnotatedElement());
}
this.valueCache.put(attributeName, value);
@@ -164,9 +161,9 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
return false;
}
- for (Method attributeMethod : getAttributeMethods(annotationType())) {
+ for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotationType())) {
Object thisValue = getAttributeValue(attributeMethod);
- Object otherValue = invokeMethod(attributeMethod, other);
+ Object otherValue = ReflectionUtils.invokeMethod(attributeMethod, other);
if (!ObjectUtils.nullSafeEquals(thisValue, otherValue)) {
return false;
}
@@ -181,7 +178,7 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
private int annotationHashCode() {
int result = 0;
- for (Method attributeMethod : getAttributeMethods(annotationType())) {
+ for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotationType())) {
Object value = getAttributeValue(attributeMethod);
int hashCode;
if (value.getClass().isArray()) {
@@ -239,7 +236,7 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
private String annotationToString() {
StringBuilder sb = new StringBuilder("@").append(annotationType().getName()).append("(");
- Iterator<Method> iterator = getAttributeMethods(annotationType()).iterator();
+ Iterator<Method> iterator = AnnotationUtils.getAttributeMethods(annotationType()).iterator();
while (iterator.hasNext()) {
Method attributeMethod = iterator.next();
sb.append(attributeMethod.getName());
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java
index a6915e76..eb287c47 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import org.springframework.core.MethodParameter;
@@ -34,7 +35,8 @@ import org.springframework.core.MethodParameter;
public class SynthesizingMethodParameter extends MethodParameter {
/**
- * Create a new {@code SynthesizingMethodParameter} for the given method.
+ * Create a new {@code SynthesizingMethodParameter} for the given method,
+ * with nesting level 1.
* @param method the Method to specify a parameter for
* @param parameterIndex the index of the parameter: -1 for the method
* return type; 0 for the first method parameter; 1 for the second method
@@ -44,6 +46,51 @@ public class SynthesizingMethodParameter extends MethodParameter {
super(method, parameterIndex);
}
+ /**
+ * Create a new {@code SynthesizingMethodParameter} for the given method.
+ * @param method the Method to specify a parameter for
+ * @param parameterIndex the index of the parameter: -1 for the method
+ * return type; 0 for the first method parameter; 1 for the second method
+ * parameter, etc.
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ */
+ public SynthesizingMethodParameter(Method method, int parameterIndex, int nestingLevel) {
+ super(method, parameterIndex, nestingLevel);
+ }
+
+ /**
+ * Create a new {@code SynthesizingMethodParameter} for the given constructor,
+ * with nesting level 1.
+ * @param constructor the Constructor to specify a parameter for
+ * @param parameterIndex the index of the parameter
+ */
+ public SynthesizingMethodParameter(Constructor<?> constructor, int parameterIndex) {
+ super(constructor, parameterIndex);
+ }
+
+ /**
+ * Create a new {@code SynthesizingMethodParameter} for the given constructor.
+ * @param constructor the Constructor to specify a parameter for
+ * @param parameterIndex the index of the parameter
+ * @param nestingLevel the nesting level of the target type
+ * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
+ * nested List, whereas 2 would indicate the element of the nested List)
+ */
+ public SynthesizingMethodParameter(Constructor<?> constructor, int parameterIndex, int nestingLevel) {
+ super(constructor, parameterIndex, nestingLevel);
+ }
+
+ /**
+ * Copy constructor, resulting in an independent {@code SynthesizingMethodParameter}
+ * based on the same metadata and cache state that the original object was in.
+ * @param original the original SynthesizingMethodParameter object to copy from
+ */
+ protected SynthesizingMethodParameter(SynthesizingMethodParameter original) {
+ super(original);
+ }
+
@Override
protected <A extends Annotation> A adaptAnnotation(A annotation) {
@@ -55,4 +102,10 @@ public class SynthesizingMethodParameter extends MethodParameter {
return AnnotationUtils.synthesizeAnnotationArray(annotations, getAnnotatedElement());
}
+
+ @Override
+ public SynthesizingMethodParameter clone() {
+ return new SynthesizingMethodParameter(this);
+ }
+
}
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 dde18ce8..ffbbd7ab 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
@@ -343,7 +343,7 @@ public class TypeDescriptor implements Serializable {
if (streamAvailable && StreamDelegate.isStream(this.type)) {
return StreamDelegate.getStreamElementType(this);
}
- return getRelatedIfResolvable(this, this.resolvableType.asCollection().getGeneric());
+ return getRelatedIfResolvable(this, this.resolvableType.asCollection().getGeneric(0));
}
/**
@@ -467,7 +467,7 @@ public class TypeDescriptor implements Serializable {
return false;
}
for (Annotation ann : getAnnotations()) {
- if (!other.hasAnnotation(ann.annotationType())) {
+ if (!ann.equals(other.getAnnotation(ann.annotationType()))) {
return false;
}
}
@@ -706,7 +706,7 @@ public class TypeDescriptor implements Serializable {
}
public static TypeDescriptor getStreamElementType(TypeDescriptor source) {
- return getRelatedIfResolvable(source, source.resolvableType.as(Stream.class).getGeneric());
+ return getRelatedIfResolvable(source, source.resolvableType.as(Stream.class).getGeneric(0));
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/converter/ConverterRegistry.java b/spring-core/src/main/java/org/springframework/core/convert/converter/ConverterRegistry.java
index 9280c4ea..7541d550 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/converter/ConverterRegistry.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/converter/ConverterRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2009 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,18 +27,19 @@ public interface ConverterRegistry {
/**
* Add a plain converter to this registry.
- * The convertible sourceType/targetType pair is derived from the Converter's parameterized types.
+ * The convertible source/target type pair is derived from the Converter's parameterized types.
* @throws IllegalArgumentException if the parameterized types could not be resolved
*/
void addConverter(Converter<?, ?> converter);
/**
* Add a plain converter to this registry.
- * The convertible sourceType/targetType pair is specified explicitly.
- * Allows for a Converter to be reused for multiple distinct pairs without having to create a Converter class for each pair.
+ * The convertible source/target type pair is specified explicitly.
+ * <p>Allows for a Converter to be reused for multiple distinct pairs without
+ * having to create a Converter class for each pair.
* @since 3.1
*/
- void addConverter(Class<?> sourceType, Class<?> targetType, Converter<?, ?> converter);
+ <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter);
/**
* Add a generic converter to this registry.
@@ -47,7 +48,7 @@ public interface ConverterRegistry {
/**
* Add a ranged converter factory to this registry.
- * The convertible sourceType/rangeType pair is derived from the ConverterFactory's parameterized types.
+ * The convertible source/target type pair is derived from the ConverterFactory's parameterized types.
* @throws IllegalArgumentException if the parameterized types could not be resolved.
*/
void addConverterFactory(ConverterFactory<?, ?> converterFactory);
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/AbstractConditionalEnumConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/AbstractConditionalEnumConverter.java
new file mode 100644
index 00000000..e6c94f65
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/AbstractConditionalEnumConverter.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.TypeDescriptor;
+import org.springframework.core.convert.converter.ConditionalConverter;
+import org.springframework.util.ClassUtils;
+
+/**
+ * A {@link ConditionalConverter} base implementation for enum-based converters.
+ *
+ * @author Stephane Nicoll
+ * @since 4.3
+ */
+abstract class AbstractConditionalEnumConverter implements ConditionalConverter {
+
+ private final ConversionService conversionService;
+
+
+ protected AbstractConditionalEnumConverter(ConversionService conversionService) {
+ this.conversionService = conversionService;
+ }
+
+
+ @Override
+ public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
+ for (Class<?> interfaceType : ClassUtils.getAllInterfacesForClass(sourceType.getType())) {
+ if (this.conversionService.canConvert(TypeDescriptor.valueOf(interfaceType), targetType)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java b/spring-core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java
index e93eabe9..1806afd2 100644
--- a/spring-core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/ConversionUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2011 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@ import org.springframework.core.convert.converter.GenericConverter;
* Internal utilities for the conversion package.
*
* @author Keith Donald
+ * @author Stephane Nicoll
* @since 3.0
*/
abstract class ConversionUtils {
@@ -65,4 +66,16 @@ abstract class ConversionUtils {
}
}
+ public static Class<?> getEnumType(Class<?> targetType) {
+ Class<?> enumType = targetType;
+ while (enumType != null && !enumType.isEnum()) {
+ enumType = enumType.getSuperclass();
+ }
+ if (enumType == null) {
+ throw new IllegalArgumentException(
+ "The target type " + targetType.getName() + " does not refer to an enum");
+ }
+ return enumType;
+ }
+
}
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 408d1367..86df6337 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-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -141,8 +141,10 @@ public class DefaultConversionService extends GenericConversionService {
converterRegistry.addConverter(Boolean.class, String.class, new ObjectToStringConverter());
converterRegistry.addConverterFactory(new StringToEnumConverterFactory());
- converterRegistry.addConverter(Enum.class, String.class,
- new EnumToStringConverter((ConversionService) converterRegistry));
+ converterRegistry.addConverter(new EnumToStringConverter((ConversionService) converterRegistry));
+
+ converterRegistry.addConverterFactory(new IntegerToEnumConverterFactory());
+ converterRegistry.addConverter(new EnumToIntegerConverter((ConversionService) converterRegistry));
converterRegistry.addConverter(new StringToLocaleConverter());
converterRegistry.addConverter(Locale.class, String.class, new ObjectToStringConverter());
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/EnumToIntegerConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/EnumToIntegerConverter.java
new file mode 100644
index 00000000..0028ef54
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/EnumToIntegerConverter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.converter.Converter;
+
+/**
+ * Calls {@link Enum#ordinal()} to convert a source Enum to a Integer.
+ * This converter will not match enums with interfaces that can be converted.
+ *
+ * @author Yanming Zhou
+ * @since 4.3
+ */
+final class EnumToIntegerConverter extends AbstractConditionalEnumConverter implements Converter<Enum<?>, Integer> {
+
+ public EnumToIntegerConverter(ConversionService conversionService) {
+ super(conversionService);
+ }
+
+ @Override
+ public Integer convert(Enum<?> source) {
+ return source.ordinal();
+ }
+
+}
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 02a5f19f..ccb06f51 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
@@ -17,10 +17,7 @@
package org.springframework.core.convert.support;
import org.springframework.core.convert.ConversionService;
-import org.springframework.core.convert.TypeDescriptor;
-import org.springframework.core.convert.converter.ConditionalConverter;
import org.springframework.core.convert.converter.Converter;
-import org.springframework.util.ClassUtils;
/**
* Calls {@link Enum#name()} to convert a source Enum to a String.
@@ -30,24 +27,10 @@ import org.springframework.util.ClassUtils;
* @author Phillip Webb
* @since 3.0
*/
-final class EnumToStringConverter implements Converter<Enum<?>, String>, ConditionalConverter {
-
- private final ConversionService conversionService;
-
+final class EnumToStringConverter extends AbstractConditionalEnumConverter implements Converter<Enum<?>, String> {
public EnumToStringConverter(ConversionService conversionService) {
- this.conversionService = conversionService;
- }
-
-
- @Override
- public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
- for (Class<?> interfaceType : ClassUtils.getAllInterfacesForClass(sourceType.getType())) {
- if (this.conversionService.canConvert(TypeDescriptor.valueOf(interfaceType), targetType)) {
- return false;
- }
- }
- return true;
+ super(conversionService);
}
@Override
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 52df6a77..e051e6cc 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -103,7 +103,7 @@ public class GenericConversionService implements ConfigurableConversionService {
}
@Override
- public void addConverter(Class<?> sourceType, Class<?> targetType, Converter<?, ?> converter) {
+ public <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter) {
addConverter(new ConverterAdapter(
converter, ResolvableType.forClass(sourceType), ResolvableType.forClass(targetType)));
}
@@ -435,7 +435,7 @@ public class GenericConversionService implements ConfigurableConversionService {
/**
* Key for use with the converter cache.
*/
- private static final class ConverterCacheKey {
+ private static final class ConverterCacheKey implements Comparable<ConverterCacheKey> {
private final TypeDescriptor sourceType;
@@ -470,6 +470,17 @@ public class GenericConversionService implements ConfigurableConversionService {
return ("ConverterCacheKey [sourceType = " + this.sourceType +
", targetType = " + this.targetType + "]");
}
+
+ @Override
+ public int compareTo(ConverterCacheKey other) {
+ int result = this.sourceType.getResolvableType().toString().compareTo(
+ other.sourceType.getResolvableType().toString());
+ if (result == 0) {
+ result = this.targetType.getResolvableType().toString().compareTo(
+ other.targetType.getResolvableType().toString());
+ }
+ return result;
+ }
}
diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/IntegerToEnumConverterFactory.java b/spring-core/src/main/java/org/springframework/core/convert/support/IntegerToEnumConverterFactory.java
new file mode 100644
index 00000000..03204e73
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/convert/support/IntegerToEnumConverterFactory.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.convert.support;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.core.convert.converter.ConverterFactory;
+
+/**
+ * Converts from a Integer to a {@link java.lang.Enum} by calling {@link Class#getEnumConstants()}.
+ *
+ * @author Yanming Zhou
+ * @author Stephane Nicoll
+ * @since 4.3
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+final class IntegerToEnumConverterFactory implements ConverterFactory<Integer, Enum> {
+
+ @Override
+ public <T extends Enum> Converter<Integer, T> getConverter(Class<T> targetType) {
+ return new IntegerToEnum(ConversionUtils.getEnumType(targetType));
+ }
+
+
+ private class IntegerToEnum<T extends Enum> implements Converter<Integer, T> {
+
+ private final Class<T> enumType;
+
+ public IntegerToEnum(Class<T> enumType) {
+ this.enumType = enumType;
+ }
+
+ @Override
+ public T convert(Integer source) {
+ return this.enumType.getEnumConstants()[source];
+ }
+ }
+
+}
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 fed1e45d..5631f2ca 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-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -155,7 +155,7 @@ final class ObjectToObjectConverter implements ConditionalGenericConverter {
method.getParameterTypes()[0] == sourceClass);
}
else if (member instanceof Constructor) {
- Constructor<?> ctor = (Constructor) member;
+ Constructor<?> ctor = (Constructor<?>) member;
return (ctor.getParameterTypes()[0] == sourceClass);
}
else {
@@ -185,9 +185,6 @@ final class ObjectToObjectConverter implements ConditionalGenericConverter {
method = ClassUtils.getStaticMethod(targetClass, "of", sourceClass);
if (method == null) {
method = ClassUtils.getStaticMethod(targetClass, "from", sourceClass);
- if (method == null) {
- return null;
- }
}
}
return method;
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 923239b9..865d1e83 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import org.springframework.core.convert.converter.ConverterFactory;
* Converts from a String to a {@link java.lang.Enum} by calling {@link Enum#valueOf(Class, String)}.
*
* @author Keith Donald
+ * @author Stephane Nicoll
* @since 3.0
*/
@SuppressWarnings({"unchecked", "rawtypes"})
@@ -30,15 +31,7 @@ final class StringToEnumConverterFactory implements ConverterFactory<String, Enu
@Override
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
- Class<?> enumType = targetType;
- while (enumType != null && !enumType.isEnum()) {
- enumType = enumType.getSuperclass();
- }
- if (enumType == null) {
- throw new IllegalArgumentException(
- "The target type " + targetType.getName() + " does not refer to an enum");
- }
- return new StringToEnum(enumType);
+ return new StringToEnum(ConversionUtils.getEnumType(targetType));
}
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 c44f5d62..25268f84 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -547,6 +547,7 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
}
@Override
+ @Deprecated
public <T> Class<T> getPropertyAsClass(String key, Class<T> targetType) {
return this.propertyResolver.getPropertyAsClass(key, targetType);
}
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 b5f18271..1c001d96 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -131,6 +131,15 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
}
}
+ @Override
+ public boolean containsProperty(String key) {
+ return (getProperty(key) != null);
+ }
+
+ @Override
+ public String getProperty(String key) {
+ return getProperty(key, String.class);
+ }
@Override
public String getProperty(String key, String defaultValue) {
@@ -145,6 +154,12 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
}
@Override
+ @Deprecated
+ public <T> Class<T> getPropertyAsClass(String key, Class<T> targetValueType) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public String getRequiredProperty(String key) throws IllegalStateException {
String value = getProperty(key);
if (value == null) {
diff --git a/spring-core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java b/spring-core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java
index e1ec8a27..bc626d64 100644
--- a/spring-core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java
+++ b/spring-core/src/main/java/org/springframework/core/env/ConfigurablePropertyResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,10 +19,10 @@ package org.springframework.core.env;
import org.springframework.core.convert.support.ConfigurableConversionService;
/**
- * Configuration interface to be implemented by most if not all {@link PropertyResolver
- * PropertyResolver} types. Provides facilities for accessing and customizing the
- * {@link org.springframework.core.convert.ConversionService ConversionService} used when
- * converting property values from one type to another.
+ * Configuration interface to be implemented by most if not all {@link PropertyResolver}
+ * types. Provides facilities for accessing and customizing the
+ * {@link org.springframework.core.convert.ConversionService ConversionService}
+ * used when converting property values from one type to another.
*
* @author Chris Beams
* @since 3.1
@@ -30,7 +30,7 @@ import org.springframework.core.convert.support.ConfigurableConversionService;
public interface ConfigurablePropertyResolver extends PropertyResolver {
/**
- * @return the {@link ConfigurableConversionService} used when performing type
+ * Return the {@link ConfigurableConversionService} used when performing type
* conversions on properties.
* <p>The configurable nature of the returned conversion service allows for
* the convenient addition and removal of individual {@code Converter} instances:
@@ -46,10 +46,10 @@ public interface ConfigurablePropertyResolver extends PropertyResolver {
/**
* Set the {@link ConfigurableConversionService} to be used when performing type
* conversions on properties.
- * <p><strong>Note:</strong> as an alternative to fully replacing the {@code
- * ConversionService}, consider adding or removing individual {@code Converter}
- * instances by drilling into {@link #getConversionService()} and calling methods
- * such as {@code #addConverter}.
+ * <p><strong>Note:</strong> as an alternative to fully replacing the
+ * {@code ConversionService}, consider adding or removing individual
+ * {@code Converter} instances by drilling into {@link #getConversionService()}
+ * and calling methods such as {@code #addConverter}.
* @see PropertyResolver#getProperty(String, Class)
* @see #getConversionService()
* @see org.springframework.core.convert.converter.ConverterRegistry#addConverter
diff --git a/spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java b/spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java
index 17570f77..b7b89930 100644
--- a/spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java
+++ b/spring-core/src/main/java/org/springframework/core/env/PropertyResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ package org.springframework.core.env;
* Interface for resolving properties against any underlying source.
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @since 3.1
* @see Environment
* @see PropertySourcesPropertyResolver
@@ -27,14 +28,14 @@ package org.springframework.core.env;
public interface PropertyResolver {
/**
- * Return whether the given property key is available for resolution, i.e.,
- * the value for the given key is not {@code null}.
+ * Return whether the given property key is available for resolution,
+ * i.e. if the value for the given key is not {@code null}.
*/
boolean containsProperty(String key);
/**
- * Return the property value associated with the given key, or {@code null}
- * if the key cannot be resolved.
+ * Return the property value associated with the given key,
+ * or {@code null} if the key cannot be resolved.
* @param key the property name to resolve
* @see #getProperty(String, String)
* @see #getProperty(String, Class)
@@ -53,8 +54,8 @@ public interface PropertyResolver {
String getProperty(String key, String defaultValue);
/**
- * Return the property value associated with the given key, or {@code null}
- * if the key cannot be resolved.
+ * Return the property value associated with the given key,
+ * or {@code null} if the key cannot be resolved.
* @param key the property name to resolve
* @param targetType the expected type of the property value
* @see #getRequiredProperty(String, Class)
@@ -62,8 +63,8 @@ public interface PropertyResolver {
<T> T getProperty(String key, Class<T> targetType);
/**
- * Return the property value associated with the given key, or
- * {@code defaultValue} if the key cannot be resolved.
+ * Return the property value associated with the given key,
+ * or {@code defaultValue} if the key cannot be resolved.
* @param key the property name to resolve
* @param targetType the expected type of the property value
* @param defaultValue the default value to return if no value is found
@@ -75,10 +76,13 @@ public interface PropertyResolver {
* Convert the property value associated with the given key to a {@code Class}
* of type {@code T} or {@code null} if the key cannot be resolved.
* @throws org.springframework.core.convert.ConversionException if class specified
- * by property value cannot be found or loaded or if targetType is not assignable
+ * by property value cannot be found or loaded or if targetType is not assignable
* from class specified by property value
* @see #getProperty(String, Class)
+ * @deprecated as of 4.3, in favor of {@link #getProperty} with manual conversion
+ * to {@code Class} via the application's {@code ClassLoader}
*/
+ @Deprecated
<T> Class<T> getPropertyAsClass(String key, Class<T> targetType);
/**
diff --git a/spring-core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java b/spring-core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java
index edf0b729..32462043 100644
--- a/spring-core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java
+++ b/spring-core/src/main/java/org/springframework/core/env/PropertySourcesPropertyResolver.java
@@ -24,6 +24,7 @@ import org.springframework.util.ClassUtils;
* an underlying set of {@link PropertySources}.
*
* @author Chris Beams
+ * @author Juergen Hoeller
* @since 3.1
* @see PropertySource
* @see PropertySources
@@ -71,56 +72,38 @@ public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
}
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
- boolean debugEnabled = logger.isDebugEnabled();
- if (logger.isTraceEnabled()) {
- logger.trace(String.format("getProperty(\"%s\", %s)", key, targetValueType.getSimpleName()));
- }
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
- if (debugEnabled) {
- logger.debug(String.format("Searching for key '%s' in [%s]", key, propertySource.getName()));
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Searching for key '%s' in [%s]", key, propertySource.getName()));
}
Object value = propertySource.getProperty(key);
if (value != null) {
- Class<?> valueType = value.getClass();
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
- if (debugEnabled) {
- logger.debug(String.format("Found key '%s' in [%s] with type [%s] and value '%s'",
- key, propertySource.getName(), valueType.getSimpleName(), value));
- }
- if (!this.conversionService.canConvert(valueType, targetValueType)) {
- throw new IllegalArgumentException(String.format(
- "Cannot convert value [%s] from source type [%s] to target type [%s]",
- value, valueType.getSimpleName(), targetValueType.getSimpleName()));
- }
+ logKeyFound(key, propertySource, value);
return this.conversionService.convert(value, targetValueType);
}
}
}
- if (debugEnabled) {
- logger.debug(String.format("Could not find key '%s' in any property source. Returning [null]", key));
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Could not find key '%s' in any property source", key));
}
return null;
}
@Override
+ @Deprecated
public <T> Class<T> getPropertyAsClass(String key, Class<T> targetValueType) {
- boolean debugEnabled = logger.isDebugEnabled();
- if (logger.isTraceEnabled()) {
- logger.trace(String.format("getPropertyAsClass(\"%s\", %s)", key, targetValueType.getSimpleName()));
- }
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
- if (debugEnabled) {
- logger.debug(String.format("Searching for key '%s' in [%s]", key, propertySource.getName()));
+ if (logger.isTraceEnabled()) {
+ logger.trace(String.format("Searching for key '%s' in [%s]", key, propertySource.getName()));
}
Object value = propertySource.getProperty(key);
if (value != null) {
- if (debugEnabled) {
- logger.debug(String.format("Found key '%s' in [%s] with value '%s'", key, propertySource.getName(), value));
- }
+ logKeyFound(key, propertySource, value);
Class<?> clazz;
if (value instanceof String) {
try {
@@ -131,7 +114,7 @@ public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
}
}
else if (value instanceof Class) {
- clazz = (Class<?>)value;
+ clazz = (Class<?>) value;
}
else {
clazz = value.getClass();
@@ -145,22 +128,42 @@ public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
}
}
}
- if (debugEnabled) {
- logger.debug(String.format("Could not find key '%s' in any property source. Returning [null]", key));
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Could not find key '%s' in any property source", key));
}
return null;
}
+ /**
+ * Log the given key as found in the given {@link PropertySource}, resulting in
+ * the given value.
+ * <p>The default implementation writes a debug log message, including the value.
+ * Subclasses may override this to change the log level and/or the log message.
+ * @param key the key found
+ * @param propertySource the {@code PropertySource} that the key has been found in
+ * @param value the corresponding value
+ * @since 4.3.1
+ */
+ protected void logKeyFound(String key, PropertySource<?> propertySource, Object value) {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Found key '%s' in [%s] with type [%s] and value '%s'",
+ key, propertySource.getName(), value.getClass().getSimpleName(), value));
+ }
+ }
+
@SuppressWarnings("serial")
+ @Deprecated
private static class ClassConversionException extends ConversionException {
public ClassConversionException(Class<?> actual, Class<?> expected) {
- super(String.format("Actual type %s is not assignable to expected type %s", actual.getName(), expected.getName()));
+ super(String.format("Actual type %s is not assignable to expected type %s",
+ actual.getName(), expected.getName()));
}
public ClassConversionException(String actual, Class<?> expected, Exception ex) {
- super(String.format("Could not find/load class %s during attempt to convert to %s", actual, expected.getName()), ex);
+ super(String.format("Could not find/load class %s during attempt to convert to %s",
+ actual, expected.getName()), ex);
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java b/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java
index d60d3b50..d72a822e 100644
--- a/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java
+++ b/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,8 +24,8 @@ import org.springframework.util.Assert;
* Specialization of {@link MapPropertySource} designed for use with
* {@linkplain AbstractEnvironment#getSystemEnvironment() system environment variables}.
* Compensates for constraints in Bash and other shells that do not allow for variables
- * containing the period character; also allows for uppercase variations on property
- * names for more idiomatic shell use.
+ * containing the period character and/or hyphen character; also allows for uppercase
+ * variations on property names for more idiomatic shell use.
*
* <p>For example, a call to {@code getProperty("foo.bar")} will attempt to find a value
* for the original property or any 'equivalent' property, returning the first found:
@@ -35,8 +35,9 @@ import org.springframework.util.Assert;
* <li>{@code FOO.BAR} - original, with upper case</li>
* <li>{@code FOO_BAR} - with underscores and upper case</li>
* </ul>
+ * Any hyphen variant of the above would work as well, or even mix dot/hyphen variants.
*
- * The same applies for calls to {@link #containsProperty(String)}, which returns
+ * <p>The same applies for calls to {@link #containsProperty(String)}, which returns
* {@code true} if any of the above properties are present, otherwise {@code false}.
*
* <p>This feature is particularly useful when specifying active or default profiles as
@@ -102,29 +103,42 @@ public class SystemEnvironmentPropertySource extends MapPropertySource {
*/
private String resolvePropertyName(String name) {
Assert.notNull(name, "Property name must not be null");
+ String resolvedName = checkPropertyName(name);
+ if (resolvedName != null) {
+ return resolvedName;
+ }
+ String uppercasedName = name.toUpperCase();
+ if (!name.equals(uppercasedName)) {
+ resolvedName = checkPropertyName(uppercasedName);
+ if (resolvedName != null) {
+ return resolvedName;
+ }
+ }
+ return name;
+ }
+
+ private String checkPropertyName(String name) {
+ // Check name as-is
if (containsKey(name)) {
return name;
}
-
- String usName = name.replace('.', '_');
- if (!name.equals(usName) && containsKey(usName)) {
- return usName;
+ // Check name with just dots replaced
+ String noDotName = name.replace('.', '_');
+ if (!name.equals(noDotName) && containsKey(noDotName)) {
+ return noDotName;
}
-
- String ucName = name.toUpperCase();
- if (!name.equals(ucName)) {
- if (containsKey(ucName)) {
- return ucName;
- }
- else {
- String usUcName = ucName.replace('.', '_');
- if (!ucName.equals(usUcName) && containsKey(usUcName)) {
- return usUcName;
- }
- }
+ // Check name with just hyphens replaced
+ String noHyphenName = name.replace('-', '_');
+ if (!name.equals(noHyphenName) && containsKey(noHyphenName)) {
+ return noHyphenName;
}
-
- return name;
+ // Check name with dots and hyphens replaced
+ String noDotNoHyphenName = noDotName.replace('-', '_');
+ if (!noDotName.equals(noDotNoHyphenName) && containsKey(noDotNoHyphenName)) {
+ return noDotNoHyphenName;
+ }
+ // Give up
+ return null;
}
private boolean containsKey(String name) {
diff --git a/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java b/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java
index a81f6c38..8f61f36b 100644
--- a/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java
+++ b/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -72,7 +72,7 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
}
/**
- * This implementation returns a File reference for the underlying class path
+ * This implementation returns a File reference for the given URI-identified
* resource, provided that it refers to a file in the file system.
* @see org.springframework.util.ResourceUtils#getFile(java.net.URI, String)
*/
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 2f22b14c..b7c5219f 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-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,9 @@ package org.springframework.core.io;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -42,6 +45,8 @@ public class DefaultResourceLoader implements ResourceLoader {
private ClassLoader classLoader;
+ private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<ProtocolResolver>(4);
+
/**
* Create a new DefaultResourceLoader.
@@ -84,10 +89,40 @@ public class DefaultResourceLoader implements ResourceLoader {
return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}
+ /**
+ * Register the given resolver with this resource loader, allowing for
+ * additional protocols to be handled.
+ * <p>Any such resolver will be invoked ahead of this loader's standard
+ * resolution rules. It may therefore also override any default rules.
+ * @since 4.3
+ * @see #getProtocolResolvers()
+ */
+ public void addProtocolResolver(ProtocolResolver resolver) {
+ Assert.notNull(resolver, "ProtocolResolver must not be null");
+ this.protocolResolvers.add(resolver);
+ }
+
+ /**
+ * Return the collection of currently registered protocol resolvers,
+ * allowing for introspection as well as modification.
+ * @since 4.3
+ */
+ public Collection<ProtocolResolver> getProtocolResolvers() {
+ return this.protocolResolvers;
+ }
+
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
+
+ for (ProtocolResolver protocolResolver : this.protocolResolvers) {
+ Resource resource = protocolResolver.resolve(location, this);
+ if (resource != null) {
+ return resource;
+ }
+ }
+
if (location.startsWith("/")) {
return getResourceByPath(location);
}
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 294a3e37..9179d921 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@ import org.springframework.util.StringUtils;
/**
* {@link Resource} implementation for {@code java.io.File} handles.
- * Obviously supports resolution as File, and also as URL.
+ * Supports resolution as a {@code File} and also as a {@code URL}.
* Implements the extended {@link WritableResource} interface.
*
* @author Juergen Hoeller
@@ -85,7 +85,6 @@ public class FileSystemResource extends AbstractResource implements WritableReso
return this.path;
}
-
/**
* This implementation returns whether the underlying file exists.
* @see java.io.File#exists()
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
index f84ccfa9..af046049 100644
--- a/spring-core/src/main/java/org/springframework/core/io/PathResource.java
+++ b/spring-core/src/main/java/org/springframework/core/io/PathResource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -182,8 +182,8 @@ public class PathResource extends AbstractResource implements WritableResource {
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
+ // 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");
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/io/ProtocolResolver.java b/spring-core/src/main/java/org/springframework/core/io/ProtocolResolver.java
new file mode 100644
index 00000000..700c866e
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/ProtocolResolver.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.io;
+
+/**
+ * A resolution strategy for protocol-specific resource handles.
+ *
+ * <p>Used as an SPI for {@link DefaultResourceLoader}, allowing for
+ * custom protocols to be handled without subclassing the loader
+ * implementation (or application context implementation).
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ * @see DefaultResourceLoader#addProtocolResolver
+ */
+public interface ProtocolResolver {
+
+ /**
+ * Resolve the given location against the given resource loader
+ * if this implementation's protocol matches.
+ * @param location the user-specified resource location
+ * @param resourceLoader the associated resource loader
+ * @return a corresponding {@code Resource} handle if the given location
+ * matches this resolver's protocol, or {@code null} otherwise
+ */
+ Resource resolve(String location, ResourceLoader resourceLoader);
+
+}
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 e3fc2a4e..ac4fcb29 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,26 +37,26 @@ import java.net.URL;
* @see #getFile()
* @see WritableResource
* @see ContextResource
- * @see FileSystemResource
- * @see ClassPathResource
* @see UrlResource
+ * @see ClassPathResource
+ * @see FileSystemResource
+ * @see PathResource
* @see ByteArrayResource
* @see InputStreamResource
- * @see PathResource
*/
public interface Resource extends InputStreamSource {
/**
- * Return whether this resource actually exists in physical form.
+ * Determine whether this resource actually exists in physical form.
* <p>This method performs a definitive existence check, whereas the
- * existence of a {@code Resource} handle only guarantees a
- * valid descriptor handle.
+ * existence of a {@code Resource} handle only guarantees a valid
+ * descriptor handle.
*/
boolean exists();
/**
- * Return whether the contents of this resource can be read,
- * e.g. via {@link #getInputStream()} or {@link #getFile()}.
+ * Indicate whether the contents of this resource can be read via
+ * {@link #getInputStream()}.
* <p>Will be {@code true} for typical resource descriptors;
* note that actual content reading may still fail when attempted.
* However, a value of {@code false} is a definitive indication
@@ -66,8 +66,8 @@ public interface Resource extends InputStreamSource {
boolean isReadable();
/**
- * Return whether this resource represents a handle with an open
- * stream. If true, the InputStream cannot be read multiple times,
+ * Indicate whether this resource represents a handle with an open stream.
+ * If {@code true}, the InputStream cannot be read multiple times,
* and must be read and closed to avoid resource leaks.
* <p>Will be {@code false} for typical resource descriptors.
*/
@@ -84,6 +84,7 @@ public interface Resource extends InputStreamSource {
* Return a URI handle for this resource.
* @throws IOException if the resource cannot be resolved as URI,
* i.e. if the resource is not available as descriptor
+ * @since 2.5
*/
URI getURI() throws IOException;
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 41aa4a03..584b67f6 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@ import org.springframework.util.StringUtils;
/**
* {@link Resource} implementation for {@code java.net.URL} locators.
- * <p>Supports resolution as a {@code URL} and also as a {@code File} in
+ * Supports resolution as a {@code URL} and also as a {@code File} in
* case of the {@code "file:"} protocol.
*
* @author Juergen Hoeller
@@ -61,6 +61,7 @@ public class UrlResource extends AbstractFileResolvingResource {
* Create a new {@code UrlResource} based on the given URI object.
* @param uri a URI
* @throws MalformedURLException if the given URL path is not valid
+ * @since 2.5
*/
public UrlResource(URI uri) throws MalformedURLException {
Assert.notNull(uri, "URI must not be null");
@@ -133,6 +134,7 @@ public class UrlResource extends AbstractFileResolvingResource {
}
}
+
/**
* Determine a cleaned URL for the given original URL.
* @param originalUrl the original URL
@@ -151,7 +153,6 @@ public class UrlResource extends AbstractFileResolvingResource {
}
}
-
/**
* This implementation opens an InputStream for the given URL.
* <p>It sets the {@code useCaches} flag to {@code false},
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/DefaultPropertySourceFactory.java b/spring-core/src/main/java/org/springframework/core/io/support/DefaultPropertySourceFactory.java
new file mode 100644
index 00000000..5ff4c9df
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/support/DefaultPropertySourceFactory.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.io.support;
+
+import java.io.IOException;
+
+import org.springframework.core.env.PropertySource;
+
+/**
+ * The default implementation for {@link PropertySourceFactory},
+ * wrapping every resource in a {@link ResourcePropertySource}.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ * @see PropertySourceFactory
+ * @see ResourcePropertySource
+ */
+public class DefaultPropertySourceFactory implements PropertySourceFactory {
+
+ @Override
+ public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
+ return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java
index d69433b1..0e049403 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
@@ -26,6 +26,7 @@ import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
@@ -144,6 +145,9 @@ import org.springframework.util.StringUtils;
* root of expanded directories. This originates from a limitation in the JDK's
* {@code ClassLoader.getResources()} method which only returns file system
* locations for a passed-in empty String (indicating potential roots to search).
+ * This {@code ResourcePatternResolver} implementation is trying to mitigate the
+ * jar root lookup limitation through {@link URLClassLoader} introspection and
+ * "java.class.path" manifest evaluation; however, without portability guarantees.
*
* <p><b>WARNING:</b> Ant-style patterns with "classpath:" resources are not
* guaranteed to find matching resources if the root package to search is available
@@ -166,6 +170,7 @@ import org.springframework.util.StringUtils;
* @author Colin Sampaleanu
* @author Marius Bogoevici
* @author Costin Leau
+ * @author Phil Webb
* @since 1.0.2
* @see #CLASSPATH_ALL_URL_PREFIX
* @see org.springframework.util.AntPathMatcher
@@ -384,6 +389,12 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
}
}
}
+
+ if (classLoader == ClassLoader.getSystemClassLoader()) {
+ // "java.class.path" manifest evaluation...
+ addClassPathManifestEntries(result);
+ }
+
if (classLoader != null) {
try {
// Hierarchy traversal...
@@ -399,6 +410,41 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
}
/**
+ * Determine jar file references from the "java.class.path." manifest property and add them
+ * to the given set of resources in the form of pointers to the root of the jar file content.
+ * @param result the set of resources to add jar roots to
+ * @since 4.3
+ */
+ protected void addClassPathManifestEntries(Set<Resource> result) {
+ try {
+ String javaClassPathProperty = System.getProperty("java.class.path");
+ for (String url : StringUtils.delimitedListToStringArray(
+ javaClassPathProperty, System.getProperty("path.separator"))) {
+ try {
+ if (url.endsWith(ResourceUtils.JAR_FILE_EXTENSION)) {
+ UrlResource jarResource = new UrlResource(ResourceUtils.JAR_URL_PREFIX +
+ ResourceUtils.FILE_URL_PREFIX + url + ResourceUtils.JAR_URL_SEPARATOR);
+ if (jarResource.exists()) {
+ result.add(jarResource);
+ }
+ }
+ }
+ catch (MalformedURLException ex) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Cannot search for matching files underneath [" + url +
+ "] because it cannot be converted to a valid 'jar:' URL: " + ex.getMessage());
+ }
+ }
+ }
+ }
+ catch (Exception ex) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Failed to evaluate 'java.class.path' manifest entries: " + ex);
+ }
+ }
+ }
+
+ /**
* Find all resources that match the given location pattern via the
* Ant-style PathMatcher. Supports resources in jar files and zip files
* and in the file system.
@@ -416,11 +462,18 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
Set<Resource> result = new LinkedHashSet<Resource>(16);
for (Resource rootDirResource : rootDirResources) {
rootDirResource = resolveRootDirResource(rootDirResource);
- if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
- result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
+ URL rootDirURL = rootDirResource.getURL();
+ if (equinoxResolveMethod != null) {
+ if (rootDirURL.getProtocol().startsWith("bundle")) {
+ rootDirURL = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirURL);
+ rootDirResource = new UrlResource(rootDirURL);
+ }
}
- else if (isJarResource(rootDirResource)) {
- result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
+ if (rootDirURL.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
+ result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirURL, subPattern, getPathMatcher()));
+ }
+ else if (ResourceUtils.isJarURL(rootDirURL) || isJarResource(rootDirResource)) {
+ result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirURL, subPattern));
}
else {
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
@@ -458,56 +511,61 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
/**
* Resolve the specified resource for path matching.
- * <p>The default implementation detects an Equinox OSGi "bundleresource:"
- * / "bundleentry:" URL and resolves it into a standard jar file URL that
- * can be traversed using Spring's standard jar file traversal algorithm.
+ * <p>By default, Equinox OSGi "bundleresource:" / "bundleentry:" URL will be
+ * resolved into a standard jar file URL that be traversed using Spring's
+ * standard jar file traversal algorithm. For any preceding custom resolution,
+ * override this method and replace the resource handle accordingly.
* @param original the resource to resolve
* @return the resolved resource (may be identical to the passed-in resource)
* @throws IOException in case of resolution failure
*/
protected Resource resolveRootDirResource(Resource original) throws IOException {
- if (equinoxResolveMethod != null) {
- URL url = original.getURL();
- if (url.getProtocol().startsWith("bundle")) {
- return new UrlResource((URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, url));
- }
- }
return original;
}
/**
* Return whether the given resource handle indicates a jar resource
* that the {@code doFindPathMatchingJarResources} method can handle.
- * <p>The default implementation checks against the URL protocols
- * "jar", "zip" and "wsjar" (the latter are used by BEA WebLogic Server
- * and IBM WebSphere, respectively, but can be treated like jar files).
+ * <p>By default, the URL protocols "jar", "zip", "vfszip and "wsjar"
+ * will be treated as jar resources. This template method allows for
+ * detecting further kinds of jar-like resources, e.g. through
+ * {@code instanceof} checks on the resource handle type.
* @param resource the resource handle to check
* (usually the root directory to start path matching from)
* @see #doFindPathMatchingJarResources
* @see org.springframework.util.ResourceUtils#isJarURL
*/
protected boolean isJarResource(Resource resource) throws IOException {
- return ResourceUtils.isJarURL(resource.getURL());
+ return false;
}
/**
* Find all resources in jar files that match the given location pattern
* via the Ant-style PathMatcher.
* @param rootDirResource the root directory as Resource
+ * @param rootDirURL the pre-resolved root directory URL
* @param subPattern the sub pattern to match (below the root directory)
* @return a mutable Set of matching Resource instances
* @throws IOException in case of I/O errors
+ * @since 4.3
* @see java.net.JarURLConnection
* @see org.springframework.util.PathMatcher
*/
- protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, String subPattern)
+ @SuppressWarnings("deprecation")
+ protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, URL rootDirURL, String subPattern)
throws IOException {
- URLConnection con = rootDirResource.getURL().openConnection();
+ // Check deprecated variant for potential overriding first...
+ Set<Resource> result = doFindPathMatchingJarResources(rootDirResource, subPattern);
+ if (result != null) {
+ return result;
+ }
+
+ URLConnection con = rootDirURL.openConnection();
JarFile jarFile;
String jarFileUrl;
String rootEntryPath;
- boolean newJarFile = false;
+ boolean closeJarFile;
if (con instanceof JarURLConnection) {
// Should usually be the case for traditional JAR files.
@@ -517,13 +575,14 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
jarFileUrl = jarCon.getJarFileURL().toExternalForm();
JarEntry jarEntry = jarCon.getJarEntry();
rootEntryPath = (jarEntry != null ? jarEntry.getName() : "");
+ closeJarFile = !jarCon.getUseCaches();
}
else {
// No JarURLConnection -> need to resort to URL file parsing.
// We'll assume URLs of the format "jar:path!/entry", with the protocol
// being arbitrary as long as following the entry format.
// We'll also handle paths with and without leading "file:" prefix.
- String urlFile = rootDirResource.getURL().getFile();
+ String urlFile = rootDirURL.getFile();
try {
int separatorIndex = urlFile.indexOf(ResourceUtils.JAR_URL_SEPARATOR);
if (separatorIndex != -1) {
@@ -536,7 +595,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
jarFileUrl = urlFile;
rootEntryPath = "";
}
- newJarFile = true;
+ closeJarFile = true;
}
catch (ZipException ex) {
if (logger.isDebugEnabled()) {
@@ -555,7 +614,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
// The Sun JRE does not return a slash here, but BEA JRockit does.
rootEntryPath = rootEntryPath + "/";
}
- Set<Resource> result = new LinkedHashSet<Resource>(8);
+ result = new LinkedHashSet<Resource>(8);
for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
JarEntry entry = entries.nextElement();
String entryPath = entry.getName();
@@ -569,15 +628,30 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
return result;
}
finally {
- // Close jar file, but only if freshly obtained -
- // not from JarURLConnection, which might cache the file reference.
- if (newJarFile) {
+ if (closeJarFile) {
jarFile.close();
}
}
}
/**
+ * Find all resources in jar files that match the given location pattern
+ * via the Ant-style PathMatcher.
+ * @param rootDirResource the root directory as Resource
+ * @param subPattern the sub pattern to match (below the root directory)
+ * @return a mutable Set of matching Resource instances
+ * @throws IOException in case of I/O errors
+ * @deprecated as of Spring 4.3, in favor of
+ * {@link #doFindPathMatchingJarResources(Resource, URL, String)}
+ */
+ @Deprecated
+ protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, String subPattern)
+ throws IOException {
+
+ return null;
+ }
+
+ /**
* Resolve the given jar file URL into a JarFile object.
*/
protected JarFile getJarFile(String jarFileUrl) throws IOException {
@@ -706,6 +780,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
}
return;
}
+ Arrays.sort(dirContents);
for (File content : dirContents) {
String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
@@ -732,8 +807,9 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
private static class VfsResourceMatchingDelegate {
public static Set<Resource> findMatchingResources(
- Resource rootResource, String locationPattern, PathMatcher pathMatcher) throws IOException {
- Object root = VfsPatternUtils.findRoot(rootResource.getURL());
+ URL rootDirURL, String locationPattern, PathMatcher pathMatcher) throws IOException {
+
+ Object root = VfsPatternUtils.findRoot(rootDirURL);
PatternVirtualFileVisitor visitor =
new PatternVirtualFileVisitor(VfsPatternUtils.getPath(root), locationPattern, pathMatcher);
VfsPatternUtils.visit(root, visitor);
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceFactory.java b/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceFactory.java
new file mode 100644
index 00000000..4ab2cfdf
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.io.support;
+
+import java.io.IOException;
+
+import org.springframework.core.env.PropertySource;
+
+/**
+ * Strategy interface for creating resource-based {@link PropertySource} wrappers.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ * @see DefaultPropertySourceFactory
+ */
+public interface PropertySourceFactory {
+
+ /**
+ * Create a {@link PropertySource} that wraps the given resource.
+ * @param name the name of the property source
+ * @param resource the resource (potentially encoded) to wrap
+ * @return the new {@link PropertySource} (never {@code null})
+ * @throws IOException if resource resolution failed
+ */
+ PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException;
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/ResourcePatternUtils.java b/spring-core/src/main/java/org/springframework/core/io/support/ResourcePatternUtils.java
index 9c0c12ee..e9721a95 100644
--- a/spring-core/src/main/java/org/springframework/core/io/support/ResourcePatternUtils.java
+++ b/spring-core/src/main/java/org/springframework/core/io/support/ResourcePatternUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,12 +17,11 @@
package org.springframework.core.io.support;
import org.springframework.core.io.ResourceLoader;
-import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
/**
* Utility class for determining whether a given URL is a resource
- * location that can be loaded via a ResourcePatternResolver.
+ * location that can be loaded via a {@link ResourcePatternResolver}.
*
* <p>Callers will usually assume that a location is a relative path
* if the {@link #isUrl(String)} method returns {@code false}.
@@ -59,7 +58,6 @@ public abstract class ResourcePatternUtils {
* @see PathMatchingResourcePatternResolver
*/
public static ResourcePatternResolver getResourcePatternResolver(ResourceLoader resourceLoader) {
- Assert.notNull(resourceLoader, "ResourceLoader must not be null");
if (resourceLoader instanceof ResourcePatternResolver) {
return (ResourcePatternResolver) resourceLoader;
}
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/ResourceRegion.java b/spring-core/src/main/java/org/springframework/core/io/support/ResourceRegion.java
new file mode 100644
index 00000000..a76be606
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/io/support/ResourceRegion.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.io.support;
+
+import org.springframework.core.io.Resource;
+import org.springframework.util.Assert;
+
+/**
+ * Region of a {@link Resource} implementation, materialized by a {@code position}
+ * within the {@link Resource} and a byte {@code count} for the length of that region.
+ *
+ * @author Arjen Poutsma
+ * @since 4.3
+ */
+public class ResourceRegion {
+
+ private final Resource resource;
+
+ private final long position;
+
+ private final long count;
+
+
+ /**
+ * Create a new {@code ResourceRegion} from a given {@link Resource}.
+ * This region of a resource is represented by a start {@code position}
+ * and a byte {@code count} within the given {@code Resource}.
+ * @param resource a Resource
+ * @param position the start position of the region in that resource
+ * @param count the byte count of the region in that resource
+ */
+ public ResourceRegion(Resource resource, long position, long count) {
+ Assert.notNull(resource, "Resource must not be null");
+ Assert.isTrue(position >= 0, "'position' must be larger than or equal to 0");
+ Assert.isTrue(count >= 0, "'count' must be larger than or equal to 0");
+ this.resource = resource;
+ this.position = position;
+ this.count = count;
+ }
+
+
+ /**
+ * Return the underlying {@link Resource} for this {@code ResourceRegion}
+ */
+ public Resource getResource() {
+ return this.resource;
+ }
+
+ /**
+ * Return the start position of this region in the underlying {@link Resource}
+ */
+ public long getPosition() {
+ return this.position;
+ }
+
+ /**
+ * Return the byte count of this region in the underlying {@link Resource}
+ */
+ public long getCount() {
+ return this.count;
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java b/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java
index d4772499..908b54d3 100644
--- a/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java
+++ b/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.core.io.support;
import java.io.IOException;
+import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
@@ -31,6 +32,7 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.UrlResource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
@@ -132,10 +134,12 @@ public abstract class SpringFactoriesLoader {
throw new IllegalArgumentException(
"Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
}
- return (T) instanceClass.newInstance();
+ Constructor<?> constructor = instanceClass.getDeclaredConstructor();
+ ReflectionUtils.makeAccessible(constructor);
+ return (T) constructor.newInstance();
}
catch (Throwable ex) {
- throw new IllegalArgumentException("Cannot instantiate factory class: " + factoryClass.getName(), ex);
+ throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/serializer/support/SerializationDelegate.java b/spring-core/src/main/java/org/springframework/core/serializer/support/SerializationDelegate.java
new file mode 100644
index 00000000..1e661d6a
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/serializer/support/SerializationDelegate.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.serializer.support;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.springframework.core.serializer.DefaultDeserializer;
+import org.springframework.core.serializer.DefaultSerializer;
+import org.springframework.core.serializer.Deserializer;
+import org.springframework.core.serializer.Serializer;
+import org.springframework.util.Assert;
+
+/**
+ * A convenient delegate with pre-arranged configuration state for common
+ * serialization needs. Implements {@link Serializer} and {@link Deserializer}
+ * itself, so can also be passed into such more specific callback methods.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ */
+public class SerializationDelegate implements Serializer<Object>, Deserializer<Object> {
+
+ private final Serializer<Object> serializer;
+
+ private final Deserializer<Object> deserializer;
+
+
+ /**
+ * Create a {@code SerializationDelegate} with a default serializer/deserializer
+ * for the given {@code ClassLoader}.
+ * @see DefaultDeserializer
+ * @see DefaultDeserializer#DefaultDeserializer(ClassLoader)
+ */
+ public SerializationDelegate(ClassLoader classLoader) {
+ this.serializer = new DefaultSerializer();
+ this.deserializer = new DefaultDeserializer(classLoader);
+ }
+
+ /**
+ * Create a {@code SerializationDelegate} with the given serializer/deserializer.
+ * @param serializer the {@link Serializer} to use (never {@code null)}
+ * @param deserializer the {@link Deserializer} to use (never {@code null)}
+ */
+ public SerializationDelegate(Serializer<Object> serializer, Deserializer<Object> deserializer) {
+ Assert.notNull(serializer, "Serializer must not be null");
+ Assert.notNull(deserializer, "Deserializer must not be null");
+ this.serializer = serializer;
+ this.deserializer = deserializer;
+ }
+
+
+ @Override
+ public void serialize(Object object, OutputStream outputStream) throws IOException {
+ this.serializer.serialize(object, outputStream);
+ }
+
+ @Override
+ public Object deserialize(InputStream inputStream) throws IOException {
+ return this.deserializer.deserialize(inputStream);
+ }
+
+}
diff --git a/spring-core/src/main/java/org/springframework/core/style/ToStringCreator.java b/spring-core/src/main/java/org/springframework/core/style/ToStringCreator.java
index 6f682939..a7291f6f 100644
--- a/spring-core/src/main/java/org/springframework/core/style/ToStringCreator.java
+++ b/spring-core/src/main/java/org/springframework/core/style/ToStringCreator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -82,7 +82,7 @@ public class ToStringCreator {
* @return this, to support call-chaining
*/
public ToStringCreator append(String fieldName, byte value) {
- return append(fieldName, new Byte(value));
+ return append(fieldName, Byte.valueOf(value));
}
/**
@@ -92,7 +92,7 @@ public class ToStringCreator {
* @return this, to support call-chaining
*/
public ToStringCreator append(String fieldName, short value) {
- return append(fieldName, new Short(value));
+ return append(fieldName, Short.valueOf(value));
}
/**
@@ -102,7 +102,7 @@ public class ToStringCreator {
* @return this, to support call-chaining
*/
public ToStringCreator append(String fieldName, int value) {
- return append(fieldName, new Integer(value));
+ return append(fieldName, Integer.valueOf(value));
}
/**
@@ -112,7 +112,7 @@ public class ToStringCreator {
* @return this, to support call-chaining
*/
public ToStringCreator append(String fieldName, long value) {
- return append(fieldName, new Long(value));
+ return append(fieldName, Long.valueOf(value));
}
/**
@@ -122,7 +122,7 @@ public class ToStringCreator {
* @return this, to support call-chaining
*/
public ToStringCreator append(String fieldName, float value) {
- return append(fieldName, new Float(value));
+ return append(fieldName, Float.valueOf(value));
}
/**
@@ -132,7 +132,7 @@ public class ToStringCreator {
* @return this, to support call-chaining
*/
public ToStringCreator append(String fieldName, double value) {
- return append(fieldName, new Double(value));
+ return append(fieldName, Double.valueOf(value));
}
/**
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 05e759ab..0483932a 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -65,6 +65,8 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implement
private ThreadFactory threadFactory;
+ private TaskDecorator taskDecorator;
+
/**
* Create a new SimpleAsyncTaskExecutor with default thread name prefix.
@@ -110,6 +112,20 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implement
}
/**
+ * Specify a custom {@link TaskDecorator} to be applied to any {@link Runnable}
+ * about to be executed.
+ * <p>Note that such a decorator is not necessarily being applied to the
+ * user-supplied {@code Runnable}/{@code Callable} but rather to the actual
+ * execution callback (which may be a wrapper around the user-supplied task).
+ * <p>The primary use case is to set some execution context around the task's
+ * invocation, or to provide some monitoring/statistics for task execution.
+ * @since 4.3
+ */
+ public final void setTaskDecorator(TaskDecorator taskDecorator) {
+ this.taskDecorator = taskDecorator;
+ }
+
+ /**
* Set the maximum number of parallel accesses allowed.
* -1 indicates no concurrency limit at all.
* <p>In principle, this limit can be changed at runtime,
@@ -163,12 +179,13 @@ public class SimpleAsyncTaskExecutor extends CustomizableThreadCreator implement
@Override
public void execute(Runnable task, long startTimeout) {
Assert.notNull(task, "Runnable must not be null");
+ Runnable taskToUse = (this.taskDecorator != null ? this.taskDecorator.decorate(task) : task);
if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
this.concurrencyThrottle.beforeAccess();
- doExecute(new ConcurrencyThrottlingRunnable(task));
+ doExecute(new ConcurrencyThrottlingRunnable(taskToUse));
}
else {
- doExecute(task);
+ doExecute(taskToUse);
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/task/TaskDecorator.java b/spring-core/src/main/java/org/springframework/core/task/TaskDecorator.java
new file mode 100644
index 00000000..d36a3ab3
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/core/task/TaskDecorator.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.task;
+
+/**
+ * A callback interface for a decorator to be applied to any {@link Runnable}
+ * about to be executed.
+ *
+ * <p>Note that such a decorator is not necessarily being applied to the
+ * user-supplied {@code Runnable}/{@code Callable} but rather to the actual
+ * execution callback (which may be a wrapper around the user-supplied task).
+ *
+ * <p>The primary use case is to set some execution context around the task's
+ * invocation, or to provide some monitoring/statistics for task execution.
+ *
+ * @author Juergen Hoeller
+ * @since 4.3
+ * @see TaskExecutor#execute(Runnable)
+ * @see SimpleAsyncTaskExecutor#setTaskDecorator
+ */
+public interface TaskDecorator {
+
+ /**
+ * Decorate the given {@code Runnable}, returning a potentially wrapped
+ * {@code Runnable} for actual execution.
+ * @param runnable the original {@code Runnable}
+ * @return the decorated {@code Runnable}
+ */
+ Runnable decorate(Runnable runnable);
+
+}
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 fad9ae09..165533df 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2013 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ import java.util.concurrent.FutureTask;
import java.util.concurrent.RejectedExecutionException;
import org.springframework.core.task.AsyncListenableTaskExecutor;
+import org.springframework.core.task.TaskDecorator;
import org.springframework.core.task.TaskRejectedException;
import org.springframework.util.Assert;
import org.springframework.util.concurrent.ListenableFuture;
@@ -45,6 +46,8 @@ public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {
private final Executor concurrentExecutor;
+ private TaskDecorator taskDecorator;
+
/**
* Create a new TaskExecutorAdapter,
@@ -58,13 +61,28 @@ public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {
/**
+ * Specify a custom {@link TaskDecorator} to be applied to any {@link Runnable}
+ * about to be executed.
+ * <p>Note that such a decorator is not necessarily being applied to the
+ * user-supplied {@code Runnable}/{@code Callable} but rather to the actual
+ * execution callback (which may be a wrapper around the user-supplied task).
+ * <p>The primary use case is to set some execution context around the task's
+ * invocation, or to provide some monitoring/statistics for task execution.
+ * @since 4.3
+ */
+ public final void setTaskDecorator(TaskDecorator taskDecorator) {
+ this.taskDecorator = taskDecorator;
+ }
+
+
+ /**
* 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);
+ doExecute(this.concurrentExecutor, this.taskDecorator, task);
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException(
@@ -80,12 +98,12 @@ public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {
@Override
public Future<?> submit(Runnable task) {
try {
- if (this.concurrentExecutor instanceof ExecutorService) {
+ if (this.taskDecorator == null && this.concurrentExecutor instanceof ExecutorService) {
return ((ExecutorService) this.concurrentExecutor).submit(task);
}
else {
FutureTask<Object> future = new FutureTask<Object>(task, null);
- this.concurrentExecutor.execute(future);
+ doExecute(this.concurrentExecutor, this.taskDecorator, future);
return future;
}
}
@@ -98,12 +116,12 @@ public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {
@Override
public <T> Future<T> submit(Callable<T> task) {
try {
- if (this.concurrentExecutor instanceof ExecutorService) {
+ if (this.taskDecorator == null && this.concurrentExecutor instanceof ExecutorService) {
return ((ExecutorService) this.concurrentExecutor).submit(task);
}
else {
FutureTask<T> future = new FutureTask<T>(task);
- this.concurrentExecutor.execute(future);
+ doExecute(this.concurrentExecutor, this.taskDecorator, future);
return future;
}
}
@@ -117,7 +135,7 @@ public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {
public ListenableFuture<?> submitListenable(Runnable task) {
try {
ListenableFutureTask<Object> future = new ListenableFutureTask<Object>(task, null);
- this.concurrentExecutor.execute(future);
+ doExecute(this.concurrentExecutor, this.taskDecorator, future);
return future;
}
catch (RejectedExecutionException ex) {
@@ -130,7 +148,7 @@ public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
try {
ListenableFutureTask<T> future = new ListenableFutureTask<T>(task);
- this.concurrentExecutor.execute(future);
+ doExecute(this.concurrentExecutor, this.taskDecorator, future);
return future;
}
catch (RejectedExecutionException ex) {
@@ -139,4 +157,20 @@ public class TaskExecutorAdapter implements AsyncListenableTaskExecutor {
}
}
+
+ /**
+ * Actually execute the given {@code Runnable} (which may be a user-supplied task
+ * or a wrapper around a user-supplied task) with the given executor.
+ * @param concurrentExecutor the underlying JDK concurrent executor to delegate to
+ * @param taskDecorator the specified decorator to be applied, if any
+ * @param runnable the runnable to execute
+ * @throws RejectedExecutionException if the given runnable cannot be accepted
+ * @since 4.3
+ */
+ protected void doExecute(Executor concurrentExecutor, TaskDecorator taskDecorator, Runnable runnable)
+ throws RejectedExecutionException{
+
+ concurrentExecutor.execute(taskDecorator != null ? taskDecorator.decorate(runnable) : runnable);
+ }
+
}
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
index 2cc12717..bd42e059 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -58,7 +58,7 @@ abstract class AbstractRecursiveAnnotationVisitor extends AnnotationVisitor {
@Override
public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) {
String annotationType = Type.getType(asmTypeDescriptor).getClassName();
- AnnotationAttributes nestedAttributes = new AnnotationAttributes();
+ AnnotationAttributes nestedAttributes = new AnnotationAttributes(annotationType, this.classLoader);
this.attributes.put(attributeName, nestedAttributes);
return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader);
}
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 ab001b50..f35ecd68 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
@@ -44,8 +44,6 @@ import org.springframework.util.ObjectUtils;
*/
final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor {
- private final String annotationType;
-
private final MultiValueMap<String, AnnotationAttributes> attributesMap;
private final Map<String, Set<String>> metaAnnotationMap;
@@ -55,38 +53,41 @@ final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttrib
MultiValueMap<String, AnnotationAttributes> attributesMap, Map<String, Set<String>> metaAnnotationMap,
ClassLoader classLoader) {
- super(annotationType, new AnnotationAttributes(), classLoader);
- this.annotationType = annotationType;
+ super(annotationType, new AnnotationAttributes(annotationType, classLoader), classLoader);
this.attributesMap = attributesMap;
this.metaAnnotationMap = metaAnnotationMap;
}
@Override
- public void doVisitEnd(Class<?> annotationClass) {
- super.doVisitEnd(annotationClass);
- 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<Annotation> visited = new LinkedHashSet<Annotation>();
- Annotation[] metaAnnotations = AnnotationUtils.getAnnotations(annotationClass);
- if (!ObjectUtils.isEmpty(metaAnnotations)) {
- for (Annotation metaAnnotation : metaAnnotations) {
- if (!AnnotationUtils.isInJavaLangAnnotationPackage(metaAnnotation)) {
- recursivelyCollectMetaAnnotations(visited, metaAnnotation);
+ public void visitEnd() {
+ super.visitEnd();
+
+ Class<?> annotationClass = this.attributes.annotationType();
+ if (annotationClass != null) {
+ List<AnnotationAttributes> attributeList = this.attributesMap.get(this.annotationType);
+ if (attributeList == null) {
+ this.attributesMap.add(this.annotationType, this.attributes);
+ }
+ else {
+ attributeList.add(0, this.attributes);
+ }
+ Set<Annotation> visited = new LinkedHashSet<Annotation>();
+ Annotation[] metaAnnotations = AnnotationUtils.getAnnotations(annotationClass);
+ if (!ObjectUtils.isEmpty(metaAnnotations)) {
+ for (Annotation metaAnnotation : metaAnnotations) {
+ if (!AnnotationUtils.isInJavaLangAnnotationPackage(metaAnnotation)) {
+ recursivelyCollectMetaAnnotations(visited, metaAnnotation);
+ }
}
}
- }
- if (this.metaAnnotationMap != null) {
- Set<String> metaAnnotationTypeNames = new LinkedHashSet<String>(visited.size());
- for (Annotation ann : visited) {
- metaAnnotationTypeNames.add(ann.annotationType().getName());
+ if (this.metaAnnotationMap != null) {
+ Set<String> metaAnnotationTypeNames = new LinkedHashSet<String>(visited.size());
+ for (Annotation ann : visited) {
+ metaAnnotationTypeNames.add(ann.annotationType().getName());
+ }
+ this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);
}
- this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);
}
}
diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java
index eea3d825..3b3a17dd 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
@@ -131,7 +131,8 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito
public AnnotationAttributes getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes(
this.attributesMap, this.metaAnnotationMap, annotationName);
- return AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, classValuesAsString);
+ return AnnotationReadingVisitorUtils.convertClassValues(
+ "class '" + getClassName() + "'", this.classLoader, raw, classValuesAsString);
}
@Override
@@ -148,7 +149,7 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito
}
for (AnnotationAttributes raw : attributes) {
for (Map.Entry<String, Object> entry : AnnotationReadingVisitorUtils.convertClassValues(
- this.classLoader, raw, classValuesAsString).entrySet()) {
+ "class '" + getClassName() + "'", this.classLoader, raw, classValuesAsString).entrySet()) {
allAttributes.add(entry.getKey(), entry.getValue());
}
}
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
index 0d7ba35f..93c3e76d 100644
--- 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
@@ -41,25 +41,29 @@ import org.springframework.util.ObjectUtils;
*/
abstract class AnnotationReadingVisitorUtils {
- public static AnnotationAttributes convertClassValues(ClassLoader classLoader, AnnotationAttributes original,
- boolean classValuesAsString) {
+ public static AnnotationAttributes convertClassValues(Object annotatedElement,
+ 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()) {
+ AnnotationAttributes result = new AnnotationAttributes(original);
+ AnnotationUtils.postProcessAnnotationAttributes(annotatedElement, result, classValuesAsString);
+
+ for (Map.Entry<String, Object> entry : result.entrySet()) {
try {
Object value = entry.getValue();
if (value instanceof AnnotationAttributes) {
- value = convertClassValues(classLoader, (AnnotationAttributes) value, classValuesAsString);
+ value = convertClassValues(
+ annotatedElement, 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);
+ values[i] = convertClassValues(annotatedElement, classLoader, values[i], classValuesAsString);
}
+ value = values;
}
else if (value instanceof Type) {
value = (classValuesAsString ? ((Type) value).getClassName() :
@@ -67,7 +71,8 @@ abstract class AnnotationReadingVisitorUtils {
}
else if (value instanceof Type[]) {
Type[] array = (Type[]) value;
- Object[] convArray = (classValuesAsString ? new String[array.length] : new Class<?>[array.length]);
+ 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()));
@@ -75,11 +80,11 @@ abstract class AnnotationReadingVisitorUtils {
value = convArray;
}
else if (classValuesAsString) {
- if (value instanceof Class) {
+ if (value instanceof Class<?>) {
value = ((Class<?>) value).getName();
}
- else if (value instanceof Class[]) {
- Class<?>[] clazzArray = (Class[]) value;
+ 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();
@@ -87,13 +92,14 @@ abstract class AnnotationReadingVisitorUtils {
value = newValue;
}
}
- result.put(entry.getKey(), value);
+ entry.setValue(value);
}
catch (Exception ex) {
// Class not found - can't resolve class reference in annotation attribute.
result.put(entry.getKey(), ex);
}
}
+
return result;
}
@@ -123,13 +129,12 @@ abstract class AnnotationReadingVisitorUtils {
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));
+ // To start with, we populate the result 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 result = new AnnotationAttributes(attributesList.get(0));
- Set<String> overridableAttributeNames = new HashSet<String>(results.keySet());
+ Set<String> overridableAttributeNames = new HashSet<String>(result.keySet());
overridableAttributeNames.remove(AnnotationUtils.VALUE);
// Since the map is a LinkedMultiValueMap, we depend on the ordering of
@@ -152,14 +157,14 @@ abstract class AnnotationReadingVisitorUtils {
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);
+ result.put(overridableAttributeName, value);
}
}
}
}
}
- return results;
+ return result;
}
}
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 00bd9cb1..9dfcfd1a 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
@@ -122,7 +122,8 @@ public class MethodMetadataReadingVisitor extends MethodVisitor implements Metho
public AnnotationAttributes getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes(
this.attributesMap, this.metaAnnotationMap, annotationName);
- return AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, classValuesAsString);
+ return AnnotationReadingVisitorUtils.convertClassValues(
+ "method '" + getMethodName() + "'", this.classLoader, raw, classValuesAsString);
}
@Override
@@ -137,8 +138,9 @@ public class MethodMetadataReadingVisitor extends MethodVisitor implements Metho
}
MultiValueMap<String, Object> allAttributes = new LinkedMultiValueMap<String, Object>();
for (AnnotationAttributes annotationAttributes : this.attributesMap.get(annotationName)) {
- for (Map.Entry<String, Object> entry : AnnotationReadingVisitorUtils.convertClassValues(
- this.classLoader, annotationAttributes, classValuesAsString).entrySet()) {
+ AnnotationAttributes convertedAttributes = AnnotationReadingVisitorUtils.convertClassValues(
+ "method '" + getMethodName() + "'", this.classLoader, annotationAttributes, classValuesAsString);
+ for (Map.Entry<String, Object> entry : convertedAttributes.entrySet()) {
allAttributes.add(entry.getKey(), entry.getValue());
}
}
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
index 3c5bd9a4..9c466b77 100644
--- 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
@@ -69,7 +69,7 @@ class RecursiveAnnotationArrayVisitor extends AbstractRecursiveAnnotationVisitor
@Override
public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) {
String annotationType = Type.getType(asmTypeDescriptor).getClassName();
- AnnotationAttributes nestedAttributes = new AnnotationAttributes();
+ AnnotationAttributes nestedAttributes = new AnnotationAttributes(annotationType, this.classLoader);
this.allNestedAttributes.add(nestedAttributes);
return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader);
}
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
index 0d2176f0..ff7b92fb 100644
--- 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
@@ -16,10 +16,6 @@
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;
@@ -30,7 +26,7 @@ import org.springframework.core.annotation.AnnotationUtils;
*/
class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVisitor {
- private final String annotationType;
+ protected final String annotationType;
public RecursiveAnnotationAttributesVisitor(
@@ -42,49 +38,8 @@ class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVi
@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 defaults 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);
- }
- }
- }
+ public void visitEnd() {
+ AnnotationUtils.registerDefaultValues(this.attributes);
}
}
diff --git a/spring-core/src/main/java/org/springframework/lang/UsesSunMisc.java b/spring-core/src/main/java/org/springframework/lang/UsesSunMisc.java
new file mode 100644
index 00000000..5bcbd4a9
--- /dev/null
+++ b/spring-core/src/main/java/org/springframework/lang/UsesSunMisc.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.lang;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that the annotated element uses an API from the {@code sun.misc}
+ * package.
+ *
+ * @author Stephane Nicoll
+ * @since 4.3
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE})
+@Documented
+public @interface UsesSunMisc {
+}
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 635f1234..5e91704b 100644
--- a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java
+++ b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -74,6 +74,8 @@ public class AntPathMatcher implements PathMatcher {
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?\\}");
+ private static final char[] WILDCARD_CHARS = { '*', '?', '{' };
+
private String pathSeparator;
@@ -81,7 +83,7 @@ public class AntPathMatcher implements PathMatcher {
private boolean caseSensitive = true;
- private boolean trimTokens = true;
+ private boolean trimTokens = false;
private volatile Boolean cachePatterns;
@@ -130,7 +132,7 @@ public class AntPathMatcher implements PathMatcher {
/**
* Specify whether to trim tokenized paths and patterns.
- * <p>Default is {@code true}.
+ * <p>Default is {@code false}.
*/
public void setTrimTokens(boolean trimTokens) {
this.trimTokens = trimTokens;
@@ -188,6 +190,10 @@ public class AntPathMatcher implements PathMatcher {
}
String[] pattDirs = tokenizePattern(pattern);
+ if (fullMatch && this.caseSensitive && !isPotentialMatch(path, pattDirs)) {
+ return false;
+ }
+
String[] pathDirs = tokenizePath(path);
int pattIdxStart = 0;
@@ -307,6 +313,59 @@ public class AntPathMatcher implements PathMatcher {
return true;
}
+ private boolean isPotentialMatch(String path, String[] pattDirs) {
+ if (!this.trimTokens) {
+ char[] pathChars = path.toCharArray();
+ int pos = 0;
+ for (String pattDir : pattDirs) {
+ int skipped = skipSeparator(path, pos, this.pathSeparator);
+ pos += skipped;
+ skipped = skipSegment(pathChars, pos, pattDir);
+ if (skipped < pattDir.length()) {
+ if (skipped > 0) {
+ return true;
+ }
+ return (pattDir.length() > 0) && isWildcardChar(pattDir.charAt(0));
+ }
+ pos += skipped;
+ }
+ }
+ return true;
+ }
+
+ private int skipSegment(char[] chars, int pos, String prefix) {
+ int skipped = 0;
+ for (char c : prefix.toCharArray()) {
+ if (isWildcardChar(c)) {
+ return skipped;
+ }
+ else if (pos + skipped >= chars.length) {
+ return 0;
+ }
+ else if (chars[pos + skipped] == c) {
+ skipped++;
+ }
+ }
+ return skipped;
+ }
+
+ private int skipSeparator(String path, int pos, String separator) {
+ int skipped = 0;
+ while (path.startsWith(separator, pos + skipped)) {
+ skipped += separator.length();
+ }
+ return skipped;
+ }
+
+ private boolean isWildcardChar(char c) {
+ for (char candidate : WILDCARD_CHARS) {
+ if (c == candidate) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Tokenize the given path pattern into parts, based on this matcher's settings.
* <p>Performs caching based on {@link #setCachePatterns}, delegating to
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 9cea1c99..4bea60c8 100644
--- a/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java
+++ b/spring-core/src/main/java/org/springframework/util/AutoPopulatingList.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -263,13 +263,16 @@ public class AutoPopulatingList<E> implements List<E>, Serializable {
public ElementInstantiationException(String msg) {
super(msg);
}
+
+ public ElementInstantiationException(String message, Throwable cause) {
+ super(message, cause);
+ }
}
/**
* Reflective implementation of the ElementFactory interface,
* using {@code Class.newInstance()} on a given element class.
- * @see Class#newInstance()
*/
private static class ReflectiveElementFactory<E> implements ElementFactory<E>, Serializable {
@@ -288,12 +291,12 @@ public class AutoPopulatingList<E> implements List<E>, Serializable {
return this.elementClass.newInstance();
}
catch (InstantiationException ex) {
- throw new ElementInstantiationException("Unable to instantiate element class [" +
- this.elementClass.getName() + "]. Root cause is " + ex);
+ throw new ElementInstantiationException(
+ "Unable to instantiate element class: " + this.elementClass.getName(), ex);
}
catch (IllegalAccessException ex) {
- throw new ElementInstantiationException("Cannot access element class [" +
- this.elementClass.getName() + "]. Root cause is " + ex);
+ throw new ElementInstantiationException(
+ "Could not access element constructor: " + this.elementClass.getName(), ex);
}
}
}
diff --git a/spring-core/src/main/java/org/springframework/util/Base64Utils.java b/spring-core/src/main/java/org/springframework/util/Base64Utils.java
index 17c48614..f10b60d6 100644
--- a/spring-core/src/main/java/org/springframework/util/Base64Utils.java
+++ b/spring-core/src/main/java/org/springframework/util/Base64Utils.java
@@ -30,11 +30,11 @@ import org.springframework.lang.UsesJava8;
* Codec present, {@link #encode}/{@link #decode} calls will throw an IllegalStateException.
* However, as of Spring 4.2, {@link #encodeToString} and {@link #decodeFromString} will
* nevertheless work since they can delegate to the JAXB DatatypeConverter as a fallback.
- * However, this does not apply when using the ...UrlSafe... methods for RFC 4648 "URL and
+ * However, this does not apply when using the "UrlSafe" methods for RFC 4648 "URL and
* Filename Safe Alphabet"; a delegate is required.
- * <p>
- * <em>Note:</em> Apache Commons Codec does not add padding ({@code =}) when encoding with
- * the URL and Filename Safe Alphabet.
+ *
+ * <p><em>Note:</em> Apache Commons Codec does not add padding ({@code =}) when encoding
+ * with the URL and Filename Safe Alphabet.
*
* @author Juergen Hoeller
* @author Gary Russell
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 93dd3d61..6d152017 100644
--- a/spring-core/src/main/java/org/springframework/util/ClassUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/ClassUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -56,16 +56,16 @@ public abstract class ClassUtils {
/** Prefix for internal non-primitive array class names: "[L" */
private static final String NON_PRIMITIVE_ARRAY_PREFIX = "[L";
- /** The package separator character '.' */
+ /** The package separator character: '.' */
private static final char PACKAGE_SEPARATOR = '.';
- /** The path separator character '/' */
+ /** The path separator character: '/' */
private static final char PATH_SEPARATOR = '/';
- /** The inner class separator character '$' */
+ /** The inner class separator character: '$' */
private static final char INNER_CLASS_SEPARATOR = '$';
- /** The CGLIB class separator character "$$" */
+ /** The CGLIB class separator: "$$" */
public static final String CGLIB_CLASS_SEPARATOR = "$$";
/** The ".class" file suffix */
@@ -1158,7 +1158,6 @@ public abstract class ClassUtils {
*/
public static Class<?> createCompositeInterface(Class<?>[] interfaces, ClassLoader classLoader) {
Assert.notEmpty(interfaces, "Interfaces must not be empty");
- Assert.notNull(classLoader, "ClassLoader must not be null");
return Proxy.getProxyClass(classLoader, interfaces);
}
diff --git a/spring-core/src/main/java/org/springframework/util/DigestUtils.java b/spring-core/src/main/java/org/springframework/util/DigestUtils.java
index 8de0b513..36b1a583 100644
--- a/spring-core/src/main/java/org/springframework/util/DigestUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/DigestUtils.java
@@ -23,14 +23,15 @@ import java.security.NoSuchAlgorithmException;
/**
* Miscellaneous methods for calculating digests.
+ *
* <p>Mainly for internal use within the framework; consider
- * <a href="http://commons.apache.org/codec/">Apache Commons Codec</a> for a
- * more comprehensive suite of digest utilities.
+ * <a href="http://commons.apache.org/codec/">Apache Commons Codec</a>
+ * for a more comprehensive suite of digest utilities.
*
* @author Arjen Poutsma
+ * @author Juergen Hoeller
* @author Craig Andrews
* @since 3.0
- * @see org.apache.commons.codec.digest.DigestUtils
*/
public abstract class DigestUtils {
@@ -50,8 +51,8 @@ public abstract class DigestUtils {
}
/**
- * Calculate the MD5 digest of the given InputStream.
- * @param inputStream the inputStream to calculate the digest over
+ * Calculate the MD5 digest of the given stream.
+ * @param inputStream the InputStream to calculate the digest over
* @return the digest
* @since 4.2
*/
@@ -60,8 +61,7 @@ public abstract class DigestUtils {
}
/**
- * Return a hexadecimal string representation of the MD5 digest of the given
- * bytes.
+ * Return a hexadecimal string representation of the MD5 digest of the given bytes.
* @param bytes the bytes to calculate the digest over
* @return a hexadecimal digest string
*/
@@ -70,9 +70,8 @@ public abstract class DigestUtils {
}
/**
- * Return a hexadecimal string representation of the MD5 digest of the given
- * inputStream.
- * @param inputStream the inputStream to calculate the digest over
+ * Return a hexadecimal string representation of the MD5 digest of the given stream.
+ * @param inputStream the InputStream to calculate the digest over
* @return a hexadecimal digest string
* @since 4.2
*/
@@ -128,7 +127,12 @@ public abstract class DigestUtils {
return messageDigest.digest();
}
else {
- return messageDigest.digest(StreamUtils.copyToByteArray(inputStream));
+ final byte[] buffer = new byte[StreamUtils.BUFFER_SIZE];
+ int bytesRead = -1;
+ while ((bytesRead = inputStream.read(buffer)) != -1) {
+ messageDigest.update(buffer, 0, bytesRead);
+ }
+ return messageDigest.digest();
}
}
diff --git a/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java b/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java
index cde4e615..51dd02c6 100644
--- a/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java
+++ b/spring-core/src/main/java/org/springframework/util/LinkedCaseInsensitiveMap.java
@@ -36,7 +36,7 @@ import java.util.Map;
@SuppressWarnings("serial")
public class LinkedCaseInsensitiveMap<V> extends LinkedHashMap<String, V> {
- private final Map<String, String> caseInsensitiveKeys;
+ private Map<String, String> caseInsensitiveKeys;
private final Locale locale;
@@ -151,6 +151,14 @@ public class LinkedCaseInsensitiveMap<V> extends LinkedHashMap<String, V> {
super.clear();
}
+ @Override
+ @SuppressWarnings("unchecked")
+ public Object clone() {
+ LinkedCaseInsensitiveMap<V> copy = (LinkedCaseInsensitiveMap<V>) super.clone();
+ copy.caseInsensitiveKeys = new HashMap<String, String>(this.caseInsensitiveKeys);
+ return copy;
+ }
+
/**
* Convert the given key to a case-insensitive key.
diff --git a/spring-core/src/main/java/org/springframework/util/MimeType.java b/spring-core/src/main/java/org/springframework/util/MimeType.java
index b3e585fb..e73602d2 100644
--- a/spring-core/src/main/java/org/springframework/util/MimeType.java
+++ b/spring-core/src/main/java/org/springframework/util/MimeType.java
@@ -22,6 +22,7 @@ import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -50,19 +51,12 @@ public class MimeType implements Comparable<MimeType>, Serializable {
private static final long serialVersionUID = 4085923477777865903L;
- protected static final String WILDCARD_TYPE = "*";
- private static final BitSet TOKEN;
+ protected static final String WILDCARD_TYPE = "*";
private static final String PARAM_CHARSET = "charset";
-
- private final String type;
-
- private final String subtype;
-
- private final Map<String, String> parameters;
-
+ private static final BitSet TOKEN;
static {
// variable names refer to RFC 2616, section 2.2
@@ -100,6 +94,13 @@ public class MimeType implements Comparable<MimeType>, Serializable {
}
+ private final String type;
+
+ private final String subtype;
+
+ private final Map<String, String> parameters;
+
+
/**
* Create a new {@code MimeType} for the given primary type.
* <p>The {@linkplain #getSubtype() subtype} is set to <code>"&#42;"</code>,
@@ -126,11 +127,23 @@ public class MimeType implements Comparable<MimeType>, Serializable {
* 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
+ * @param charset the character set
+ * @throws IllegalArgumentException if any of the parameters contains 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, subtype, parameters of the given {@code MimeType},
+ * and allows to set the specified character set.
+ * @param other the other media type
+ * @param charset the character set
* @throws IllegalArgumentException if any of the parameters contains illegal characters
+ * @since 4.3
*/
- public MimeType(String type, String subtype, Charset charSet) {
- this(type, subtype, Collections.singletonMap(PARAM_CHARSET, charSet.name()));
+ public MimeType(MimeType other, Charset charset) {
+ this(other.getType(), other.getSubtype(), addCharsetParameter(charset, other.getParameters()));
}
/**
@@ -261,13 +274,25 @@ public class MimeType implements Comparable<MimeType>, Serializable {
/**
* Return the character set, as indicated by a {@code charset} parameter, if any.
* @return the character set, or {@code null} if not available
+ * @since 4.3
*/
- public Charset getCharSet() {
+ public Charset getCharset() {
String charSet = getParameter(PARAM_CHARSET);
return (charSet != null ? Charset.forName(unquote(charSet)) : null);
}
/**
+ * Return the character set, as indicated by a {@code charset} parameter, if any.
+ * @return the character set, or {@code null} if not available
+ * @deprecated as of Spring 4.3, in favor of {@link #getCharset()} with its name
+ * aligned with the Java return type name
+ */
+ @Deprecated
+ public Charset getCharSet() {
+ return getCharset();
+ }
+
+ /**
* Return a generic parameter value, given a parameter name.
* @param name the parameter name
* @return the parameter value, or {@code null} if not present
@@ -374,50 +399,6 @@ public class MimeType implements Comparable<MimeType>, Serializable {
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) {
@@ -439,22 +420,22 @@ public class MimeType implements Comparable<MimeType>, Serializable {
* for {@link Charset}s.
* @since 4.2
*/
- private boolean parametersAreEqual(MimeType that) {
- if (this.parameters.size() != that.parameters.size()) {
+ private boolean parametersAreEqual(MimeType other) {
+ if (this.parameters.size() != other.parameters.size()) {
return false;
}
for (String key : this.parameters.keySet()) {
- if (!that.parameters.containsKey(key)) {
+ if (!other.parameters.containsKey(key)) {
return false;
}
if (PARAM_CHARSET.equals(key)) {
- if (!ObjectUtils.nullSafeEquals(this.getCharSet(), that.getCharSet())) {
+ if (!ObjectUtils.nullSafeEquals(getCharset(), other.getCharset())) {
return false;
}
}
- else if (!ObjectUtils.nullSafeEquals(this.parameters.get(key), that.parameters.get(key))) {
+ else if (!ObjectUtils.nullSafeEquals(this.parameters.get(key), other.parameters.get(key))) {
return false;
}
}
@@ -494,6 +475,52 @@ public class MimeType implements Comparable<MimeType>, Serializable {
}
/**
+ * 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;
+ }
+
+
+ /**
* 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}.
@@ -503,6 +530,12 @@ public class MimeType implements Comparable<MimeType>, Serializable {
return MimeTypeUtils.parseMimeType(value);
}
+ private static Map<String, String> addCharsetParameter(Charset charset, Map<String, String> parameters) {
+ Map<String, String> map = new LinkedHashMap<String, String>(parameters);
+ map.put(PARAM_CHARSET, charset.name());
+ return map;
+ }
+
public static class SpecificityComparator<T extends MimeType> implements Comparator<T> {
diff --git a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java
index 0bf62d58..a0ad5cc0 100644
--- a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -220,6 +220,9 @@ public abstract class MimeTypeUtils {
throw new InvalidMimeTypeException(mimeType, "'mimeType' must not be empty");
}
String[] parts = StringUtils.tokenizeToStringArray(mimeType, ";");
+ if (parts.length == 0) {
+ throw new InvalidMimeTypeException(mimeType, "'mimeType' must not be empty");
+ }
String fullType = parts[0].trim();
// java.net.HttpURLConnection returns a *; q=.2 Accept header
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 9df023c3..46b3f868 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-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -87,39 +87,29 @@ public abstract class NumberUtils {
return (T) number;
}
else if (Byte.class == targetClass) {
- long value = number.longValue();
+ long value = checkedLongValue(number, targetClass);
if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) {
raiseOverflowException(number, targetClass);
}
return (T) Byte.valueOf(number.byteValue());
}
else if (Short.class == targetClass) {
- long value = number.longValue();
+ long value = checkedLongValue(number, targetClass);
if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) {
raiseOverflowException(number, targetClass);
}
return (T) Short.valueOf(number.shortValue());
}
else if (Integer.class == targetClass) {
- long value = number.longValue();
+ long value = checkedLongValue(number, targetClass);
if (value < Integer.MIN_VALUE || value > Integer.MAX_VALUE) {
raiseOverflowException(number, targetClass);
}
return (T) Integer.valueOf(number.intValue());
}
else if (Long.class == targetClass) {
- 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) Long.valueOf(number.longValue());
+ long value = checkedLongValue(number, targetClass);
+ return (T) Long.valueOf(value);
}
else if (BigInteger.class == targetClass) {
if (number instanceof BigDecimal) {
@@ -149,10 +139,34 @@ public abstract class NumberUtils {
}
/**
+ * Check for a {@code BigInteger}/{@code BigDecimal} long overflow
+ * before returning the given number as a long value.
+ * @param number the number to convert
+ * @param targetClass the target class to convert to
+ * @return the long value, if convertible without overflow
+ * @throws IllegalArgumentException if there is an overflow
+ * @see #raiseOverflowException
+ */
+ private static long checkedLongValue(Number number, Class<? extends Number> targetClass) {
+ 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 number.longValue();
+ }
+
+ /**
* Raise an <em>overflow</em> exception for the given number and target class.
* @param number the number we tried to convert
* @param targetClass the target class we tried to convert to
- * @throws IllegalArgumentException
+ * @throws IllegalArgumentException if there is an overflow
*/
private static void raiseOverflowException(Number number, Class<?> targetClass) {
throw new IllegalArgumentException("Could not convert number [" + number + "] of type [" +
diff --git a/spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java b/spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java
index cb2cdfa2..c4487ccf 100644
--- a/spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java
+++ b/spring-core/src/main/java/org/springframework/util/ResizableByteArrayOutputStream.java
@@ -23,7 +23,7 @@ import java.io.ByteArrayOutputStream;
* <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>
+ * to get more control over the size of the internal buffer</li>
* <li>has a higher initial capacity (256) by default</li>
* </ul>
*
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 3ea92707..9108053f 100644
--- a/spring-core/src/main/java/org/springframework/util/ResourceUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/ResourceUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -235,6 +235,7 @@ public abstract class ResourceUtils {
* @return a corresponding File object
* @throws FileNotFoundException if the URL cannot be resolved to
* a file in the file system
+ * @since 2.5
*/
public static File getFile(URI resourceUri) throws FileNotFoundException {
return getFile(resourceUri, "URI");
@@ -249,6 +250,7 @@ public abstract class ResourceUtils {
* @return a corresponding File object
* @throws FileNotFoundException if the URL cannot be resolved to
* a file in the file system
+ * @since 2.5
*/
public static File getFile(URI resourceUri, String description) throws FileNotFoundException {
Assert.notNull(resourceUri, "Resource URI must not be null");
diff --git a/spring-core/src/main/java/org/springframework/util/StopWatch.java b/spring-core/src/main/java/org/springframework/util/StopWatch.java
index 1ba39880..b4701164 100644
--- a/spring-core/src/main/java/org/springframework/util/StopWatch.java
+++ b/spring-core/src/main/java/org/springframework/util/StopWatch.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -134,7 +134,7 @@ public class StopWatch {
/**
* Stop the current task. The results are undefined if timing
* methods are called without invoking at least one pair
- * {@code #start()} / {@code #stop()} methods.
+ * {@code start()} / {@code stop()} methods.
* @see #start()
*/
public void stop() throws IllegalStateException {
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 1bc2170c..c04ce058 100644
--- a/spring-core/src/main/java/org/springframework/util/StreamUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/StreamUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@ import java.nio.charset.Charset;
*
* @author Juergen Hoeller
* @author Phillip Webb
+ * @author Brian Clozel
* @since 3.2.2
* @see FileCopyUtils
*/
@@ -132,6 +133,62 @@ public abstract class StreamUtils {
}
/**
+ * Copy a range of content of the given InputStream to the given OutputStream.
+ * <p>If the specified range exceeds the length of the InputStream, this copies
+ * up to the end of the stream and returns the actual number of copied bytes.
+ * <p>Leaves both streams open when done.
+ * @param in the InputStream to copy from
+ * @param out the OutputStream to copy to
+ * @param start the position to start copying from
+ * @param end the position to end copying
+ * @return the number of bytes copied
+ * @throws IOException in case of I/O errors
+ * @since 4.3
+ */
+ public static long copyRange(InputStream in, OutputStream out, long start, long end) throws IOException {
+ long skipped = in.skip(start);
+ if (skipped < start) {
+ throw new IOException("Skipped only " + skipped + " bytes out of " + start + " required.");
+ }
+ long bytesToCopy = end - start + 1;
+ byte buffer[] = new byte[StreamUtils.BUFFER_SIZE];
+ while (bytesToCopy > 0) {
+ int bytesRead = in.read(buffer);
+ if (bytesRead == -1) {
+ break;
+ }
+ else if (bytesRead <= bytesToCopy) {
+ out.write(buffer, 0, bytesRead);
+ bytesToCopy -= bytesRead;
+ }
+ else {
+ out.write(buffer, 0, (int) bytesToCopy);
+ bytesToCopy = 0;
+ }
+ }
+ return end - start + 1 - bytesToCopy;
+ }
+
+ /**
+ * Drain the remaining content of the given InputStream.
+ * Leaves the InputStream open when done.
+ * @param in the InputStream to drain
+ * @return the number of bytes read
+ * @throws IOException in case of I/O errors
+ * @since 4.3
+ */
+ public static int drain(InputStream in) throws IOException {
+ Assert.notNull(in, "No InputStream specified");
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int bytesRead = -1;
+ int byteCount = 0;
+ while ((bytesRead = in.read(buffer)) != -1) {
+ byteCount += bytesRead;
+ }
+ return byteCount;
+ }
+
+ /**
* Return an efficient empty {@link InputStream}.
* @return a {@link ByteArrayInputStream} based on an empty byte array
* @since 4.2.2
diff --git a/spring-core/src/main/java/org/springframework/util/UpdateMessageDigestInputStream.java b/spring-core/src/main/java/org/springframework/util/UpdateMessageDigestInputStream.java
index 37890be9..90bfa4a7 100644
--- a/spring-core/src/main/java/org/springframework/util/UpdateMessageDigestInputStream.java
+++ b/spring-core/src/main/java/org/springframework/util/UpdateMessageDigestInputStream.java
@@ -33,7 +33,7 @@ abstract class UpdateMessageDigestInputStream extends InputStream {
* Update the message digest with the rest of the bytes in this stream.
* <p>Using this method is more optimized since it avoids creating new
* byte arrays for each call.
- * @param messageDigest The message digest to update
+ * @param messageDigest the message digest to update
* @throws IOException when propagated from {@link #read()}
*/
public void updateMessageDigest(MessageDigest messageDigest) throws IOException {
@@ -47,7 +47,7 @@ abstract class UpdateMessageDigestInputStream extends InputStream {
* Update the message digest with the next len bytes in this stream.
* <p>Using this method is more optimized since it avoids creating new
* byte arrays for each call.
- * @param messageDigest The message digest to update
+ * @param messageDigest the message digest to update
* @param len how many bytes to read from this stream and use to update the message digest
* @throws IOException when propagated from {@link #read()}
*/
diff --git a/spring-core/src/main/java/org/springframework/util/backoff/BackOff.java b/spring-core/src/main/java/org/springframework/util/backoff/BackOff.java
index 0543ac91..2eb4b3ce 100644
--- a/spring-core/src/main/java/org/springframework/util/backoff/BackOff.java
+++ b/spring-core/src/main/java/org/springframework/util/backoff/BackOff.java
@@ -26,7 +26,7 @@ package org.springframework.util.backoff;
* BackOffExecution exec = backOff.start();
*
* // In the operation recovery/retry loop:
- * long waitInterval = exec.nextBackOffMillis();
+ * long waitInterval = exec.nextBackOff();
* if (waitInterval == BackOffExecution.STOP) {
* // do not retry operation
* }
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 a1051404..c04735c8 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
@@ -24,7 +24,7 @@ import java.util.List;
import org.springframework.util.Assert;
/**
- * A comparator that chains a sequence of one or more more Comparators.
+ * A comparator that chains a sequence of one or more Comparators.
*
* <p>A compound comparator calls each Comparator in sequence until a single
* Comparator returns a non-zero result, or the comparators are exhausted and
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 19d9d9bf..8f033bad 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
@@ -43,7 +43,7 @@ public class InstanceComparator<T> implements Comparator<T> {
/**
* Create a new {@link InstanceComparator} instance.
* @param instanceOrder the ordered list of classes that should be used when comparing
- * objects. Classes earlier in the list will be be given a higher priority.
+ * objects. Classes earlier in the list will be given a higher priority.
*/
public InstanceComparator(Class<?>... instanceOrder) {
Assert.notNull(instanceOrder, "'instanceOrder' must not be null");
diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/CompletableToListenableFutureAdapter.java b/spring-core/src/main/java/org/springframework/util/concurrent/CompletableToListenableFutureAdapter.java
index f42a88f4..1a6cd14f 100644
--- a/spring-core/src/main/java/org/springframework/util/concurrent/CompletableToListenableFutureAdapter.java
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/CompletableToListenableFutureAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +24,6 @@ import java.util.function.BiFunction;
import org.springframework.lang.UsesJava8;
-
/**
* Adapts a {@link CompletableFuture} into a {@link ListenableFuture}.
*
@@ -38,6 +37,7 @@ public class CompletableToListenableFutureAdapter<T> implements ListenableFuture
private final ListenableFutureCallbackRegistry<T> callbacks = new ListenableFutureCallbackRegistry<T>();
+
public CompletableToListenableFutureAdapter(CompletableFuture<T> completableFuture) {
this.completableFuture = completableFuture;
this.completableFuture.handle(new BiFunction<T, Throwable, Object>() {
@@ -54,6 +54,7 @@ public class CompletableToListenableFutureAdapter<T> implements ListenableFuture
});
}
+
@Override
public void addCallback(ListenableFutureCallback<? super T> callback) {
this.callbacks.addCallback(callback);
diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/FailureCallback.java b/spring-core/src/main/java/org/springframework/util/concurrent/FailureCallback.java
index caac2874..20f21420 100644
--- a/spring-core/src/main/java/org/springframework/util/concurrent/FailureCallback.java
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/FailureCallback.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,7 @@
package org.springframework.util.concurrent;
/**
- * Defines the contract for failure callbacks that accept the result of a
- * {@link ListenableFuture}.
+ * Failure callback for a {@link ListenableFuture}.
*
* @author Sebastien Deleuze
* @since 4.1
@@ -26,8 +25,9 @@ package org.springframework.util.concurrent;
public interface FailureCallback {
/**
- * Called when the {@link ListenableFuture} fails to complete.
- * @param ex the exception that triggered the failure
+ * Called when the {@link ListenableFuture} completes with failure.
+ * <p>Note that Exceptions raised by this method are ignored.
+ * @param ex the failure
*/
void onFailure(Throwable ex);
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
index 37aac931..2a36c9e6 100644
--- a/spring-core/src/main/java/org/springframework/util/concurrent/FutureAdapter.java
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/FutureAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,10 +24,9 @@ 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.
+ * 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
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
index 6250b08f..5b842973 100644
--- a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFuture.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,11 +19,11 @@ 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.
+ * Extend {@link Future} with the capability to accept completion callbacks.
+ * If the future has completed when the callback is added, the callback is
+ * triggered immediately.
* <p>Inspired by {@code com.google.common.util.concurrent.ListenableFuture}.
-
+ *
* @author Arjen Poutsma
* @author Sebastien Deleuze
* @since 4.0
@@ -31,20 +31,15 @@ import java.util.concurrent.Future;
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.
+ * Register the given {@code ListenableFutureCallback}.
* @param callback the callback to register
*/
void addCallback(ListenableFutureCallback<? super T> callback);
/**
- * Registers the given success and failure callbacks to this {@code ListenableFuture}.
- * The callback will be triggered when this {@code Future} is complete or, if it is
- * already complete immediately. This is a Java 8 lambdas compliant alternative to
- * {@link #addCallback(ListenableFutureCallback)}.
- * @param successCallback the success callback to register
- * @param failureCallback the failure callback to register
+ * Java 8 lambda-friendly alternative with success and failure callbacks.
+ * @param successCallback the success callback
+ * @param failureCallback the failure callback
* @since 4.1
*/
void addCallback(SuccessCallback<? super T> successCallback, FailureCallback failureCallback);
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
index 268a6496..0f1624aa 100644
--- a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureAdapter.java
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -52,16 +52,20 @@ public abstract class ListenableFutureAdapter<T, S> extends FutureAdapter<T, S>
listenableAdaptee.addCallback(new ListenableFutureCallback<S>() {
@Override
public void onSuccess(S result) {
+ T adapted;
try {
- successCallback.onSuccess(adaptInternal(result));
+ adapted = adaptInternal(result);
}
catch (ExecutionException ex) {
Throwable cause = ex.getCause();
onFailure(cause != null ? cause : ex);
+ return;
}
catch (Throwable ex) {
onFailure(ex);
+ return;
}
+ successCallback.onSuccess(adapted);
}
@Override
public void onFailure(Throwable ex) {
diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java
index 63379445..4ce858c1 100644
--- a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallback.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@
package org.springframework.util.concurrent;
/**
- * Defines the contract for callbacks that accept the result of a
+ * Callback mechanism for the outcome, success or failure, from a
* {@link ListenableFuture}.
*
* @author Arjen Poutsma
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
index 1c4be730..46eb746b 100644
--- a/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.java
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/ListenableFutureCallbackRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,12 +22,14 @@ import java.util.Queue;
import org.springframework.util.Assert;
/**
- * Registry for {@link ListenableFutureCallback} instances.
+ * Helper class for {@link ListenableFuture} implementations that maintains a
+ * of success and failure callbacks and helps to notify them.
*
* <p>Inspired by {@code com.google.common.util.concurrent.ExecutionList}.
*
* @author Arjen Poutsma
* @author Sebastien Deleuze
+ * @author Rossen Stoyanchev
* @since 4.0
*/
public class ListenableFutureCallbackRegistry<T> {
@@ -47,7 +49,6 @@ public class ListenableFutureCallbackRegistry<T> {
* 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) {
@@ -57,15 +58,34 @@ public class ListenableFutureCallbackRegistry<T> {
this.failureCallbacks.add(callback);
break;
case SUCCESS:
- callback.onSuccess((T) this.result);
+ notifySuccess(callback);
break;
case FAILURE:
- callback.onFailure((Throwable) this.result);
+ notifyFailure(callback);
break;
}
}
}
+ @SuppressWarnings("unchecked")
+ private void notifySuccess(SuccessCallback<? super T> callback) {
+ try {
+ callback.onSuccess((T) this.result);
+ }
+ catch (Throwable ex) {
+ // Ignore
+ }
+ }
+
+ private void notifyFailure(FailureCallback callback) {
+ try {
+ callback.onFailure((Throwable) this.result);
+ }
+ catch (Throwable ex) {
+ // Ignore
+ }
+ }
+
/**
* Add the given success callback to this registry.
* @param callback the success callback to add
@@ -80,7 +100,7 @@ public class ListenableFutureCallbackRegistry<T> {
this.successCallbacks.add(callback);
break;
case SUCCESS:
- callback.onSuccess((T) this.result);
+ notifySuccess(callback);
break;
}
}
@@ -99,7 +119,7 @@ public class ListenableFutureCallbackRegistry<T> {
this.failureCallbacks.add(callback);
break;
case FAILURE:
- callback.onFailure((Throwable) this.result);
+ notifyFailure(callback);
break;
}
}
@@ -115,7 +135,7 @@ public class ListenableFutureCallbackRegistry<T> {
this.state = State.SUCCESS;
this.result = result;
while (!this.successCallbacks.isEmpty()) {
- this.successCallbacks.poll().onSuccess(result);
+ notifySuccess(this.successCallbacks.poll());
}
}
}
@@ -130,7 +150,7 @@ public class ListenableFutureCallbackRegistry<T> {
this.state = State.FAILURE;
this.result = ex;
while (!this.failureCallbacks.isEmpty()) {
- this.failureCallbacks.poll().onFailure(ex);
+ notifyFailure(this.failureCallbacks.poll());
}
}
}
diff --git a/spring-core/src/main/java/org/springframework/util/concurrent/SuccessCallback.java b/spring-core/src/main/java/org/springframework/util/concurrent/SuccessCallback.java
index 65f30411..b20bcad1 100644
--- a/spring-core/src/main/java/org/springframework/util/concurrent/SuccessCallback.java
+++ b/spring-core/src/main/java/org/springframework/util/concurrent/SuccessCallback.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,7 @@
package org.springframework.util.concurrent;
/**
- * Defines the contract for success callbacks that accept the result of a
- * {@link ListenableFuture}.
+ * Success callback for a {@link ListenableFuture}.
*
* @author Sebastien Deleuze
* @since 4.1
@@ -26,7 +25,8 @@ package org.springframework.util.concurrent;
public interface SuccessCallback<T> {
/**
- * Called when the {@link ListenableFuture} successfully completes.
+ * Called when the {@link ListenableFuture} completes with success.
+ * <p>Note that Exceptions raised by this method are ignored.
* @param result the result
*/
void onSuccess(T result);
diff --git a/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java b/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java
index d924fd15..227b216c 100644
--- a/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java
+++ b/spring-core/src/main/java/org/springframework/util/xml/XmlValidationModeDetector.java
@@ -120,7 +120,7 @@ public class XmlValidationModeDetector {
/**
- * Does the content contain the the DTD DOCTYPE declaration?
+ * Does the content contain the DTD DOCTYPE declaration?
*/
private boolean hasDoctype(String content) {
return content.contains(DOCTYPE);