/* * 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; import java.util.Iterator; import java.util.Map; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import static org.springframework.core.annotation.AnnotationUtils.*; /** * {@link InvocationHandler} for an {@link Annotation} that Spring has * synthesized (i.e., wrapped in a dynamic proxy) with additional * functionality. * *

{@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., * lazily). * * @author Sam Brannen * @since 4.2 * @see Annotation * @see AliasFor * @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement) */ class SynthesizedAnnotationInvocationHandler implements InvocationHandler { private final AnnotatedElement annotatedElement; private final Annotation annotation; private final Class annotationType; private final Map aliasMap; SynthesizedAnnotationInvocationHandler(AnnotatedElement annotatedElement, Annotation annotation, Map aliasMap) { this.annotatedElement = annotatedElement; this.annotation = annotation; this.annotationType = annotation.annotationType(); this.aliasMap = aliasMap; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); Class[] parameterTypes = method.getParameterTypes(); int parameterCount = parameterTypes.length; if ("equals".equals(methodName) && (parameterCount == 1) && (parameterTypes[0] == Object.class)) { return equals(proxy, args[0]); } else if ("toString".equals(methodName) && (parameterCount == 0)) { return toString(proxy); } Class returnType = method.getReturnType(); boolean nestedAnnotation = (Annotation[].class.isAssignableFrom(returnType) || Annotation.class.isAssignableFrom(returnType)); String aliasedAttributeName = aliasMap.get(methodName); boolean aliasPresent = (aliasedAttributeName != null); ReflectionUtils.makeAccessible(method); Object value = ReflectionUtils.invokeMethod(method, this.annotation, args); // No custom processing necessary? if (!aliasPresent && !nestedAnnotation) { return value; } if (aliasPresent) { Method aliasedMethod = null; try { aliasedMethod = this.annotationType.getDeclaredMethod(aliasedAttributeName); } catch (NoSuchMethodException e) { String msg = String.format("In annotation [%s], attribute [%s] is declared as an @AliasFor [%s], " + "but attribute [%s] does not exist.", this.annotationType.getName(), methodName, aliasedAttributeName, aliasedAttributeName); throw new AnnotationConfigurationException(msg); } ReflectionUtils.makeAccessible(aliasedMethod); Object aliasedValue = ReflectionUtils.invokeMethod(aliasedMethod, this.annotation, args); Object defaultValue = getDefaultValue(this.annotation, methodName); if (!ObjectUtils.nullSafeEquals(value, aliasedValue) && !ObjectUtils.nullSafeEquals(value, defaultValue) && !ObjectUtils.nullSafeEquals(aliasedValue, defaultValue)) { String elementName = (this.annotatedElement == null ? "unknown element" : this.annotatedElement.toString()); 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.", this.annotationType.getName(), elementName, methodName, aliasedAttributeName, 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) { value = synthesizeAnnotation((Annotation) value, this.annotatedElement); } else if (value instanceof Annotation[]) { Annotation[] annotations = (Annotation[]) value; for (int i = 0; i < annotations.length; i++) { annotations[i] = synthesizeAnnotation(annotations[i], this.annotatedElement); } } return value; } 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; } private String toString(Object proxy) { StringBuilder sb = new StringBuilder("@").append(annotationType.getName()).append("("); Iterator iterator = getAttributeMethods(this.annotationType).iterator(); 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); } }