cd0c9954903167948c844ebcdbf3d96760ca122c
[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 com.google.common.base.Joiner;
11 import com.google.common.base.Optional;
12 import com.google.common.base.Preconditions;
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.Iterator;
19 import java.util.Map;
20 import java.util.Map.Entry;
21 import java.util.NoSuchElementException;
22 import java.util.Set;
23 import javax.annotation.Nonnull;
24 import org.jaxen.DefaultNavigator;
25 import org.jaxen.NamedAccessNavigator;
26 import org.jaxen.Navigator;
27 import org.jaxen.UnsupportedAxisException;
28 import org.jaxen.XPath;
29 import org.jaxen.saxpath.SAXPathException;
30 import org.opendaylight.yangtools.yang.common.QName;
31 import org.opendaylight.yangtools.yang.common.QNameModule;
32 import org.opendaylight.yangtools.yang.data.api.AttributesContainer;
33 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
34 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
38 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
40 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
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 = Preconditions.checkNotNull(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         // The child may be a structural node
243         final NormalizedNode<?, ?> child = maybeChild.get();
244         if (child instanceof MapNode) {
245             return Iterators.transform(((MapNode)child).getValue().iterator(), ctx);
246         }
247         if (child instanceof LeafSetNode) {
248             return Iterators.transform(((LeafSetNode<?>)child).getValue().iterator(), ctx);
249         }
250
251         return Iterators.singletonIterator(ctx.apply(child));
252     }
253
254     @Override
255     public Iterator<? extends Entry<?, ?>> getAttributeAxisIterator(final Object contextNode) {
256         final NormalizedNode<?, ?> node = contextNode(contextNode);
257         if (node instanceof AttributesContainer) {
258             final Map<QName, String> attributes = ((AttributesContainer) node).getAttributes();
259             if (attributes.isEmpty()) {
260                 return null;
261             }
262
263             return attributes.entrySet().iterator();
264         }
265
266         return null;
267     }
268
269     @Override
270     public Iterator<? extends Entry<?, ?>> getAttributeAxisIterator(final Object contextNode, final String localName,
271             final String namespacePrefix, final String namespaceURI) {
272         final NormalizedNode<?, ?> node = contextNode(contextNode);
273         if (node instanceof AttributesContainer) {
274             final Map<QName, String> attributes = ((AttributesContainer) node).getAttributes();
275             if (attributes.isEmpty()) {
276                 return null;
277             }
278
279             final QName qname = resolveQName(node, namespacePrefix, localName);
280             final String value = attributes.get(qname);
281             return value == null ? null : Iterators.singletonIterator(new SimpleImmutableEntry<>(qname, value));
282         }
283
284         return null;
285     }
286
287     @Override
288     public Iterator<NormalizedNodeContext> getParentAxisIterator(final Object contextNode) {
289         final NormalizedNodeContext parent = cast(contextNode).getParent();
290         return parent == null ? null : Iterators.singletonIterator(parent);
291     }
292
293     @Override
294     public Iterator<NormalizedNodeContext> getAncestorAxisIterator(final Object contextNode)
295             throws UnsupportedAxisException {
296         final NormalizedNodeContext parent = cast(contextNode).getParent();
297         return parent == null ? null : new NormalizedNodeContextIterator(parent);
298     }
299
300     @Override
301     public Iterator<NormalizedNodeContext> getSelfAxisIterator(final Object contextNode)
302             throws UnsupportedAxisException {
303         return Iterators.singletonIterator(cast(contextNode));
304     }
305
306     @Override
307     public Iterator<NormalizedNodeContext> getAncestorOrSelfAxisIterator(final Object contextNode)
308             throws UnsupportedAxisException {
309         return new NormalizedNodeContextIterator(cast(contextNode));
310     }
311
312     @Override
313     public NormalizedNodeContext getParentNode(final Object contextNode) throws UnsupportedAxisException {
314         return cast(contextNode).getParent();
315     }
316
317     NormalizedNode<?, ?> getRootNode() {
318         return document.getRootNode();
319     }
320
321     @Nonnull
322     SchemaContext getSchemaContext() {
323         return document.getSchemaContext();
324     }
325
326     private static final class NormalizedNodeContextIterator extends UnmodifiableIterator<NormalizedNodeContext> {
327         private NormalizedNodeContext next;
328
329         NormalizedNodeContextIterator(final NormalizedNodeContext initial) {
330             this.next = Preconditions.checkNotNull(initial);
331         }
332
333         @Override
334         public boolean hasNext() {
335             return next != null;
336         }
337
338         @Override
339         public NormalizedNodeContext next() {
340             if (next == null) {
341                 throw new NoSuchElementException();
342             }
343
344             final NormalizedNodeContext ret = next;
345             next = next.getParent();
346             return ret;
347         }
348     }
349 }