2 * Copyright (c) 2014 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.data.util;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.annotations.Beta;
14 import com.google.common.escape.Escaper;
15 import com.google.common.escape.Escapers;
16 import javax.xml.XMLConstants;
17 import org.eclipse.jdt.annotation.NonNull;
18 import org.eclipse.jdt.annotation.Nullable;
19 import org.opendaylight.yangtools.yang.common.QName;
20 import org.opendaylight.yangtools.yang.common.QNameModule;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
24 import org.opendaylight.yangtools.yang.data.api.codec.InstanceIdentifierCodec;
25 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
26 import org.opendaylight.yangtools.yang.model.util.LeafrefResolver;
29 * Abstract utility class for representations which encode {@link YangInstanceIdentifier} as a
30 * prefix:name tuple. Typical uses are RESTCONF/JSON (module:name) and XML (prefix:name).
33 public abstract class AbstractStringInstanceIdentifierCodec extends AbstractNamespaceCodec<YangInstanceIdentifier>
34 implements InstanceIdentifierCodec<String> {
35 // Escaper as per https://www.rfc-editor.org/rfc/rfc7950#section-6.1.3
36 private static final Escaper DQUOT_ESCAPER = Escapers.builder()
37 .addEscape('\n', "\\n")
38 .addEscape('\t', "\\t")
39 .addEscape('"', "\\\"")
40 .addEscape('\\', "\\\\")
44 protected final String serializeImpl(final YangInstanceIdentifier data) {
45 final StringBuilder sb = new StringBuilder();
46 DataSchemaContextNode<?> current = getDataContextTree().getRoot();
47 QNameModule lastModule = null;
48 for (var arg : data.getPathArguments()) {
49 current = current.getChild(arg);
50 checkArgument(current != null, "Invalid input %s: schema for argument %s (after %s) not found", data, arg,
53 if (current.isMixin()) {
55 * XML/YANG instance identifier does not have concept
56 * of augmentation identifier, or list as whole which
57 * identifies a mixin (same as the parent element),
58 * so we can safely ignore it if it is part of path
59 * (since child node) is identified in same fashion.
64 final var qname = arg.getNodeType();
66 appendQName(sb, qname, lastModule);
67 lastModule = qname.getModule();
69 if (arg instanceof NodeIdentifierWithPredicates) {
70 for (var entry : ((NodeIdentifierWithPredicates) arg).entrySet()) {
71 final var keyName = entry.getKey();
72 appendQName(sb.append('['), keyName, lastModule).append('=');
73 appendValue(sb, keyName.getModule(), entry.getValue()).append(']');
75 } else if (arg instanceof NodeWithValue) {
76 appendValue(sb.append("[.="), lastModule, ((NodeWithValue<?>) arg).getValue()).append(']');
82 private StringBuilder appendValue(final StringBuilder sb, final QNameModule currentModule,
84 if (value instanceof QName) {
85 // QName implies identity-ref, which can never be escaped
86 return appendQName(sb.append('\''), (QName) value, currentModule).append('\'');
89 final var str = String.valueOf(value);
91 // We have two specifications here: Section 6.1.3 of both RFC6020 and RFC7950:
93 // RFC6020 Section 6.1.3:
94 // If a string contains any space or tab characters, a semicolon (";"),
95 // braces ("{" or "}"), or comment sequences ("//", "/*", or "*/"), then
96 // it MUST be enclosed within double or single quotes.
98 // RFC7950 Section 6.1.3:
99 // An unquoted string is any sequence of characters that does not
100 // contain any space, tab, carriage return, or line feed characters, a
101 // single or double quote character, a semicolon (";"), braces ("{" or
102 // "}"), or comment sequences ("//", "/*", or "*/").
104 // Plus the common part:
105 // A single-quoted string (enclosed within ' ') preserves each character
106 // within the quotes. A single quote character cannot occur in a
107 // single-quoted string, even when preceded by a backslash.
109 // Unquoted strings are not interesting, as we are embedding the value in a string, not a YANG document, hence
110 // we have to use quotes. Single-quoted case is simpler, as it does not involve any escaping. The only case
111 // where we cannot use it is when the value itself has a single-quote in itself -- then we call back to
114 return str.indexOf('\'') == -1
115 // No escaping needed, use single quotes
116 ? sb.append('\'').append(str).append('\'')
117 // Escaping needed: use double quotes
118 : sb.append('"').append(DQUOT_ESCAPER.escape(str)).append('"');
122 * Returns DataSchemaContextTree associated with SchemaContext for which
123 * serialization / deserialization occurs.
126 * Implementations MUST provide non-null Data Tree context, in order
127 * for correct serialization / deserialization of PathArguments,
128 * since XML representation does not have Augmentation arguments
129 * and does not provide path arguments for cases.
132 * This effectively means same input XPath representation of Path Argument
133 * may result in different YangInstanceIdentifiers if models are different
134 * in uses of choices and cases.
136 * @return DataSchemaContextTree associated with SchemaContext for which
137 * serialization / deserialization occurs.
139 protected abstract @NonNull DataSchemaContextTree getDataContextTree();
141 protected Object deserializeKeyValue(final DataSchemaNode schemaNode, final LeafrefResolver resolver,
142 final String value) {
147 protected final YangInstanceIdentifier deserializeImpl(final String data) {
148 return YangInstanceIdentifier.create(
149 new XpathStringParsingPathArgumentBuilder(this, requireNonNull(data)).build());
153 * Create QName from unprefixed name, potentially taking last QNameModule encountered into account.
155 * @param lastModule last QNameModule encountered, potentially null
156 * @param localName Local name string
157 * @return A newly-created QName
159 protected QName createQName(final @Nullable QNameModule lastModule, final String localName) {
160 // This implementation handles both XML encoding, where we follow XML namespace rules and old JSON encoding,
161 // which is the same thing: always encode prefixes
162 return createQName(XMLConstants.DEFAULT_NS_PREFIX, localName);