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