blob: 0733dce83e146b11d09639f0a987d8af5470a375 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2007, 2018 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.ltk.ast.core.util;
import java.lang.reflect.InvocationTargetException;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.ltk.ast.core.IAstNode;
import org.eclipse.statet.ltk.ast.core.ICommonAstVisitor;
/**
* Converts source range to a selection of AST nodes.
*/
@NonNullByDefault
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 start;
private int stop;
private @Nullable IAstNode lastCovering;
private @Nullable IAstNode beforeChild;
private @Nullable IAstNode firstChild;
private @Nullable IAstNode lastChild;
private @Nullable IAstNode afterChild;
private class CoveringGreaterFinder implements ICommonAstVisitor {
private int inCovering= SEARCH_STATE_BEFORE;
@Override
public void visit(final IAstNode node) throws InvocationTargetException {
if (this.inCovering >= SEARCH_STATE_BEHIND) {
return;
}
if ((node.getOffset() < AstSelection.this.start && AstSelection.this.stop <= node.getEndOffset())
|| (node.getOffset() == AstSelection.this.start && AstSelection.this.stop < node.getEndOffset())) {
// covering
clearChilds();
AstSelection.this.lastCovering= node;
this.inCovering= SEARCH_STATE_MATCH;
node.acceptInChildren(this);
this.inCovering= (AstSelection.this.start == AstSelection.this.stop && node.getEndOffset() == AstSelection.this.stop) ? SEARCH_STATE_MATCHED : SEARCH_STATE_BEHIND;
return;
}
if (this.inCovering == SEARCH_STATE_MATCH) {
checkChild(node);
return;
}
if (this.inCovering == SEARCH_STATE_MATCHED) {
this.inCovering= SEARCH_STATE_BEHIND;
}
}
}
private class CoveringSameFirstFinder implements ICommonAstVisitor {
private int inCovering= SEARCH_STATE_BEFORE;
@Override
public void visit(final IAstNode node) throws InvocationTargetException {
if (this.inCovering >= SEARCH_STATE_BEHIND) {
return;
}
if (node.getOffset() <= AstSelection.this.start && AstSelection.this.stop <= node.getEndOffset()) {
// covering
clearChilds();
AstSelection.this.lastCovering= node;
if (node.getOffset() != AstSelection.this.start || AstSelection.this.stop != node.getEndOffset()) {
this.inCovering= SEARCH_STATE_MATCH;
node.acceptInChildren(this);
}
this.inCovering= SEARCH_STATE_BEHIND;
return;
}
if (this.inCovering == SEARCH_STATE_MATCH) {
checkChild(node);
return;
}
}
}
private class CoveringSameLastFinder implements ICommonAstVisitor {
private int inCovering= SEARCH_STATE_BEFORE;
@Override
public void visit(final IAstNode node) throws InvocationTargetException {
if (this.inCovering >= SEARCH_STATE_BEHIND) {
return;
}
if (node.getOffset() <= AstSelection.this.start && AstSelection.this.stop <= node.getEndOffset()) {
// covering
clearChilds();
AstSelection.this.lastCovering= node;
this.inCovering= SEARCH_STATE_MATCH;
node.acceptInChildren(this);
this.inCovering= SEARCH_STATE_BEHIND;
return;
}
if (this.inCovering == SEARCH_STATE_MATCH) {
checkChild(node);
return;
}
}
}
AstSelection() {
}
protected final void clearChilds() {
this.beforeChild= null;
this.firstChild= null;
this.lastChild= null;
this.afterChild= null;
}
protected final void checkChild(final IAstNode node) {
if (node.getEndOffset() < this.start) {
this.beforeChild= node;
return;
}
if (node.getOffset() > this.stop) {
if (this.afterChild == null) {
this.afterChild= node;
}
return;
}
// touching
if (this.firstChild == null) {
this.firstChild= node;
}
this.lastChild= node;
}
public static AstSelection search(final IAstNode rootNode, final int start, final int stop, final int mode) {
final AstSelection selection= new AstSelection();
selection.start= start;
selection.stop= stop;
final 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 this.start;
}
public int getStopOffset() {
return this.stop;
}
public final @Nullable IAstNode getCovering() {
return this.lastCovering;
}
public final @Nullable IAstNode getChildBefore() {
return this.beforeChild;
}
public final @Nullable IAstNode getChildFirstTouching() {
return this.firstChild;
}
public final @Nullable IAstNode getChildLastTouching() {
return this.lastChild;
}
public final @Nullable IAstNode getChildAfter() {
return this.afterChild;
}
}