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