Fold MainVarsWrapper 30/83330/3
authorRobert Varga <robert.varga@pantheon.tech>
Wed, 31 Jul 2019 00:41:17 +0000 (02:41 +0200)
committerRobert Varga <nite@hq.sk>
Fri, 2 Aug 2019 17:33:11 +0000 (17:33 +0000)
It does not make any sense to keep a separate object to keep
state, while YangInstanceIdentifierDeserializer is pure static.

Eliminate MainVarsWrapper as a separate subclass, encapsulating
it in an YangInstanceIdentifierDeserializer instead. This allows
us manipulate state data freely, without having to go through
accessors, and thus make it much easier to follow as to what is
happening.

Change-Id: Ifa241c7343e13fe524ad8cf7d05cb3a6bbd6a18b
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/utils/parser/YangInstanceIdentifierDeserializer.java

index 7f73387d1072785eeaa017a013da11233abe998f..d518d316804dc7fb4e10f51fef0a52724680c5a7 100644 (file)
@@ -14,8 +14,8 @@ import com.google.common.base.CharMatcher;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.util.ArrayList;
 import java.util.Iterator;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Optional;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
@@ -26,7 +26,6 @@ import org.opendaylight.restconf.common.validation.RestconfValidationUtils;
 import org.opendaylight.restconf.nb.rfc8040.codecs.RestCodec;
 import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
 import org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants;
