All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.xtext.nodemodel.util.NodeModelUtils Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2010 itemis AG (http://www.itemis.eu) 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
 *******************************************************************************/
package org.eclipse.xtext.nodemodel.util;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.Action;
import org.eclipse.xtext.Assignment;
import org.eclipse.xtext.GrammarUtil;
import org.eclipse.xtext.ParserRule;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.grammaranalysis.impl.GrammarElementTitleSwitch;
import org.eclipse.xtext.nodemodel.BidiIterator;
import org.eclipse.xtext.nodemodel.BidiTreeIterator;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.ILeafNode;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.SyntaxErrorMessage;
import org.eclipse.xtext.nodemodel.impl.AbstractNode;
import org.eclipse.xtext.resource.EObjectAtOffsetHelper;
import org.eclipse.xtext.resource.ILocationInFileProvider;

import com.google.common.collect.Lists;

/**
 * The NodeModelUtils are a collection of useful methods when dealing with the node model directly. They encapsulate the
 * default construction semantics of the node model as it is created by the parser.
 * 
 * This API is quite low level and internal functionality of the framework relies on the implemened contracts.
 * Clients should rather use the language specific APIs that provide almost the same functionality, e.g.
 * {@link ILocationInFileProvider} and {@link EObjectAtOffsetHelper} if they want to to access the region
 * of a {@link EObject semantic object}.
 * 
 * @author Sebastian Zarnekow - Initial contribution and API
 */
public class NodeModelUtils {

	/**
	 * Find the leaf node at the given offset. May return null if the given offset is not valid for the
	 * node (sub-)tree.
	 * 
	 * A node matches the leafNodeOffset if it fulfills the following condition:
	 * 
	 *  node.totalOffset <= leafNodeOffset &&
	 *  node.totalEndOffset > leafNodeOffset 
	 * 
* * @param node the container node. May not be null. * @param leafNodeOffset the offset that is covered by the searched node. * @return the leaf node at the given offset or null. */ @Nullable public static ILeafNode findLeafNodeAtOffset(@NonNull INode node, int leafNodeOffset) { INode localNode = node; while(!(localNode instanceof AbstractNode)) { localNode = localNode.getParent(); } int offset = localNode.getTotalOffset(); int length = localNode.getTotalLength(); BidiTreeIterator iterator = ((AbstractNode) localNode).basicIterator(); if (leafNodeOffset > (offset + length) / 2) { while (iterator.hasPrevious()) { AbstractNode previous = iterator.previous(); int previousOffset = previous.getTotalOffset(); int previousLength = previous.getTotalLength(); if (!intersects(previousOffset, previousLength, leafNodeOffset)) { if (previousOffset + previousLength <= leafNodeOffset) { return null; } iterator.prune(); } else { if (previous instanceof ILeafNode) return (ILeafNode) previous; } } } else { while (iterator.hasNext()) { AbstractNode next = iterator.next(); int nextOffset = next.getTotalOffset(); int nextLength = next.getTotalLength(); if (!intersects(nextOffset, nextLength, leafNodeOffset)) { if (nextOffset > leafNodeOffset) { return null; } iterator.prune(); } else { if (next instanceof ILeafNode) return (ILeafNode) next; } } } return null; } private static boolean intersects(int offset, int length, int lookupOffset) { if (offset <= lookupOffset && offset + length > lookupOffset) return true; return false; } /** * Returns the node that is directly associated with the given object by means of an EMF-Adapter. * * @param object the semantic object whose direct node should be provided. * @return the node that is directly associated with the given object. * @see NodeModelUtils#findActualNodeFor(EObject) */ @Nullable public static ICompositeNode getNode(@Nullable EObject object) { if (object == null) return null; List adapters = object.eAdapters(); for (int i = 0; i < adapters.size(); i++) { Adapter adapter = adapters.get(i); if (adapter instanceof ICompositeNode) return (ICompositeNode) adapter; } return null; } /** * Returns the list of nodes that were used to assign values to the given feature for the given object. * * @return the list of nodes that were used to assign values to the given feature for the given object. */ @NonNull public static List findNodesForFeature(EObject semanticObject, EStructuralFeature structuralFeature) { ICompositeNode node = findActualNodeFor(semanticObject); if (node != null) { return findNodesForFeature(semanticObject, node, structuralFeature); } return Collections.emptyList(); } private static List findNodesForFeature(EObject semanticElement, INode node, EStructuralFeature structuralFeature) { List result = Lists.newArrayList(); String featureName = structuralFeature.getName(); BidiTreeIterator iterator = node.getAsTreeIterable().iterator(); while (iterator.hasNext()) { INode child = iterator.next(); EObject grammarElement = child.getGrammarElement(); if (grammarElement != null) { if (grammarElement instanceof Action) { Action action = (Action) grammarElement; if (child.getSemanticElement() == semanticElement) { child = iterator.next(); if (featureName.equals(action.getFeature())) { result.add(child); } } else { // navigate the action's left side (first child) until we find an assignment (a rule call) // the assignment will tell us about the feature to which we assigned // the semantic object that has been created by the action INode firstChild = ((ICompositeNode) child).getFirstChild(); while (firstChild.getGrammarElement() instanceof Action) { firstChild = ((ICompositeNode) firstChild).getFirstChild(); } EObject firstChildGrammarElement = firstChild.getGrammarElement(); Assignment assignment = GrammarUtil.containingAssignment(firstChildGrammarElement); if (assignment != null && featureName.equals(assignment.getFeature())) { result.add(child); } } iterator.prune(); } else if (child != node) { Assignment assignment = GrammarUtil.containingAssignment(grammarElement); if (assignment != null) { if (featureName.equals(assignment.getFeature())) { result.add(child); } iterator.prune(); } } } } return result; } /** *

Returns the node that covers all assigned values of the given object. It handles the semantics of {@link Action * actions} and {@link RuleCall unassigned rule calls}. The returned node will include unassigned surrounding leafs, * e.g. if you use something like {@code Parenthesized expressions} redundant parentheses will be part of the returned node.

*

Consider the following simple expression (a number literal): *

	 *   ((1))
	 * 
* Assuming it was parsed from a grammar like this: *
	 * Expression: Number | Parentheses;
	 * Parentheses: '(' Expression ')';
	 * Number: value=INT
	 * 
* The actual node for the only semantic object that was produced from the input {@code ((1))} is the root node * even though the minimal node would be the one with the text {@code 1}. * * @param semanticObject the semantic object whose node should be provided. * @return the node that covers all assigned values of the given object. */ @Nullable public static ICompositeNode findActualNodeFor(@Nullable EObject semanticObject) { ICompositeNode node = getNode(semanticObject); if (node != null) { while (GrammarUtil.containingAssignment(node.getGrammarElement()) == null && node.getParent() != null && !node.getParent().hasDirectSemanticElement()) { node = node.getParent(); } } return node; } /** * Returns the semantic object that is really associated with the actual container node of the given node. It * handles the structural semantics that results from {@link Action Actions} and {@link RuleCall unassigned rule * calls}. * * @return the semantic object that is really associated with the actual container node of the given node. */ @Nullable public static EObject findActualSemanticObjectFor(@Nullable INode node) { if (node == null) return null; if (node.hasDirectSemanticElement()) return node.getSemanticElement(); EObject grammarElement = node.getGrammarElement(); ICompositeNode parent = node.getParent(); if (grammarElement == null) return findActualSemanticObjectFor(parent); Assignment assignment = GrammarUtil.containingAssignment(grammarElement); if (assignment != null) { if (parent.hasDirectSemanticElement()) return findActualSemanticObjectFor(parent); INode sibling = parent.getFirstChild(); while(sibling != node) { EObject siblingGrammarElement = sibling.getGrammarElement(); if (siblingGrammarElement != null && GrammarUtil.containingAssignment(siblingGrammarElement) == null) { if (GrammarUtil.isEObjectRuleCall(siblingGrammarElement)) return findActualSemanticObjectFor(sibling); } sibling = sibling.getNextSibling(); } } else { EObject result = findActualSemanticObjectInChildren(node, grammarElement); if (result != null) return result; } return findActualSemanticObjectFor(parent); } @Nullable private static EObject findActualSemanticObjectInChildren(@NonNull INode node, @Nullable EObject grammarElement) { if (node.hasDirectSemanticElement()) return node.getSemanticElement(); AbstractRule rule = null; if (grammarElement instanceof RuleCall) { rule = ((RuleCall) grammarElement).getRule(); } else if (grammarElement instanceof AbstractRule) { rule = (AbstractRule) grammarElement; } if (rule instanceof ParserRule && !GrammarUtil.isDatatypeRule(rule)) { if (node instanceof ICompositeNode) { for (INode child : ((ICompositeNode) node).getChildren()) { if (child instanceof ICompositeNode) { EObject childGrammarElement = child.getGrammarElement(); if (childGrammarElement instanceof Action) { EObject result = findActualSemanticObjectInChildren(child, childGrammarElement); if (result != null) return result; } else if (childGrammarElement instanceof RuleCall) { RuleCall childRuleCall = (RuleCall) childGrammarElement; if (childRuleCall.getRule() instanceof ParserRule && !GrammarUtil.isDatatypeRule(childRuleCall.getRule())) { EObject result = findActualSemanticObjectInChildren(child, childRuleCall); if (result != null) return result; } } } } } } return null; } /** * Creates a string representation of the given node. Useful for debugging. * * @return a debug string for the given node. */ public static String compactDump(INode node, boolean showHidden) { StringBuilder result = new StringBuilder(); try { compactDump(node, showHidden, "", result); } catch (IOException e) { return e.getMessage(); } return result.toString(); } private static void compactDump(INode node, boolean showHidden, String prefix, Appendable result) throws IOException { if (!showHidden && node instanceof ILeafNode && ((ILeafNode) node).isHidden()) return; if (prefix.length() != 0) { result.append("\n"); result.append(prefix); } if (node instanceof ICompositeNode) { if (node.getGrammarElement() != null) result.append(new GrammarElementTitleSwitch().doSwitch(node.getGrammarElement())); else result.append("(unknown)"); String newPrefix = prefix + " "; result.append(" {"); BidiIterator children = ((ICompositeNode) node).getChildren().iterator(); while (children.hasNext()) { INode child = children.next(); compactDump(child, showHidden, newPrefix, result); } result.append("\n"); result.append(prefix); result.append("}"); SyntaxErrorMessage error = node.getSyntaxErrorMessage(); if (error != null) result.append(" SyntaxError: [" + error.getIssueCode() + "] " + error.getMessage()); } else if (node instanceof ILeafNode) { if (((ILeafNode) node).isHidden()) result.append("hidden "); if (node.getGrammarElement() != null) result.append(new GrammarElementTitleSwitch().doSwitch(node.getGrammarElement())); else result.append("(unknown)"); result.append(" => '"); result.append(node.getText()); result.append("'"); SyntaxErrorMessage error = node.getSyntaxErrorMessage(); if (error != null) result.append(" SyntaxError: [" + error.getIssueCode() + "] " + error.getMessage()); } else if (node == null) { result.append("(null)"); } else { result.append("unknown type "); result.append(node.getClass().getName()); } } /** * This method converts a node to text. * * Leading and trailing text from hidden tokens (whitespace/comments) is removed. Text from hidden tokens that is * surrounded by text from non-hidden tokens is summarized to a single whitespace. * * The preferred use case of this method is to convert the {@link ICompositeNode} that has been created for a data * type rule to text. * * This is also the recommended way to convert a node to text if you want to invoke * {@link org.eclipse.xtext.conversion.IValueConverterService#toValue(String, String, INode)} * */ public static String getTokenText(INode node) { if (node instanceof ILeafNode) return ((ILeafNode) node).getText(); else { StringBuilder builder = new StringBuilder(Math.max(node.getTotalLength(), 1)); boolean hiddenSeen = false; for (ILeafNode leaf : node.getLeafNodes()) { if (!leaf.isHidden()) { if (hiddenSeen && builder.length() > 0) builder.append(' '); builder.append(leaf.getText()); hiddenSeen = false; } else { hiddenSeen = true; } } return builder.toString(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy