1414
1515package org .eclipse .e4 .emf .xpath .internal .java ;
1616
17+ import java .io .StringWriter ;
1718import java .util .HashMap ;
1819import java .util .Iterator ;
1920import java .util .List ;
2021import java .util .Map ;
22+ import java .util .Map .Entry ;
2123import java .util .Objects ;
2224import java .util .stream .Stream ;
2325import java .util .stream .StreamSupport ;
2426
27+ import javax .xml .XMLConstants ;
28+ import javax .xml .namespace .NamespaceContext ;
2529import javax .xml .namespace .QName ;
2630import javax .xml .parsers .DocumentBuilder ;
2731import javax .xml .parsers .DocumentBuilderFactory ;
2832import javax .xml .parsers .ParserConfigurationException ;
33+ import javax .xml .transform .OutputKeys ;
34+ import javax .xml .transform .Transformer ;
35+ import javax .xml .transform .TransformerFactory ;
36+ import javax .xml .transform .dom .DOMSource ;
37+ import javax .xml .transform .stream .StreamResult ;
2938import javax .xml .xpath .XPath ;
3039import javax .xml .xpath .XPathExpressionException ;
3140import javax .xml .xpath .XPathFactory ;
3645import org .eclipse .e4 .emf .xpath .XPathContext ;
3746import org .eclipse .e4 .emf .xpath .XPathContextFactory ;
3847import org .eclipse .e4 .emf .xpath .XPathNotFoundException ;
39- import org .eclipse .emf .ecore .EAttribute ;
40- import org .eclipse .emf .ecore .EClass ;
4148import org .eclipse .emf .ecore .EObject ;
42- import org .eclipse .emf .ecore .EStructuralFeature ;
43- import org .eclipse .emf .ecore .util .EcoreUtil ;
49+ import org .eclipse .emf .ecore .EcorePackage ;
50+ import org .eclipse .emf .ecore .xmi .XMLResource ;
51+ import org .eclipse .emf .ecore .xmi .impl .DefaultDOMHandlerImpl ;
52+ import org .eclipse .emf .ecore .xmi .impl .XMIHelperImpl ;
53+ import org .eclipse .emf .ecore .xmi .impl .XMLSaveImpl ;
4454import org .w3c .dom .Attr ;
4555import org .w3c .dom .Document ;
4656import org .w3c .dom .Element ;
57+ import org .w3c .dom .NamedNodeMap ;
4758import org .w3c .dom .Node ;
4859import org .w3c .dom .NodeList ;
4960
5061public class JavaXPathContextFactoryImpl <T > extends XPathContextFactory <T > {
5162
63+ private static final boolean DEBUG = false ;
64+
5265 @ Override
5366 public XPathContext newContext (T contextBean ) {
5467 return newContext (null , contextBean );
@@ -59,39 +72,30 @@ public XPathContext newContext(XPathContext parentContext, T contextBean) {
5972 if (!(contextBean instanceof EObject rootObject )) {
6073 throw new IllegalArgumentException ();
6174 }
75+
6276 if (parentContext != null ) {
6377 EObjectContext parent = ((EObjectContext ) parentContext );
64- Element rootElement = parent .object2domProxy . get (contextBean );
78+ Element rootElement = parent .domMapper . getElement (contextBean );
6579 if (rootElement == null ) {
6680 throw new IllegalArgumentException ("Context bean is not from the same tree its parent context" );
6781 }
68- return new EObjectContext (rootElement , parent );
82+ return new EObjectContext (rootElement , parent . xpath . getNamespaceContext (), parent . domMapper );
6983 }
84+
7085 DocumentBuilder documentBuilder ;
7186 try {
7287 documentBuilder = DocumentBuilderFactory .newDefaultInstance ().newDocumentBuilder ();
7388 } catch (ParserConfigurationException e ) {
7489 throw new IllegalStateException (e );
7590 }
76- Document proxyDoc = documentBuilder .newDocument ();
77-
78- Map <Node , EObject > domProxy2object = new HashMap <>();
79- Map <EObject , Element > object2domProxy = new HashMap <>();
80- // The xpath '/' actually referes to the document but it's also expected to
81- // match the root application
82- domProxy2object .put (proxyDoc , rootObject );
8391
84- Element rootElement = createElement (rootObject , proxyDoc );
85- object2domProxy .put (rootObject , rootElement );
86- domProxy2object .put (rootElement , rootObject );
87-
88- rootObject .eAllContents ().forEachRemaining (eObject -> {
89- Element parent = object2domProxy .get (eObject .eContainer ());
90- Element proxy = object2domProxy .computeIfAbsent (eObject , o -> createElement (o , parent ));
91- domProxy2object .put (proxy , eObject );
92- });
93-
94- return new EObjectContext (rootElement , domProxy2object , object2domProxy );
92+ Document document = documentBuilder .newDocument ();
93+ DOMMapping domHandler = new DOMMapping ();
94+ Element rootElement = createElement (rootObject , document , domHandler );
95+ if (DEBUG ) {
96+ dump (document );
97+ }
98+ return new EObjectContext (rootElement , createNamespaceContext (rootElement ), domHandler );
9599 }
96100
97101 private static class EObjectContext implements XPathContext {
@@ -100,25 +104,16 @@ private static class EObjectContext implements XPathContext {
100104
101105 private final XPath xpath ;
102106 private final Element rootElement ;
103- private final Map <Node , EObject > domProxy2object ;
104- private final Map <EObject , Element > object2domProxy ;
107+ private final DOMMapping domMapper ;
105108
106- private EObjectContext (Element rootElement , Map <Node , EObject > domProxy2object ,
107- Map <EObject , Element > object2domProxy ) {
109+ private EObjectContext (Element rootElement , NamespaceContext namespaceContext , DOMMapping domMapper ) {
108110 this .rootElement = rootElement ;
109- this .domProxy2object = Map .copyOf (domProxy2object );
110- this .object2domProxy = Map .copyOf (object2domProxy );
111+ this .domMapper = domMapper ;
111112 this .xpath = XPATH_FACTORY .newXPath ();
113+ this .xpath .setNamespaceContext (namespaceContext );
112114 this .xpath .setXPathFunctionResolver (this ::resolveEMFFunctions );
113115 }
114116
115- private EObjectContext (Element rootElement , EObjectContext parentContext ) {
116- this .rootElement = rootElement ;
117- this .domProxy2object = Map .copyOf (parentContext .domProxy2object );
118- this .object2domProxy = Map .copyOf (parentContext .object2domProxy );
119- this .xpath = parentContext .xpath ;
120- }
121-
122117 @ Override
123118 public <R > Stream <R > stream (String path , Class <R > resultType ) {
124119 // See XPathResultType for generally supported result types
@@ -128,8 +123,6 @@ public <R> Stream<R> stream(String path, Class<R> resultType) {
128123 type = resultType ;
129124 }
130125
131- // Fix the different root and allow .[predicate] and ..[predicate] which is
132- // actually not permitted in XPath-1
133126 String pathEnhanced = path ;
134127 if (path .equals ("/" )) {
135128 pathEnhanced = "/" + rootElement .getTagName ();
@@ -138,7 +131,11 @@ public <R> Stream<R> stream(String path, Class<R> resultType) {
138131 // match the root object
139132 pathEnhanced = "/" + rootElement .getTagName () + path ;
140133 }
134+
135+ // Fix the different root and allow .[predicate] and ..[predicate] which is
136+ // actually not permitted in XPath-1
141137 pathEnhanced = pathEnhanced .replace ("..[" , "parent::node()[" ).replace (".[" , "self::node()[" );
138+
142139 Object result ;
143140 try {
144141 result = xpath .evaluateExpression (pathEnhanced , rootElement , type );
@@ -153,7 +150,7 @@ public <R> Stream<R> stream(String path, Class<R> resultType) {
153150 }
154151 return StreamSupport .stream (pathNodes .spliterator (), false ).map (node -> {
155152 if (node instanceof Element || node instanceof Document ) {
156- return domProxy2object . get (node );
153+ return ( EObject ) domMapper . getValue (node );
157154 } else if (node instanceof Attr attribute ) {
158155 return attribute .getValue ();
159156 }
@@ -179,11 +176,12 @@ public Object getValue(String xpath) {
179176 }
180177
181178 private XPathFunction resolveEMFFunctions (QName functionName , int arity ) {
182- if (arity == 1 && "ecore" .equals (functionName .getNamespaceURI ())
179+ if (arity == 1 && EcorePackage . eNS_URI .equals (functionName .getNamespaceURI ())
183180 && "eClassName" .equals (functionName .getLocalPart ())) {
184181 return args -> {
185182 Node item = getSingleNodeArgument (args );
186- return EObjectContext .this .domProxy2object .get (item ).eClass ().getName ();
183+ EObject eObject = (EObject ) EObjectContext .this .domMapper .getValue (item );
184+ return eObject == null ? null : eObject .eClass ().getName ();
187185 };
188186 }
189187 return null ;
@@ -200,37 +198,75 @@ private static Node getSingleNodeArgument(List<?> args) throws XPathFunctionExce
200198 }
201199 throw new XPathFunctionException ("Not a single node list: " + args );
202200 }
201+ }
202+
203+ private static Element createElement (EObject eObject , Document document , DOMMapping domMapper ) {
204+ new XMLSaveImpl (Map .of (), new XMIHelperImpl (), "UTF-8" ).save (null , document ,
205+ Map .of (XMLResource .OPTION_ROOT_OBJECTS , List .of (eObject )), domMapper );
206+ return document .getDocumentElement ();
207+ }
208+
209+ private static NamespaceContext createNamespaceContext (Element element ) {
210+ element .setAttributeNS (XMLConstants .XMLNS_ATTRIBUTE_NS_URI , "xmlns:ecore" , EcorePackage .eNS_URI );
211+ return new NamespaceContext () {
212+ final Map <String , String > xmlnsPrefixMap = new HashMap <>();
213+ final Map <String , String > xmlnsPrefixMapInverse = new HashMap <>();
214+
215+ {
216+ NamedNodeMap attributes = element .getAttributes ();
217+ for (int i = 0 , length = attributes .getLength (); i < length ; ++i ) {
218+ Attr attribute = (Attr ) attributes .item (i );
219+ if ("xmlns" .equals (attribute .getPrefix ())) {
220+ String prefix = attribute .getLocalName ();
221+ String namespace = attribute .getValue ();
222+ xmlnsPrefixMap .put (prefix , namespace );
223+ xmlnsPrefixMapInverse .put (namespace , prefix );
224+ }
225+ }
226+ }
227+
228+ @ Override
229+ public String getNamespaceURI (String prefix ) {
230+ return xmlnsPrefixMap .get (prefix );
231+ }
232+
233+ @ Override
234+ public String getPrefix (String namespaceURI ) {
235+ return xmlnsPrefixMapInverse .get (namespaceURI );
236+ }
203237
238+ @ Override
239+ public Iterator <String > getPrefixes (String namespaceURI ) {
240+ String prefix = getPrefix (namespaceURI );
241+ List <String > list = prefix == null ? List .of () : List .of (prefix );
242+ return list .iterator ();
243+ }
244+ };
204245 }
205246
206- private static Element createElement (EObject eObject , Node parent ) {
207- EStructuralFeature containingFeature = eObject .eContainingFeature ();
208- EStructuralFeature containmentFeature = eObject .eContainmentFeature ();
209- String className = eObject .eClass ().getName ();
210- String qName = containingFeature != null ? containingFeature .getName () : className ;
211-
212- Document document = parent instanceof Document documentParent ? documentParent : parent .getOwnerDocument ();
213- Element element = document .createElement (qName );
214- EClass eClass = eObject .eClass ();
215- for (EAttribute attribute : eClass .getEAllAttributes ()) {
216- // TODO: or check how lists could be serialized as attributes? CSV? With
217- // leading/trailing square-bracket?
218- if (!attribute .isMany () && attribute .getEAttributeType ().isSerializable () && eObject .eIsSet (attribute )) {
219- Object value = eObject .eGet (attribute );
220- try {
221- String stringValue = EcoreUtil .convertToString (attribute .getEAttributeType (), value );
222- element .setAttribute (attribute .getName (), stringValue );
223- } catch (Exception e ) {
224- // TODO: avoid the occurrence of exceptions
247+ private static class DOMMapping extends DefaultDOMHandlerImpl {
248+ public Element getElement (Object object ) {
249+ for (Entry <Node , Object > entry : nodeToObject .entrySet ()) {
250+ if (Objects .equals (entry .getValue (), object )) {
251+ return (Element ) entry .getKey ();
225252 }
226253 }
254+ return null ;
227255 }
228- // TODO: set the xsi:type? This requires to declare the EPackage nsURI as xml
229- // namespace initially, but it's unsure to me if that's even necessary.
230- // String name = eClass.getName();
231- // childElement.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "xsi:type", name);
256+ }
232257
233- parent .appendChild (element );
234- return element ;
258+ private static void dump (Document document ) {
259+ try {
260+ TransformerFactory transformerFactory = TransformerFactory .newInstance ();
261+ Transformer transformer = transformerFactory .newTransformer ();
262+ transformer .setOutputProperty (OutputKeys .ENCODING , "UTF-8" );
263+ transformer .setOutputProperty (OutputKeys .INDENT , "yes" );
264+ StringWriter out = new StringWriter ();
265+ transformer .transform (new DOMSource (document ), new StreamResult (out ));
266+ out .close ();
267+ System .out .print (out );
268+ } catch (Exception ex ) {
269+ throw new RuntimeException (ex );
270+ }
235271 }
236272}
0 commit comments