| /******************************************************************************* |
| * Copyright (c) 2005 BEA Systems, Inc. |
| * 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: |
| * tyeung@bea.com - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.apt.core.internal.util; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.jdt.core.dom.ASTNode; |
| import org.eclipse.jdt.core.dom.ASTVisitor; |
| import org.eclipse.jdt.core.dom.Annotation; |
| import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; |
| import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; |
| import org.eclipse.jdt.core.dom.Block; |
| import org.eclipse.jdt.core.dom.BodyDeclaration; |
| import org.eclipse.jdt.core.dom.DoStatement; |
| import org.eclipse.jdt.core.dom.EnumConstantDeclaration; |
| import org.eclipse.jdt.core.dom.EnumDeclaration; |
| import org.eclipse.jdt.core.dom.FieldDeclaration; |
| import org.eclipse.jdt.core.dom.ForStatement; |
| import org.eclipse.jdt.core.dom.IExtendedModifier; |
| import org.eclipse.jdt.core.dom.IfStatement; |
| import org.eclipse.jdt.core.dom.MarkerAnnotation; |
| import org.eclipse.jdt.core.dom.MethodDeclaration; |
| import org.eclipse.jdt.core.dom.NormalAnnotation; |
| import org.eclipse.jdt.core.dom.SingleMemberAnnotation; |
| import org.eclipse.jdt.core.dom.SingleVariableDeclaration; |
| import org.eclipse.jdt.core.dom.TryStatement; |
| import org.eclipse.jdt.core.dom.TypeDeclaration; |
| |
| /** |
| * Home for ast visitors of various flavors. |
| */ |
| public class Visitors { |
| |
| /** |
| * Traverse the ast looking for annotations at the declaration level. |
| * This visitor only operates at the declaration level. Method body |
| * and field initializers and static block will be ignored. |
| */ |
| public static final class AnnotationVisitor extends ASTVisitor |
| { |
| private final List<Annotation> _annotations; |
| /** |
| * @param annotations to be populated by this visitor |
| */ |
| public AnnotationVisitor(final List<Annotation> annotations) |
| { _annotations = annotations; } |
| |
| public boolean visit(MarkerAnnotation annotation) |
| { |
| _annotations.add(annotation); |
| return false; |
| } |
| |
| public boolean visit(SingleMemberAnnotation annotation) |
| { |
| _annotations.add(annotation); |
| return false; |
| } |
| |
| public boolean visit(NormalAnnotation annotation) |
| { |
| _annotations.add(annotation); |
| return false; |
| } |
| |
| // make sure we don't hit Arguments other than formal parameters. |
| public boolean visit(Block blk){ return false; } |
| public boolean visit(DoStatement doStatement){ return false; } |
| public boolean visit(ForStatement forStatement){ return false; } |
| public boolean visit(IfStatement ifStatement){ return false; } |
| public boolean visit(TryStatement tryStatement){ return false; } |
| |
| public void reset(){ _annotations.clear(); } |
| } |
| |
| /** |
| * Locate all the annotations and the declaration that they annotate. |
| * This visitor only operates at the declaration level. Method body |
| * and field initializers and static block will be ignored. |
| */ |
| public static final class AnnotatedNodeVisitor extends ASTVisitor |
| { |
| private final Map<ASTNode, List<Annotation>> _result; |
| |
| /** |
| * @param map to be populated by this visitor. |
| * Key is the declaration ast node and the value is the list |
| * of annotation ast nodes that annotate the declaration. |
| */ |
| public AnnotatedNodeVisitor(Map<ASTNode, List<Annotation>> map) |
| { |
| _result = map; |
| } |
| |
| /** |
| * visit package declaration |
| */ |
| public boolean visit(org.eclipse.jdt.core.dom.PackageDeclaration node) |
| { |
| final List<Annotation> annotations = node.annotations(); |
| if( !annotations.isEmpty() ) |
| _result.put(node, annotations); |
| |
| return false; |
| } |
| |
| /** |
| * visit class and interface declaration |
| */ |
| public boolean visit(org.eclipse.jdt.core.dom.TypeDeclaration node) |
| { |
| visitBodyDeclaration(node); |
| return true; |
| } |
| |
| /** |
| * visit annotation type declaration |
| */ |
| public boolean visit(org.eclipse.jdt.core.dom.AnnotationTypeDeclaration node) |
| { |
| visitBodyDeclaration(node); |
| return true; |
| } |
| |
| /** |
| * visit enum type declaration |
| */ |
| public boolean visit(org.eclipse.jdt.core.dom.EnumDeclaration node) |
| { |
| visitBodyDeclaration(node); |
| return true; |
| } |
| |
| /** |
| * visit field declaration |
| */ |
| public boolean visit(org.eclipse.jdt.core.dom.FieldDeclaration node) |
| { |
| visitBodyDeclaration(node); |
| return true; |
| } |
| |
| /** |
| * visit enum constant declaration |
| */ |
| public boolean visit(org.eclipse.jdt.core.dom.EnumConstantDeclaration node) |
| { |
| visitBodyDeclaration(node); |
| return true; |
| } |
| |
| /** |
| * visit method declaration |
| */ |
| public boolean visit(MethodDeclaration node) |
| { |
| visitBodyDeclaration(node); |
| return true; |
| } |
| |
| /** |
| * visit annotation type member |
| */ |
| public boolean visit(AnnotationTypeMemberDeclaration node) |
| { |
| visitBodyDeclaration(node); |
| return true; |
| } |
| |
| private void visitBodyDeclaration(final BodyDeclaration node) |
| { |
| final List<IExtendedModifier> extMods = node.modifiers(); |
| List<Annotation> annos = null; |
| for( IExtendedModifier extMod : extMods ){ |
| if( extMod.isAnnotation() ){ |
| if( annos == null ){ |
| annos = new ArrayList<Annotation>(2); |
| _result.put(node, annos); |
| } |
| annos.add((Annotation)extMod); |
| } |
| } |
| } |
| |
| /** |
| * visiting formal parameter declaration. |
| */ |
| public boolean visit(SingleVariableDeclaration node) |
| { |
| final List<IExtendedModifier> extMods = node.modifiers(); |
| List<Annotation> annos = null; |
| for( IExtendedModifier extMod : extMods ){ |
| if( extMod.isAnnotation() ){ |
| if( annos == null ){ |
| annos = new ArrayList<Annotation>(2); |
| _result.put(node, annos); |
| } |
| annos.add((Annotation)extMod); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * @return false so we skip everything beyond declaration level. |
| */ |
| public boolean visit(Block node) |
| { // so we don't look into anything beyond declaration level. |
| return false; |
| } |
| public boolean visit(MarkerAnnotation node){ return false; } |
| public boolean visit(NormalAnnotation node){ return false; } |
| public boolean visit(SingleMemberAnnotation node){ return false; } |
| } |
| |
| /** |
| * Given an annotation locate the declaration that its annotates. |
| * This visitor only operates at the declaration level. Method body |
| * and field initializers and static block will be ignored. |
| * |
| */ |
| public static final class DeclarationFinder extends ASTVisitor |
| { |
| private final Annotation _anno; |
| // The declaration, could be a body declaration or a parameter |
| // could also remain null if the annotation doesn't actually |
| // annotates anything. |
| private ASTNode _result = null; |
| public DeclarationFinder(final Annotation annotation) |
| { |
| _anno = annotation; |
| } |
| |
| /** |
| * @return back the result of the search. |
| */ |
| public ASTNode getAnnotatedNode(){return _result;} |
| |
| /** |
| * We only visit nodes that can have annotations on them |
| */ |
| public boolean visit(AnnotationTypeDeclaration node) { |
| return internalVisit(node); |
| } |
| |
| public boolean visit(AnnotationTypeMemberDeclaration node) { |
| return internalVisit(node); |
| } |
| |
| public boolean visit(EnumDeclaration node) { |
| return internalVisit(node); |
| } |
| |
| public boolean visit(EnumConstantDeclaration node) { |
| return internalVisit(node); |
| } |
| |
| public boolean visit(FieldDeclaration node) { |
| return internalVisit(node); |
| } |
| |
| public boolean visit(MethodDeclaration node) { |
| return internalVisit(node); |
| } |
| |
| public boolean visit(TypeDeclaration node) { |
| return internalVisit(node); |
| } |
| |
| public boolean visit(SingleVariableDeclaration node) { |
| return internalVisit(node); |
| } |
| |
| private boolean internalVisit(ASTNode node) { |
| // terminate the search. |
| if( _result != null ) return false; |
| int nodeStart = node.getStartPosition(); |
| int nodeEnd = nodeStart + node.getLength(); |
| int annoStart = _anno.getStartPosition(); |
| int annoEnd = annoStart + _anno.getLength(); |
| |
| if (nodeStart > annoEnd) { |
| // We've passed our position. No need to search any further |
| return false; |
| } |
| if (nodeEnd > annoStart) { // nodeStart <= annoEnd && nodeEnd > annoStart |
| // This annotation declaration surrounds the offset |
| List<IExtendedModifier> extendedModifiers; |
| if (node.getNodeType() == ASTNode.SINGLE_VARIABLE_DECLARATION) { |
| SingleVariableDeclaration declaration = (SingleVariableDeclaration)node; |
| extendedModifiers = declaration.modifiers(); |
| } |
| else { |
| BodyDeclaration declaration = (BodyDeclaration)node; |
| extendedModifiers = declaration.modifiers(); |
| } |
| for (IExtendedModifier modifier : extendedModifiers) { |
| // found what we came to look for. |
| if( modifier == _anno ){ |
| _result = node; |
| return false; |
| } |
| } |
| } |
| |
| // Keep searching |
| return true; |
| } |
| |
| /** |
| * @return false so we skip everything beyond declaration level. |
| */ |
| public boolean visit(Block node) |
| { // so we don't look into anything beyond declaration level. |
| return false; |
| } |
| public boolean visit(MarkerAnnotation node){ return false; } |
| public boolean visit(NormalAnnotation node){ return false; } |
| public boolean visit(SingleMemberAnnotation node){ return false; } |
| } |
| /** |
| * Responsible for finding the ending offset of the tighest ast node match that starts |
| * at a given offset. This ast visitor can operator on an array of offsets in one pass. |
| * @author tyeung |
| */ |
| public static class EndingOffsetFinder extends ASTVisitor |
| { |
| private final int[] _sortedStartingOffset; |
| /** |
| * parallel to <code>_sortedOffsets</code> and contains |
| * the ending offset of the ast node that has the tightest match for the |
| * corresponding starting offset. |
| */ |
| private final int[] _endingOffsets; |
| |
| /** |
| * @param offsets the array of offsets which will be sorted. |
| * @throws IllegalArgumentException if <code>offsets</code> is <code>null</code>. |
| */ |
| public EndingOffsetFinder(int[] offsets) |
| { |
| if(offsets == null) |
| throw new IllegalArgumentException("argument cannot be null."); //$NON-NLS-1$ |
| // sort the array first |
| Arrays.sort(offsets); |
| |
| // look for duplicates. |
| int count = 0; |
| for( int i=0, len=offsets.length; i<len; i++){ |
| if( i == 0 ) ; // do nothing |
| else if( offsets[i-1] == offsets[i] ) |
| continue; |
| count ++; |
| } |
| |
| if( count != offsets.length ){ |
| _sortedStartingOffset = new int[count]; |
| |
| int index = 0; |
| for( int i=0, len=offsets.length; i<len; i++){ |
| if( i != 0 && offsets[i-1] == offsets[i] ) |
| continue; |
| _sortedStartingOffset[index++] = offsets[i]; |
| } |
| } |
| else{ |
| _sortedStartingOffset = offsets; |
| } |
| |
| _endingOffsets = new int[count]; |
| for( int i=0; i<count; i++ ) |
| _endingOffsets[i] = 0; |
| } |
| |
| public void preVisit(ASTNode node) |
| { |
| final int startingOffset = node.getStartPosition(); |
| final int endingOffset = startingOffset + node.getLength(); |
| // starting offset is inclusive |
| int startIndex = Arrays.binarySearch(_sortedStartingOffset, startingOffset); |
| // ending offset is exclusive |
| int endIndex = Arrays.binarySearch(_sortedStartingOffset, endingOffset); |
| if( startIndex < 0 ) |
| startIndex = - startIndex - 1; |
| if( endIndex < 0 ) |
| endIndex = - endIndex - 1; |
| else |
| // endIndex needs to be exclusive and we want to |
| // include the 'endIndex'th entry in our computation. |
| endIndex ++; |
| if( startIndex >= _sortedStartingOffset.length ) |
| return; |
| |
| for( int i=startIndex; i<endIndex; i++ ){ |
| if( _endingOffsets[i] == 0 ) |
| _endingOffsets[i] = endingOffset; |
| else if( endingOffset < _endingOffsets[i] ) |
| _endingOffsets[i] = endingOffset; |
| } |
| } |
| |
| |
| public int getEndingOffset(final int startingOffset) |
| { |
| int index = Arrays.binarySearch(_sortedStartingOffset, startingOffset); |
| if( index == -1 ) return 0; |
| return _endingOffsets[index]; |
| } |
| } |
| } |