All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opendaylight.yangtools.yang.data.util.AbstractStringInstanceIdentifierCodec Maven / Gradle / Ivy

There is a newer version: 14.0.4
Show newest version
/*
 * Copyright (c) 2014 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.util;

import static java.util.Objects.requireNonNull;

import com.google.common.annotations.Beta;
import com.google.common.escape.Escaper;
import com.google.common.escape.Escapers;
import java.util.Set;
import javax.xml.XMLConstants;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.QNameModule;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
import org.opendaylight.yangtools.yang.data.api.codec.InstanceIdentifierCodec;
import org.opendaylight.yangtools.yang.data.util.DataSchemaContext.Composite;
import org.opendaylight.yangtools.yang.data.util.DataSchemaContext.PathMixin;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.util.LeafrefResolver;

/**
 * Abstract utility class for representations which encode {@link YangInstanceIdentifier} as a
 * prefix:name tuple. Typical uses are RESTCONF/JSON (module:name) and XML (prefix:name).
 */
@Beta
public abstract class AbstractStringInstanceIdentifierCodec extends AbstractNamespaceCodec
        implements InstanceIdentifierCodec {
    // Escaper as per https://www.rfc-editor.org/rfc/rfc7950#section-6.1.3
    private static final Escaper DQUOT_ESCAPER = Escapers.builder()
        .addEscape('\n', "\\n")
        .addEscape('\t', "\\t")
        .addEscape('"', "\\\"")
        .addEscape('\\', "\\\\")
        .build();

    @Override
    protected final String serializeImpl(final YangInstanceIdentifier data) {
        final StringBuilder sb = new StringBuilder();
        DataSchemaContext current = getDataContextTree().getRoot();
        QNameModule lastModule = null;
        for (var arg : data.getPathArguments()) {
            current = current instanceof Composite composite ? composite.childByArg(arg) : null;
            if (current == null) {
                throw new IllegalArgumentException(
                    "Invalid input %s: schema for argument %s (after \"%s\") not found".formatted(data, arg, sb));
            }

            if (current instanceof PathMixin) {
                /*
                 * XML/YANG instance identifier does not have concept of augmentation identifier, or list as whole which
                 * identifies a mixin (same as the parent element), so we can safely ignore it if it is part of path
                 * (since child node) is identified in same fashion.
                 */
                continue;
            }

            final var qname = arg.getNodeType();
            sb.append('/');
            appendQName(sb, qname, lastModule);
            lastModule = qname.getModule();

            if (arg instanceof NodeIdentifierWithPredicates nip) {
                for (var entry : nip.entrySet()) {
                    final var keyName = entry.getKey();
                    appendQName(sb.append('['), keyName, lastModule).append('=');
                    appendValue(sb, keyName.getModule(), entry.getValue()).append(']');
                }
            } else if (arg instanceof NodeWithValue val) {
                appendValue(sb.append("[.="), lastModule, val.getValue()).append(']');
            }
        }
        return sb.toString();
    }

    private StringBuilder appendValue(final StringBuilder sb, final QNameModule currentModule,
            final Object value) {
        if (value instanceof QName qname) {
            // QName implies identity-ref, which can never be escaped
            return appendQName(sb.append('\''), qname, currentModule).append('\'');
        }
        // FIXME: YANGTOOLS-1426: update once we have a dedicated type
        if (value instanceof Set bits) {
            // Set implies bits, which can never be escaped and need to be serialized as space-separated items
            sb.append('\'');

            final var it = bits.iterator();
            if (it.hasNext()) {
                sb.append(checkBitsItem(it.next()));
                while (it.hasNext()) {
                    sb.append(' ').append(checkBitsItem(it.next()));
                }
            }

            return sb.append('\'');
        }

        final var str = value instanceof YangInstanceIdentifier id ? serialize(id) : String.valueOf(value);

        // We have two specifications here: Section 6.1.3 of both RFC6020 and RFC7950:
        //
        // RFC6020 Section 6.1.3:
        //        If a string contains any space or tab characters, a semicolon (";"),
        //        braces ("{" or "}"), or comment sequences ("//", "/*", or "*/"), then
        //        it MUST be enclosed within double or single quotes.
        //
        // RFC7950 Section 6.1.3:
        //        An unquoted string is any sequence of characters that does not
        //        contain any space, tab, carriage return, or line feed characters, a
        //        single or double quote character, a semicolon (";"), braces ("{" or
        //        "}"), or comment sequences ("//", "/*", or "*/").
        //
        // Plus the common part:
        //        A single-quoted string (enclosed within ' ') preserves each character
        //        within the quotes.  A single quote character cannot occur in a
        //        single-quoted string, even when preceded by a backslash.
        //
        // Unquoted strings are not interesting, as we are embedding the value in a string, not a YANG document, hence
        // we have to use quotes. Single-quoted case is simpler, as it does not involve any escaping. The only case
        // where we cannot use it is when the value itself has a single-quote in itself -- then we call back to
        // double-quoting.

        return str.indexOf('\'') == -1
            // No escaping needed, use single quotes
            ? sb.append('\'').append(str).append('\'')
            // Escaping needed: use double quotes
            : sb.append('"').append(DQUOT_ESCAPER.escape(str)).append('"');
    }

    /**
     * Returns DataSchemaContextTree associated with SchemaContext for which
     * serialization / deserialization occurs.
     *
     * 

* Implementations MUST provide non-null Data Tree context, in order * for correct serialization / deserialization of PathArguments, * since XML representation does not have Augmentation arguments * and does not provide path arguments for cases. * *

* This effectively means same input XPath representation of Path Argument * may result in different YangInstanceIdentifiers if models are different * in uses of choices and cases. * * @return DataSchemaContextTree associated with SchemaContext for which * serialization / deserialization occurs. */ protected abstract @NonNull DataSchemaContextTree getDataContextTree(); protected Object deserializeKeyValue(final DataSchemaNode schemaNode, final LeafrefResolver resolver, final String value) { return value; } @Override protected final YangInstanceIdentifier deserializeImpl(final String data) { return YangInstanceIdentifier.of( new XpathStringParsingPathArgumentBuilder(this, requireNonNull(data)).build()); } /** * Create QName from unprefixed name, potentially taking last QNameModule encountered into account. * * @param lastModule last QNameModule encountered, potentially null * @param localName Local name string * @return A newly-created QName */ protected QName createQName(final @Nullable QNameModule lastModule, final String localName) { // This implementation handles both XML encoding, where we follow XML namespace rules and old JSON encoding, // which is the same thing: always encode prefixes return createQName(XMLConstants.DEFAULT_NS_PREFIX, localName); } // FIXME: YANGTOOLS-1426: this will not be necessary when we have dedicated bits type private static @NonNull String checkBitsItem(final Object obj) { if (obj instanceof String str) { return str; } throw new IllegalArgumentException("Unexpected bits component " + obj); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy