ExactAnnotationTypePattern.java
/* *******************************************************************
* Copyright (c) 2004 IBM Corporation.
* All rights reserved.
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License v 2.0
* which accompanies this distribution and is available at
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
*
* ******************************************************************/
package org.aspectj.weaver.patterns;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.aspectj.bridge.IMessage;
import org.aspectj.bridge.MessageUtil;
import org.aspectj.util.FuzzyBoolean;
import org.aspectj.weaver.AjAttribute.WeaverVersionInfo;
import org.aspectj.weaver.AnnotatedElement;
import org.aspectj.weaver.AnnotationAJ;
import org.aspectj.weaver.BCException;
import org.aspectj.weaver.CompressingDataOutputStream;
import org.aspectj.weaver.ISourceContext;
import org.aspectj.weaver.ReferenceType;
import org.aspectj.weaver.ResolvedMember;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.TypeVariableReference;
import org.aspectj.weaver.UnresolvedType;
import org.aspectj.weaver.VersionedDataInputStream;
import org.aspectj.weaver.WeaverMessages;
import org.aspectj.weaver.World;
/**
* Matches an annotation of a given type
*/
public class ExactAnnotationTypePattern extends AnnotationTypePattern {
protected UnresolvedType annotationType;
protected String formalName;
protected boolean resolved = false;
protected boolean bindingPattern = false;
private Map<String, String> annotationValues;
// OPTIMIZE is annotationtype really unresolved???? surely it is resolved by
// now...
public ExactAnnotationTypePattern(UnresolvedType annotationType, Map<String, String> annotationValues) {
this.annotationType = annotationType;
this.annotationValues = annotationValues;
this.resolved = (annotationType instanceof ResolvedType);
}
// Used when deserializing, values will be added
private ExactAnnotationTypePattern(UnresolvedType annotationType) {
this.annotationType = annotationType;
this.resolved = (annotationType instanceof ResolvedType);
}
protected ExactAnnotationTypePattern(String formalName) {
this.formalName = formalName;
this.resolved = false;
this.bindingPattern = true;
// will be turned into BindingAnnotationTypePattern during resolution
}
public ResolvedType getResolvedAnnotationType() {
if (!resolved) {
throw new IllegalStateException("I need to be resolved first!");
}
return (ResolvedType) annotationType;
}
public UnresolvedType getAnnotationType() {
return annotationType;
}
public Map<String, String> getAnnotationValues() {
return annotationValues;
}
@Override
public FuzzyBoolean fastMatches(AnnotatedElement annotated) {
if (annotated.hasAnnotation(annotationType) && annotationValues == null) {
return FuzzyBoolean.YES;
} else {
// could be inherited, but we don't know that until we are
// resolved, and we're not yet...
return FuzzyBoolean.MAYBE;
}
}
@Override
public FuzzyBoolean matches(AnnotatedElement annotated) {
return matches(annotated, null);
}
@Override
public FuzzyBoolean matches(AnnotatedElement annotated, ResolvedType[] parameterAnnotations) {
if (!isForParameterAnnotationMatch()) {
boolean checkSupers = false;
if (getResolvedAnnotationType().isInheritedAnnotation()) {
if (annotated instanceof ResolvedType) {
checkSupers = true;
}
}
if (annotated.hasAnnotation(annotationType)) {
if (annotationType instanceof ReferenceType) {
ReferenceType rt = (ReferenceType) annotationType;
if (rt.getRetentionPolicy() != null && rt.getRetentionPolicy().equals("SOURCE")) {
rt.getWorld()
.getMessageHandler()
.handleMessage(
MessageUtil.warn(WeaverMessages.format(WeaverMessages.NO_MATCH_BECAUSE_SOURCE_RETENTION,
annotationType, annotated), getSourceLocation()));
return FuzzyBoolean.NO;
}
}
// Are we also matching annotation values?
if (annotationValues != null) {
AnnotationAJ theAnnotation = annotated.getAnnotationOfType(annotationType);
// Check each one
Set<String> keys = annotationValues.keySet();
for (String k : keys) {
boolean notEqual = false;
String v = annotationValues.get(k);
// if the key has a trailing '!' then it means the source expressed k!=v - so we are looking for
// something other than the value specified
if (k.endsWith("!")) {
notEqual = true;
k = k.substring(0, k.length() - 1);
}
if (theAnnotation.hasNamedValue(k)) {
// Simple case, value is 'name=value' and the
// annotation specified the same thing
if (notEqual) {
if (theAnnotation.hasNameValuePair(k, v)) {
return FuzzyBoolean.NO;
}
} else {
if (!theAnnotation.hasNameValuePair(k, v)) {
return FuzzyBoolean.NO;
}
}
} else {
// Complex case, look at the default value
ResolvedMember[] ms = ((ResolvedType) annotationType).getDeclaredMethods();
boolean foundMatch = false;
for (int i = 0; i < ms.length && !foundMatch; i++) {
if (ms[i].isAbstract() && ms[i].getParameterTypes().length == 0 && ms[i].getName().equals(k)) {
// we might be onto something
String s = ms[i].getAnnotationDefaultValue();
if (s != null && s.equals(v)) {
foundMatch = true;
}
}
}
if (notEqual) {
if (foundMatch) {
return FuzzyBoolean.NO;
}
} else {
if (!foundMatch) {
return FuzzyBoolean.NO;
}
}
}
}
}
return FuzzyBoolean.YES;
} else if (checkSupers) {
ResolvedType toMatchAgainst = ((ResolvedType) annotated).getSuperclass();
while (toMatchAgainst != null) {
if (toMatchAgainst.hasAnnotation(annotationType)) {
// Are we also matching annotation values?
if (annotationValues != null) {
AnnotationAJ theAnnotation = toMatchAgainst.getAnnotationOfType(annotationType);
// Check each one
Set<String> keys = annotationValues.keySet();
for (String k : keys) {
String v = annotationValues.get(k);
if (theAnnotation.hasNamedValue(k)) {
// Simple case, value is 'name=value' and
// the annotation specified the same thing
if (!theAnnotation.hasNameValuePair(k, v)) {
return FuzzyBoolean.NO;
}
} else {
// Complex case, look at the default value
ResolvedMember[] ms = ((ResolvedType) annotationType).getDeclaredMethods();
boolean foundMatch = false;
for (int i = 0; i < ms.length && !foundMatch; i++) {
if (ms[i].isAbstract() && ms[i].getParameterTypes().length == 0
&& ms[i].getName().equals(k)) {
// we might be onto something
String s = ms[i].getAnnotationDefaultValue();
if (s != null && s.equals(v)) {
foundMatch = true;
}
}
}
if (!foundMatch) {
return FuzzyBoolean.NO;
}
}
}
}
return FuzzyBoolean.YES;
}
toMatchAgainst = toMatchAgainst.getSuperclass();
}
}
} else {
// check parameter annotations
if (parameterAnnotations == null) {
return FuzzyBoolean.NO;
}
for (ResolvedType parameterAnnotation : parameterAnnotations) {
if (annotationType.equals(parameterAnnotation)) {
// Are we also matching annotation values?
if (annotationValues != null) {
parameterAnnotation
.getWorld()
.getMessageHandler()
.handleMessage(
MessageUtil
.error("Compiler limitation: annotation value matching for parameter annotations not yet supported"));
return FuzzyBoolean.NO;
}
return FuzzyBoolean.YES;
}
}
}
return FuzzyBoolean.NO;
}
// this version should be called for @this, @target, @args
public FuzzyBoolean matchesRuntimeType(AnnotatedElement annotated) {
if (getResolvedAnnotationType().isInheritedAnnotation()) {
// a static match is good enough
if (matches(annotated).alwaysTrue()) {
return FuzzyBoolean.YES;
}
}
// a subtype could match at runtime
return FuzzyBoolean.MAYBE;
}
@Override
public void resolve(World world) {
if (!resolved) {
annotationType = annotationType.resolve(world);
resolved = true;
}
}
/*
* (non-Javadoc)
*
* @see org.aspectj.weaver.patterns.AnnotationTypePattern#resolveBindings(org .aspectj.weaver.patterns.IScope,
* org.aspectj.weaver.patterns.Bindings, boolean)
*/
@Override
public AnnotationTypePattern resolveBindings(IScope scope, Bindings bindings, boolean allowBinding) {
if (resolved) {
return this;
}
resolved = true;
String simpleName = maybeGetSimpleName();
if (simpleName != null) {
FormalBinding formalBinding = scope.lookupFormal(simpleName);
if (formalBinding != null) {
if (bindings == null) {
scope.message(IMessage.ERROR, this, "negation doesn't allow binding");
return this;
}
if (!allowBinding) {
scope.message(IMessage.ERROR, this, "name binding only allowed in @pcds, args, this, and target");
return this;
}
formalName = simpleName;
bindingPattern = true;
verifyIsAnnotationType(formalBinding.getType().resolve(scope.getWorld()), scope);
BindingAnnotationTypePattern binding = new BindingAnnotationTypePattern(formalBinding);
binding.copyLocationFrom(this);
bindings.register(binding, scope);
binding.resolveBinding(scope.getWorld());
if (isForParameterAnnotationMatch()) {
binding.setForParameterAnnotationMatch();
}
return binding;
}
}
// Non binding case
String cleanname = annotationType.getName();
annotationType = scope.getWorld().resolve(annotationType, true);
// We may not have found it if it is in a package, lets look it up...
if (ResolvedType.isMissing(annotationType)) {
UnresolvedType type = null;
while (ResolvedType.isMissing(type = scope.lookupType(cleanname, this))) {
int lastDot = cleanname.lastIndexOf('.');
if (lastDot == -1) {
break;
}
cleanname = cleanname.substring(0, lastDot) + "$" + cleanname.substring(lastDot + 1);
}
annotationType = scope.getWorld().resolve(type, true);
}
verifyIsAnnotationType((ResolvedType) annotationType, scope);
return this;
}
@Override
public AnnotationTypePattern parameterizeWith(Map<String,UnresolvedType> typeVariableMap, World w) {
UnresolvedType newAnnotationType = annotationType;
if (annotationType.isTypeVariableReference()) {
TypeVariableReference t = (TypeVariableReference) annotationType;
String key = t.getTypeVariable().getName();
if (typeVariableMap.containsKey(key)) {
newAnnotationType = typeVariableMap.get(key);
}
} else if (annotationType.isParameterizedType()) {
newAnnotationType = annotationType.parameterize(typeVariableMap);
}
ExactAnnotationTypePattern ret = new ExactAnnotationTypePattern(newAnnotationType, annotationValues);
ret.formalName = formalName;
ret.bindingPattern = bindingPattern;
ret.copyLocationFrom(this);
if (isForParameterAnnotationMatch()) {
ret.setForParameterAnnotationMatch();
}
return ret;
}
protected String maybeGetSimpleName() {
if (formalName != null) {
return formalName;
}
String ret = annotationType.getName();
return (ret.indexOf('.') == -1) ? ret : null;
}
protected void verifyIsAnnotationType(ResolvedType type, IScope scope) {
if (!type.isAnnotation()) {
IMessage m = MessageUtil.error(WeaverMessages.format(WeaverMessages.REFERENCE_TO_NON_ANNOTATION_TYPE, type.getName()),
getSourceLocation());
scope.getWorld().getMessageHandler().handleMessage(m);
resolved = false;
}
}
private static byte VERSION = 1; // rev if serialisation form changes
/*
* (non-Javadoc)
*
* @see org.aspectj.weaver.patterns.PatternNode#write(java.io.DataOutputStream)
*/
@Override
public void write(CompressingDataOutputStream s) throws IOException {
s.writeByte(AnnotationTypePattern.EXACT);
s.writeByte(VERSION);
s.writeBoolean(bindingPattern);
if (bindingPattern) {
s.writeUTF(formalName);
} else {
annotationType.write(s);
}
writeLocation(s);
s.writeBoolean(isForParameterAnnotationMatch());
if (annotationValues == null) {
s.writeInt(0);
} else {
s.writeInt(annotationValues.size());
Set<String> key = annotationValues.keySet();
for (String k : key) {
s.writeUTF(k);
s.writeUTF(annotationValues.get(k));
}
}
}
public static AnnotationTypePattern read(VersionedDataInputStream s, ISourceContext context) throws IOException {
ExactAnnotationTypePattern ret;
byte version = s.readByte();
if (version > VERSION) {
throw new BCException("ExactAnnotationTypePattern was written by a newer version of AspectJ");
}
boolean isBindingPattern = s.readBoolean();
if (isBindingPattern) {
ret = new ExactAnnotationTypePattern(s.readUTF());
} else {
ret = new ExactAnnotationTypePattern(UnresolvedType.read(s));
}
ret.readLocation(context, s);
if (s.getMajorVersion() >= WeaverVersionInfo.WEAVER_VERSION_MAJOR_AJ160) {
if (s.readBoolean()) {
ret.setForParameterAnnotationMatch();
}
}
if (s.getMajorVersion() >= WeaverVersionInfo.WEAVER_VERSION_MAJOR_AJ160M2) {
int annotationValueCount = s.readInt();
if (annotationValueCount > 0) {
Map<String, String> aValues = new HashMap<>();
for (int i = 0; i < annotationValueCount; i++) {
String key = s.readUTF();
String val = s.readUTF();
aValues.put(key, val);
}
ret.annotationValues = aValues;
}
}
return ret;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ExactAnnotationTypePattern)) {
return false;
}
ExactAnnotationTypePattern other = (ExactAnnotationTypePattern) obj;
return (other.annotationType.equals(annotationType))
&& isForParameterAnnotationMatch() == other.isForParameterAnnotationMatch()
&& (annotationValues == null ? other.annotationValues == null : annotationValues.equals(other.annotationValues));
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return (((annotationType.hashCode()) * 37 + (isForParameterAnnotationMatch() ? 0 : 1)) * 37)
+ (annotationValues == null ? 0 : annotationValues.hashCode());
}
@Override
public String toString() {
if (!resolved && formalName != null) {
return formalName;
}
String ret = "@" + annotationType.toString();
if (formalName != null) {
ret = ret + " " + formalName;
}
return ret;
}
@Override
public Object accept(PatternNodeVisitor visitor, Object data) {
return visitor.visit(this, data);
}
}