blob: d57094e379ca100461adeed054ea26bbddac0480 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2007, 2020 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.AstNode;
import org.eclipse.statet.ltk.ast.core.AstVisitor;
/**
* 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 AstNode lastCovering;
private @Nullable AstNode beforeChild;
private @Nullable AstNode firstChild;
private @Nullable AstNode lastChild;
private @Nullable AstNode afterChild;
private class CoveringGreaterFinder implements AstVisitor {
private int inCovering= SEARCH_STATE_BEFORE;
@Override
public void visit(final AstNode node) throws InvocationTargetException {
if (this.inCovering >= SEARCH_STATE_BEHIND) {
return;
}
if ((node.getStartOffset() < AstSelection.this.start && AstSelection.this.stop <= node.getEndOffset())
|| (node.getStartOffset() == 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 AstVisitor {
private int inCovering= SEARCH_STATE_BEFORE;
@Override
public void visit(final AstNode node) throws InvocationTargetException {
if (this.inCovering >= SEARCH_STATE_BEHIND) {
return;
}
if (node.getStartOffset() <= AstSelection.this.start && AstSelection.this.stop <= node.getEndOffset()) {
// covering
clearChilds();
AstSelection.this.lastCovering= node;
if (node.getStartOffset() != 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 AstVisitor {
private int inCovering= SEARCH_STATE_BEFORE;
@Override
public void visit(final AstNode node) throws InvocationTargetException {
if (this.inCovering >= SEARCH_STATE_BEHIND) {
return;
}
if (node.getStartOffset() <= 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 AstNode node) {
if (node.getEndOffset() < this.start) {
this.beforeChild= node;
return;
}
if (node.getStartOffset() > 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 AstNode rootNode, final int start, final int stop, final int mode) {
final AstSelection selection= new AstSelection();
selection.start= start;
selection.stop= stop;
final AstVisitor 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 AstNode getCovering() {
return this.lastCovering;
}
public final @Nullable AstNode getChildBefore() {
return this.beforeChild;
}
public final @Nullable AstNode getChildFirstTouching() {
return this.firstChild;
}
public final @Nullable AstNode getChildLastTouching() {
return this.lastChild;
}
public final @Nullable AstNode getChildAfter() {
return this.afterChild;
}
}