Cleanup yang-parser-impl Utils
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / rfc6020 / Utils.java
1 /*
2  * Copyright (c) 2015 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.parser.stmt.rfc6020;
9
10 import static org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils.firstAttributeOf;
11 import com.google.common.base.CharMatcher;
12 import com.google.common.base.Preconditions;
13 import com.google.common.base.Splitter;
14 import com.google.common.base.Strings;
15 import com.google.common.collect.Iterables;
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.Collection;
19 import java.util.Date;
20 import java.util.HashSet;
21 import java.util.Iterator;
22 import java.util.LinkedList;
23 import java.util.List;
24 import java.util.Objects;
25 import java.util.Set;
26 import java.util.regex.Pattern;
27 import javax.annotation.Nullable;
28 import javax.annotation.concurrent.GuardedBy;
29 import javax.xml.xpath.XPath;
30 import javax.xml.xpath.XPathExpressionException;
31 import javax.xml.xpath.XPathFactory;
32 import org.antlr.v4.runtime.tree.TerminalNode;
33 import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser;
34 import org.opendaylight.yangtools.yang.common.QName;
35 import org.opendaylight.yangtools.yang.common.QNameModule;
36 import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
37 import org.opendaylight.yangtools.yang.common.YangConstants;
38 import org.opendaylight.yangtools.yang.model.api.Deviation;
39 import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier;
40 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
41 import org.opendaylight.yangtools.yang.model.api.Status;
42 import org.opendaylight.yangtools.yang.model.api.stmt.AugmentStatement;
43 import org.opendaylight.yangtools.yang.model.api.stmt.BelongsToStatement;
44 import org.opendaylight.yangtools.yang.model.api.stmt.ChoiceStatement;
45 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleStatement;
46 import org.opendaylight.yangtools.yang.model.api.stmt.RefineStatement;
47 import org.opendaylight.yangtools.yang.model.api.stmt.RevisionStatement;
48 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
49 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Relative;
50 import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleStatement;
51 import org.opendaylight.yangtools.yang.model.api.stmt.UsesStatement;
52 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
53 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
54 import org.opendaylight.yangtools.yang.parser.spi.source.BelongsToPrefixToModuleName;
55 import org.opendaylight.yangtools.yang.parser.spi.source.ImpPrefixToModuleIdentifier;
56 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleCtxToModuleQName;
57 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleIdentifierToModuleQName;
58 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleNameToModuleQName;
59 import org.opendaylight.yangtools.yang.parser.spi.source.PrefixToModule;
60 import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinition;
61 import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace;
62 import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace.ValidationBundleType;
63 import org.opendaylight.yangtools.yang.parser.stmt.reactor.RootStatementContext;
64 import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase;
65 import org.slf4j.Logger;
66 import org.slf4j.LoggerFactory;
67
68 public final class Utils {
69
70     private static final Logger LOG = LoggerFactory.getLogger(Utils.class);
71     private static final CharMatcher DOUBLE_QUOTE_MATCHER = CharMatcher.is('"');
72     private static final CharMatcher SINGLE_QUOTE_MATCHER = CharMatcher.is('\'');
73     private static final Splitter SLASH_SPLITTER = Splitter.on('/').omitEmptyStrings().trimResults();
74     private static final Splitter SPACE_SPLITTER = Splitter.on(' ').omitEmptyStrings().trimResults();
75     private static final Pattern PATH_ABS = Pattern.compile("/[^/].*");
76
77     // XPathFactory is documented as non-threadsafe
78     @GuardedBy("Utils.class")
79     private static final XPathFactory XPATH_FACTORY = XPathFactory.newInstance();
80
81     private Utils() {
82         throw new UnsupportedOperationException();
83     }
84
85     public static Collection<SchemaNodeIdentifier.Relative> transformKeysStringToKeyNodes(final StmtContext<?, ?, ?> ctx,
86             final String value) {
87         List<String> keyTokens = SPACE_SPLITTER.splitToList(value);
88
89         // to detect if key contains duplicates
90         if ((new HashSet<>(keyTokens)).size() < keyTokens.size()) {
91             // FIXME: report all duplicate keys
92             throw new IllegalArgumentException();
93         }
94
95         Set<SchemaNodeIdentifier.Relative> keyNodes = new HashSet<>();
96
97         for (String keyToken : keyTokens) {
98
99             SchemaNodeIdentifier.Relative keyNode = (Relative) SchemaNodeIdentifier.Relative.create(false,
100                     Utils.qNameFromArgument(ctx, keyToken));
101             keyNodes.add(keyNode);
102         }
103
104         return keyNodes;
105     }
106
107     public static List<String> splitPathToNodeNames(final String path) {
108         return SLASH_SPLITTER.splitToList(path);
109     }
110
111     public static void validateXPath(final StmtContext<?, ?, ?> ctx, final String path) {
112         final XPath xPath;
113         synchronized (Utils.class) {
114             xPath = XPATH_FACTORY.newXPath();
115         }
116
117         try {
118             xPath.compile(path);
119         } catch (XPathExpressionException e) {
120             throw new IllegalArgumentException(String.format("Argument %s is not valid XPath string at %s", path, ctx
121                     .getStatementSourceReference()), e);
122         }
123     }
124
125     private static String trimSingleLastSlashFromXPath(final String path) {
126         return path.replaceAll("/$", "");
127     }
128
129     public static boolean isXPathAbsolute(final StmtContext<?, ?, ?> ctx, final String path) {
130
131         validateXPath(ctx, trimSingleLastSlashFromXPath(path));
132
133         return PATH_ABS.matcher(path).matches();
134     }
135
136     public static QName trimPrefix(final QName identifier) {
137         String prefixedLocalName = identifier.getLocalName();
138         String[] namesParts = prefixedLocalName.split(":");
139
140         if (namesParts.length == 2) {
141             String localName = namesParts[1];
142             return QName.create(identifier.getModule(), localName);
143         }
144
145         return identifier;
146     }
147
148     public static String getPrefixFromArgument(final String prefixedLocalName) {
149         String[] namesParts = prefixedLocalName.split(":");
150         if (namesParts.length == 2) {
151             return namesParts[0];
152         }
153         return null;
154     }
155
156     public static boolean isValidStatementDefinition(final PrefixToModule prefixes, final QNameToStatementDefinition stmtDef,
157             final QName identifier) {
158         if (stmtDef.get(identifier) != null) {
159             return true;
160         } else {
161             String prefixedLocalName = identifier.getLocalName();
162             String[] namesParts = prefixedLocalName.split(":");
163
164             if (namesParts.length == 2) {
165                 String prefix = namesParts[0];
166                 String localName = namesParts[1];
167                 if (prefixes != null && prefixes.get(prefix) != null
168                         && stmtDef.get(QName.create(YangConstants.RFC6020_YIN_MODULE, localName)) != null) {
169                     return true;
170                 } else {
171                     if (stmtDef.get(QName.create(YangConstants.RFC6020_YIN_MODULE, localName)) != null) {
172                         return true;
173                     }
174                 }
175             }
176         }
177         return false;
178     }
179
180     public static Iterable<QName> parseXPath(final StmtContext<?, ?, ?> ctx, final String path) {
181
182         String trimmedPath = trimSingleLastSlashFromXPath(path);
183
184         validateXPath(ctx, trimmedPath);
185
186         List<String> nodeNames = splitPathToNodeNames(trimmedPath);
187         List<QName> qNames = new ArrayList<>(nodeNames.size());
188
189         for (String nodeName : nodeNames) {
190             try {
191                 final QName qName = Utils.qNameFromArgument(ctx, nodeName);
192                 qNames.add(qName);
193             } catch (Exception e) {
194                 throw new IllegalArgumentException(e);
195             }
196         }
197
198         return qNames;
199     }
200
201     public static String stringFromStringContext(final YangStatementParser.ArgumentContext context) {
202         StringBuilder sb = new StringBuilder();
203         List<TerminalNode> strings = context.STRING();
204         if (strings.isEmpty()) {
205             strings = Arrays.asList(context.IDENTIFIER());
206         }
207         for (TerminalNode stringNode : strings) {
208             final String str = stringNode.getText();
209             char firstChar = str.charAt(0);
210             final CharMatcher quoteMatcher;
211             if (SINGLE_QUOTE_MATCHER.matches(firstChar)) {
212                 quoteMatcher = SINGLE_QUOTE_MATCHER;
213             } else if (DOUBLE_QUOTE_MATCHER.matches(firstChar)) {
214                 quoteMatcher = DOUBLE_QUOTE_MATCHER;
215             } else {
216                 sb.append(str);
217                 continue;
218             }
219             sb.append(quoteMatcher.removeFrom(str.substring(1, str.length() - 1)));
220         }
221         return sb.toString();
222     }
223
224     public static QName qNameFromArgument(StmtContext<?, ?, ?> ctx, final String value) {
225         if (Strings.isNullOrEmpty(value)) {
226             return ctx.getPublicDefinition().getStatementName();
227         }
228
229         String prefix;
230         QNameModule qNameModule = null;
231         String localName = null;
232
233         String[] namesParts = value.split(":");
234         switch (namesParts.length) {
235         case 1:
236             localName = namesParts[0];
237             qNameModule = getRootModuleQName(ctx);
238             break;
239         default:
240             prefix = namesParts[0];
241             localName = namesParts[1];
242             qNameModule = getModuleQNameByPrefix(ctx, prefix);
243             // in case of unknown statement argument, we're not going to parse it
244             if (qNameModule == null
245                     && ctx.getPublicDefinition().getDeclaredRepresentationClass()
246                     .isAssignableFrom(UnknownStatementImpl.class)) {
247                 localName = value;
248                 qNameModule = getRootModuleQName(ctx);
249             }
250             if (qNameModule == null
251                     && Iterables.getLast(ctx.getCopyHistory()) == StmtContext.TypeOfCopy.ADDED_BY_AUGMENTATION) {
252                 ctx = ctx.getOriginalCtx();
253                 qNameModule = getModuleQNameByPrefix(ctx, prefix);
254             }
255             break;
256         }
257
258         Preconditions.checkArgument(qNameModule != null, "Error in module '%s': can not resolve QNameModule for '%s'.",
259                 ctx.getRoot().rawStatementArgument(), value);
260
261         QNameModule resultQNameModule = qNameModule.getRevision() == null ? QNameModule.create(
262                 qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV) : qNameModule;
263
264         return QName.create(resultQNameModule, localName);
265     }
266
267     public static QNameModule getModuleQNameByPrefix(final StmtContext<?, ?, ?> ctx, final String prefix) {
268         QNameModule qNameModule;
269         ModuleIdentifier impModIdentifier = ctx.getRoot().getFromNamespace(ImpPrefixToModuleIdentifier.class, prefix);
270         qNameModule = ctx.getFromNamespace(ModuleIdentifierToModuleQName.class, impModIdentifier);
271
272         if (qNameModule == null && StmtContextUtils.producesDeclared(ctx.getRoot(), SubmoduleStatement.class)) {
273             String moduleName = ctx.getRoot().getFromNamespace(BelongsToPrefixToModuleName.class, prefix);
274             qNameModule = ctx.getFromNamespace(ModuleNameToModuleQName.class, moduleName);
275         }
276         return qNameModule;
277     }
278
279     public static QNameModule getRootModuleQName(final StmtContext<?, ?, ?> ctx) {
280
281         if (ctx == null) {
282             return null;
283         }
284
285         StmtContext<?, ?, ?> rootCtx = ctx.getRoot();
286         QNameModule qNameModule = null;
287
288         if (StmtContextUtils.producesDeclared(rootCtx, ModuleStatement.class)) {
289             qNameModule = rootCtx.getFromNamespace(ModuleCtxToModuleQName.class, rootCtx);
290         } else if (StmtContextUtils.producesDeclared(rootCtx, SubmoduleStatement.class)) {
291             String belongsToModuleName = firstAttributeOf(rootCtx.substatements(),
292                     BelongsToStatement.class);
293             qNameModule = rootCtx.getFromNamespace(ModuleNameToModuleQName.class, belongsToModuleName);
294         }
295
296         return qNameModule.getRevision() == null ? QNameModule.create(qNameModule.getNamespace(),
297                 SimpleDateFormatUtil.DEFAULT_DATE_REV) : qNameModule;
298     }
299
300     @Nullable
301     public static StatementContextBase<?, ?, ?> findNode(final StatementContextBase<?, ?, ?> rootStmtCtx,
302             final SchemaNodeIdentifier node) {
303         StatementContextBase<?, ?, ?> current = rootStmtCtx;
304         Iterator<QName> arguments = node.getPathFromRoot().iterator();
305         while(current != null && arguments.hasNext()) {
306             current = (StatementContextBase<?, ?, ?>) current.getFromNamespace(ChildSchemaNodes.class, arguments.next());
307         }
308         return current;
309     }
310
311     public static SchemaPath getSchemaPath(final StmtContext<?, ?, ?> ctx) {
312
313         if (ctx == null) {
314             return null;
315         }
316
317         final Iterator<StmtContext<?, ?, ?>> iteratorFromRoot = ctx.getStmtContextsFromRoot().iterator();
318         // skip root argument
319         if (iteratorFromRoot.hasNext()) {
320             iteratorFromRoot.next();
321         }
322
323         List<QName> qNamesFromRoot = new LinkedList<>();
324         while (iteratorFromRoot.hasNext()) {
325             StmtContext<?, ?, ?> nextStmtCtx = iteratorFromRoot.next();
326             Object nextStmtArgument = nextStmtCtx.getStatementArgument();
327             if (nextStmtArgument instanceof QName) {
328                 QName qname = (QName) nextStmtArgument;
329                 if (StmtContextUtils.producesDeclared(nextStmtCtx, UsesStatement.class)) {
330                     continue;
331                 }
332                 if (StmtContextUtils.producesDeclared(nextStmtCtx.getParentContext(), ChoiceStatement.class)
333                         && isSupportedAsShorthandCase(nextStmtCtx)) {
334                     qNamesFromRoot.add(qname);
335                 }
336                 qNamesFromRoot.add(qname);
337             } else if (nextStmtArgument instanceof String) {
338                 // FIXME: This may yield illegal argument exceptions
339                 StatementContextBase<?, ?, ?> originalCtx = ctx
340                         .getOriginalCtx();
341                 final QName qName = (originalCtx != null) ? qNameFromArgument(
342                         originalCtx, (String) nextStmtArgument)
343                         : qNameFromArgument(ctx, (String) nextStmtArgument);
344                 qNamesFromRoot.add(qName);
345             } else if ((StmtContextUtils.producesDeclared(nextStmtCtx, AugmentStatement.class)
346                        || StmtContextUtils.producesDeclared(nextStmtCtx, RefineStatement.class))
347                     && nextStmtArgument instanceof SchemaNodeIdentifier) {
348                 addQNamesFromSchemaNodeIdentifierToList(qNamesFromRoot, (SchemaNodeIdentifier) nextStmtArgument);
349             } else if (isUnknownNode(nextStmtCtx)) {
350                 qNamesFromRoot.add(nextStmtCtx.getPublicDefinition().getStatementName());
351             } else {
352                 return SchemaPath.SAME;
353             }
354         }
355
356         final SchemaPath schemaPath = SchemaPath.create(qNamesFromRoot, true);
357         return schemaPath;
358     }
359
360     public static boolean isUnknownNode(final StmtContext<?, ?, ?> stmtCtx) {
361         return stmtCtx.getPublicDefinition().getDeclaredRepresentationClass()
362                 .isAssignableFrom(UnknownStatementImpl.class);
363     }
364
365     private static boolean isSupportedAsShorthandCase(final StmtContext<?, ?, ?> statementCtx) {
366
367         Collection<?> supportedCaseShorthands = statementCtx.getFromNamespace(ValidationBundlesNamespace.class,
368                 ValidationBundleType.SUPPORTED_CASE_SHORTHANDS);
369
370         return supportedCaseShorthands == null || supportedCaseShorthands.contains(statementCtx.getPublicDefinition());
371     }
372
373     private static void addQNamesFromSchemaNodeIdentifierToList(final List<QName> qNamesFromRoot,
374             final SchemaNodeIdentifier augmentTargetPath) {
375         for (QName qname : augmentTargetPath.getPathFromRoot()) {
376             qNamesFromRoot.add(qname);
377         }
378     }
379
380     public static Deviation.Deviate parseDeviateFromString(final String deviate) {
381
382         // Yang constants should be lowercase so we have throw if value does not
383         // suit this
384         String deviateUpper = deviate.toUpperCase();
385         Preconditions.checkArgument(!Objects.equals(deviate, deviateUpper),
386             "String %s is not valid deviate argument", deviate);
387
388         // but Java enum is uppercase so we cannot use lowercase here
389         try {
390             return Deviation.Deviate.valueOf(deviateUpper);
391         } catch (IllegalArgumentException e) {
392             throw new IllegalArgumentException(String.format("String %s is not valid deviate argument", deviate), e);
393         }
394     }
395
396     public static Status parseStatus(final String value) {
397
398         Status status = null;
399         switch (value) {
400         case "current":
401             status = Status.CURRENT;
402             break;
403         case "deprecated":
404             status = Status.DEPRECATED;
405             break;
406         case "obsolete":
407             status = Status.OBSOLETE;
408             break;
409         default:
410             LOG.warn("Invalid 'status' statement: " + value);
411         }
412
413         return status;
414     }
415
416     public static Date getLatestRevision(final RootStatementContext<?, ?, ?> root) {
417         return getLatestRevision(root.declaredSubstatements());
418     }
419
420     public static Date getLatestRevision(final Iterable<? extends StmtContext<?, ?, ?>> subStmts) {
421         Date revision = null;
422         for (StmtContext<?, ?, ?> subStmt : subStmts) {
423             if (subStmt.getPublicDefinition().getDeclaredRepresentationClass().isAssignableFrom(RevisionStatement
424                     .class)) {
425                 if (revision == null && subStmt.getStatementArgument() != null) {
426                     revision = (Date) subStmt.getStatementArgument();
427                 } else if (subStmt.getStatementArgument() != null && ((Date) subStmt.getStatementArgument()).compareTo
428                         (revision) > 0) {
429                     revision = (Date) subStmt.getStatementArgument();
430                 }
431             }
432         }
433         return revision;
434     }
435
436     public static boolean isModuleIdentifierWithoutSpecifiedRevision(final Object o) {
437         return (o instanceof ModuleIdentifier)
438                 && (((ModuleIdentifier) o).getRevision() == SimpleDateFormatUtil.DEFAULT_DATE_IMP ||
439                         ((ModuleIdentifier) o).getRevision() == SimpleDateFormatUtil.DEFAULT_BELONGS_TO_DATE);
440     }
441 }