/******************************************************************************* * Copyright (c) 2000, 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.ASTVisitor; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.impl.Constant; import org.eclipse.jdt.internal.compiler.impl.IrritantSet; import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding; import org.eclipse.jdt.internal.compiler.lookup.Binding; import org.eclipse.jdt.internal.compiler.lookup.BlockScope; import org.eclipse.jdt.internal.compiler.lookup.ElementValuePair; import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; import org.eclipse.jdt.internal.compiler.lookup.PackageBinding; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; import org.eclipse.jdt.internal.compiler.lookup.Scope; import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TagBits; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; /** * Annotation */ public abstract class Annotation extends Expression { final static MemberValuePair[] NoValuePairs= new MemberValuePair[0]; public int declarationSourceEnd; public Binding recipient; public TypeReference type; /** * The representation of this annotation in the type system. */ private AnnotationBinding compilerAnnotation= null; public static long getRetentionPolicy(char[] policyName) { if (policyName == null || policyName.length == 0) return 0; switch (policyName[0]) { case 'C': if (CharOperation.equals(policyName, TypeConstants.UPPER_CLASS)) return TagBits.AnnotationClassRetention; break; case 'S': if (CharOperation.equals(policyName, TypeConstants.UPPER_SOURCE)) return TagBits.AnnotationSourceRetention; break; case 'R': if (CharOperation.equals(policyName, TypeConstants.UPPER_RUNTIME)) return TagBits.AnnotationRuntimeRetention; break; } return 0; // unknown } public static long getTargetElementType(char[] elementName) { if (elementName == null || elementName.length == 0) return 0; switch (elementName[0]) { case 'A': if (CharOperation.equals(elementName, TypeConstants.UPPER_ANNOTATION_TYPE)) return TagBits.AnnotationForAnnotationType; break; case 'C': if (CharOperation.equals(elementName, TypeConstants.UPPER_CONSTRUCTOR)) return TagBits.AnnotationForConstructor; break; case 'F': if (CharOperation.equals(elementName, TypeConstants.UPPER_FIELD)) return TagBits.AnnotationForField; break; case 'L': if (CharOperation.equals(elementName, TypeConstants.UPPER_LOCAL_VARIABLE)) return TagBits.AnnotationForLocalVariable; break; case 'M': if (CharOperation.equals(elementName, TypeConstants.UPPER_METHOD)) return TagBits.AnnotationForMethod; break; case 'P': if (CharOperation.equals(elementName, TypeConstants.UPPER_PARAMETER)) return TagBits.AnnotationForParameter; else if (CharOperation.equals(elementName, TypeConstants.UPPER_PACKAGE)) return TagBits.AnnotationForPackage; break; case 'T': if (CharOperation.equals(elementName, TypeConstants.TYPE)) return TagBits.AnnotationForType; break; } return 0; // unknown } public ElementValuePair[] computeElementValuePairs() { return Binding.NO_ELEMENT_VALUE_PAIRS; } /** * Compute the bit pattern for recognized standard annotations the compiler may need to act upon */ private long detectStandardAnnotation(Scope scope, ReferenceBinding annotationType, MemberValuePair valueAttribute) { long tagBits= 0; switch (annotationType.id) { // retention annotation case TypeIds.T_JavaLangAnnotationRetention: if (valueAttribute != null) { Expression expr= valueAttribute.value; if ((expr.bits & Binding.VARIABLE) == Binding.FIELD) { FieldBinding field= ((Reference)expr).fieldBinding(); if (field != null && field.declaringClass.id == T_JavaLangAnnotationRetentionPolicy) { tagBits|= getRetentionPolicy(field.name); } } } break; // target annotation case TypeIds.T_JavaLangAnnotationTarget: tagBits|= TagBits.AnnotationTarget; // target specified (could be empty) if (valueAttribute != null) { Expression expr= valueAttribute.value; if (expr instanceof ArrayInitializer) { ArrayInitializer initializer= (ArrayInitializer)expr; final Expression[] expressions= initializer.expressions; if (expressions != null) { for (int i= 0, length= expressions.length; i < length; i++) { Expression initExpr= expressions[i]; if ((initExpr.bits & Binding.VARIABLE) == Binding.FIELD) { FieldBinding field= ((Reference)initExpr).fieldBinding(); if (field != null && field.declaringClass.id == T_JavaLangAnnotationElementType) { long element= getTargetElementType(field.name); if ((tagBits & element) != 0) { scope.problemReporter().duplicateTargetInTargetAnnotation(annotationType, (NameReference)initExpr); } else { tagBits|= element; } } } } } } else if ((expr.bits & Binding.VARIABLE) == Binding.FIELD) { FieldBinding field= ((Reference)expr).fieldBinding(); if (field != null && field.declaringClass.id == T_JavaLangAnnotationElementType) { tagBits|= getTargetElementType(field.name); } } } break; // marker annotations case TypeIds.T_JavaLangDeprecated: tagBits|= TagBits.AnnotationDeprecated; break; case TypeIds.T_JavaLangAnnotationDocumented: tagBits|= TagBits.AnnotationDocumented; break; case TypeIds.T_JavaLangAnnotationInherited: tagBits|= TagBits.AnnotationInherited; break; case TypeIds.T_JavaLangOverride: tagBits|= TagBits.AnnotationOverride; break; case TypeIds.T_JavaLangSuppressWarnings: tagBits|= TagBits.AnnotationSuppressWarnings; break; } return tagBits; } public AnnotationBinding getCompilerAnnotation() { return this.compilerAnnotation; } public abstract MemberValuePair[] memberValuePairs(); public StringBuffer printExpression(int indent, StringBuffer output) { output.append('@'); this.type.printExpression(0, output); return output; } public void recordSuppressWarnings(Scope scope, int startSuppresss, int endSuppress, boolean isSuppressingWarnings) { IrritantSet suppressWarningIrritants= null; MemberValuePair[] pairs= memberValuePairs(); pairLoop: for (int i= 0, length= pairs.length; i < length; i++) { MemberValuePair pair= pairs[i]; if (CharOperation.equals(pair.name, TypeConstants.VALUE)) { Expression value= pair.value; if (value instanceof ArrayInitializer) { ArrayInitializer initializer= (ArrayInitializer)value; Expression[] inits= initializer.expressions; if (inits != null) { for (int j= 0, initsLength= inits.length; j < initsLength; j++) { Constant cst= inits[j].constant; if (cst != Constant.NotAConstant && cst.typeID() == T_JavaLangString) { IrritantSet irritants= CompilerOptions.warningTokenToIrritants(cst.stringValue()); if (irritants != null) { if (suppressWarningIrritants == null) { suppressWarningIrritants= new IrritantSet(irritants); } else if (suppressWarningIrritants.set(irritants) == null) { scope.problemReporter().unusedWarningToken(inits[j]); } } else { scope.problemReporter().unhandledWarningToken(inits[j]); } } } } } else { Constant cst= value.constant; if (cst != Constant.NotAConstant && cst.typeID() == T_JavaLangString) { IrritantSet irritants= CompilerOptions.warningTokenToIrritants(cst.stringValue()); if (irritants != null) { suppressWarningIrritants= new IrritantSet(irritants); // TODO: should check for unused warning token against enclosing annotation as well ? } else { scope.problemReporter().unhandledWarningToken(value); } } } break pairLoop; } } if (isSuppressingWarnings && suppressWarningIrritants != null) { scope.referenceCompilationUnit().recordSuppressWarnings(suppressWarningIrritants, this, startSuppresss, endSuppress); } } public TypeBinding resolveType(BlockScope scope) { if (this.compilerAnnotation != null) return this.resolvedType; this.constant= Constant.NotAConstant; TypeBinding typeBinding= this.type.resolveType(scope); if (typeBinding == null) { return null; } this.resolvedType= typeBinding; // ensure type refers to an annotation type if (!typeBinding.isAnnotationType() && typeBinding.isValidBinding()) { scope.problemReporter().typeMismatchError(typeBinding, scope.getJavaLangAnnotationAnnotation(), this.type, null); return null; } ReferenceBinding annotationType= (ReferenceBinding)this.resolvedType; MethodBinding[] methods= annotationType.methods(); // clone valuePairs to keep track of unused ones MemberValuePair[] originalValuePairs= memberValuePairs(); MemberValuePair valueAttribute= null; // remember the first 'value' pair MemberValuePair[] pairs; int pairsLength= originalValuePairs.length; if (pairsLength > 0) { System.arraycopy(originalValuePairs, 0, pairs= new MemberValuePair[pairsLength], 0, pairsLength); } else { pairs= originalValuePairs; } nextMember: for (int i= 0, requiredLength= methods.length; i < requiredLength; i++) { MethodBinding method= methods[i]; char[] selector= method.selector; boolean foundValue= false; nextPair: for (int j= 0; j < pairsLength; j++) { MemberValuePair pair= pairs[j]; if (pair == null) continue nextPair; char[] name= pair.name; if (CharOperation.equals(name, selector)) { if (valueAttribute == null && CharOperation.equals(name, TypeConstants.VALUE)) { valueAttribute= pair; } pair.binding= method; pair.resolveTypeExpecting(scope, method.returnType); pairs[j]= null; // consumed foundValue= true; // check duplicates boolean foundDuplicate= false; for (int k= j + 1; k < pairsLength; k++) { MemberValuePair otherPair= pairs[k]; if (otherPair == null) continue; if (CharOperation.equals(otherPair.name, selector)) { foundDuplicate= true; scope.problemReporter().duplicateAnnotationValue(annotationType, otherPair); otherPair.binding= method; otherPair.resolveTypeExpecting(scope, method.returnType); pairs[k]= null; } } if (foundDuplicate) { scope.problemReporter().duplicateAnnotationValue(annotationType, pair); continue nextMember; } } } if (!foundValue && (method.modifiers & ClassFileConstants.AccAnnotationDefault) == 0 && (this.bits & IsRecovered) == 0) { scope.problemReporter().missingValueForAnnotationMember(this, selector); } } // check unused pairs for (int i= 0; i < pairsLength; i++) { if (pairs[i] != null) { scope.problemReporter().undefinedAnnotationValue(annotationType, pairs[i]); pairs[i].resolveTypeExpecting(scope, null); // resilient } } // if (scope.compilerOptions().storeAnnotations) this.compilerAnnotation= scope.environment().createAnnotation((ReferenceBinding)this.resolvedType, computeElementValuePairs()); // recognize standard annotations ? long tagBits= detectStandardAnnotation(scope, annotationType, valueAttribute); // record annotation positions in the compilation result scope.referenceCompilationUnit().recordSuppressWarnings(IrritantSet.NLS, null, this.sourceStart, this.declarationSourceEnd); if (this.recipient != null) { if (tagBits != 0) { // tag bits onto recipient switch (this.recipient.kind()) { case Binding.PACKAGE: ((PackageBinding)this.recipient).tagBits|= tagBits; break; case Binding.TYPE: case Binding.GENERIC_TYPE: SourceTypeBinding sourceType= (SourceTypeBinding)this.recipient; sourceType.tagBits|= tagBits; if ((tagBits & TagBits.AnnotationSuppressWarnings) != 0) { TypeDeclaration typeDeclaration= sourceType.scope.referenceContext; int start; if (scope.referenceCompilationUnit().types[0] == typeDeclaration) { start= 0; } else { start= typeDeclaration.declarationSourceStart; } recordSuppressWarnings(scope, start, typeDeclaration.declarationSourceEnd, scope.compilerOptions().suppressWarnings); } break; case Binding.METHOD: MethodBinding sourceMethod= (MethodBinding)this.recipient; sourceMethod.tagBits|= tagBits; if ((tagBits & TagBits.AnnotationSuppressWarnings) != 0) { sourceType= (SourceTypeBinding)sourceMethod.declaringClass; AbstractMethodDeclaration methodDeclaration= sourceType.scope.referenceContext.declarationOf(sourceMethod); recordSuppressWarnings(scope, methodDeclaration.declarationSourceStart, methodDeclaration.declarationSourceEnd, scope.compilerOptions().suppressWarnings); } break; case Binding.FIELD: FieldBinding sourceField= (FieldBinding)this.recipient; sourceField.tagBits|= tagBits; if ((tagBits & TagBits.AnnotationSuppressWarnings) != 0) { sourceType= (SourceTypeBinding)sourceField.declaringClass; FieldDeclaration fieldDeclaration= sourceType.scope.referenceContext.declarationOf(sourceField); recordSuppressWarnings(scope, fieldDeclaration.declarationSourceStart, fieldDeclaration.declarationSourceEnd, scope.compilerOptions().suppressWarnings); } break; case Binding.LOCAL: LocalVariableBinding variable= (LocalVariableBinding)this.recipient; variable.tagBits|= tagBits; if ((tagBits & TagBits.AnnotationSuppressWarnings) != 0) { LocalDeclaration localDeclaration= variable.declaration; recordSuppressWarnings(scope, localDeclaration.declarationSourceStart, localDeclaration.declarationSourceEnd, scope.compilerOptions().suppressWarnings); } break; } } // check (meta)target compatibility checkTargetCompatibility: { long metaTagBits= annotationType.getAnnotationTagBits(); // could be forward reference if ((metaTagBits & TagBits.AnnotationTargetMASK) == 0) // does not specify any target restriction break checkTargetCompatibility; switch (this.recipient.kind()) { case Binding.PACKAGE: if ((metaTagBits & TagBits.AnnotationForPackage) != 0) break checkTargetCompatibility; break; case Binding.TYPE: case Binding.GENERIC_TYPE: if (((ReferenceBinding)this.recipient).isAnnotationType()) { if ((metaTagBits & (TagBits.AnnotationForAnnotationType | TagBits.AnnotationForType)) != 0) break checkTargetCompatibility; } else if ((metaTagBits & TagBits.AnnotationForType) != 0) { break checkTargetCompatibility; } else if ((metaTagBits & TagBits.AnnotationForPackage) != 0) { if (CharOperation.equals(((ReferenceBinding)this.recipient).sourceName, TypeConstants.PACKAGE_INFO_NAME)) break checkTargetCompatibility; } break; case Binding.METHOD: if (((MethodBinding)this.recipient).isConstructor()) { if ((metaTagBits & TagBits.AnnotationForConstructor) != 0) break checkTargetCompatibility; } else if ((metaTagBits & TagBits.AnnotationForMethod) != 0) break checkTargetCompatibility; break; case Binding.FIELD: if ((metaTagBits & TagBits.AnnotationForField) != 0) break checkTargetCompatibility; break; case Binding.LOCAL: if ((((LocalVariableBinding)this.recipient).tagBits & TagBits.IsArgument) != 0) { if ((metaTagBits & TagBits.AnnotationForParameter) != 0) break checkTargetCompatibility; } else if ((annotationType.tagBits & TagBits.AnnotationForLocalVariable) != 0) break checkTargetCompatibility; break; } scope.problemReporter().disallowedTargetForAnnotation(this); } } return this.resolvedType; } public abstract void traverse(ASTVisitor visitor, BlockScope scope); }