-import org.opendaylight.yangtools.concepts.Codec;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
@@ -57,78 +56,77 @@ import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
  *
  */
 public final class YangInstanceIdentifierDeserializer {
+    private final SchemaContext schemaContext;
+    private final String data;
 
-    private YangInstanceIdentifierDeserializer() {
-        throw new UnsupportedOperationException("Util class.");
+    private DataSchemaContextNode<?> current;
+    private int offset;
+
+    private YangInstanceIdentifierDeserializer(final SchemaContext schemaContext, final String data) {
+        this.schemaContext = requireNonNull(schemaContext);
+        this.data = requireNonNull(data);
+        current = DataSchemaContextTree.from(schemaContext).getRoot();
     }
 
     /**
-     * Method to create {@link Iterable} from {@link PathArgument} which are
-     * parsing from data by {@link SchemaContext}.
+     * Method to create {@link Iterable} from {@link PathArgument} which are parsing from data by {@link SchemaContext}.
      *
-     * @param schemaContext
-     *             for validate of parsing path arguments
-     * @param data
-     *             path to data
+     * @param schemaContext for validate of parsing path arguments
+     * @param data path to data, in URL string form
      * @return {@link Iterable} of {@link PathArgument}
      */
     public static Iterable<PathArgument> create(final SchemaContext schemaContext, final String data) {
-        final List<PathArgument> path = new LinkedList<>();
-        final MainVarsWrapper variables = new YangInstanceIdentifierDeserializer.MainVarsWrapper(
-                data, DataSchemaContextTree.from(schemaContext).getRoot(),
-                YangInstanceIdentifierDeserializer.MainVarsWrapper.STARTING_OFFSET, schemaContext);
+        return new YangInstanceIdentifierDeserializer(schemaContext, data).parse();
+    }
+
+    private List<PathArgument> parse() {
+        final List<PathArgument> path = new ArrayList<>();
 
-        while (!variables.allCharsConsumed()) {
-            variables.validArg();
-            final QName qname = prepareQName(variables);
+        while (!allCharsConsumed()) {
+            validArg();
+            final QName qname = prepareQName();
 
             // this is the last identifier (input is consumed) or end of identifier (slash)
-            if (variables.allCharsConsumed() || variables.currentChar() == RestconfConstants.SLASH) {
-                prepareIdentifier(qname, path, variables);
-                if (variables.getCurrent() == null) {
-                    path.add(NodeIdentifier.create(qname));
-                } else {
-                    path.add(variables.getCurrent().getIdentifier());
-                }
-            } else if (variables.currentChar() == ParserBuilderConstants.Deserializer.EQUAL) {
-                if (nextContextNode(qname, path, variables).getDataSchemaNode() instanceof ListSchemaNode) {
-                    prepareNodeWithPredicates(qname, path, variables,
-                            (ListSchemaNode) variables.getCurrent().getDataSchemaNode());
+            if (allCharsConsumed() || currentChar() == RestconfConstants.SLASH) {
+                prepareIdentifier(qname, path);
+                path.add(current == null ? NodeIdentifier.create(qname) : current.getIdentifier());
+            } else if (currentChar() == ParserBuilderConstants.Deserializer.EQUAL) {
+                if (nextContextNode(qname, path).getDataSchemaNode() instanceof ListSchemaNode) {
+                    prepareNodeWithPredicates(qname, path, (ListSchemaNode) current.getDataSchemaNode());
                 } else {
-                    prepareNodeWithValue(qname, path, variables);
+                    prepareNodeWithValue(qname, path);
                 }
             } else {
-                throw new IllegalArgumentException(
-                        "Bad char " + variables.currentChar() + " on position " + variables.getOffset() + ".");
+                throw new IllegalArgumentException("Bad char " + currentChar() + " on position " + offset + ".");
             }
         }
 
         return ImmutableList.copyOf(path);
     }
 
-    private static void prepareNodeWithPredicates(final QName qname, final List<PathArgument> path,
-            final MainVarsWrapper variables, final ListSchemaNode listSchemaNode) {
-        variables.checkValid(listSchemaNode != null, "Data schema node is null");
+    private void prepareNodeWithPredicates(final QName qname, final List<PathArgument> path,
+            final ListSchemaNode listSchemaNode) {
+        checkValid(listSchemaNode != null, "Data schema node is null");
 
         final Iterator<QName> keys = listSchemaNode.getKeyDefinition().iterator();
         final ImmutableMap.Builder<QName, Object> values = ImmutableMap.builder();
 
         // skip already expected equal sign
-        variables.skipCurrentChar();
+        skipCurrentChar();
 
         // read key value separated by comma
-        while (keys.hasNext() && !variables.allCharsConsumed() && variables.currentChar() != RestconfConstants.SLASH) {
+        while (keys.hasNext() && !allCharsConsumed() && currentChar() != RestconfConstants.SLASH) {
 
             // empty key value
-            if (variables.currentChar() == ParserBuilderConstants.Deserializer.COMMA) {
+            if (currentChar() == ParserBuilderConstants.Deserializer.COMMA) {
                 values.put(keys.next(), ParserBuilderConstants.Deserializer.EMPTY_STRING);
-                variables.skipCurrentChar();
+                skipCurrentChar();
                 continue;
             }
 
             // check if next value is parsable
             RestconfValidationUtils.checkDocumentedError(
-                    ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE.matches(variables.currentChar()),
+                    ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE.matches(currentChar()),
                     RestconfError.ErrorType.PROTOCOL,
                     RestconfError.ErrorTag.MALFORMED_MESSAGE,
                     ""
@@ -142,22 +140,21 @@ public final class YangInstanceIdentifierDeserializer {
                         RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT);
             }
 
-            final String value = findAndParsePercentEncoded(variables.nextIdentifierFromNextSequence(
+            final String value = findAndParsePercentEncoded(nextIdentifierFromNextSequence(
                     ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE));
-            final Object valueByType = prepareValueByType(leafSchemaNode.get(), value, variables);
+            final Object valueByType = prepareValueByType(leafSchemaNode.get(), value);
             values.put(key, valueByType);
 
 
             // skip comma
-            if (keys.hasNext() && !variables.allCharsConsumed()
-                    && variables.currentChar() == ParserBuilderConstants.Deserializer.COMMA) {
-                variables.skipCurrentChar();
+            if (keys.hasNext() && !allCharsConsumed() && currentChar() == ParserBuilderConstants.Deserializer.COMMA) {
+                skipCurrentChar();
             }
         }
 
         // the last key is considered to be empty
         if (keys.hasNext()) {
-            if (variables.allCharsConsumed() || variables.currentChar() == RestconfConstants.SLASH) {
+            if (allCharsConsumed() || currentChar() == RestconfConstants.SLASH) {
                 values.put(keys.next(), ParserBuilderConstants.Deserializer.EMPTY_STRING);
             }
 
@@ -173,8 +170,7 @@ public final class YangInstanceIdentifierDeserializer {
         path.add(new YangInstanceIdentifier.NodeIdentifierWithPredicates(qname, values.build()));
     }
 
-    private static Object prepareValueByType(final DataSchemaNode schemaNode, final String value,
-            final MainVarsWrapper vars) {
+    private Object prepareValueByType(final DataSchemaNode schemaNode, final String value) {
         Object decoded = null;
 
         TypeDefinition<? extends TypeDefinition<?>> typedef = null;
@@ -185,89 +181,45 @@ public final class YangInstanceIdentifierDeserializer {
         }
         final TypeDefinition<?> baseType = RestUtil.resolveBaseTypeFrom(typedef);
         if (baseType instanceof LeafrefTypeDefinition) {
-            typedef = SchemaContextUtil.getBaseTypeForLeafRef((LeafrefTypeDefinition) baseType, vars.getSchemaContext(),
+            typedef = SchemaContextUtil.getBaseTypeForLeafRef((LeafrefTypeDefinition) baseType, schemaContext,
                     schemaNode);
         }
-        final Codec<Object, Object> codec = RestCodec.from(typedef, null, vars.getSchemaContext());
-        decoded = codec.deserialize(value);
+        decoded = RestCodec.from(typedef, null, schemaContext).deserialize(value);
         if (decoded == null) {
             if (baseType instanceof IdentityrefTypeDefinition) {
-                decoded = toQName(value, schemaNode, vars.getSchemaContext());
+                decoded = toQName(value, schemaNode, schemaContext);
             }
         }
         return decoded;
     }
 
-    private static Object toQName(final String value, final DataSchemaNode schemaNode,
-            final SchemaContext schemaContext) {
-        final String moduleName = toModuleName(value);
-        final String nodeName = toNodeName(value);
-        final Module module = schemaContext.findModules(moduleName).iterator().next();
-        for (final IdentitySchemaNode identitySchemaNode : module.getIdentities()) {
-            final QName qName = identitySchemaNode.getQName();
-            if (qName.getLocalName().equals(nodeName)) {
-                return qName;
-            }
-        }
-        return QName.create(schemaNode.getQName().getNamespace(), schemaNode.getQName().getRevision(), nodeName);
-    }
-
-    private static String toNodeName(final String str) {
-        final int idx = str.indexOf(':');
-        if (idx == -1) {
-            return str;
-        }
-
-        if (str.indexOf(':', idx + 1) != -1) {
-            return str;
-        }
-
-        return str.substring(idx + 1);
-    }
-
-    private static String toModuleName(final String str) {
-        final int idx = str.indexOf(':');
-        if (idx == -1) {
-            return null;
-        }
-
-        if (str.indexOf(':', idx + 1) != -1) {
-            return null;
-        }
-
-        return str.substring(0, idx);
-    }
-
-    private static QName prepareQName(final MainVarsWrapper variables) {
-        variables.checkValid(ParserBuilderConstants.Deserializer.IDENTIFIER_FIRST_CHAR.matches(variables.currentChar()),
+    private QName prepareQName() {
+        checkValid(ParserBuilderConstants.Deserializer.IDENTIFIER_FIRST_CHAR.matches(currentChar()),
                 "Identifier must start with character from set 'a-zA-Z_'");
-        final String preparedPrefix = variables.nextIdentifierFromNextSequence(
-                ParserBuilderConstants.Deserializer.IDENTIFIER);
+        final String preparedPrefix = nextIdentifierFromNextSequence(ParserBuilderConstants.Deserializer.IDENTIFIER);
         final String prefix;
         final String localName;
 
-        if (variables.allCharsConsumed()) {
-            return getQNameOfDataSchemaNode(preparedPrefix, variables);
+        if (allCharsConsumed()) {
+            return getQNameOfDataSchemaNode(preparedPrefix);
         }
 
-        switch (variables.currentChar()) {
+        switch (currentChar()) {
             case RestconfConstants.SLASH:
             case ParserBuilderConstants.Deserializer.EQUAL:
                 prefix = preparedPrefix;
-                return getQNameOfDataSchemaNode(prefix, variables);
+                return getQNameOfDataSchemaNode(prefix);
             case ParserBuilderConstants.Deserializer.COLON:
                 prefix = preparedPrefix;
-                variables.skipCurrentChar();
-                variables.checkValid(
-                        ParserBuilderConstants.Deserializer.IDENTIFIER_FIRST_CHAR.matches(variables.currentChar()),
+                skipCurrentChar();
+                checkValid(ParserBuilderConstants.Deserializer.IDENTIFIER_FIRST_CHAR.matches(currentChar()),
                         "Identifier must start with character from set 'a-zA-Z_'");
-                localName = variables.nextIdentifierFromNextSequence(ParserBuilderConstants.Deserializer.IDENTIFIER);
+                localName = nextIdentifierFromNextSequence(ParserBuilderConstants.Deserializer.IDENTIFIER);
 
-                if (!variables.allCharsConsumed()
-                        && variables.currentChar() == ParserBuilderConstants.Deserializer.EQUAL) {
-                    return getQNameOfDataSchemaNode(localName, variables);
+                if (!allCharsConsumed() && currentChar() == ParserBuilderConstants.Deserializer.EQUAL) {
+                    return getQNameOfDataSchemaNode(localName);
                 } else {
-                    final Module module = moduleForPrefix(prefix, variables.getSchemaContext());
+                    final Module module = moduleForPrefix(prefix);
                     checkArgument(module != null, "Failed to lookup prefix %s", prefix);
                     return QName.create(module.getQNameModule(), localName);
                 }
@@ -276,11 +228,9 @@ public final class YangInstanceIdentifierDeserializer {
         }
     }
 
-    private static void prepareNodeWithValue(final QName qname, final List<PathArgument> path,
-            final MainVarsWrapper variables) {
-        variables.skipCurrentChar();
-        final String value = variables.nextIdentifierFromNextSequence(
-                ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE);
+    private void prepareNodeWithValue(final QName qname, final List<PathArgument> path) {
+        skipCurrentChar();
+        final String value = nextIdentifierFromNextSequence(ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE);
 
         // exception if value attribute is missing
         RestconfValidationUtils.checkDocumentedError(
@@ -289,32 +239,29 @@ public final class YangInstanceIdentifierDeserializer {
                 RestconfError.ErrorTag.MISSING_ATTRIBUTE,
                 "Value missing for: " + qname
         );
-        final DataSchemaNode dataSchemaNode = variables.getCurrent().getDataSchemaNode();
-        final Object valueByType = prepareValueByType(dataSchemaNode, findAndParsePercentEncoded(value), variables);
+        final DataSchemaNode dataSchemaNode = current.getDataSchemaNode();
+        final Object valueByType = prepareValueByType(dataSchemaNode, findAndParsePercentEncoded(value));
         path.add(new YangInstanceIdentifier.NodeWithValue<>(qname, valueByType));
     }
 
-    private static void prepareIdentifier(final QName qname, final List<PathArgument> path,
-            final MainVarsWrapper variables) {
-        final DataSchemaContextNode<?> currentNode = nextContextNode(qname, path, variables);
+    private void prepareIdentifier(final QName qname, final List<PathArgument> path) {
+        final DataSchemaContextNode<?> currentNode = nextContextNode(qname, path);
         if (currentNode == null) {
             return;
         }
-        variables.checkValid(!currentNode.isKeyedEntry(),
-            "Entry " + qname + " requires key or value predicate to be present");
+        checkValid(!currentNode.isKeyedEntry(), "Entry " + qname + " requires key or value predicate to be present");
     }
 
-    @SuppressFBWarnings("NP_NULL_ON_SOME_PATH") // code does check for null 'current' but FB doesn't recognize it
-    private static DataSchemaContextNode<?> nextContextNode(final QName qname, final List<PathArgument> path,
-            final MainVarsWrapper variables) {
-        final DataSchemaContextNode<?> initialContext = variables.getCurrent();
+    @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH",
+            justification = "code does check for null 'current' but FB doesn't recognize it")
+    private DataSchemaContextNode<?> nextContextNode(final QName qname, final List<PathArgument> path) {
+        final DataSchemaContextNode<?> initialContext = current;
         final DataSchemaNode initialDataSchema = initialContext.getDataSchemaNode();
 
-        DataSchemaContextNode<?> current = initialContext.getChild(qname);
-        variables.setCurrent(current);
+        current = initialContext.getChild(qname);
 
         if (current == null) {
-            final Optional<Module> module = variables.getSchemaContext().findModule(qname.getModule());
+            final Optional<Module> module = schemaContext.findModule(qname.getModule());
             if (module.isPresent()) {
                 for (final RpcDefinition rpcDefinition : module.get().getRpcs()) {
                     if (rpcDefinition.getQName().getLocalName().equals(qname.getLocalName())) {
@@ -326,39 +273,61 @@ public final class YangInstanceIdentifierDeserializer {
                 return null;
             }
         }
-        variables.checkValid(current != null, qname + " is not correct schema node identifier.");
+        checkValid(current != null, qname + " is not correct schema node identifier.");
         while (current.isMixin()) {
             path.add(current.getIdentifier());
             current = current.getChild(qname);
-            variables.setCurrent(current);
         }
         return current;
     }
 
-    private static String findAndParsePercentEncoded(final String preparedPrefix) {
-        if (!preparedPrefix.contains(String.valueOf(ParserBuilderConstants.Deserializer.PERCENT_ENCODING))) {
-            return preparedPrefix;
-        }
+    private Module moduleForPrefix(final String prefix) {
+        return schemaContext.findModules(prefix).stream().findFirst().orElse(null);
+    }
 
-        final StringBuilder parsedPrefix = new StringBuilder(preparedPrefix);
-        final CharMatcher matcher = CharMatcher.is(ParserBuilderConstants.Deserializer.PERCENT_ENCODING);
+    private boolean allCharsConsumed() {
+        return offset == data.length();
+    }
 
-        while (matcher.matchesAnyOf(parsedPrefix)) {
-            final int percentCharPosition = matcher.indexIn(parsedPrefix);
-            parsedPrefix.replace(
-                    percentCharPosition,
-                    percentCharPosition + ParserBuilderConstants.Deserializer.LAST_ENCODED_CHAR,
-                    String.valueOf((char) Integer.parseInt(parsedPrefix.substring(
-                            percentCharPosition + ParserBuilderConstants.Deserializer.FIRST_ENCODED_CHAR,
-                            percentCharPosition + ParserBuilderConstants.Deserializer.LAST_ENCODED_CHAR),
-                            ParserBuilderConstants.Deserializer.PERCENT_ENCODED_RADIX)));
+    private void checkValid(final boolean condition, final String errorMsg) {
+        checkArgument(condition, "Could not parse Instance Identifier '%s'. Offset: %s : Reason: %s", data, offset,
+            errorMsg);
+    }
+
+    private char currentChar() {
+        return data.charAt(offset);
+    }
+
+    private void skipCurrentChar() {
+        offset++;
+    }
+
+    private String nextIdentifierFromNextSequence(final CharMatcher matcher) {
+        final int start = offset;
+        while (!allCharsConsumed() && matcher.matches(currentChar())) {
+            skipCurrentChar();
         }
+        return data.substring(start, offset);
+    }
 
-        return parsedPrefix.toString();
+    private void validArg() {
+        // every identifier except of the first MUST start with slash
+        if (offset != 0) {
+            checkValid(RestconfConstants.SLASH == currentChar(), "Identifier must start with '/'.");
+
+            // skip consecutive slashes, users often assume restconf URLs behave just as HTTP does by squashing
+            // multiple slashes into a single one
+            while (!allCharsConsumed() && RestconfConstants.SLASH == currentChar()) {
+                skipCurrentChar();
+            }
+
+            // check if slash is not also the last char in identifier
+            checkValid(!allCharsConsumed(), "Identifier cannot end with '/'.");
+        }
     }
 
-    private static QName getQNameOfDataSchemaNode(final String nodeName, final MainVarsWrapper variables) {
-        final DataSchemaNode dataSchemaNode = variables.getCurrent().getDataSchemaNode();
+    private QName getQNameOfDataSchemaNode(final String nodeName) {
+        final DataSchemaNode dataSchemaNode = current.getDataSchemaNode();
         if (dataSchemaNode instanceof ContainerSchemaNode) {
             return getQNameOfDataSchemaNode((ContainerSchemaNode) dataSchemaNode, nodeName);
         } else if (dataSchemaNode instanceof ListSchemaNode) {
@@ -380,10 +349,6 @@ public final class YangInstanceIdentifierDeserializer {
         return node.getQName();
     }
 
-    private static Module moduleForPrefix(final String prefix, final SchemaContext schemaContext) {
-        return schemaContext.findModules(prefix).stream().findFirst().orElse(null);
-    }
-
     private static Optional<ActionDefinition> findActionDefinition(final SchemaNode dataSchemaNode,
             final String nodeName) {
         requireNonNull(dataSchemaNode, "DataSchema Node must not be null.");
@@ -394,78 +359,65 @@ public final class YangInstanceIdentifierDeserializer {
         return Optional.empty();
     }
 
-    private static final class MainVarsWrapper {
-        private static final int STARTING_OFFSET = 0;
-
-        private final SchemaContext schemaContext;
-        private final String data;
-
-        private DataSchemaContextNode<?> current;
-        private int offset;
-
-        MainVarsWrapper(final String data, final DataSchemaContextNode<?> current, final int offset,
-                final SchemaContext schemaContext) {
-            this.data = data;
-            this.current = current;
-            this.offset = offset;
-            this.schemaContext = schemaContext;
-        }
-
-        boolean allCharsConsumed() {
-            return offset == data.length();
+    private static String findAndParsePercentEncoded(final String preparedPrefix) {
+        if (!preparedPrefix.contains(String.valueOf(ParserBuilderConstants.Deserializer.PERCENT_ENCODING))) {
+            return preparedPrefix;
         }
 
-        void checkValid(final boolean condition, final String errorMsg) {
-            checkArgument(condition, "Could not parse Instance Identifier '%s'. Offset: %s : Reason: %s", data, offset,
-                errorMsg);
-        }
+        final StringBuilder parsedPrefix = new StringBuilder(preparedPrefix);
+        final CharMatcher matcher = CharMatcher.is(ParserBuilderConstants.Deserializer.PERCENT_ENCODING);
 
-        char currentChar() {
-            return data.charAt(offset);
+        while (matcher.matchesAnyOf(parsedPrefix)) {
+            final int percentCharPosition = matcher.indexIn(parsedPrefix);
+            parsedPrefix.replace(
+                    percentCharPosition,
+                    percentCharPosition + ParserBuilderConstants.Deserializer.LAST_ENCODED_CHAR,
+                    String.valueOf((char) Integer.parseInt(parsedPrefix.substring(
+                            percentCharPosition + ParserBuilderConstants.Deserializer.FIRST_ENCODED_CHAR,
+                            percentCharPosition + ParserBuilderConstants.Deserializer.LAST_ENCODED_CHAR),
+                            ParserBuilderConstants.Deserializer.PERCENT_ENCODED_RADIX)));
         }
 
-        void skipCurrentChar() {
-            offset++;
-        }
+        return parsedPrefix.toString();
+    }
 
-        String nextIdentifierFromNextSequence(final CharMatcher matcher) {
-            final int start = offset;
-            while (!allCharsConsumed() && matcher.matches(currentChar())) {
-                skipCurrentChar();
+    private static Object toQName(final String value, final DataSchemaNode schemaNode,
+            final SchemaContext schemaContext) {
+        final String moduleName = toModuleName(value);
+        final String nodeName = toNodeName(value);
+        final Module module = schemaContext.findModules(moduleName).iterator().next();
+        for (final IdentitySchemaNode identitySchemaNode : module.getIdentities()) {
+            final QName qName = identitySchemaNode.getQName();
+            if (qName.getLocalName().equals(nodeName)) {
+                return qName;
             }
-            return data.substring(start, offset);
         }
+        return QName.create(schemaNode.getQName().getNamespace(), schemaNode.getQName().getRevision(), nodeName);
+    }
 
-        void validArg() {
-            // every identifier except of the first MUST start with slash
-            if (offset != MainVarsWrapper.STARTING_OFFSET) {
-                checkValid(RestconfConstants.SLASH == currentChar(), "Identifier must start with '/'.");
-
-                // skip consecutive slashes, users often assume restconf URLs behave just as HTTP does by squashing
-                // multiple slashes into a single one
-                while (!allCharsConsumed() && RestconfConstants.SLASH == currentChar()) {
-                    skipCurrentChar();
-                }
-
-                // check if slash is not also the last char in identifier
-                checkValid(!allCharsConsumed(), "Identifier cannot end with '/'.");
-            }
+    private static String toNodeName(final String str) {
+        final int idx = str.indexOf(':');
+        if (idx == -1) {
+            return str;
         }
 
-        public DataSchemaContextNode<?> getCurrent() {
-            return current;
+        if (str.indexOf(':', idx + 1) != -1) {
+            return str;
         }
 
-        public void setCurrent(final DataSchemaContextNode<?> current) {
-            this.current = current;
-        }
+        return str.substring(idx + 1);
+    }
 
-        public int getOffset() {
-            return offset;
+    private static String toModuleName(final String str) {
+        final int idx = str.indexOf(':');
+        if (idx == -1) {
+            return null;
         }
 
-        public SchemaContext getSchemaContext() {
-            return schemaContext;
+        if (str.indexOf(':', idx + 1) != -1) {
+            return null;
         }
+
+        return str.substring(0, idx);
     }
 }