Skip to content

Commit d75b9cd

Browse files
HannesWellmerks
andcommitted
[FIXUP] Incorporate Ed's suggestions and improve parent-handling?
Co-authored-by: Ed Merks <[email protected]>
1 parent 91be80a commit d75b9cd

File tree

2 files changed

+93
-79
lines changed

2 files changed

+93
-79
lines changed

bundles/org.eclipse.e4.emf.xpath/META-INF/MANIFEST.MF

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ Bundle-SymbolicName: org.eclipse.e4.emf.xpath
55
Bundle-Version: 0.6.0.qualifier
66
Bundle-RequiredExecutionEnvironment: JavaSE-17
77
Require-Bundle: org.eclipse.emf.ecore;bundle-version="2.35.0",
8-
org.eclipse.core.runtime;bundle-version="3.29.0"
8+
org.eclipse.core.runtime;bundle-version="3.29.0",
9+
org.eclipse.emf.ecore.xmi;bundle-version="2.38.0"
910
Export-Package: org.eclipse.e4.emf.xpath
1011
Bundle-Vendor: %Bundle-Vendor
1112
Automatic-Module-Name: org.eclipse.e4.emf.xpath

bundles/org.eclipse.e4.emf.xpath/src/org/eclipse/e4/emf/xpath/internal/java/JavaXPathContextFactoryImpl.java

Lines changed: 91 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.util.stream.Stream;
2424
import java.util.stream.StreamSupport;
2525

26+
import javax.xml.XMLConstants;
27+
import javax.xml.namespace.NamespaceContext;
2628
import javax.xml.namespace.QName;
2729
import javax.xml.parsers.DocumentBuilder;
2830
import javax.xml.parsers.DocumentBuilderFactory;
@@ -37,14 +39,16 @@
3739
import org.eclipse.e4.emf.xpath.XPathContext;
3840
import org.eclipse.e4.emf.xpath.XPathContextFactory;
3941
import org.eclipse.e4.emf.xpath.XPathNotFoundException;
40-
import org.eclipse.emf.ecore.EAttribute;
41-
import org.eclipse.emf.ecore.EClass;
4242
import org.eclipse.emf.ecore.EObject;
43-
import org.eclipse.emf.ecore.EStructuralFeature;
44-
import org.eclipse.emf.ecore.util.EcoreUtil;
43+
import org.eclipse.emf.ecore.EcorePackage;
44+
import org.eclipse.emf.ecore.xmi.XMLResource;
45+
import org.eclipse.emf.ecore.xmi.impl.DefaultDOMHandlerImpl;
46+
import org.eclipse.emf.ecore.xmi.impl.XMIHelperImpl;
47+
import org.eclipse.emf.ecore.xmi.impl.XMLSaveImpl;
4548
import org.w3c.dom.Attr;
4649
import org.w3c.dom.Document;
4750
import org.w3c.dom.Element;
51+
import org.w3c.dom.NamedNodeMap;
4852
import org.w3c.dom.Node;
4953
import org.w3c.dom.NodeList;
5054

