/*
* Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.yangtools.yang.data.jaxen;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Verify;
import com.google.common.collect.Iterators;
import com.google.common.collect.UnmodifiableIterator;
import com.google.common.io.BaseEncoding;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.annotation.Nonnull;
import org.jaxen.DefaultNavigator;
import org.jaxen.NamedAccessNavigator;
import org.jaxen.Navigator;
import org.jaxen.UnsupportedAxisException;
import org.jaxen.XPath;
import org.jaxen.saxpath.SAXPathException;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.QNameModule;
import org.opendaylight.yangtools.yang.data.api.AttributesContainer;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
/**
* A {@link Navigator} implementation for YANG XPaths instantiated on a particular root {@link NormalizedNode}.
*/
final class NormalizedNodeNavigator extends DefaultNavigator implements NamedAccessNavigator {
private static final long serialVersionUID = 1L;
private static final Joiner JOINER = Joiner.on(" ").skipNulls();
private final ConverterNamespaceContext namespaceContext;
private final JaxenDocument document;
NormalizedNodeNavigator(final ConverterNamespaceContext context, final JaxenDocument document) {
this.namespaceContext = Preconditions.checkNotNull(context);
this.document = document;
}
private static NormalizedNodeContext cast(final Object context) {
Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context node %s", context);
return (NormalizedNodeContext) context;
}
private static NormalizedNode<?, ?> contextNode(final Object context) {
return cast(context).getNode();
}
private QName resolveQName(final NormalizedNode<?, ?> node, final String prefix, final String localName) {
final QNameModule module;
if (prefix.isEmpty()) {
module = node.getNodeType().getModule();
} else {
module = namespaceContext.convert(prefix);
}
return QName.create(module, localName);
}
@SuppressWarnings("unchecked")
private static Entry<QName, String> attribute(final Object attr) {
Verify.verify(attr instanceof Entry, "Unhandled attribute %s", attr);
return (Entry<QName, String>) attr;
}
@Override
public String getElementNamespaceUri(final Object element) {
return contextNode(element).getNodeType().getNamespace().toString();
}
@Override
public String getElementName(final Object element) {
return contextNode(element).getNodeType().getLocalName();
}
@Override
public String getElementQName(final Object element) {
return namespaceContext.jaxenQName(contextNode(element).getNodeType());
}
@Override
public String getAttributeNamespaceUri(final Object attr) {
return attribute(attr).getKey().getNamespace().toString();
}
@Override
public String getAttributeName(final Object attr) {
return attribute(attr).getKey().getLocalName();
}
@Override
public String getAttributeQName(final Object attr) {
return namespaceContext.jaxenQName(attribute(attr).getKey());
}
@Override
public NormalizedNodeContext getDocumentNode(final Object contextNode) {
NormalizedNodeContext ctx = cast(contextNode);
while (ctx.getParent() != null) {
ctx = ctx.getParent();
}
return ctx;
}
@Override
public boolean isDocument(final Object object) {
return cast(object).getParent() == null;
}
@Override
public boolean isElement(final Object object) {
return object instanceof NormalizedNodeContext;
}
@Override
public boolean isAttribute(final Object object) {
return object instanceof Entry;
}
@Override
public boolean isNamespace(final Object object) {
return false;
}
@Override
public boolean isComment(final Object object) {
return false;
}
@Override
public boolean isText(final Object object) {
return object instanceof String;
}
@Override
public boolean isProcessingInstruction(final Object object) {
return false;
}
@Override
public String getCommentStringValue(final Object comment) {
throw new UnsupportedOperationException();
}
@Override
public String getElementStringValue(final Object element) {
final NormalizedNode<?, ?> node = contextNode(element);
if (node instanceof LeafNode || node instanceof LeafSetEntryNode) {
final Object value = node.getValue();
// TODO: This is a rather poor approximation of what the codec infrastructure, but it should be sufficient
// to work for now. Tracking SchemaPath will mean we will need to wrap each NormalizedNode with a
// corresponding SchemaPath. That in turn would complicate this class and result in a lot of object
// allocations.
if (value instanceof byte[]) {
// Binary
return BaseEncoding.base64().encode((byte[]) value);
}
if (value instanceof Set) {
// Bits
return JOINER.join((Set<?>)value);
}
if (value != null) {
// Everything else...
return String.valueOf(value);
}
}
return "";
}
@Override
public String getAttributeStringValue(final Object attr) {
return attribute(attr).getValue();
}
@Override
public String getNamespaceStringValue(final Object ns) {
throw new UnsupportedOperationException();
}
@Override
public String getTextStringValue(final Object text) {
return text.toString();
}
@Override
public String getNamespacePrefix(final Object ns) {
throw new UnsupportedOperationException();
}
@Override
public XPath parseXPath(final String xpath) throws SAXPathException {
// FIXME: need to bind YangXPath probably
throw new UnsupportedOperationException();
}
@Override
public Iterator<NormalizedNodeContext> getChildAxisIterator(final Object contextNode, final String localName,
final String namespacePrefix, final String namespaceURI) {
final NormalizedNodeContext ctx = cast(contextNode);
final NormalizedNode<?, ?> node = ctx.getNode();
if (!(node instanceof DataContainerNode)) {
return null;
}
final QName qname = resolveQName(node, namespacePrefix, localName);
@SuppressWarnings({ "unchecked", "rawtypes" })
final Optional<NormalizedNode<?, ?>> maybeChild = ((DataContainerNode)node).getChild(new NodeIdentifier(qname));
if (!maybeChild.isPresent()) {
return null;
}
// The child may be a structural node
final NormalizedNode<?, ?> child = maybeChild.get();
if (child instanceof MapNode) {
return Iterators.transform(((MapNode)child).getValue().iterator(), ctx);
}
if (child instanceof LeafSetNode) {
return Iterators.transform(((LeafSetNode<?>)child).getValue().iterator(), ctx);
}
return Iterators.singletonIterator(ctx.apply(child));
}
@Override
public Iterator<? extends Entry<?, ?>> getAttributeAxisIterator(final Object contextNode, final String localName, final String namespacePrefix,
final String namespaceURI) {
final NormalizedNode<?, ?> node = contextNode(contextNode);
if (node instanceof AttributesContainer) {
final Map<QName, String> attributes = ((AttributesContainer) node).getAttributes();
if (attributes.isEmpty()) {
return null;
}
final QName qname = resolveQName(node, namespacePrefix, localName);
final String value = attributes.get(qname);
return value == null ? null : Iterators.singletonIterator(new SimpleImmutableEntry<>(qname, value));
}
return null;
}
@Override
public Iterator<NormalizedNodeContext> getChildAxisIterator(final Object contextNode) {
final NormalizedNodeContext ctx = cast(contextNode);
final NormalizedNode<?, ?> node = ctx.getNode();
if (node instanceof DataContainerNode) {
return Iterators.transform(((DataContainerNode<?>) node).getValue().iterator(), ctx);
}
return null;
}
@Override
public Iterator<NormalizedNodeContext> getParentAxisIterator(final Object contextNode) {
final NormalizedNodeContext parent = cast(contextNode).getParent();
return parent == null ? null : Iterators.singletonIterator(parent);
}
@Override
public Iterator<NormalizedNodeContext> getAncestorAxisIterator(final Object contextNode) throws UnsupportedAxisException {
final NormalizedNodeContext parent = cast(contextNode).getParent();
return parent == null ? null : new NormalizedNodeContextIterator(parent);
}
@Override
public Iterator<? extends Entry<?, ?>> getAttributeAxisIterator(final Object contextNode) {
final NormalizedNode<?, ?> node = contextNode(contextNode);
if (node instanceof AttributesContainer) {
final Map<QName, String> attributes = ((AttributesContainer) node).getAttributes();
if (attributes.isEmpty()) {
return null;
}
return attributes.entrySet().iterator();
}
return null;
}
@Override
public Iterator<NormalizedNodeContext> getSelfAxisIterator(final Object contextNode) throws UnsupportedAxisException {
return Iterators.singletonIterator(cast(contextNode));
}
@Override
public Iterator<NormalizedNodeContext> getAncestorOrSelfAxisIterator(final Object contextNode) throws UnsupportedAxisException {
return new NormalizedNodeContextIterator(cast(contextNode));
}
@Override
public NormalizedNodeContext getParentNode(final Object contextNode) throws UnsupportedAxisException {
return cast(contextNode).getParent();
}
NormalizedNode<?, ?> getRootNode() {
return document.getRootNode();
}
@Nonnull
SchemaContext getSchemaContext() {
return document.getSchemaContext();
}
private static final class NormalizedNodeContextIterator extends UnmodifiableIterator<NormalizedNodeContext> {
private NormalizedNodeContext next;
NormalizedNodeContextIterator(final NormalizedNodeContext initial) {
this.next = Preconditions.checkNotNull(initial);
}
@Override
public boolean hasNext() {
return next != null;
}
@Override
public NormalizedNodeContext next() {
if (next == null) {
throw new NoSuchElementException();
}
final NormalizedNodeContext ret = next;
next = next.getParent();
return ret;
}
}
}