Jaxen binding should use DataSchemaContextTree
[yangtools.git] / yang / yang-data-jaxen / src / main / java / org / opendaylight / yangtools / yang / data / jaxen / NormalizedNodeNavigator.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.data.jaxen;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.base.Joiner;
13 import com.google.common.base.Verify;
14 import com.google.common.collect.Iterators;
15 import com.google.common.collect.UnmodifiableIterator;
16 import com.google.common.io.BaseEncoding;
17 import java.util.AbstractMap.SimpleImmutableEntry;
18 import java.util.Collection;
19 import java.util.Iterator;
20 import java.util.Map;
21 import java.util.Map.Entry;
22 import java.util.NoSuchElementException;
23 import java.util.Optional;
24 import java.util.Set;
25 import org.jaxen.DefaultNavigator;
26 import org.jaxen.NamedAccessNavigator;
27 import org.jaxen.Navigator;
28 import org.jaxen.UnsupportedAxisException;
29 import org.jaxen.XPath;
30 import org.jaxen.saxpath.SAXPathException;
31 import org.opendaylight.yangtools.yang.common.QName;
32 import org.opendaylight.yangtools.yang.common.QNameModule;
33 import org.opendaylight.yangtools.yang.data.api.AttributesContainer;
34 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
35 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
38 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
41
42 /**
43  * A {@link Navigator} implementation for YANG XPaths instantiated on a particular root {@link NormalizedNode}.
44  */
45 final class NormalizedNodeNavigator extends DefaultNavigator implements NamedAccessNavigator {
46     private static final long serialVersionUID = 1L;
47     private static final Joiner JOINER = Joiner.on(" ").skipNulls();
48     private final ConverterNamespaceContext namespaceContext;
49     private final JaxenDocument document;
50
51     NormalizedNodeNavigator(final ConverterNamespaceContext context, final JaxenDocument document) {
52         this.namespaceContext = requireNonNull(context);
53         this.document = document;
54     }
55
56     private static NormalizedNodeContext cast(final Object context) {
57         Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context node %s", context);
58         return (NormalizedNodeContext) context;
59     }
60
61     private static NormalizedNode<?, ?> contextNode(final Object context) {
62         return cast(context).getNode();
63     }
64
65     private QName resolveQName(final NormalizedNode<?, ?> node, final String prefix, final String localName) {
66         final QNameModule module;
67         if (prefix.isEmpty()) {
68             module = node.getNodeType().getModule();
69         } else {
70             module = namespaceContext.convert(prefix);
71         }
72
73         return QName.create(module, localName);
74     }
75
76     @SuppressWarnings("unchecked")
77     private static Entry<QName, String> attribute(final Object attr) {
78         Verify.verify(attr instanceof Entry, "Unhandled attribute %s", attr);
79         return (Entry<QName, String>) attr;
80     }
81
82     @Override
83     public String getElementNamespaceUri(final Object element) {
84         return contextNode(element).getNodeType().getNamespace().toString();
85     }
86
87     @Override
88     public String getElementName(final Object element) {
89         return contextNode(element).getNodeType().getLocalName();
90     }
91
92     @Override
93     public String getElementQName(final Object element) {
94         return namespaceContext.jaxenQName(contextNode(element).getNodeType());
95     }
96
97     @Override
98     public String getAttributeNamespaceUri(final Object attr) {
99         return attribute(attr).getKey().getNamespace().toString();
100     }
101
102     @Override
103     public String getAttributeName(final Object attr) {
104         return attribute(attr).getKey().getLocalName();
105     }
106
107     @Override
108     public String getAttributeQName(final Object attr) {
109         return namespaceContext.jaxenQName(attribute(attr).getKey());
110     }
111
112     @Override
113     public NormalizedNodeContext getDocumentNode(final Object contextNode) {
114         NormalizedNodeContext ctx = cast(contextNode);
115         while (ctx.getParent() != null) {
116             ctx = ctx.getParent();
117         }
118
119         return ctx;
120     }
121
122     @Override
123     public boolean isDocument(final Object object) {
124         return cast(object).getParent() == null;
125     }
126
127     @Override
128     public boolean isElement(final Object object) {
129         return object instanceof NormalizedNodeContext;
130     }
131
132     @Override
133     public boolean isAttribute(final Object object) {
134         return object instanceof Entry;
135     }
136
137     @Override
138     public boolean isNamespace(final Object object) {
139         return false;
140     }
141
142     @Override
143     public boolean isComment(final Object object) {
144         return false;
145     }
146
147     @Override
148     public boolean isText(final Object object) {
149         return object instanceof String;
150     }
151
152     @Override
153     public boolean isProcessingInstruction(final Object object) {
154         return false;
155     }
156
157     @Override
158     public String getCommentStringValue(final Object comment) {
159         throw new UnsupportedOperationException();
160     }
161
162     @Override
163     public String getElementStringValue(final Object element) {
164         final NormalizedNode<?, ?> node = contextNode(element);
165         if (node instanceof LeafNode || node instanceof LeafSetEntryNode) {
166             final Object value = node.getValue();
167
168             // TODO: This is a rather poor approximation of what the codec infrastructure, but it should be sufficient
169             //       to work for now. Tracking SchemaPath will mean we will need to wrap each NormalizedNode with a
170             //       corresponding SchemaPath. That in turn would complicate this class and result in a lot of object
171             //       allocations.
172             if (value instanceof byte[]) {
173                 // Binary
174                 return BaseEncoding.base64().encode((byte[]) value);
175             }
176             if (value instanceof Set) {
177                 // Bits
178                 return JOINER.join((Set<?>)value);
179             }
180             if (value != null) {
181                 // Everything else...
182                 return String.valueOf(value);
183             }
184         }
185
186         return "";
187     }
188
189     @Override
190     public String getAttributeStringValue(final Object attr) {
191         return attribute(attr).getValue();
192     }
193
194     @Override
195     public String getNamespaceStringValue(final Object ns) {
196         throw new UnsupportedOperationException();
197     }
198
199     @Override
200     public String getTextStringValue(final Object text) {
201         return text.toString();
202     }
203
204     @Override
205     public String getNamespacePrefix(final Object ns) {
206         throw new UnsupportedOperationException();
207     }
208
209     @Override
210     public XPath parseXPath(final String xpath) throws SAXPathException {
211         // FIXME: need to bind YangXPath probably
212         throw new UnsupportedOperationException();
213     }
214
215     @Override
216     public Iterator<NormalizedNodeContext> getChildAxisIterator(final Object contextNode) {
217         final NormalizedNodeContext ctx = cast(contextNode);
218         final NormalizedNode<?, ?> node = ctx.getNode();
219         if (node instanceof DataContainerNode) {
220             return Iterators.transform(((DataContainerNode<?>) node).getValue().iterator(), ctx);
221         }
222
223         return null;
224     }
225
226     @Override
227     public Iterator<NormalizedNodeContext> getChildAxisIterator(final Object contextNode, final String localName,
228             final String namespacePrefix, final String namespaceURI) {
229         final NormalizedNodeContext ctx = cast(contextNode);
230         final NormalizedNode<?, ?> node = ctx.getNode();
231         if (!(node instanceof DataContainerNode)) {
232             return null;
233         }
234
235         final QName qname = resolveQName(node, namespacePrefix, localName);
236         @SuppressWarnings({ "unchecked", "rawtypes" })
237         final Optional<NormalizedNode<?, ?>> maybeChild = ((DataContainerNode)node).getChild(new NodeIdentifier(qname));
238         if (!maybeChild.isPresent()) {
239             return null;
240         }
241
242         final NormalizedNode<?, ?> child = maybeChild.get();
243         final Collection<? extends NormalizedNode<?, ?>> collection;
244
245         // The child may be a structural node
246         if (child instanceof MapNode) {
247             collection = ((MapNode)child).getValue();
248         } else if (child instanceof LeafSetNode) {
249             collection = ((LeafSetNode<?>)child).getValue();
250         } else {
251             return Iterators.singletonIterator(ctx.apply(child));
252         }
253
254         return Iterators.transform(collection.iterator(), ctx);
255     }
256
257     @Override
258     public Iterator<? extends Entry<?, ?>> getAttributeAxisIterator(final Object contextNode) {
259         final NormalizedNode<?, ?> node = contextNode(contextNode);
260         if (node instanceof AttributesContainer) {
261             final Map<QName, String> attributes = ((AttributesContainer) node).getAttributes();
262             if (attributes.isEmpty()) {
263                 return null;
264             }
265
266             return attributes.entrySet().iterator();
267         }
268
269         return null;
270     }
271
272     @Override
273     public Iterator<? extends Entry<?, ?>> getAttributeAxisIterator(final Object contextNode, final String localName,
274             final String namespacePrefix, final String namespaceURI) {
275         final NormalizedNode<?, ?> node = contextNode(contextNode);
276         if (node instanceof AttributesContainer) {
277             final Map<QName, String> attributes = ((AttributesContainer) node).getAttributes();
278             if (attributes.isEmpty()) {
279                 return null;
280             }
281
282             final QName qname = resolveQName(node, namespacePrefix, localName);
283             final String value = attributes.get(qname);
284             return value == null ? null : Iterators.singletonIterator(new SimpleImmutableEntry<>(qname, value));
285         }
286
287         return null;
288     }
289
290     @Override
291     public Iterator<NormalizedNodeContext> getParentAxisIterator(final Object contextNode) {
292         final NormalizedNodeContext parent = cast(contextNode).getParent();
293         return parent == null ? null : Iterators.singletonIterator(parent);
294     }
295
296     @Override
297     public Iterator<NormalizedNodeContext> getAncestorAxisIterator(final Object contextNode)
298             throws UnsupportedAxisException {
299         final NormalizedNodeContext parent = cast(contextNode).getParent();
300         return parent == null ? null : new NormalizedNodeContextIterator(parent);
301     }
302
303     @Override
304     public Iterator<NormalizedNodeContext> getSelfAxisIterator(final Object contextNode)
305             throws UnsupportedAxisException {
306         return Iterators.singletonIterator(cast(contextNode));
307     }
308
309     @Override
310     public Iterator<NormalizedNodeContext> getAncestorOrSelfAxisIterator(final Object contextNode)
311             throws UnsupportedAxisException {
312         return new NormalizedNodeContextIterator(cast(contextNode));
313     }
314
315     @Override
316     public NormalizedNodeContext getParentNode(final Object contextNode) throws UnsupportedAxisException {
317         return cast(contextNode).getParent();
318     }
319
320     JaxenDocument getDocument() {
321         return document;
322     }
323
324     private static final class NormalizedNodeContextIterator extends UnmodifiableIterator<NormalizedNodeContext> {
325         private NormalizedNodeContext next;
326
327         NormalizedNodeContextIterator(final NormalizedNodeContext initial) {
328             this.next = requireNonNull(initial);
329         }
330
331         @Override
332         public boolean hasNext() {
333             return next != null;
334         }
335
336         @Override
337         public NormalizedNodeContext next() {
338             if (next == null) {
339                 throw new NoSuchElementException();
340             }
341
342             final NormalizedNodeContext ret = next;
343             next = next.getParent();
344             return ret;
345         }
346     }
347 }