2 * Copyright (c) 2016 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.restconf.parser.builder;
10 import com.google.common.base.CharMatcher;
11 import com.google.common.base.Preconditions;
12 import com.google.common.collect.ImmutableList;
13 import com.google.common.collect.ImmutableMap;
14 import java.util.LinkedList;
15 import java.util.List;
16 import org.opendaylight.restconf.utils.RestconfConstants;
17 import org.opendaylight.restconf.utils.parser.builder.ParserBuilderConstants;
18 import org.opendaylight.restconf.utils.schema.context.RestconfSchemaUtil;
19 import org.opendaylight.yangtools.yang.common.QName;
20 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
22 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
23 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
24 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
25 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
26 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.Module;
28 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
31 * Deserializer for {@link String} to {@link YangInstanceIdentifier} for
35 public final class YangInstanceIdentifierDeserializer {
37 private YangInstanceIdentifierDeserializer() {
38 throw new UnsupportedOperationException("Util class.");
42 * Method to create {@link Iterable} from {@link PathArgument} which are
43 * parsing from data by {@link SchemaContext}.
45 * @param schemaContext
46 * - for validate of parsing path arguments
49 * @return {@link Iterable} of {@link PathArgument}
51 public static Iterable<PathArgument> create(final SchemaContext schemaContext, final String data) {
52 final List<PathArgument> path = new LinkedList<>();
53 DataSchemaContextNode<?> current = DataSchemaContextTree.from(schemaContext).getRoot();
55 final MainVarsWrapper variables = new YangInstanceIdentifierDeserializer.MainVarsWrapper(data,
56 current, offset, schemaContext);
58 while (!allCharsConsumed(variables)) {
60 final QName qname = prepareQName(variables);
62 // this is the last identifier (input is consumed) or end of identifier (slash)
63 if (allCharsConsumed(variables)
64 || currentChar(variables.getOffset(), variables.getData()) == RestconfConstants.SLASH) {
65 prepareIdentifier(qname, path, variables);
66 path.add(variables.getCurrent().getIdentifier());
67 } else if (currentChar(variables.getOffset(),
68 variables.getData()) == ParserBuilderConstants.Deserializer.EQUAL) {
69 current = nextContextNode(qname, path, variables);
70 if (!current.isKeyedEntry()) {
71 prepareNodeWithValue(qname, path, variables);
73 prepareNodeWithPredicates(qname, path, variables);
76 throw new IllegalArgumentException(
77 "Bad char " + currentChar(offset, data) + " on position " + offset + ".");
81 return ImmutableList.copyOf(path);
84 private static void prepareNodeWithPredicates(final QName qname, final List<PathArgument> path,
85 final MainVarsWrapper variables) {
86 final List<QName> keyDefinitions = ((ListSchemaNode) variables.getCurrent().getDataSchemaNode())
88 final ImmutableMap.Builder<QName, Object> keyValues = ImmutableMap.builder();
90 for (final QName keyQName : keyDefinitions) {
91 skipCurrentChar(variables);
93 if ((currentChar(variables.getOffset(), variables.getData()) == ParserBuilderConstants.Deserializer.COMMA)
94 || (currentChar(variables.getOffset(), variables.getData()) == RestconfConstants.SLASH)) {
95 value = ParserBuilderConstants.Deserializer.EMPTY_STRING;
97 value = nextIdentifierFromNextSequence(ParserBuilderConstants.Deserializer.IDENTIFIER_PREDICATE,
100 value = findAndParsePercentEncoded(value);
101 keyValues.put(keyQName, value);
103 path.add(new YangInstanceIdentifier.NodeIdentifierWithPredicates(qname, keyValues.build()));
106 private static QName prepareQName(final MainVarsWrapper variables) {
108 ParserBuilderConstants.Deserializer.IDENTIFIER_FIRST_CHAR
109 .matches(currentChar(variables.getOffset(), variables.getData())),
110 "Identifier must start with character from set 'a-zA-Z_'", variables.getData(), variables.getOffset());
111 final String preparedPrefix = nextIdentifierFromNextSequence(ParserBuilderConstants.Deserializer.IDENTIFIER, variables);
112 final String prefix, localName;
114 switch (currentChar(variables.getOffset(), variables.getData())) {
115 case ParserBuilderConstants.Deserializer.COLON:
116 prefix = preparedPrefix;
117 skipCurrentChar(variables);
119 ParserBuilderConstants.Deserializer.IDENTIFIER_FIRST_CHAR
120 .matches(currentChar(variables.getOffset(), variables.getData())),
121 "Identifier must start with character from set 'a-zA-Z_'", variables.getData(),
122 variables.getOffset());
123 localName = nextIdentifierFromNextSequence(ParserBuilderConstants.Deserializer.IDENTIFIER, variables);
125 final Module module = moduleForPrefix(prefix, variables.getSchemaContext());
126 Preconditions.checkArgument(module != null, "Failed to lookup prefix %s", prefix);
128 return QName.create(module.getQNameModule(), localName);
129 case ParserBuilderConstants.Deserializer.EQUAL:
130 prefix = preparedPrefix;
131 return getQNameOfDataSchemaNode(prefix, variables);
133 throw new IllegalArgumentException("Failed build path.");
137 private static String nextIdentifierFromNextSequence(final CharMatcher matcher, final MainVarsWrapper variables) {
138 final int start = variables.getOffset();
139 nextSequenceEnd(matcher, variables);
140 return variables.getData().substring(start, variables.getOffset());
143 private static void nextSequenceEnd(final CharMatcher matcher, final MainVarsWrapper variables) {
144 while (!allCharsConsumed(variables)
145 && matcher.matches(variables.getData().charAt(variables.getOffset()))) {
146 variables.setOffset(variables.getOffset() + 1);
150 private static void prepareNodeWithValue(final QName qname, final List<PathArgument> path,
151 final MainVarsWrapper variables) {
152 skipCurrentChar(variables);
153 String value = nextIdentifierFromNextSequence(ParserBuilderConstants.Deserializer.IDENTIFIER, variables);
154 value = findAndParsePercentEncoded(value);
155 path.add(new YangInstanceIdentifier.NodeWithValue<>(qname, value));
158 private static void prepareIdentifier(final QName qname, final List<PathArgument> path,
159 final MainVarsWrapper variables) {
160 final DataSchemaContextNode<?> currentNode = nextContextNode(qname, path, variables);
161 checkValid(!currentNode.isKeyedEntry(), "Entry " + qname + " requires key or value predicate to be present",
162 variables.getData(), variables.getOffset());
165 private static DataSchemaContextNode<?> nextContextNode(final QName qname, final List<PathArgument> path,
166 final MainVarsWrapper variables) {
167 variables.setCurrent(variables.getCurrent().getChild(qname));
168 DataSchemaContextNode<?> current = variables.getCurrent();
169 checkValid(current != null, qname + " is not correct schema node identifier.", variables.getData(),
170 variables.getOffset());
171 while (current.isMixin()) {
172 path.add(current.getIdentifier());
173 current = current.getChild(qname);
174 variables.setCurrent(current);
179 private static String findAndParsePercentEncoded(String preparedPrefix) {
180 if (!preparedPrefix.contains(String.valueOf(ParserBuilderConstants.Deserializer.PERCENT_ENCODING))) {
181 return preparedPrefix;
183 final StringBuilder newPrefix = new StringBuilder();
186 int endPoint = preparedPrefix.length();
187 while ((i = preparedPrefix.indexOf(ParserBuilderConstants.Deserializer.PERCENT_ENCODING)) != -1) {
188 newPrefix.append(preparedPrefix.substring(startPoint, i));
191 final String hex = preparedPrefix.substring(startPoint, startPoint + 2);
193 newPrefix.append((char) Integer.parseInt(hex, 16));
194 preparedPrefix = preparedPrefix.substring(startPoint, endPoint);
196 endPoint = preparedPrefix.length();
198 return newPrefix.toString();
201 private static QName getQNameOfDataSchemaNode(final String nodeName, final MainVarsWrapper variables) {
202 final DataSchemaNode dataSchemaNode = variables.getCurrent().getDataSchemaNode();
203 if (dataSchemaNode instanceof ContainerSchemaNode) {
204 final ContainerSchemaNode contSchemaNode = (ContainerSchemaNode) dataSchemaNode;
205 final DataSchemaNode node = RestconfSchemaUtil.findSchemaNodeInCollection(contSchemaNode.getChildNodes(),
207 return node.getQName();
208 } else if (dataSchemaNode instanceof ListSchemaNode) {
209 final ListSchemaNode listSchemaNode = (ListSchemaNode) dataSchemaNode;
210 final DataSchemaNode node = RestconfSchemaUtil.findSchemaNodeInCollection(listSchemaNode.getChildNodes(),
212 return node.getQName();
214 throw new UnsupportedOperationException();
217 private static Module moduleForPrefix(final String prefix, final SchemaContext schemaContext) {
218 return schemaContext.findModuleByName(prefix, null);
221 private static void validArg(final MainVarsWrapper variables) {
222 checkValid(RestconfConstants.SLASH == currentChar(variables.getOffset(), variables.getData()),
223 "Identifier must start with '/'.", variables.getData(), variables.getOffset());
224 skipCurrentChar(variables);
225 checkValid(!allCharsConsumed(variables), "Identifier cannot end with '/'.",
226 variables.getData(), variables.getOffset());
229 private static void skipCurrentChar(final MainVarsWrapper variables) {
230 variables.setOffset(variables.getOffset() + 1);
233 private static char currentChar(final int offset, final String data) {
234 return data.charAt(offset);
237 private static void checkValid(final boolean condition, final String errorMsg, final String data,
239 Preconditions.checkArgument(condition, "Could not parse Instance Identifier '%s'. Offset: %s : Reason: %s",
240 data, offset, errorMsg);
243 private static boolean allCharsConsumed(final MainVarsWrapper variables) {
244 return variables.getOffset() == variables.getData().length();
247 private static class MainVarsWrapper {
249 private final SchemaContext schemaContext;
250 private final String data;
251 private DataSchemaContextNode<?> current;
254 public MainVarsWrapper(final String data, final DataSchemaContextNode<?> current, final int offset,
255 final SchemaContext schemaContext) {
257 this.schemaContext = schemaContext;
258 this.setCurrent(current);
259 this.setOffset(offset);
262 public String getData() {
266 public DataSchemaContextNode<?> getCurrent() {
270 public void setCurrent(final DataSchemaContextNode<?> current) {
271 this.current = current;
274 public int getOffset() {
278 public void setOffset(final int offset) {
279 this.offset = offset;
282 public SchemaContext getSchemaContext() {
283 return this.schemaContext;