Merge "BUG-597: removed dependency on GeneratedTOBuilderImpl from BindingGeneratorUti...
[yangtools.git] / yang / yang-data-impl / src / main / java / org / opendaylight / yangtools / yang / data / impl / codec / xml / InstanceIdentifierForXmlCodec.java
1 /*
2  * Copyright (c) 2014 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.yangtools.yang.data.impl.codec.xml;
9
10 import com.google.common.base.Preconditions;
11 import com.google.common.base.Splitter;
12
13 import java.net.URI;
14 import java.net.URISyntaxException;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.HashMap;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.concurrent.ThreadLocalRandom;
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
24
25 import org.opendaylight.yangtools.yang.common.QName;
26 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
28 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeWithValue;
29 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
30 import org.opendaylight.yangtools.yang.model.api.Module;
31 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
32 import org.w3c.dom.Element;
33
34 public final class InstanceIdentifierForXmlCodec {
35     private static final Pattern PREDICATE_PATTERN = Pattern.compile("\\[(.*?)\\]");
36     private static final Splitter SLASH_SPLITTER = Splitter.on('/');
37     private static final Splitter COLON_SPLITTER = Splitter.on(':');
38
39     private InstanceIdentifierForXmlCodec() {
40         throw new UnsupportedOperationException("Utility class");
41     }
42
43     public static InstanceIdentifier deserialize(final Element element, final SchemaContext schemaContext) {
44         Preconditions.checkNotNull(element, "Value of element for deserialization can't be null");
45         Preconditions.checkNotNull(schemaContext,
46                 "Schema context for deserialization of instance identifier type can't be null");
47
48         final String valueTrimmed = element.getTextContent().trim();
49         final Iterator<String> xPathParts = SLASH_SPLITTER.split(valueTrimmed).iterator();
50
51         // must be at least "/pr:node"
52         if (!xPathParts.hasNext() || !xPathParts.next().isEmpty() || !xPathParts.hasNext()) {
53             return null;
54         }
55
56         List<PathArgument> result = new ArrayList<>();
57         while (xPathParts.hasNext()) {
58             String xPathPartTrimmed = xPathParts.next().trim();
59
60             PathArgument pathArgument = toPathArgument(xPathPartTrimmed, element, schemaContext);
61             if (pathArgument != null) {
62                 result.add(pathArgument);
63             }
64         }
65         return InstanceIdentifier.create(result);
66     }
67
68     public static Element serialize(final InstanceIdentifier data, final Element element) {
69         Preconditions.checkNotNull(data, "Variable should contain instance of instance identifier and can't be null");
70         Preconditions.checkNotNull(element, "DOM element can't be null");
71         Map<String, String> prefixes = new HashMap<>();
72         StringBuilder textContent = new StringBuilder();
73         for (PathArgument pathArgument : data.getPathArguments()) {
74             textContent.append('/');
75             writeIdentifierWithNamespacePrefix(element, textContent, pathArgument.getNodeType(), prefixes);
76             if (pathArgument instanceof NodeIdentifierWithPredicates) {
77                 Map<QName, Object> predicates = ((NodeIdentifierWithPredicates) pathArgument).getKeyValues();
78
79                 for (QName keyValue : predicates.keySet()) {
80                     String predicateValue = String.valueOf(predicates.get(keyValue));
81                     textContent.append('[');
82                     writeIdentifierWithNamespacePrefix(element, textContent, keyValue, prefixes);
83                     textContent.append("='");
84                     textContent.append(predicateValue);
85                     textContent.append("']");
86                 }
87             } else if (pathArgument instanceof NodeWithValue) {
88                 textContent.append("[.='");
89                 textContent.append(((NodeWithValue) pathArgument).getValue());
90                 textContent.append("']");
91             }
92         }
93         element.setTextContent(textContent.toString());
94         return element;
95     }
96
97     private static String getIdAndPrefixAsStr(final String pathPart) {
98         int predicateStartIndex = pathPart.indexOf('[');
99         return predicateStartIndex == -1 ? pathPart : pathPart.substring(0, predicateStartIndex);
100     }
101
102     private static PathArgument toPathArgument(final String xPathArgument, final Element element, final SchemaContext schemaContext) {
103         final QName mainQName = toIdentity(xPathArgument, element, schemaContext);
104
105         // predicates
106         final Matcher matcher = PREDICATE_PATTERN.matcher(xPathArgument);
107         final Map<QName, Object> predicates = new HashMap<>();
108         QName currentQName = mainQName;
109
110         while (matcher.find()) {
111             final String predicateStr = matcher.group(1).trim();
112             final int indexOfEqualityMark = predicateStr.indexOf('=');
113             if (indexOfEqualityMark != -1) {
114                 final String predicateValue = toPredicateValue(predicateStr.substring(indexOfEqualityMark + 1));
115                 if (predicateValue == null) {
116                     return null;
117                 }
118
119                 if (predicateStr.charAt(0) != '.') {
120                     // target is not a leaf-list
121                     currentQName = toIdentity(predicateStr.substring(0, indexOfEqualityMark), element, schemaContext);
122                     if (currentQName == null) {
123                         return null;
124                     }
125                 }
126                 predicates.put(currentQName, predicateValue);
127             }
128         }
129
130         if (predicates.isEmpty()) {
131             return new InstanceIdentifier.NodeIdentifier(mainQName);
132         } else {
133             return new InstanceIdentifier.NodeIdentifierWithPredicates(mainQName, predicates);
134         }
135
136     }
137
138     public static QName toIdentity(final String xPathArgument, final Element element, final SchemaContext schemaContext) {
139         final String xPathPartTrimmed = getIdAndPrefixAsStr(xPathArgument).trim();
140         final Iterator<String> it = COLON_SPLITTER.split(xPathPartTrimmed).iterator();
141
142         // Empty string
143         if (!it.hasNext()) {
144             return null;
145         }
146
147         final String prefix = it.next().trim();
148         if (prefix.isEmpty()) {
149             return null;
150         }
151
152         // it is not "prefix:value"
153         if (!it.hasNext()) {
154             return null;
155         }
156
157         final String identifier = it.next().trim();
158         if (identifier.isEmpty()) {
159             return null;
160         }
161
162         URI namespace = null;
163         String namespaceStr = null;
164         try {
165             namespaceStr = element.lookupNamespaceURI(prefix);
166             namespace = new URI(namespaceStr);
167         } catch (URISyntaxException e) {
168             throw new IllegalArgumentException("It wasn't possible to convert " + namespaceStr + " to URI object.");
169         } catch (NullPointerException e) {
170             throw new IllegalArgumentException("I wasn't possible to get namespace for prefix " + prefix);
171         }
172
173         Module youngestModule = findYoungestModuleByNamespace(schemaContext, namespace);
174         return QName.create(namespace, youngestModule.getRevision(), identifier);
175     }
176
177     // FIXME: this method should be provided by SchemaContext
178     private static Module findYoungestModuleByNamespace(final SchemaContext schemaContext, final URI namespace) {
179         Module result = null;
180         for (Module module : schemaContext.findModuleByNamespace(namespace)) {
181             if (result != null) {
182                 if (module.getRevision().after(result.getRevision())) {
183                     result = module;
184                 }
185             } else {
186                 result = module;
187             }
188         }
189         return result;
190     }
191
192     private static String trimIfEndIs(final String str, final char end) {
193         final int l = str.length() - 1;
194         if (str.charAt(l) != end) {
195             return null;
196         }
197
198         return str.substring(1, l);
199     }
200
201     private static String toPredicateValue(final String predicatedValue) {
202         final String predicatedValueTrimmed = predicatedValue.trim();
203         if (predicatedValue.isEmpty()) {
204             return null;
205         }
206
207         switch (predicatedValueTrimmed.charAt(0)) {
208         case '"':
209             return trimIfEndIs(predicatedValueTrimmed, '"');
210         case '\'':
211             return trimIfEndIs(predicatedValueTrimmed, '\'');
212         default:
213             return null;
214         }
215     }
216
217     private static void writeIdentifierWithNamespacePrefix(final Element element, final StringBuilder textContent, final QName qName,
218             final Map<String, String> prefixes) {
219         String namespace = qName.getNamespace().toString();
220         String prefix = prefixes.get(namespace);
221         if (prefix == null) {
222             prefix = qName.getPrefix();
223             if (prefix == null || prefix.isEmpty() || prefixes.containsValue(prefix)) {
224                 prefix = generateNewPrefix(prefixes.values());
225             }
226         }
227
228         element.setAttribute("xmlns:" + prefix, namespace.toString());
229         textContent.append(prefix);
230         prefixes.put(namespace, prefix);
231
232         textContent.append(':');
233         textContent.append(qName.getLocalName());
234     }
235
236     private static String generateNewPrefix(final Collection<String> prefixes) {
237         String result;
238
239         final ThreadLocalRandom random = ThreadLocalRandom.current();
240         do {
241             StringBuilder sb = new StringBuilder();
242             for (int i = 0; i < 4; i++) {
243                 sb.append('a' + random.nextInt(25));
244             }
245
246             result = sb.toString();
247         } while (prefixes.contains(result));
248
249         return result;
250     }
251 }