/*=============================================================================#
# Copyright (c) 2007-2016 Stephan Wahlbrink (WalWare.de) 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:
# Stephan Wahlbrink - initial API and implementation
#=============================================================================*/
package de.walware.ecommons.ltk.ast;
import java.lang.reflect.InvocationTargetException;
/**
* Converts source range to a selection of AST nodes.
*/
public class AstSelection {
/** Selects the node, greater than the selected range */
public static final int MODE_COVERING_GREATER= 1;
/** Selects the outermost node, greater or equal than the selected range */
public static final int MODE_COVERING_SAME_FIRST= 2;
/** Selects the innermost node, greater or equal than the selected range */
public static final int MODE_COVERING_SAME_LAST= 3;
private static final int SEARCH_STATE_BEFORE= -1;
private static final int SEARCH_STATE_MATCH= 0;
private static final int SEARCH_STATE_MATCHED= 1;
private static final int SEARCH_STATE_BEHIND= 2;
private int fStart;
private int fStop;
private IAstNode fLastCovering;
private IAstNode fBeforeChild;
private IAstNode fFirstChild;
private IAstNode fLastChild;
private IAstNode fAfterChild;
private class CoveringGreaterFinder implements ICommonAstVisitor {
private int fInCovering = SEARCH_STATE_BEFORE;
@Override
public void visit(final IAstNode node) throws InvocationTargetException {
if (fInCovering >= SEARCH_STATE_BEHIND) {
return;
}
if ((node.getOffset() < fStart && fStop <= node.getEndOffset())
|| (node.getOffset() == fStart && fStop < node.getEndOffset())) {
// covering
clearChilds();
fLastCovering = node;
fInCovering = SEARCH_STATE_MATCH;
node.acceptInChildren(this);
fInCovering = (fStart == fStop && node.getEndOffset() == fStop) ? SEARCH_STATE_MATCHED : SEARCH_STATE_BEHIND;
return;
}
if (fInCovering == SEARCH_STATE_MATCH) {
checkChild(node);
return;
}
if (fInCovering == SEARCH_STATE_MATCHED) {
fInCovering = SEARCH_STATE_BEHIND;
}
}
}
private class CoveringSameFirstFinder implements ICommonAstVisitor {
private int fInCovering = SEARCH_STATE_BEFORE;
@Override
public void visit(final IAstNode node) throws InvocationTargetException {
if (fInCovering >= SEARCH_STATE_BEHIND) {
return;
}
if (node.getOffset() <= fStart && fStop <= node.getEndOffset()) {
// covering
clearChilds();
fLastCovering = node;
if (node.getOffset() != fStart || fStop != node.getEndOffset()) {
fInCovering = SEARCH_STATE_MATCH;
node.acceptInChildren(this);
}
fInCovering = SEARCH_STATE_BEHIND;
return;
}
if (fInCovering == SEARCH_STATE_MATCH) {
checkChild(node);
return;
}
}
}
private class CoveringSameLastFinder implements ICommonAstVisitor {
private int fInCovering = SEARCH_STATE_BEFORE;
@Override
public void visit(final IAstNode node) throws InvocationTargetException {
if (fInCovering >= SEARCH_STATE_BEHIND) {
return;
}
if (node.getOffset() <= fStart && fStop <= node.getEndOffset()) {
// covering
clearChilds();
fLastCovering = node;
fInCovering = SEARCH_STATE_MATCH;
node.acceptInChildren(this);
fInCovering = SEARCH_STATE_BEHIND;
return;
}
if (fInCovering == SEARCH_STATE_MATCH) {
checkChild(node);
return;
}
}
}
AstSelection() {
}
protected final void clearChilds() {
fBeforeChild = null;
fFirstChild = null;
fLastChild = null;
fAfterChild = null;
}
protected final void checkChild(final IAstNode node) {
if (node.getEndOffset() < fStart) {
fBeforeChild = node;
return;
}
if (node.getOffset() > fStop) {
if (fAfterChild == null) {
fAfterChild = node;
}
return;
}
// touching
if (fFirstChild == null) {
fFirstChild = node;
}
fLastChild = node;
}
public static AstSelection search(final IAstNode rootNode, final int start, final int stop, final int mode) {
final AstSelection selection = new AstSelection();
selection.fStart = start;
selection.fStop = stop;
ICommonAstVisitor finder;
switch (mode) {
case MODE_COVERING_GREATER:
finder = selection.new CoveringGreaterFinder();
break;
case MODE_COVERING_SAME_FIRST:
finder = selection.new CoveringSameFirstFinder();
break;
case MODE_COVERING_SAME_LAST:
finder = selection.new CoveringSameLastFinder();
break;
default:
throw new IllegalArgumentException("Wrong search mode"); //$NON-NLS-1$
}
try {
finder.visit(rootNode);
} catch (final InvocationTargetException e) {
}
return selection;
}
public int getStartOffset() {
return fStart;
}
public int getStopOffset() {
return fStop;
}
public final IAstNode getCovering() {
return fLastCovering;
}
public final IAstNode getChildBefore() {
return fBeforeChild;
}
public final IAstNode getChildFirstTouching() {
return fFirstChild;
}
public final IAstNode getChildLastTouching() {
return fLastChild;
}
public final IAstNode getChildAfter() {
return fAfterChild;
}
}