BUG-4145: check if entire YangInstanceIdentifier is present in SchemaContext
[yangtools.git] / yang / yang-data-util / src / main / java / org / opendaylight / yangtools / yang / data / util / AbstractStringInstanceIdentifierCodec.java
index 3e47e966cf94a1f1f6b0e6b485fb922678f02ce0..6e1c0a72e5ec73099ac24c78d617ea53ff9259de 100644 (file)
@@ -9,21 +9,15 @@ package org.opendaylight.yangtools.yang.data.util;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Preconditions;
-import com.google.common.base.Splitter;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
 import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
 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.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.codec.InstanceIdentifierCodec;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 
 /**
  * Abstract utility class for representations which encode {@link YangInstanceIdentifier} as a
@@ -31,21 +25,23 @@ import org.opendaylight.yangtools.yang.data.api.codec.InstanceIdentifierCodec;
  */
 @Beta
 public abstract class AbstractStringInstanceIdentifierCodec extends AbstractNamespaceCodec implements InstanceIdentifierCodec<String> {
-    private static final Pattern PREDICATE_PATTERN = Pattern.compile("\\[(.*?)\\]");
-    private static final Splitter SLASH_SPLITTER = Splitter.on('/');
 
     @Override
     public final String serialize(final YangInstanceIdentifier data) {
         StringBuilder sb = new StringBuilder();
+        DataSchemaContextNode<?> current = getDataContextTree().getRoot();
         for (PathArgument arg : data.getPathArguments()) {
-            if(arg instanceof AugmentationIdentifier) {
+            current = current.getChild(arg);
+            Preconditions.checkArgument(current != null,
+                    "Invalid input %s: schema for argument %s (after %s) not found", data, arg, sb);
+
+            if (current.isMixin()) {
                 /*
                  * XML/YANG instance identifier does not have concept
-                 * of augmentation identifier, which identifies
-                 * mixin (same as paretn element), so we can safely
-                 * ignore it if it is part of path (since child node)
-                 * is identified in same fashion.
-                 *
+                 * 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;
             }
@@ -67,90 +63,37 @@ public abstract class AbstractStringInstanceIdentifierCodec extends AbstractName
                 sb.append("']");
             }
         }
-
         return sb.toString();
     }
 
+    /**
+     *
+     * 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 String value) {
+        return value;
+    }
+
     @Override
     public final YangInstanceIdentifier deserialize(final String data) {
         Preconditions.checkNotNull(data, "Data may not be null");
-
-        final Iterator<String> xPathParts = SLASH_SPLITTER.split(data).iterator();
-
-        // must be at least "/pr:node"
-        if (!xPathParts.hasNext() || !xPathParts.next().isEmpty() || !xPathParts.hasNext()) {
-            return null;
-        }
-
-        List<PathArgument> result = new ArrayList<>();
-        while (xPathParts.hasNext()) {
-            String xPathPartTrimmed = xPathParts.next().trim();
-
-            PathArgument pathArgument = toPathArgument(xPathPartTrimmed);
-            if (pathArgument != null) {
-                result.add(pathArgument);
-            }
-        }
-        return YangInstanceIdentifier.create(result);
-    }
-
-    private PathArgument toPathArgument(final String xPathArgument) {
-        final QName mainQName = parseQName(xPathArgument);
-
-        // predicates
-        final Matcher matcher = PREDICATE_PATTERN.matcher(xPathArgument);
-        final Map<QName, Object> predicates = new LinkedHashMap<>();
-        QName currentQName = mainQName;
-
-        while (matcher.find()) {
-            final String predicateStr = matcher.group(1).trim();
-            final int indexOfEqualityMark = predicateStr.indexOf('=');
-            if (indexOfEqualityMark != -1) {
-                final String predicateValue = toPredicateValue(predicateStr.substring(indexOfEqualityMark + 1));
-                if (predicateValue == null) {
-                    return null;
-                }
-
-                if (predicateStr.charAt(0) != '.') {
-                    // target is not a leaf-list
-                    currentQName = parseQName(predicateStr.substring(0, indexOfEqualityMark));
-                    if (currentQName == null) {
-                        return null;
-                    }
-                }
-                predicates.put(currentQName, predicateValue);
-            }
-        }
-
-        if (predicates.isEmpty()) {
-            return new YangInstanceIdentifier.NodeIdentifier(mainQName);
-        } else {
-            return new YangInstanceIdentifier.NodeIdentifierWithPredicates(mainQName, predicates);
-        }
+        XpathStringParsingPathArgumentBuilder builder = new XpathStringParsingPathArgumentBuilder(this, data);
+        return YangInstanceIdentifier.create(builder.build());
     }
 
-    private static String toPredicateValue(final String predicatedValue) {
-        final String predicatedValueTrimmed = predicatedValue.trim();
-        if (predicatedValue.isEmpty()) {
-            return null;
-        }
-
-        switch (predicatedValueTrimmed.charAt(0)) {
-        case '"':
-            return trimIfEndIs(predicatedValueTrimmed, '"');
-        case '\'':
-            return trimIfEndIs(predicatedValueTrimmed, '\'');
-        default:
-            return null;
-        }
-    }
-
-    private static String trimIfEndIs(final String str, final char end) {
-        final int l = str.length() - 1;
-        if (str.charAt(l) != end) {
-            return null;
-        }
-
-        return str.substring(1, l);
-    }
 }