/*
* 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.parser.rfc6020.repo;
import static org.opendaylight.yangtools.yang.parser.rfc6020.repo.StatementSourceReferenceHandler.extractRef;
import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.net.URI;
import java.net.URISyntaxException;
import javax.annotation.Nonnull;
import javax.xml.transform.TransformerException;
import org.opendaylight.yangtools.concepts.Identifiable;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
import org.opendaylight.yangtools.yang.model.repo.api.YinDomSchemaSource;
import org.opendaylight.yangtools.yang.model.repo.api.YinXmlSchemaSource;
import org.opendaylight.yangtools.yang.parser.spi.meta.ModelProcessingPhase;
import org.opendaylight.yangtools.yang.parser.spi.source.PrefixToModule;
import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinition;
import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
import org.opendaylight.yangtools.yang.parser.spi.source.StatementStreamSource;
import org.opendaylight.yangtools.yang.parser.spi.source.StatementWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* A {@link StatementStreamSource} based on a {@link YinXmlSchemaSource}. Internal implementation works on top
* of {@link YinDomSchemaSource} and its DOM document.
*
* @author Robert Varga
*/
@Beta
public final class YinStatementStreamSource implements Identifiable<SourceIdentifier>, StatementStreamSource {
private static final Logger LOG = LoggerFactory.getLogger(YinStatementStreamSource.class);
private static final LoadingCache<String, URI> URI_CACHE = CacheBuilder.newBuilder().weakValues().build(
new CacheLoader<String, URI>() {
@Override
public URI load(@Nonnull final String key) throws URISyntaxException {
return new URI(key);
}
});
private final SourceIdentifier identifier;
private final Node root;
private YinStatementStreamSource(final SourceIdentifier identifier, final Node root) {
this.identifier = Preconditions.checkNotNull(identifier);
this.root = Preconditions.checkNotNull(root);
}
public static StatementStreamSource create(final YinXmlSchemaSource source) throws TransformerException {
return create(YinDomSchemaSource.transform(source));
}
public static StatementStreamSource create(final YinDomSchemaSource source) {
return new YinStatementStreamSource(source.getIdentifier(), source.getSource().getNode());
}
@Override
public SourceIdentifier getIdentifier() {
return identifier;
}
private static StatementDefinition getValidDefinition(final Node node, final StatementWriter writer,
final QNameToStatementDefinition stmtDef, final StatementSourceReference ref) {
final URI uri = URI_CACHE.getUnchecked(node.getNamespaceURI());
final StatementDefinition def = stmtDef.getByNamespaceAndLocalName(uri, node.getLocalName());
if (def == null) {
SourceException.throwIf(writer.getPhase().equals(ModelProcessingPhase.FULL_DECLARATION), ref,
"%s is not a YIN statement or use of extension.", node.getLocalName());
}
return def;
}
private static void processAttribute(final int childId, final Attr attr, final StatementWriter writer,
final QNameToStatementDefinition stmtDef, final StatementSourceReference ref) {
final StatementDefinition def = getValidDefinition(attr, writer, stmtDef, ref);
if (def == null) {
return;
}
final String value = attr.getValue();
writer.startStatement(childId, def.getStatementName(), value.isEmpty() ? null : value, ref);
writer.endStatement(ref);
}
private static String getArgValue(final Element element, final QName argName, final boolean yinElement) {
if (yinElement) {
final NodeList children = element.getElementsByTagNameNS(argName.getNamespace().toString(),
argName.getLocalName());
if (children.getLength() == 0) {
return null;
}
return children.item(0).getTextContent();
}
final Attr attr = element.getAttributeNode(argName.getLocalName());
if (attr == null) {
return null;
}
return attr.getValue();
}
private static void processElement(final int childId, final Element element, final StatementWriter writer,
final QNameToStatementDefinition stmtDef) {
final StatementSourceReference ref = extractRef(element);
final StatementDefinition def = getValidDefinition(element, writer, stmtDef, ref);
if (def == null) {
LOG.debug("Skipping element {}", element);
return;
}
final QName argName = def.getArgumentName();
final String argValue;
final boolean allAttrs;
final boolean allElements;
if (argName != null) {
allAttrs = def.isArgumentYinElement();
allElements = !allAttrs;
argValue = getArgValue(element, argName, allAttrs);
SourceException.throwIfNull(argValue, ref, "Statement {} is missing mandatory argument %s",
def.getStatementName(), argName);
} else {
argValue = null;
allAttrs = false;
allElements = false;
}
writer.startStatement(childId, def.getStatementName(), argValue, ref);
// Child counter
int childCounter = 0;
// First process any statements defined as attributes. We need to skip argument, if present
final NamedNodeMap attributes = element.getAttributes();
if (attributes != null) {
for (int i = 0, len = attributes.getLength(); i < len; ++i) {
final Attr attr = (Attr) attributes.item(i);
if (allAttrs || !isArgument(argName, attr)) {
processAttribute(childCounter++, attr, writer, stmtDef, ref);
}
}
}
// Now process child elements, if present
final NodeList children = element.getChildNodes();
for (int i = 0, len = children.getLength(); i < len; ++i) {
final Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
if (allElements || !isArgument(argName, child)) {
processElement(childCounter++, (Element) child, writer, stmtDef);
}
}
}
writer.endStatement(ref);
}
private static boolean isArgument(final QName argName, final Node node) {
return argName != null && argName.getLocalName().equals(node.getLocalName()) && node.getPrefix() == null;
}
private void walkTree(final StatementWriter writer, final QNameToStatementDefinition stmtDef) {
final NodeList children = root.getChildNodes();
int childCounter = 0;
for (int i = 0, len = children.getLength(); i < len; ++i) {
final Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
processElement(childCounter++, (Element) child, writer, stmtDef);
}
}
}
@Override
public void writePreLinkage(final StatementWriter writer, final QNameToStatementDefinition stmtDef) {
walkTree(writer, stmtDef);
}
@Override
public void writeLinkage(final StatementWriter writer, final QNameToStatementDefinition stmtDef,
final PrefixToModule preLinkagePrefixes) {
walkTree(writer, stmtDef);
}
@Override
public void writeLinkageAndStatementDefinitions(final StatementWriter writer,
final QNameToStatementDefinition stmtDef, final PrefixToModule prefixes) {
walkTree(writer, stmtDef);
}
@Override
public void writeFull(final StatementWriter writer, final QNameToStatementDefinition stmtDef,
final PrefixToModule prefixes) {
walkTree(writer, stmtDef);
}
}