SynthesizedAnnotationInvocationHandler.java 6.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
 * 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.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
23
import java.util.Iterator;
24 25 26 27
import java.util.Map;

import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
28
import org.springframework.util.StringUtils;
29

30 31
import static org.springframework.core.annotation.AnnotationUtils.*;

32
/**
33 34 35 36 37 38 39 40 41
 * {@link InvocationHandler} for an {@link Annotation} that Spring has
 * <em>synthesized</em> (i.e., wrapped in a dynamic proxy) with additional
 * functionality.
 *
 * <p>{@code SynthesizedAnnotationInvocationHandler} transparently enforces
 * attribute alias semantics for annotation attributes that are annotated
 * with {@link AliasFor @AliasFor}. In addition, nested annotations and
 * arrays of nested annotations will be synthesized upon first access (i.e.,
 * <em>lazily</em>).
42 43 44
 *
 * @author Sam Brannen
 * @since 4.2
45
 * @see Annotation
46 47
 * @see AliasFor
 * @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement)
48 49 50 51 52 53 54
 */
class SynthesizedAnnotationInvocationHandler implements InvocationHandler {

	private final AnnotatedElement annotatedElement;

	private final Annotation annotation;

55
	private final Class<? extends Annotation> annotationType;
56

57
	private final Map<String, String> aliasMap;
58 59


60
	SynthesizedAnnotationInvocationHandler(AnnotatedElement annotatedElement, Annotation annotation,
61
			Map<String, String> aliasMap) {
62 63
		this.annotatedElement = annotatedElement;
		this.annotation = annotation;
64 65
		this.annotationType = annotation.annotationType();
		this.aliasMap = aliasMap;
66 67 68 69
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
70
		String methodName = method.getName();
71 72
		Class<?>[] parameterTypes = method.getParameterTypes();
		int parameterCount = parameterTypes.length;
73

74 75 76 77
		if ("equals".equals(methodName) && (parameterCount == 1) && (parameterTypes[0] == Object.class)) {
			return equals(proxy, args[0]);
		}
		else if ("toString".equals(methodName) && (parameterCount == 0)) {
78 79 80
			return toString(proxy);
		}

81 82
		Class<?> returnType = method.getReturnType();
		boolean nestedAnnotation = (Annotation[].class.isAssignableFrom(returnType) || Annotation.class.isAssignableFrom(returnType));
83
		String aliasedAttributeName = aliasMap.get(methodName);
84
		boolean aliasPresent = (aliasedAttributeName != null);
85 86 87 88

		ReflectionUtils.makeAccessible(method);
		Object value = ReflectionUtils.invokeMethod(method, this.annotation, args);

89
		// No custom processing necessary?
90 91 92 93 94 95 96
		if (!aliasPresent && !nestedAnnotation) {
			return value;
		}

		if (aliasPresent) {
			Method aliasedMethod = null;
			try {
97
				aliasedMethod = this.annotationType.getDeclaredMethod(aliasedAttributeName);
98 99 100
			}
			catch (NoSuchMethodException e) {
				String msg = String.format("In annotation [%s], attribute [%s] is declared as an @AliasFor [%s], "
101
						+ "but attribute [%s] does not exist.", this.annotationType.getName(), methodName,
102 103 104 105 106 107
					aliasedAttributeName, aliasedAttributeName);
				throw new AnnotationConfigurationException(msg);
			}

			ReflectionUtils.makeAccessible(aliasedMethod);
			Object aliasedValue = ReflectionUtils.invokeMethod(aliasedMethod, this.annotation, args);
108
			Object defaultValue = getDefaultValue(this.annotation, methodName);
109 110 111

			if (!ObjectUtils.nullSafeEquals(value, aliasedValue) && !ObjectUtils.nullSafeEquals(value, defaultValue)
					&& !ObjectUtils.nullSafeEquals(aliasedValue, defaultValue)) {
112 113
				String elementName = (this.annotatedElement == null ? "unknown element"
						: this.annotatedElement.toString());
114 115 116
				String msg = String.format(
					"In annotation [%s] declared on [%s], attribute [%s] and its alias [%s] are "
							+ "declared with values of [%s] and [%s], but only one declaration is permitted.",
117
					this.annotationType.getName(), elementName, methodName, aliasedAttributeName,
118 119 120 121 122 123 124 125 126 127 128 129 130
					ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue));
				throw new AnnotationConfigurationException(msg);
			}

			// If the user didn't declare the annotation with an explicit value, return
			// the value of the alias.
			if (ObjectUtils.nullSafeEquals(value, defaultValue)) {
				value = aliasedValue;
			}
		}

		// Synthesize nested annotations before returning them.
		if (value instanceof Annotation) {
131
			value = synthesizeAnnotation((Annotation) value, this.annotatedElement);
132 133 134 135
		}
		else if (value instanceof Annotation[]) {
			Annotation[] annotations = (Annotation[]) value;
			for (int i = 0; i < annotations.length; i++) {
136
				annotations[i] = synthesizeAnnotation(annotations[i], this.annotatedElement);
137 138 139 140 141 142
			}
		}

		return value;
	}

143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
	private boolean equals(Object proxy, Object other) {
		if (this == other) {
			return true;
		}
		if (!this.annotationType.isInstance(other)) {
			return false;
		}

		for (Method attributeMethod : getAttributeMethods(this.annotationType)) {
			Object thisValue = ReflectionUtils.invokeMethod(attributeMethod, proxy);
			Object otherValue = ReflectionUtils.invokeMethod(attributeMethod, other);
			if (!ObjectUtils.nullSafeEquals(thisValue, otherValue)) {
				return false;
			}
		}

		return true;
	}

162 163 164
	private String toString(Object proxy) {
		StringBuilder sb = new StringBuilder("@").append(annotationType.getName()).append("(");

165
		Iterator<Method> iterator = getAttributeMethods(this.annotationType).iterator();
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
		while (iterator.hasNext()) {
			Method attributeMethod = iterator.next();
			sb.append(attributeMethod.getName());
			sb.append('=');
			sb.append(valueToString(ReflectionUtils.invokeMethod(attributeMethod, proxy)));
			sb.append(iterator.hasNext() ? ", " : "");
		}

		return sb.append(")").toString();
	}

	private String valueToString(Object value) {
		if (value instanceof Object[]) {
			return "[" + StringUtils.arrayToDelimitedString((Object[]) value, ", ") + "]";
		}

		// else
		return String.valueOf(value);
	}

186
}