Remove use NodeIdentifierWithPredices.getKeyValues()
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / utils / parser / YangInstanceIdentifierSerializer.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.restconf.nb.rfc8040.utils.parser;
9
10 import com.google.common.base.Preconditions;
11 import java.util.Iterator;
12 import java.util.Locale;
13 import java.util.Map.Entry;
14 import org.opendaylight.restconf.nb.rfc8040.utils.RestconfConstants;
15 import org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants;
16 import org.opendaylight.restconf.nb.rfc8040.utils.parser.builder.ParserBuilderConstants.Serializer;
17 import org.opendaylight.yangtools.yang.common.QName;
18 import org.opendaylight.yangtools.yang.common.QNameModule;
19 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
20 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
23 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextNode;
24 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
25 import org.opendaylight.yangtools.yang.model.api.Module;
26 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
27
28 /**
29  * Serializer for {@link YangInstanceIdentifier} to {@link String} for restconf.
30  */
31 public final class YangInstanceIdentifierSerializer {
32
33     private YangInstanceIdentifierSerializer() {
34         throw new UnsupportedOperationException("Util class.");
35     }
36
37     /**
38      * Method to create String from {@link Iterable} of {@link PathArgument}
39      * which are parsing from data by {@link SchemaContext}.
40      *
41      * @param schemaContext
42      *             for validate of parsing path arguments
43      * @param data
44      *             path to data
45      * @return {@link String}
46      */
47     public static String create(final SchemaContext schemaContext, final YangInstanceIdentifier data) {
48         final DataSchemaContextNode<?> current = DataSchemaContextTree.from(schemaContext).getRoot();
49         final MainVarsWrapper variables = new MainVarsWrapper(current);
50         final StringBuilder path = new StringBuilder();
51
52         QNameModule parentModule = null;
53         for (int i = 0; i < data.getPathArguments().size(); i++) {
54             // get module of the parent
55             if (!variables.getCurrent().isMixin()) {
56                 parentModule = variables.getCurrent().getDataSchemaNode().getQName().getModule();
57             }
58
59             final PathArgument arg = data.getPathArguments().get(i);
60             variables.setCurrent(variables.getCurrent().getChild(arg));
61
62             Preconditions.checkArgument(variables.getCurrent() != null,
63                     "Invalid input %s: schema for argument %s (after %s) not found", data, arg, path);
64
65             if (variables.getCurrent().isMixin()) {
66                 continue;
67             }
68
69             // append namespace before every node which is defined in other module than its parent
70             // condition is satisfied also for the first path argument
71             if (!arg.getNodeType().getModule().equals(parentModule)) {
72                 // append slash if it is not the first path argument
73                 if (path.length() > 0) {
74                     path.append(RestconfConstants.SLASH);
75                 }
76
77                 path.append(prefixForNamespace(arg.getNodeType(), schemaContext));
78                 path.append(ParserBuilderConstants.Deserializer.COLON);
79             } else {
80                 path.append(RestconfConstants.SLASH);
81             }
82
83             if (arg instanceof NodeIdentifierWithPredicates) {
84                 prepareNodeWithPredicates(path, arg);
85             } else if (arg instanceof NodeWithValue) {
86                 prepareNodeWithValue(path, arg);
87             } else {
88                 appendQName(path, arg.getNodeType());
89             }
90         }
91
92         return path.toString();
93     }
94
95     private static void prepareNodeWithValue(final StringBuilder path, final PathArgument arg) {
96         path.append(arg.getNodeType().getLocalName());
97         path.append(ParserBuilderConstants.Deserializer.EQUAL);
98
99         String value = String.valueOf(((NodeWithValue<String>) arg).getValue());
100         if (Serializer.PERCENT_ENCODE_CHARS.matchesAnyOf(value)) {
101             value = parsePercentEncodeChars(value);
102         }
103         path.append(value);
104     }
105
106     private static void prepareNodeWithPredicates(final StringBuilder path, final PathArgument arg) {
107         path.append(arg.getNodeType().getLocalName());
108
109         final Iterator<Entry<QName, Object>> iterator = ((NodeIdentifierWithPredicates) arg).entrySet().iterator();
110         if (iterator.hasNext()) {
111             path.append(ParserBuilderConstants.Deserializer.EQUAL);
112         }
113
114         while (iterator.hasNext()) {
115             String valueOf = String.valueOf(iterator.next().getValue());
116             if (Serializer.PERCENT_ENCODE_CHARS.matchesAnyOf(valueOf)) {
117                 valueOf = parsePercentEncodeChars(valueOf);
118             }
119             path.append(valueOf);
120             if (iterator.hasNext()) {
121                 path.append(ParserBuilderConstants.Deserializer.COMMA);
122             }
123         }
124     }
125
126     /**
127      * Encode {@link Serializer#DISABLED_CHARS} chars to percent encoded chars.
128      *
129      * @param valueOf
130      *             string to encode
131      * @return encoded {@link String}
132      */
133     private static String parsePercentEncodeChars(final String valueOf) {
134         final StringBuilder sb = new StringBuilder();
135         int start = 0;
136         while (start < valueOf.length()) {
137             if (Serializer.PERCENT_ENCODE_CHARS.matches(valueOf.charAt(start))) {
138                 final String format = String.format("%x", (int) valueOf.charAt(start));
139                 final String upperCase = format.toUpperCase(Locale.ROOT);
140                 sb.append(ParserBuilderConstants.Deserializer.PERCENT_ENCODING + upperCase);
141             } else {
142                 sb.append(valueOf.charAt(start));
143             }
144             start++;
145         }
146         return sb.toString();
147     }
148
149     /**
150      * Add {@link QName} to the serialized string.
151      *
152      * @param path
153      *             {@link StringBuilder}
154      * @param qname
155      *             {@link QName} node
156      * @return {@link StringBuilder}
157      */
158     private static StringBuilder appendQName(final StringBuilder path, final QName qname) {
159         path.append(qname.getLocalName());
160         return path;
161     }
162
163     /**
164      * Create prefix of namespace from {@link QName}.
165      *
166      * @param qname
167      *             {@link QName}
168      * @return {@link String}
169      */
170     private static String prefixForNamespace(final QName qname, final SchemaContext schemaContext) {
171         final Module module = schemaContext.findModule(qname.getModule()).orElse(null);
172         return module.getName();
173     }
174
175     private static final class MainVarsWrapper {
176
177         private DataSchemaContextNode<?> current;
178
179         MainVarsWrapper(final DataSchemaContextNode<?> current) {
180             this.setCurrent(current);
181         }
182
183         public DataSchemaContextNode<?> getCurrent() {
184             return this.current;
185         }
186
187         public void setCurrent(final DataSchemaContextNode<?> current) {
188             this.current = current;
189         }
190
191     }
192 }