BUG-1270: use QNameModule where possible
[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 module = schemaContext.findModuleByNamespaceAndRevision(namespace, null);
174         return QName.create(module.getQNameModule(), identifier);
175     }
176
177     private static String trimIfEndIs(final String str, final char end) {
178         final int l = str.length() - 1;
179         if (str.charAt(l) != end) {
180             return null;
181         }
182
183         return str.substring(1, l);
184     }
185
186     private static String toPredicateValue(final String predicatedValue) {
187         final String predicatedValueTrimmed = predicatedValue.trim();
188         if (predicatedValue.isEmpty()) {
189             return null;
190         }
191
192         switch (predicatedValueTrimmed.charAt(0)) {
193         case '"':
194             return trimIfEndIs(predicatedValueTrimmed, '"');
195         case '\'':
196             return trimIfEndIs(predicatedValueTrimmed, '\'');
197         default:
198             return null;
199         }
200     }
201
202     private static void writeIdentifierWithNamespacePrefix(final Element element, final StringBuilder textContent, final QName qName,
203             final Map<String, String> prefixes) {
204         String namespace = qName.getNamespace().toString();
205         String prefix = prefixes.get(namespace);
206         if (prefix == null) {
207             prefix = qName.getPrefix();
208             if (prefix == null || prefix.isEmpty() || prefixes.containsValue(prefix)) {
209                 prefix = generateNewPrefix(prefixes.values());
210             }
211         }
212
213         element.setAttribute("xmlns:" + prefix, namespace.toString());
214         textContent.append(prefix);
215         prefixes.put(namespace, prefix);
216
217         textContent.append(':');
218         textContent.append(qName.getLocalName());
219     }
220
221     private static String generateNewPrefix(final Collection<String> prefixes) {
222         String result;
223
224         final ThreadLocalRandom random = ThreadLocalRandom.current();
225         do {
226             StringBuilder sb = new StringBuilder();
227             for (int i = 0; i < 4; i++) {
228                 sb.append('a' + random.nextInt(25));
229             }
230
231             result = sb.toString();
232         } while (prefixes.contains(result));
233
234         return result;
235     }
236 }