/*******************************************************************************
* Copyright (c) 2008, 2010 Wind River Systems, Inc. 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:
* Markus Schorn - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.internal.core.dom.parser;
import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
import org.eclipse.cdt.core.dom.ast.IASTImageLocation;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroExpansion;
/**
* For searching ast-nodes by offset and length, instances of this class can be used to
* determine whether a node matches or not.
*
* @since 5.0
*/
public class ASTNodeSpecification<T extends IASTNode> {
public enum Relation { FIRST_CONTAINED, EXACT_MATCH, ENCLOSING, STRICTLY_ENCLOSING }
private final Class<T> fClass;
private final Relation fRelation;
private final int fFileOffset;
private final int fFileEndOffset;
private int fSeqNumber;
private int fSeqEndNumber;
private int fBestOffset;
private int fBestEndOffset;
private T fBestNode;
private boolean fSearchInExpansion;
private boolean fZeroToLeft= false;
public ASTNodeSpecification(Relation relation, Class<T> clazz, int fileOffset, int fileLength) {
fRelation= relation;
fClass= clazz;
fFileOffset= fileOffset;
fFileEndOffset= fileOffset + fileLength;
}
public void setRangeInSequence(int offsetInSeq, int lengthInSeq) {
fSeqNumber= offsetInSeq;
fSeqEndNumber= offsetInSeq + lengthInSeq;
}
public void setRangeInSequence(int offsetInSeq, int lengthInSeq, boolean zeroRangeToLeft) {
setRangeInSequence(offsetInSeq, lengthInSeq);
fZeroToLeft= zeroRangeToLeft;
}
public void setSearchInExpansion(boolean searchInExpansion) {
fSearchInExpansion= searchInExpansion;
}
public Relation getRelationToSelection() {
return fRelation;
}
public int getSequenceStart() {
return fSeqNumber;
}
public int getSequenceEnd() {
return fSeqEndNumber;
}
public T getBestNode() {
return fBestNode;
}
public boolean requiresClass(Class<? extends IASTNode> clazz) {
return clazz.isAssignableFrom(fClass);
}
@SuppressWarnings("unchecked")
public void visit(ASTNode astNode) {
if (isAcceptableNode(astNode) && isMatchingRange(astNode.getOffset(), astNode.getLength(), fSeqNumber, fSeqEndNumber)) {
IASTFileLocation loc= astNode.getFileLocation();
if (loc != null) {
storeIfBest(loc, (T) astNode);
}
}
}
@SuppressWarnings("unchecked")
public void visit(ASTNode astNode, IASTImageLocation imageLocation) {
if (isAcceptableNode(astNode) && imageLocation != null) {
if (isMatchingRange(imageLocation.getNodeOffset(), imageLocation.getNodeLength(), fFileOffset, fFileEndOffset)) {
storeIfBest(imageLocation, (T) astNode);
}
}
}
private boolean isMatchingRange(int offset, int length, int selOffset, int selEndOffset) {
final int endOffset= offset+length;
switch(fRelation) {
case EXACT_MATCH:
return selOffset == offset && selEndOffset == endOffset;
case FIRST_CONTAINED:
return selOffset <= offset && endOffset <= selEndOffset;
case ENCLOSING:
return offset <= selOffset && selEndOffset <= endOffset;
case STRICTLY_ENCLOSING:
if (offset <= selOffset && selEndOffset <= endOffset) {
return offset != selOffset || selEndOffset != endOffset;
}
return false;
}
assert false;
return false;
}
public boolean isAcceptableNode(IASTNode astNode) {
if (astNode == null || !fClass.isAssignableFrom(astNode.getClass()))
return false;
if (fSearchInExpansion) {
IASTNode check= astNode instanceof IASTName ? astNode.getParent() : astNode;
if (check instanceof IASTPreprocessorMacroExpansion) {
return false;
}
}
return true;
}
/**
* Returns whether the node can contain matches in its range.
*/
public boolean canContainMatches(ASTNode node) {
final int offset= node.getOffset();
final int endOffset= offset+node.getLength();
switch(fRelation) {
case EXACT_MATCH:
case ENCLOSING:
return offset <= fSeqNumber && fSeqEndNumber <= endOffset;
case STRICTLY_ENCLOSING:
if (offset <= fSeqNumber && fSeqEndNumber <= endOffset) {
return offset != fSeqNumber || fSeqEndNumber != endOffset;
}
return false;
case FIRST_CONTAINED:
return offset <= fSeqEndNumber && fSeqNumber <= endOffset;
}
assert false;
return false;
}
private void storeIfBest(IASTFileLocation loc, T astNode) {
if (loc != null) {
final int offset = loc.getNodeOffset();
final int length = loc.getNodeLength();
if (isBetterMatch(offset, length, astNode)) {
fBestNode= astNode;
fBestOffset= offset;
fBestEndOffset= offset+length;
}
}
}
/**
* Assuming that the given range matches, this method returns whether the match is better
* than the best match stored.
*/
private boolean isBetterMatch(int offset, int length, IASTNode cand) {
if (fBestNode == null) {
return true;
}
final int endOffset= offset+length;
switch(fRelation) {
case EXACT_MATCH:
return isParent(fBestNode, cand);
case FIRST_CONTAINED:
if (offset < fBestOffset) {
return true;
}
if (offset == fBestOffset) {
if (endOffset < fBestEndOffset) {
return true;
}
return endOffset == fBestEndOffset && isParent(fBestNode, cand);
}
return false;
case ENCLOSING:
case STRICTLY_ENCLOSING:
final int bestLength= fBestEndOffset-fBestOffset;
if (length < bestLength) {
return true;
}
return length == bestLength && isParent(fBestNode, cand);
default:
assert false;
return false;
}
}
private boolean isParent(IASTNode cand1, IASTNode cand2) {
while(cand2 != null) {
if (cand2 == cand1) {
return true;
}
cand2= cand2.getParent();
}
return false;
}
public IASTPreprocessorMacroExpansion findLeadingMacroExpansion(ASTNodeSelector nodeSelector) {
IASTPreprocessorMacroExpansion exp= nodeSelector.findEnclosingMacroExpansion(fZeroToLeft ? fFileOffset-1 : fFileOffset, 1);
if (fRelation == Relation.ENCLOSING || fRelation == Relation.STRICTLY_ENCLOSING)
return exp;
if (exp != null) {
IASTFileLocation loc= exp.getFileLocation();
if (loc != null) {
final int offset= loc.getNodeOffset();
final int endOffset= offset+loc.getNodeLength();
if (offset == fFileOffset && endOffset <= fFileEndOffset)
return exp;
}
}
return null;
}
public IASTPreprocessorMacroExpansion findTrailingMacroExpansion(ASTNodeSelector nodeSelector) {
IASTPreprocessorMacroExpansion exp= nodeSelector.findEnclosingMacroExpansion(fFileEndOffset==fFileOffset && !fZeroToLeft ? fFileEndOffset : fFileEndOffset-1, 1);
if (fRelation == Relation.ENCLOSING || fRelation == Relation.STRICTLY_ENCLOSING)
return exp;
if (exp != null) {
IASTFileLocation loc= exp.getFileLocation();
if (loc != null) {
final int offset= loc.getNodeOffset();
final int endOffset= offset+loc.getNodeLength();
if (endOffset == fFileEndOffset && offset >= fFileOffset)
return exp;
}
}
return null;
}
}