/******************************************************************************* * Copyright (c) 2012 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.xpect.xtext.lib.util; import java.util.Set; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.xtext.Assignment; import org.eclipse.xtext.GrammarUtil; import org.eclipse.xtext.nodemodel.ICompositeNode; import org.eclipse.xtext.nodemodel.ILeafNode; import org.eclipse.xtext.nodemodel.INode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import org.eclipse.xtext.parsetree.reconstr.impl.NodeIterator; import org.eclipse.xtext.resource.XtextResource; import org.xpect.XpectImport; import org.xpect.XpectInvocation; import org.xpect.parameter.OffsetRegion; import org.xpect.setup.ThisArgumentType; import org.xpect.setup.XpectSetupFactory; import org.xpect.state.Creates; import org.xpect.text.OffsetToString; import org.xpect.xtext.lib.setup.ThisResource; import org.xpect.xtext.lib.util.XtextOffsetAdapter.EObjectProvider; import org.xpect.xtext.lib.util.XtextOffsetAdapter.NodeProvider; import com.google.common.collect.Sets; /** * @author Moritz Eysholdt - Initial contribution and API */ @XpectImport({ NodeProvider.class, EObjectProvider.class }) public class XtextOffsetAdapter { protected static class CrossEReferenceAndEObject extends EStructuralFeatureAndEObject implements ICrossEReferenceAndEObject { public CrossEReferenceAndEObject(EObject object, EStructuralFeature feature) { super(object, feature); } public EReference getCrossEReference() { return (EReference) getEStructuralFeature(); } } @XpectSetupFactory protected static class CrossEReferenceAndEObjectProvider extends EStructuralFeatureAndEObjectProvider { public CrossEReferenceAndEObjectProvider(@ThisResource XtextResource resource, XpectInvocation statement) { super(resource, statement); } public CrossEReferenceAndEObjectProvider(@ThisResource XtextResource resource, XpectInvocation statement, OffsetRegion region) { super(resource, statement, region); } @Override protected EStructuralFeatureAndEObject create(EObject object, EStructuralFeature feat) { return new CrossEReferenceAndEObject(object, feat); } @Creates public ICrossEReferenceAndEObject createCrossEReferenceAndEObject() { return (ICrossEReferenceAndEObject) createStructuralFeatureAndEObject(); } @Override protected boolean matches(EObject object, EStructuralFeature feature) { return feature instanceof EReference && !((EReference) feature).isContainment(); } @Creates public ICrossEReferenceAndEObject create() { return (ICrossEReferenceAndEObject) createStructuralFeatureAndEObject(); } } protected static class EAttributeAndEObject extends EStructuralFeatureAndEObject implements IEAttributeAndEObject { public EAttributeAndEObject(EObject object, EStructuralFeature feature) { super(object, feature); } public EAttribute getEAttribute() { return (EAttribute) getEStructuralFeature(); } } @XpectSetupFactory protected static class EAttributeAndEObjectProvider extends EStructuralFeatureAndEObjectProvider { public EAttributeAndEObjectProvider(XtextResource resource, XpectInvocation statement) { super(resource, statement); } public EAttributeAndEObjectProvider(XtextResource resource, XpectInvocation statement, OffsetRegion region) { super(resource, statement, region); } @Override protected EStructuralFeatureAndEObject create(EObject object, EStructuralFeature feat) { return new EAttributeAndEObject(object, feat); } @Creates public IEAttributeAndEObject createEAttributeAndEObject() { return (IEAttributeAndEObject) createStructuralFeatureAndEObject(); } @Override protected boolean matches(EObject object, EStructuralFeature feature) { return feature instanceof EAttribute; } } @XpectSetupFactory protected static class EObjectProvider<T extends EObject> { private final T eObject; public EObjectProvider(@ThisResource XtextResource resource, XpectInvocation statement, @ThisArgumentType Class<T> expectedType) { ICompositeNode statementNode = NodeModelUtils.getNode(statement); this.eObject = find(resource, statementNode.getOffset() + statementNode.getLength(), expectedType); } public EObjectProvider(@ThisResource XtextResource resource, XpectInvocation statement, @ThisArgumentType Class<T> expectedType, OffsetRegion offset) { this.eObject = find(resource, offset.getMatchedOffset(), expectedType); } public boolean canProvide(Class<?> expectedType) { return EObject.class.isAssignableFrom(expectedType); } protected T find(Class<T> expectedType, INode node, Set<EObject> visited) { EObject current = node.getSemanticElement(); int startoffset = node.getOffset(); T result = null; while (current != null && NodeModelUtils.getNode(current).getOffset() >= startoffset) { if (expectedType.isInstance(current)) result = expectedType.cast(current); visited.add(current); current = current.eContainer(); } return result; } protected T find(XtextResource res, int offset, Class<T> expectedType) { INode leaf = NodeModelUtils.findLeafNodeAtOffset(res.getParseResult().getRootNode(), offset); Set<EObject> visited = Sets.newHashSet(); NodeIterator ni = null; while (ni == null || ni.hasNext()) { INode next = ni == null ? leaf : ni.next(); if (ni == null) ni = new NodeIterator(leaf); T result = find(expectedType, next, visited); if (result != null) return result; } return null; } @Creates public T getEObject() { return eObject; } } protected static class EReferenceAndEObject extends EStructuralFeatureAndEObject implements IEReferenceAndEObject { public EReferenceAndEObject(EObject object, EStructuralFeature feature) { super(object, feature); } public EReference getEReference() { return (EReference) getEStructuralFeature(); } } @XpectSetupFactory protected static class EReferenceAndEObjectProvider extends EStructuralFeatureAndEObjectProvider { public EReferenceAndEObjectProvider(XtextResource resource, XpectInvocation statement) { super(resource, statement); } public EReferenceAndEObjectProvider(XtextResource resource, XpectInvocation statement, OffsetRegion region) { super(resource, statement, region); } @Override protected EStructuralFeatureAndEObject create(EObject object, EStructuralFeature feat) { return new EReferenceAndEObject(object, feat); } @Creates public IEReferenceAndEObject createReferenceAndEObject() { return (IEReferenceAndEObject) createStructuralFeatureAndEObject(); } @Override protected boolean matches(EObject object, EStructuralFeature feature) { return feature instanceof EReference; } } protected static class EStructuralFeatureAndEObject implements IEStructuralFeatureAndEObject { private final EStructuralFeature feature; private final EObject object; public EStructuralFeatureAndEObject(EObject object, EStructuralFeature feature) { this.object = object; this.feature = feature; } public EObject getEObject() { return object; } public EStructuralFeature getEStructuralFeature() { return feature; } } @XpectSetupFactory protected static class EStructuralFeatureAndEObjectProvider { private final IEStructuralFeatureAndEObject structuralFeatureAndEObject; public EStructuralFeatureAndEObjectProvider(@ThisResource XtextResource resource, XpectInvocation statement) { ICompositeNode statementNode = NodeModelUtils.getNode(statement); int offset = statementNode.getOffset() + statementNode.getLength(); this.structuralFeatureAndEObject = findAfterOffset(resource, offset); } public EStructuralFeatureAndEObjectProvider(@ThisResource XtextResource resource, XpectInvocation statement, OffsetRegion region) { int offset = region.getMatchedOffset(); if (region.getMatchedRegion() == null) this.structuralFeatureAndEObject = findAfterOffset(resource, offset); else this.structuralFeatureAndEObject = findAtOffset(resource, region, offset); } protected EStructuralFeatureAndEObject create(EObject object, EStructuralFeature feat) { return new EStructuralFeatureAndEObject(object, feat); } @Creates public IEStructuralFeatureAndEObject createStructuralFeatureAndEObject() { return this.structuralFeatureAndEObject; } protected IEStructuralFeatureAndEObject findInParent(INode node) { return null; } protected IEStructuralFeatureAndEObject findAfterOffset(XtextResource resource, int offset) { INode leaf = NodeModelUtils.findLeafNodeAtOffset(resource.getParseResult().getRootNode(), offset); NodeIterator ni = null; while (ni == null || ni.hasNext()) { INode next = ni == null ? leaf : ni.next(); if (ni == null) ni = new NodeIterator(leaf); EObject object = NodeModelUtils.findActualSemanticObjectFor(next); INode current = next; do { Assignment ass = GrammarUtil.containingAssignment(current.getGrammarElement()); if (ass != null) { EStructuralFeature feat = object.eClass().getEStructuralFeature(ass.getFeature()); if (feat != null && matches(object, feat)) return create(object, feat); } current = current.getParent(); } while (current != null && object == NodeModelUtils.findActualSemanticObjectFor(current)); } throw new RuntimeException("No EStructuralFeature found at offset " + offset); } protected IEStructuralFeatureAndEObject findAtOffset(XtextResource resource, OffsetRegion region, int offset) { ILeafNode leaf = NodeModelUtils.findLeafNodeAtOffset(resource.getParseResult().getRootNode(), offset); EObject object = NodeModelUtils.findActualSemanticObjectFor(leaf); INode current = leaf; do { Assignment ass = GrammarUtil.containingAssignment(current.getGrammarElement()); if (ass != null) { EStructuralFeature feat = object.eClass().getEStructuralFeature(ass.getFeature()); if (feat != null) { if (matches(object, feat)) return create(object, feat); String name = feat.eClass().getName() + " '" + feat.getEContainingClass().getName() + "." + feat.getName() + "'"; String offsetString = new OffsetToString().with(offset, region.getDocument()).toString(); throw new RuntimeException("The " + name + " found at " + offsetString + " is not valid here."); } } current = current.getParent(); } while (current != null && object == NodeModelUtils.findActualSemanticObjectFor(current)); String offsetString = new OffsetToString().with(offset, region.getDocument()).toString(); throw new RuntimeException("no EStructuralFeature found at '" + offsetString + "'"); } protected boolean matches(EObject object, EStructuralFeature feature) { return true; } } @XpectImport(CrossEReferenceAndEObjectProvider.class) public static interface ICrossEReferenceAndEObject extends IEObjectOwner { EReference getCrossEReference(); } @XpectImport(EAttributeAndEObjectProvider.class) public static interface IEAttributeAndEObject extends IEObjectOwner { EAttribute getEAttribute(); } public static interface IEObjectOwner { EObject getEObject(); } @XpectImport(EReferenceAndEObjectProvider.class) public static interface IEReferenceAndEObject extends IEObjectOwner { EReference getEReference(); } @XpectImport(EStructuralFeatureAndEObjectProvider.class) public static interface IEStructuralFeatureAndEObject extends IEObjectOwner { EStructuralFeature getEStructuralFeature(); } @XpectSetupFactory protected static class NodeProvider { private final ILeafNode leaf; public NodeProvider(@ThisResource XtextResource resource, XpectInvocation statement) { ICompositeNode statementNode = NodeModelUtils.getNode(statement); int offset = statementNode.getOffset() + statementNode.getLength(); ILeafNode node = NodeModelUtils.findLeafNodeAtOffset(resource.getParseResult().getRootNode(), offset); NodeIterator it = new NodeIterator(node); ILeafNode cand = null; while (cand == null && it.hasNext()) { INode next = it.next(); if (next instanceof ILeafNode && !((ILeafNode) next).isHidden()) cand = (ILeafNode) next; } this.leaf = cand; } public NodeProvider(@ThisResource XtextResource resource, XpectInvocation statement, OffsetRegion offset) { this.leaf = NodeModelUtils.findLeafNodeAtOffset(resource.getParseResult().getRootNode(), offset.getMatchedOffset()); } @Creates public ICompositeNode getCompositeNode() { return leaf.getParent(); } @Creates public ILeafNode getLeafNode() { return leaf; } @Creates public INode getNode() { return leaf; } } }