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