2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.yang.model.spi.source;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12 import static org.opendaylight.yangtools.yang.common.YangConstants.RFC6020_YIN_MODULE;
13 import static org.opendaylight.yangtools.yang.model.api.YangStmtMapping.MODULE;
14 import static org.opendaylight.yangtools.yang.model.api.YangStmtMapping.REVISION;
15 import static org.opendaylight.yangtools.yang.model.api.YangStmtMapping.SUBMODULE;
17 import com.google.common.base.MoreObjects;
18 import com.google.common.base.MoreObjects.ToStringHelper;
19 import javax.xml.transform.Source;
20 import javax.xml.transform.TransformerException;
21 import javax.xml.transform.TransformerFactory;
22 import javax.xml.transform.dom.DOMResult;
23 import javax.xml.transform.dom.DOMSource;
24 import org.eclipse.jdt.annotation.NonNull;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.opendaylight.yangtools.yang.common.QName;
27 import org.opendaylight.yangtools.yang.common.YangConstants;
28 import org.opendaylight.yangtools.yang.model.api.source.SourceIdentifier;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31 import org.w3c.dom.Attr;
32 import org.w3c.dom.Element;
33 import org.w3c.dom.Node;
34 import org.w3c.dom.NodeList;
37 * Utility {@link YinXmlSource} exposing a W3C {@link DOMSource} representation of YIN model.
39 public abstract sealed class YinDomSource implements YinXmlSource {
40 private static final Logger LOG = LoggerFactory.getLogger(YinDomSource.class);
41 private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance();
42 private static final QName REVISION_STMT = REVISION.getStatementName();
43 private static final String MODULE_ARG = MODULE.getArgumentDefinition().orElseThrow()
44 .argumentName().getLocalName();
45 private static final String REVISION_ARG = REVISION.getArgumentDefinition().orElseThrow()
46 .argumentName().getLocalName();
49 * Create a new {@link YinDomSource} using an identifier and a source.
51 * @param identifier Schema source identifier
52 * @param source W3C DOM source
53 * @param symbolicName Source symbolic name
54 * @return A new {@link YinDomSource} instance.
56 public static @NonNull YinDomSource create(final @NonNull SourceIdentifier identifier,
57 final @NonNull DOMSource source, final @Nullable String symbolicName) {
59 final Node root = source.getNode().getFirstChild();
60 final String rootNs = root.getNamespaceURI();
62 // Let whoever is using this deal with this
63 return new Simple(identifier, source, symbolicName);
66 final QName qname = QName.create(rootNs, root.getLocalName());
67 checkArgument(RFC6020_YIN_MODULE.equals(qname.getModule()),
68 "Root node namepsace %s does not match %s", rootNs, YangConstants.RFC6020_YIN_NAMESPACE);
69 checkArgument(MODULE.getStatementName().equals(qname)
70 || SUBMODULE.getStatementName().equals(qname), "Root element %s is not a module nor a submodule", qname);
72 checkArgument(root instanceof Element, "Root node %s is not an element", root);
73 final Element element = (Element)root;
75 final Attr nameAttr = element.getAttributeNode(MODULE_ARG);
76 checkArgument(nameAttr != null, "No %s name argument found in %s", element.getLocalName());
78 final NodeList revisions = element.getElementsByTagNameNS(REVISION_STMT.getNamespace().toString(),
79 REVISION_STMT.getLocalName());
80 if (revisions.getLength() == 0) {
81 // FIXME: is module name important (as that may have changed)
82 return new Simple(identifier, source, symbolicName);
85 final Element revisionStmt = (Element) revisions.item(0);
86 final Attr dateAttr = revisionStmt.getAttributeNode(REVISION_ARG);
87 checkArgument(dateAttr != null, "No revision statement argument found in %s", revisionStmt);
89 final var parsedId = new SourceIdentifier(nameAttr.getValue(), dateAttr.getValue());
90 final SourceIdentifier id;
91 if (!parsedId.equals(identifier)) {
92 LOG.debug("Changed identifier from {} to {}", identifier, parsedId);
98 return new Simple(id, source, symbolicName);
102 * Create a {@link YinDomSource} from a {@link YinXmlSource}. If the argument is already a
103 * YinDomSchemaSource, this method returns the same instance. The source will be translated on first access,
104 * at which point an {@link IllegalStateException} may be raised.
106 * @param xmlSchemaSource Backing schema source
107 * @return A {@link YinDomSource} instance
109 public static @NonNull YinDomSource lazyTransform(final YinXmlSource xmlSchemaSource) {
110 final var cast = castSchemaSource(xmlSchemaSource);
111 return cast != null ? cast : new Transforming(xmlSchemaSource);
115 * Create a {@link YinDomSource} from a {@link YinXmlSource}. If the argument is already a
116 * YinDomSchemaSource, this method returns the same instance. The source will be translated immediately.
118 * @param xmlSchemaSource Backing schema source
119 * @return A {@link YinDomSource} instance
120 * @throws TransformerException when the provided source fails to transform
122 public static @NonNull YinDomSource transform(final YinXmlSource xmlSchemaSource)
123 throws TransformerException {
124 final var cast = castSchemaSource(xmlSchemaSource);
125 return cast != null ? cast :
126 create(xmlSchemaSource.sourceId(), transformSource(xmlSchemaSource.getSource()),
127 xmlSchemaSource.symbolicName());
131 public final Class<YinDomSource> getType() {
132 return YinDomSource.class;
136 public abstract DOMSource getSource();
139 public final String toString() {
140 return addToStringAttributes(MoreObjects.toStringHelper(this).add("sourceId", sourceId())).toString();
144 * Add subclass-specific attributes to the output {@link #toString()} output. Since
145 * subclasses are prevented from overriding {@link #toString()} for consistency
146 * reasons, they can add their specific attributes to the resulting string by attaching
147 * attributes to the supplied {@link ToStringHelper}.
149 * @param toStringHelper ToStringHelper onto the attributes can be added
150 * @return ToStringHelper supplied as input argument.
152 protected abstract ToStringHelper addToStringAttributes(ToStringHelper toStringHelper);
154 static @NonNull DOMSource transformSource(final Source source) throws TransformerException {
155 final DOMResult result = new DOMResult();
156 TRANSFORMER_FACTORY.newTransformer().transform(source, result);
158 return new DOMSource(result.getNode(), result.getSystemId());
161 private static @Nullable YinDomSource castSchemaSource(final YinXmlSource xmlSchemaSource) {
162 if (xmlSchemaSource instanceof YinDomSource yinDom) {
166 final var source = xmlSchemaSource.getSource();
167 if (source instanceof DOMSource dom) {
168 return create(xmlSchemaSource.sourceId(), dom, xmlSchemaSource.symbolicName());
173 private static final class Simple extends YinDomSource {
174 private final @NonNull SourceIdentifier sourceId;
175 private final @NonNull DOMSource source;
176 private final String symbolicName;
178 Simple(final @NonNull SourceIdentifier sourceId, final @NonNull DOMSource source,
179 final @Nullable String symbolicName) {
180 this.sourceId = requireNonNull(sourceId);
181 this.source = requireNonNull(source);
182 this.symbolicName = symbolicName;
186 public SourceIdentifier sourceId() {
191 public DOMSource getSource() {
196 public String symbolicName() {
201 protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
202 return toStringHelper.add("source", source);
206 private static final class Transforming extends YinDomSource {
207 private final YinXmlSource xmlSchemaSource;
209 private volatile DOMSource source;
211 Transforming(final YinXmlSource xmlSchemaSource) {
212 this.xmlSchemaSource = requireNonNull(xmlSchemaSource);
216 public SourceIdentifier sourceId() {
217 return xmlSchemaSource.sourceId();
221 public String symbolicName() {
222 return xmlSchemaSource.symbolicName();
226 public DOMSource getSource() {
227 DOMSource ret = source;
229 synchronized (this) {
233 ret = transformSource(xmlSchemaSource.getSource());
234 } catch (TransformerException e) {
235 throw new IllegalStateException("Failed to transform schema source " + xmlSchemaSource, e);
246 protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
247 return toStringHelper.add("xmlSchemaSource", xmlSchemaSource);