blob: ed2d3f4b1dd078f67ebd33e6e98d2be4ebc98236 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2020 Obeo.
* 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:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.acceleo.query.parser;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.emf.ecore.EObject;
/**
* Keep track of positions.
*
* @author <a href="mailto:yvan.lussaud@obeo.fr">Yvan Lussaud</a>
*/
public class Positions {
/**
* Mapping from an {@link EObject} to its start position in the parsed text.
*/
private final Map<EObject, Integer> startPositions = new HashMap<EObject, Integer>();
/**
* Mapping from an {@link EObject} to its start line in the parsed text.
*/
private final Map<EObject, Integer> startLines = new HashMap<EObject, Integer>();
/**
* Mapping from an {@link EObject} to its start column in the parsed text.
*/
private final Map<EObject, Integer> startColumns = new HashMap<EObject, Integer>();
/**
* Mapping from an {@link EObject} to its end position in the parsed text.
*/
private final Map<EObject, Integer> endPositions = new HashMap<EObject, Integer>();
/**
* Mapping from an {@link EObject} to its end line in the parsed text.
*/
private final Map<EObject, Integer> endLines = new HashMap<EObject, Integer>();
/**
* Mapping from an {@link EObject} to its end column in the parsed text.
*/
private final Map<EObject, Integer> endColumns = new HashMap<EObject, Integer>();
/**
* Gets the start position in the parsed text for the given {@link EObject}.
*
* @param node
* the {@link EObject}
* @return the start position in the parsed text
*/
public Integer getStartPositions(EObject node) {
return startPositions.get(node);
}
/**
* Gets the start line in the parsed text for the given {@link EObject}.
*
* @param node
* the {@link EObject}
* @return the start line in the parsed text
*/
public Integer getStartLines(EObject node) {
return startLines.get(node);
}
/**
* Gets the start column in the parsed text for the given {@link EObject}.
*
* @param node
* the {@link EObject}
* @return the start column in the parsed text
*/
public Integer getStartColumns(EObject node) {
return startColumns.get(node);
}
/**
* Gets the end position in the parsed text for the given {@link EObject}.
*
* @param node
* the {@link EObject}
* @return the end position in the parsed text
*/
public Integer getEndPositions(EObject node) {
return endPositions.get(node);
}
/**
* Gets the end line in the parsed text for the given {@link EObject}.
*
* @param node
* the {@link EObject}
* @return the end line in the parsed text
*/
public Integer getEndLines(EObject node) {
return endLines.get(node);
}
/**
* Gets the end column in the parsed text for the given {@link EObject}.
*
* @param node
* the {@link EObject}
* @return the end column in the parsed text
*/
public Integer getEndColumns(EObject node) {
return endColumns.get(node);
}
/**
* Sets the start position in the parsed text for the given {@link EObject}.
*
* @param node
* the {@link EObject}
* @param position
* the position
*/
public void setStartPositions(EObject node, Integer position) {
startPositions.put(node, position);
}
/**
* Sets the start line in the parsed text for the given {@link EObject}.
*
* @param node
* the {@link EObject}
* @param line
* the line
*/
public void setStartLines(EObject node, Integer line) {
startLines.put(node, line);
}
/**
* Sets the start column in the parsed text for the given {@link EObject}.
*
* @param node
* the {@link EObject}
* @param column
* the column
*/
public void setStartColumns(EObject node, Integer column) {
startColumns.put(node, column);
}
/**
* Sets the end position in the parsed text for the given {@link EObject}.
*
* @param node
* the {@link EObject}
* @param position
* the position
*/
public void setEndPositions(EObject node, Integer position) {
endPositions.put(node, position);
}
/**
* Sets the end line in the parsed text for the given {@link EObject}.
*
* @param node
* the {@link EObject}
* @param line
* the line
*/
public void setEndLines(EObject node, Integer line) {
endLines.put(node, line);
}
/**
* Sets the end column in the parsed text for the given {@link EObject}.
*
* @param node
* the {@link EObject}
* @param column
* the column
*/
public void setEndColumns(EObject node, Integer column) {
endColumns.put(node, column);
}
/**
* Removes the positions of the given {@link EObject}.
*
* @param node
* the {@link EObject}
*/
public void remove(EObject node) {
startPositions.remove(node);
startLines.remove(node);
startColumns.remove(node);
endPositions.remove(node);
endLines.remove(node);
endColumns.remove(node);
}
/**
* Adds all others {@link Positions} with the given offset shift.
*
* @param others
* the others {@link Positions}
* @param offsetPosition
* the offset position
* @param offsetLine
* the offset line
* @param offsetColumn
* the offset column
*/
public void addAll(Positions others, int offsetPosition, int offsetLine, int offsetColumn) {
for (Entry<EObject, Integer> entry : others.startPositions.entrySet()) {
startPositions.put(entry.getKey(), entry.getValue() + offsetPosition);
}
for (Entry<EObject, Integer> entry : others.startLines.entrySet()) {
startLines.put(entry.getKey(), entry.getValue() + offsetLine);
}
for (Entry<EObject, Integer> entry : others.startColumns.entrySet()) {
startColumns.put(entry.getKey(), entry.getValue() + offsetColumn);
}
for (Entry<EObject, Integer> entry : others.endPositions.entrySet()) {
endPositions.put(entry.getKey(), entry.getValue() + offsetPosition);
}
for (Entry<EObject, Integer> entry : others.endLines.entrySet()) {
endLines.put(entry.getKey(), entry.getValue() + offsetLine);
}
for (Entry<EObject, Integer> entry : others.endColumns.entrySet()) {
endColumns.put(entry.getKey(), entry.getValue() + offsetColumn);
}
}
/**
* Tells if the given position is on the range of the given node.
*
* @param node
* the node
* @param position
* the position
* @return <code>true</code> if the given position is on the range of the given node, <code>false</code>
* otherwise
*/
public boolean isInRange(EObject node, int position) {
return isInRange(node, position, -1, -1);
}
/**
* Tells if the given line and column position is on the range of the given node.
*
* @param node
* the node
* @param line
* the line
* @param column
* the column
* @return <code>true</code> if the given line and column position is on the range of the given node,
* <code>false</code> otherwise
*/
public boolean isInRange(EObject node, int line, int column) {
return isInRange(node, -1, line, column);
}
/**
* Gets the node representing the narrowest range at the given position starting from the given node.
*
* @param node
* the root node
* @param position
* the position
* @return the node representing the narrowest range at the given position starting from the given node if
* any, <code>null</code> otherwise
*/
public EObject getNodeAt(EObject node, int position) {
return getNodeAt(node, position, -1, -1);
}
/**
* Gets the node representing the narrowest range at the given line and column position starting from the
* given node.
*
* @param node
* the root node
* @param line
* the line
* @param column
* the column
* @return the node representing the narrowest range at the given line and column position starting from
* the given node if any, <code>null</code> otherwise
*/
public EObject getNodeAt(EObject node, int line, int column) {
return getNodeAt(node, -1, line, column);
}
/**
* Gets the node representing the narrowest range at the given position starting from the given node.
*
* @param node
* the root node
* @param position
* the position
* @param line
* the line
* @param column
* the column
* @return the node representing the narrowest range at the given position starting from the given node if
* any, <code>null</code> otherwise
*/
protected EObject getNodeAt(EObject node, int position, int line, int column) {
EObject res = null;
if (!isInRange(node, position, line, column)) {
throw new IllegalArgumentException("the root node can't contain the given position.");
}
for (EObject child : node.eContents()) {
if (getStartPositions(child) != null && getEndPositions(child) != null) {
// When parsing an [elseif clause of an IfStatement, the AST constructed is instead a
// "virtual" [else] clause with an IfStatement inside.
// This "virtual" clause has no actual positions in the source text, so we want to ignore
// these candidates.
if (isInRange(child, position, line, column)) {
res = getNodeAt(child, position, line, column);
break;
}
}
}
if (res == null) {
res = node;
}
return res;
}
/**
* Tells if the given position is on the range of the given node.
*
* @param node
* the node
* @param position
* the position
* @param line
* the line
* @param column
* the column
* @return <code>true</code> if the given position is on the range of the given node, <code>false</code>
* otherwise
*/
protected boolean isInRange(EObject node, int position, int line, int column) {
return compareStart(node, position, line, column) <= 0 && compareEnd(node, position, line,
column) >= 0;
}
/**
* Compares the given position with the start position of the given node.
*
* @param node
* the node
* @param position
* the position
* @param line
* the line
* @param column
* the column
* @return
* <p>
* <ul>
* <li>less than 0 if the position is after the start</li>
* <li>0 if the position is at the start</li>
* <li>more than 0 if the position is after the start</li>
* <ul>
* <p>
*/
protected int compareStart(EObject node, int position, int line, int column) {
final int res;
if (position != -1) {
res = getStartPositions(node) - position;
} else if (line != -1) {
final int lineDelta = getStartLines(node) - line;
if (lineDelta == 0) {
if (column != -1) {
res = getStartColumns(node) - column;
} else {
res = lineDelta;
}
} else {
res = lineDelta;
}
} else {
throw new IllegalArgumentException("at least one of position or line must be different from -1.");
}
return res;
}
/**
* Compares the given position with the end position of the given node.
*
* @param node
* the node
* @param position
* the position
* @param line
* the line
* @param column
* the column
* @return
* <p>
* <ul>
* <li>less than 0 if the position is after the end</li>
* <li>0 if the position is at the end</li>
* <li>more than 0 if the position is after the end</li>
* <ul>
* <p>
*/
protected int compareEnd(EObject node, int position, int line, int column) {
final int res;
if (position != -1) {
res = getEndPositions(node) - position;
} else if (line != -1) {
final int lineDelta = getEndLines(node) - line;
if (lineDelta == 0) {
if (column != -1) {
res = getEndColumns(node) - column;
} else {
res = lineDelta;
}
} else {
res = lineDelta;
}
} else {
throw new IllegalArgumentException("at least one of position or line must be different from -1.");
}
return res;
}
}