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 java.util.Objects.requireNonNull;
12 import com.google.common.escape.Escaper;
13 import com.google.common.escape.Escapers;
15 import javax.xml.XMLConstants;
16 import org.eclipse.jdt.annotation.NonNull;
17 import org.eclipse.jdt.annotation.Nullable;
18 import org.opendaylight.yangtools.yang.common.QName;
19 import org.opendaylight.yangtools.yang.common.QNameModule;
20 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
23 import org.opendaylight.yangtools.yang.data.api.codec.InstanceIdentifierCodec;
24 import org.opendaylight.yangtools.yang.data.util.DataSchemaContext.Composite;
25 import org.opendaylight.yangtools.yang.data.util.DataSchemaContext.PathMixin;
26 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
28 import org.opendaylight.yangtools.yang.model.util.LeafrefResolver;
31 * Abstract utility class for representations which encode {@link YangInstanceIdentifier} as a
32 * prefix:name tuple. Typical uses are RESTCONF/JSON (module:name) and XML (prefix:name).
34 public abstract class AbstractStringInstanceIdentifierCodec extends AbstractNamespaceCodec<YangInstanceIdentifier>
35 implements InstanceIdentifierCodec<String> {
36 // Escaper as per https://www.rfc-editor.org/rfc/rfc7950#section-6.1.3
37 private static final Escaper DQUOT_ESCAPER = Escapers.builder()
38 .addEscape('\n', "\\n")
39 .addEscape('\t', "\\t")
40 .addEscape('"', "\\\"")
41 .addEscape('\\', "\\\\")
45 protected final String serializeImpl(final YangInstanceIdentifier data) {
46 final StringBuilder sb = new StringBuilder();
47 DataSchemaContext current = getDataContextTree().getRoot();
48 QNameModule lastModule = null;
49 for (var arg : data.getPathArguments()) {
50 current = current instanceof Composite composite ? composite.childByArg(arg) : null;
51 if (current == null) {
52 throw new IllegalArgumentException(
53 "Invalid input %s: schema for argument %s (after \"%s\") not found".formatted(data, arg, sb));
56 if (current instanceof PathMixin) {
58 * XML/YANG instance identifier does not have concept of augmentation identifier, or list as whole which
59 * identifies a mixin (same as the parent element), so we can safely ignore it if it is part of path
60 * (since child node) is identified in same fashion.
65 final var qname = arg.getNodeType();
67 appendQName(sb, qname, lastModule);
68 lastModule = qname.getModule();
70 if (arg instanceof NodeIdentifierWithPredicates nip) {
71 for (var entry : nip.entrySet()) {
72 final var keyName = entry.getKey();
73 appendQName(sb.append('['), keyName, lastModule).append('=');
74 appendValue(sb, keyName.getModule(), entry.getValue()).append(']');
76 } else if (arg instanceof NodeWithValue<?> val) {
77 appendValue(sb.append("[.="), lastModule, val.getValue()).append(']');
83 private StringBuilder appendValue(final StringBuilder sb, final QNameModule currentModule,
85 if (value instanceof QName qname) {
86 // QName implies identity-ref, which can never be escaped
87 return appendQName(sb.append('\''), qname, currentModule).append('\'');
89 // FIXME: YANGTOOLS-1426: update once we have a dedicated type
90 if (value instanceof Set<?> bits) {
91 // Set implies bits, which can never be escaped and need to be serialized as space-separated items
94 final var it = bits.iterator();
96 sb.append(checkBitsItem(it.next()));
97 while (it.hasNext()) {
98 sb.append(' ').append(checkBitsItem(it.next()));
102 return sb.append('\'');
105 final var str = value instanceof YangInstanceIdentifier id ? serialize(id) : String.valueOf(value);
107 // We have two specifications here: Section 6.1.3 of both RFC6020 and RFC7950:
109 // RFC6020 Section 6.1.3:
110 // If a string contains any space or tab characters, a semicolon (";"),
111 // braces ("{" or "}"), or comment sequences ("//", "/*", or "*/"), then
112 // it MUST be enclosed within double or single quotes.
114 // RFC7950 Section 6.1.3:
115 // An unquoted string is any sequence of characters that does not
116 // contain any space, tab, carriage return, or line feed characters, a
117 // single or double quote character, a semicolon (";"), braces ("{" or
118 // "}"), or comment sequences ("//", "/*", or "*/").
120 // Plus the common part:
121 // A single-quoted string (enclosed within ' ') preserves each character
122 // within the quotes. A single quote character cannot occur in a
123 // single-quoted string, even when preceded by a backslash.
125 // Unquoted strings are not interesting, as we are embedding the value in a string, not a YANG document, hence
126 // we have to use quotes. Single-quoted case is simpler, as it does not involve any escaping. The only case
127 // where we cannot use it is when the value itself has a single-quote in itself -- then we call back to
130 return str.indexOf('\'') == -1
131 // No escaping needed, use single quotes
132 ? sb.append('\'').append(str).append('\'')
133 // Escaping needed: use double quotes
134 : sb.append('"').append(DQUOT_ESCAPER.escape(str)).append('"');
138 * Returns DataSchemaContextTree associated with SchemaContext for which
139 * serialization / deserialization occurs.
142 * Implementations MUST provide non-null Data Tree context, in order
143 * for correct serialization / deserialization of PathArguments,
144 * since XML representation does not have Augmentation arguments
145 * and does not provide path arguments for cases.
148 * This effectively means same input XPath representation of Path Argument
149 * may result in different YangInstanceIdentifiers if models are different
150 * in uses of choices and cases.
152 * @return DataSchemaContextTree associated with SchemaContext for which
153 * serialization / deserialization occurs.
155 protected abstract @NonNull DataSchemaContextTree getDataContextTree();
157 protected abstract @NonNull Object deserializeKeyValue(@NonNull DataSchemaNode schemaNode,
158 @NonNull LeafrefResolver resolver, String value);
161 protected final YangInstanceIdentifier deserializeImpl(final String data) {
162 return YangInstanceIdentifier.of(
163 new XpathStringParsingPathArgumentBuilder(this, requireNonNull(data)).build());
167 * Create QName from unprefixed name, potentially taking last QNameModule encountered into account.
169 * @param lastModule last QNameModule encountered, potentially null
170 * @param localName Local name string
171 * @return A newly-created QName
173 protected QName createQName(final @Nullable QNameModule lastModule, final String localName) {
174 // This implementation handles both XML encoding, where we follow XML namespace rules and old JSON encoding,
175 // which is the same thing: always encode prefixes
176 return createQName(XMLConstants.DEFAULT_NS_PREFIX, localName);
180 protected final QName createQName(final String prefix, final String localName) {
181 final var module = moduleForPrefix(prefix);
182 if (module != null) {
183 return QName.create(module.localQNameModule(), localName);
185 throw new IllegalArgumentException("Failed to lookup prefix " + prefix);
189 * Resolve a string prefix into the corresponding module.
191 * @param prefix Prefix
192 * @return module mapped to prefix, or null if the module cannot be resolved
194 protected abstract @Nullable ModuleEffectiveStatement moduleForPrefix(@NonNull String prefix);
196 // FIXME: YANGTOOLS-1426: this will not be necessary when we have dedicated bits type
197 private static @NonNull String checkBitsItem(final Object obj) {
198 if (obj instanceof String str) {
201 throw new IllegalArgumentException("Unexpected bits component " + obj);