@@ -60,66 +64,51 @@ public XPathContext newContext(XPathContext parentContext, T contextBean) {
6064
if (!(contextBean instanceof EObject rootObject)) {
6165
throw new IllegalArgumentException();
6266
}
67+
XPath xpath;
68+
DOMMapping domMapping;
69+
Element rootElement = null;
70+
6371
if (parentContext != null) {
64-
EObjectContext parent = ((EObjectContext) parentContext);
65-
Element rootElement = parent.object2domProxy.get(contextBean);
66-
if (rootElement == null) {
67-
throw new IllegalArgumentException("Context bean is not from the same tree its parent context");
68-
}
69-
return new EObjectContext(rootElement, parent);
72+
EObjectContext parent = (EObjectContext) parentContext;
73+
xpath = parent.xpath;
74+
rootElement = parent.domMapping.getElement(contextBean);
75+
} else {
76+
xpath = XPATH_FACTORY.newXPath();
7077
}
71-
DocumentBuilder documentBuilder;
72-
try {
73-
documentBuilder = DocumentBuilderFactory.newDefaultInstance().newDocumentBuilder();
74-
} catch (ParserConfigurationException e) {
75-
throw new IllegalStateException(e);
76-
}
77-
Document document = documentBuilder.newDocument();
78-
79-
Map<Node, EObject> domProxy2object = new HashMap<>();
80-
Map<EObject, Element> object2domProxy = new HashMap<>();
81-
// The xpath '/' actually referes to the document but it's also expected to
82-
// match the root application
83-
domProxy2object.put(document, rootObject);
8478

85-
Element rootElement = createElement(rootObject, document);
86-
object2domProxy.put(rootObject, rootElement);
87-
domProxy2object.put(rootElement, rootObject);
88-
89-
rootObject.eAllContents().forEachRemaining(eObject -> {
90-
Element parent = object2domProxy.get(eObject.eContainer());
91-
Element proxy = object2domProxy.computeIfAbsent(eObject, o -> createElement(o, parent));
92-
domProxy2object.put(proxy, eObject);
93-
});
79+
if (rootElement != null) {
80+
domMapping = ((EObjectContext) parentContext).domMapping;
81+
} else {
82+
DocumentBuilder documentBuilder;
83+
try {
84+
documentBuilder = DocumentBuilderFactory.newDefaultInstance().newDocumentBuilder();
85+
} catch (ParserConfigurationException e) {
86+
throw new IllegalStateException(e);
87+
}
88+
Document document = documentBuilder.newDocument();
9489

95-
return new EObjectContext(rootElement, domProxy2object, object2domProxy);
90+
domMapping = new DOMMapping();
91+
rootElement = createElement(rootObject, document, domMapping);
92+
xpath.setNamespaceContext(createNamespaceContext(rootElement));
93+
}
94+
return new EObjectContext(rootElement, domMapping, xpath);
9695
}
9796

