| /*=============================================================================# |
| # 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; |
| } |
| |
| } |