/******************************************************************************* * Copyright (c) 2012 Michael Vorburger (http://www.vorburger.ch). * 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 ch.vorburger.xtext.xml; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.EStructuralFeature.Setting; import org.eclipse.emf.ecore.InternalEObject; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.xtext.naming.IQualifiedNameConverter; import org.eclipse.xtext.naming.IQualifiedNameProvider; import org.eclipse.xtext.naming.QualifiedName; import org.eclipse.xtext.nodemodel.INode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import org.eclipse.xtext.resource.IEObjectDescription; import org.eclipse.xtext.scoping.IScope; import org.eclipse.xtext.scoping.IScopeProvider; import com.google.inject.Inject; /** * Default Implementation of NameURISwapper. * * @author Michael Vorburger */ public class NameURISwapperImpl implements NameURISwapper { // intentionally protected - there should be no need for this prefix to be visible outside. // Please use the getNameFromProxy() helper to access the content of name:/ URI Proxies. private static final String NAME_SCHEME = "name"; private @Inject IQualifiedNameProvider nameProvider; private @Inject IQualifiedNameConverter nameConverter; private @Inject IScopeProvider scopeProvider; @Override public <T extends EObject> T cloneAndReplaceAllReferencesByNameURIProxies(T rootObject) { // Note that EcoreUtil2.clone / ECoreUtil.copy (should) already resolve all proxies in the original T clonedRootObject = EcoreUtil.copy(rootObject); replaceReferences(clonedRootObject, new XTextByNameURIReferenceReplacer()); return clonedRootObject; } @Override public void replaceAllNameURIProxiesByReferences(EObject rootObject) { replaceReferences(rootObject, new NameURIByXTextReferenceReplacer()); } protected <T extends EObject> void replaceReferences(T rootObject, ReferenceReplacer r) { Map<EObject, Collection<Setting>> map = EcoreUtil.CrossReferencer.find(Collections.singleton(rootObject)); for (Map.Entry<EObject, Collection<EStructuralFeature.Setting>> entry : map.entrySet()) { EObject crossReference = entry.getKey(); for (Setting setting : entry.getValue()) { EReference eReference = (EReference)setting.getEStructuralFeature(); if (eReference.isContainment()) continue; if (!eReference.isChangeable()) continue; // if (!crossReference.eIsProxy()) // continue; // if (eReference.isContainment()) // throw new IllegalArgumentException(NameURISwapper.class + " doesn't know how to deal with a Proxy EReference which isContained: " + crossReference.toString()); // if (!eReference.isChangeable()) // throw new IllegalArgumentException(NameURISwapper.class + " doesn't know how to deal with a Proxy EReference which isNotChangeable: " + crossReference.toString()); final EObject object = setting.getEObject(); if (!eReference.isMany()) { EObject newProxyOrProxy = r.replaceReference(object, eReference, crossReference); setting.set(newProxyOrProxy); } else { @SuppressWarnings("unchecked") List<EObject> list = (List<EObject>) setting.get(false); // == object.eGet(eReference); for (int i = 0; i < list.size(); i++) { EObject crossReferenceInList = list.get(i); EObject newProxyOrProxy = r.replaceReference(object, eReference, crossReferenceInList); list.set(i, newProxyOrProxy); } } } } } protected interface ReferenceReplacer { EObject replaceReference(EObject eObject, EReference eReference, EObject crossReference); } protected class XTextByNameURIReferenceReplacer implements ReferenceReplacer { @Override public EObject replaceReference(EObject eObject, EReference eReference, EObject crossReference) { //System.out.println("Going to replace: " + eReference + "\n in: " + eObject + "\n from currently " + crossReference); if (crossReference.eIsProxy()) { URI proxyURI = ((InternalEObject)crossReference).eProxyURI(); if (NAME_SCHEME.equals(proxyURI.scheme())) // Sometimes CrossReferencer gives us duplicates, which we already processed.. return crossReference; String nodeText = ""; List<INode> nodes = NodeModelUtils.findNodesForFeature(eObject, eReference); if (nodes.size() > 0) nodeText = NodeModelUtils.getTokenText(nodes.get(0)); throw new IllegalArgumentException(NameURISwapperImpl.class + " found a Proxy when an non-Proxy EObject was expected - why did it not get resolved before reaching this point? URI=" + crossReference.toString() + ", eReference=" + eReference + ", NodeText=" + nodeText); } // final IScope scope = scopeProvider.getScope(setting.getEObject(), eReference); // final IEObjectDescription objectDescription = scope.getSingleElement(crossReference); // final QualifiedName qName = objectDescription.getQualifiedName(); final QualifiedName qName = nameProvider.getFullyQualifiedName(crossReference); if (qName == null) throw new IllegalArgumentException(NameURISwapperImpl.class + " IQualifiedNameProvider returned null for " + crossReference.toString()); final String name = nameConverter.toString(qName); if (name == null) throw new IllegalArgumentException(NameURISwapperImpl.class + " IQualifiedNameConverter returned null for " + qName); // Note the URI's '#' suffix... this is needed because otherwise the // org.eclipse.emf.ecore.xmi.impl.XMLHandler's setValueFromId() // does not create Proxy objects but just ignores such href from XML. final URI uri = URI.createURI(NAME_SCHEME + ":/" + name + "#"); final EObject newProxy = EcoreUtil3.createProxy(uri, eReference.getEReferenceType()); return newProxy; } } protected class NameURIByXTextReferenceReplacer implements ReferenceReplacer { @Override public EObject replaceReference(EObject eObject, EReference eReference, EObject crossReference) { final URI uri = EcoreUtil.getURI(crossReference); if (NAME_SCHEME.equals(uri.scheme())) { // path() ignores the '#' which we added above final String crossRefString = uri.path().substring(1); final QualifiedName name = nameConverter.toQualifiedName(crossRefString); final IScope scope = scopeProvider.getScope(eObject, eReference); final IEObjectDescription objectDescription = scope.getSingleElement(name); if (objectDescription != null) { return objectDescription.getEObjectOrProxy(); } else { // If we can't find Name in Scope then the referenced Object // may not exist in the Workspace yet (or has not yet been // indexed by the Builder). We leave the name:/ URI Proxy // as is - and have the NameURISupportingCrossReferenceSerializer // deal with it! return crossReference; } } else { return crossReference; } } } static public String getNameFromProxy(EObject proxy) { if (proxy.eIsProxy()) { final URI proxyURI = EcoreUtil.getURI(proxy); if (NAME_SCHEME.equals(proxyURI.scheme())) { return proxyURI.path().substring(1); } } return null; } }