98-
private static class EObjectContext implements XPathContext {
97+
private static final XPathFactory XPATH_FACTORY = XPathFactory.newInstance();
9998

100-
private static final XPathFactory XPATH_FACTORY = XPathFactory.newInstance();
99+
private static class EObjectContext implements XPathContext {
101100

102101
private final XPath xpath;
103102
private final Element rootElement;
104-
private final Map<Node, EObject> domProxy2object;
105-
private final Map<EObject, Element> object2domProxy;
103+
private final DOMMapping domMapping;
106104

107-
private EObjectContext(Element rootElement, Map<Node, EObject> domProxy2object,
108-
Map<EObject, Element> object2domProxy) {
105+
private EObjectContext(Element rootElement, DOMMapping domMapping, XPath xpath) {
109106
this.rootElement = rootElement;
110-
this.domProxy2object = Map.copyOf(domProxy2object);
111-
this.object2domProxy = Map.copyOf(object2domProxy);
112-
this.xpath = XPATH_FACTORY.newXPath();
107+
this.domMapping = domMapping;
108+
this.xpath = xpath;
113109
this.xpath.setXPathFunctionResolver(this::resolveEMFFunctions);
114110
}
115111

116-
private EObjectContext(Element rootElement, EObjectContext parentContext) {
117-
this.rootElement = rootElement;
118-
this.domProxy2object = Map.copyOf(parentContext.domProxy2object);
119-
this.object2domProxy = Map.copyOf(parentContext.object2domProxy);
120-
this.xpath = parentContext.xpath;
121-
}
122-
123112
@Override
124113
public <R> Stream<R> stream(String path, Class<R> resultType) {
125114
// See XPathResultType for generally supported result types
@@ -151,7 +140,7 @@ public <R> Stream<R> stream(String path, Class<R> resultType) {
151140
}
152141
return StreamSupport.stream(pathNodes.spliterator(), false).map(node -> {
153142
if (node instanceof Element || node instanceof Document) {
154-
return domProxy2object.get(node);
143+
return (EObject) domMapping.getValue(node);
155144
} else if (node instanceof Attr attribute) {
156145
return attribute.getValue();
157146
}
@@ -177,11 +166,12 @@ public Object getValue(String xpath) {
177166
}
178167

179168
private XPathFunction resolveEMFFunctions(QName functionName, int arity) {
180-
if (arity == 1 && "ecore".equals(functionName.getNamespaceURI())
169+
if (arity == 1 && EcorePackage.eNS_URI.equals(functionName.getNamespaceURI())
181170
&& "eClassName".equals(functionName.getLocalPart())) {
182171
return args -> {
183172
Node item = getSingleNodeArgument(args);
184-
return EObjectContext.this.domProxy2object.get(item).eClass().getName();
173+
EObject eObject = (EObject) EObjectContext.this.domMapping.getValue(item);
174+
return eObject == null ? null : eObject.eClass().getName();
185175
};
186176
}
187177
return null;
@@ -198,37 +188,60 @@ private static Node getSingleNodeArgument(List<?> args) throws XPathFunctionExce
198188
}
199189
throw new XPathFunctionException("Not a single node list: " + args);
200190
}
191+
}
192+
193+
private static Element createElement(EObject eObject, Document document, DOMMapping domMapper) {
194+
new XMLSaveImpl(Map.of(), new XMIHelperImpl(), "UTF-8").save(null, document,
195+
Map.of(XMLResource.OPTION_ROOT_OBJECTS, List.of(eObject)), domMapper);
196+
return document.getDocumentElement();
197+
}
198+
199+
private static NamespaceContext createNamespaceContext(Element element) {
200+
element.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:ecore", EcorePackage.eNS_URI);
201+
final Map<String, String> xmlnsPrefixMap = new HashMap<>();
202+
final Map<String, String> xmlnsPrefixMapInverse = new HashMap<>();
203+
204+
NamedNodeMap attributes = element.getAttributes();
205+
for (int i = 0, length = attributes.getLength(); i < length; ++i) {
206+
Attr attribute = (Attr) attributes.item(i);
207+
if ("xmlns".equals(attribute.getPrefix())) {
208+
String prefix = attribute.getLocalName();
209+
String namespace = attribute.getValue();
210+
xmlnsPrefixMap.put(prefix, namespace);
211+
xmlnsPrefixMapInverse.put(namespace, prefix);
212+
}
213+
}
214+
215+
return new NamespaceContext() {
216+
@Override
217+
public String getNamespaceURI(String prefix) {
218+
return xmlnsPrefixMap.get(prefix);
219+
}
201220

221+
@Override
222+
public String getPrefix(String namespaceURI) {
223+
return xmlnsPrefixMapInverse.get(namespaceURI);
224+
}
225+
226+
@Override
227+
public Iterator<String> getPrefixes(String namespaceURI) {
228+
String prefix = getPrefix(namespaceURI);
229+
List<String> list = prefix == null ? List.of() : List.of(prefix);
230+
return list.iterator();
231+
}
232+
};
202233
}
203234

204-
private static Element createElement(EObject eObject, Node parent) {
205-
EStructuralFeature containingFeature = eObject.eContainingFeature();
206-
EStructuralFeature containmentFeature = eObject.eContainmentFeature();
207-
String className = eObject.eClass().getName();
208-
String qName = containingFeature != null ? containingFeature.getName() : className;
209-
210-
Document document = parent instanceof Document documentParent ? documentParent : parent.getOwnerDocument();
211-
Element element = document.createElement(qName);
212-
EClass eClass = eObject.eClass();
213-
for (EAttribute attribute : eClass.getEAllAttributes()) {
214-
// TODO: or check how lists could be serialized as attributes? CSV? With
215-
// leading/trailing square-bracket?
216-
if (!attribute.isMany() && attribute.getEAttributeType().isSerializable() && eObject.eIsSet(attribute)) {
217-
Object value = eObject.eGet(attribute);
218-
try {
219-
String stringValue = EcoreUtil.convertToString(attribute.getEAttributeType(), value);
220-
element.setAttribute(attribute.getName(), stringValue);
221-
} catch (Exception e) {
222-
// TODO: avoid the occurrence of exceptions
235+
private static class DOMMapping extends DefaultDOMHandlerImpl {
236+
237+
public Element getElement(Object object) {
238+
for (Map.Entry<Node, Object> entry : nodeToObject.entrySet()) {
239+
if (Objects.equals(entry.getValue(), object)) {
240+
return (Element) entry.getKey();
223241
}
224242
}
243+
return null;
225244
}
226-
// TODO: set the xsi:type? This requires to declare the EPackage nsURI as xml
227-
// namespace initially, but it's unsure to me if that's even necessary.
228-
// String name = eClass.getName();
229-
// childElement.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "xsi:type", name);
230-
231-
parent.appendChild(element);
232-
return element;
233245
}
246+
234247
}

0 commit comments

Comments
 (0)