38baa4301626c30492c1c02fe263e91ea7ab0a27
[yangtools.git] / yang / yang-parser-rfc7950 / src / main / java / org / opendaylight / yangtools / yang / parser / rfc7950 / stmt / ArgumentUtils.java
1 /*
2  * Copyright (c) 2017 Pantheon Technologies, s.r.o. 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.rfc7950.stmt;
9
10 import static org.opendaylight.yangtools.yang.common.YangConstants.RFC6020_YANG_NAMESPACE_STRING;
11 import static org.opendaylight.yangtools.yang.common.YangConstants.YANG_XPATH_FUNCTIONS_PREFIX;
12
13 import com.google.common.annotations.Beta;
14 import com.google.common.base.Splitter;
15 import com.google.common.collect.ImmutableBiMap;
16 import java.math.BigDecimal;
17 import java.util.ArrayList;
18 import java.util.List;
19 import java.util.regex.Matcher;
20 import java.util.regex.Pattern;
21 import javax.xml.xpath.XPath;
22 import javax.xml.xpath.XPathExpressionException;
23 import javax.xml.xpath.XPathFactory;
24 import org.checkerframework.checker.regex.qual.Regex;
25 import org.eclipse.jdt.annotation.NonNull;
26 import org.opendaylight.yangtools.yang.common.QName;
27 import org.opendaylight.yangtools.yang.common.YangVersion;
28 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
29 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
30 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
31 import org.opendaylight.yangtools.yang.model.api.stmt.UnresolvedNumber;
32 import org.opendaylight.yangtools.yang.model.util.RevisionAwareXPathImpl;
33 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
34 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
35 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 /**
40  * Utility class for dealing with arguments encountered by StatementSupport classes. Note that using this class may
41  * result in thread-local state getting attached. To clean up this state, please invoke
42  * {@link #detachFromCurrentThread()} when appropriate.
43  */
44 @Beta
45 public final class ArgumentUtils {
46     public static final Splitter PIPE_SPLITTER = Splitter.on('|').trimResults();
47     public static final Splitter TWO_DOTS_SPLITTER = Splitter.on("..").trimResults();
48
49     private static final Logger LOG = LoggerFactory.getLogger(ArgumentUtils.class);
50
51     @Regex
52     private static final String YANG_XPATH_FUNCTIONS_STRING =
53             "(re-match|deref|derived-from(-or-self)?|enum-value|bit-is-set)([ \t\r\n]*)(\\()";
54     private static final Pattern YANG_XPATH_FUNCTIONS_PATTERN = Pattern.compile(YANG_XPATH_FUNCTIONS_STRING);
55
56     @Regex
57     private static final String PATH_ABS_STR = "/[^/].*";
58     private static final Pattern PATH_ABS = Pattern.compile(PATH_ABS_STR);
59     private static final Splitter SLASH_SPLITTER = Splitter.on('/').omitEmptyStrings().trimResults();
60
61     // XPathFactory is not thread-safe, rather than locking around a shared instance, we use a thread-local one.
62     private static final ThreadLocal<XPathFactory> XPATH_FACTORY = new ThreadLocal<>() {
63         @Override
64         protected XPathFactory initialValue() {
65             return XPathFactory.newInstance();
66         }
67     };
68
69     // these objects are to compare whether range has MAX or MIN value
70     // none of these values should appear as Yang number according to spec so they are safe to use
71     private static final BigDecimal YANG_MIN_NUM = BigDecimal.valueOf(-Double.MAX_VALUE);
72     private static final BigDecimal YANG_MAX_NUM = BigDecimal.valueOf(Double.MAX_VALUE);
73
74     private ArgumentUtils() {
75         throw new UnsupportedOperationException();
76     }
77
78     public static int compareNumbers(final Number n1, final Number n2) {
79         final BigDecimal num1 = yangConstraintToBigDecimal(n1);
80         final BigDecimal num2 = yangConstraintToBigDecimal(n2);
81         return new BigDecimal(num1.toString()).compareTo(new BigDecimal(num2.toString()));
82     }
83
84     public static String internBoolean(final String input) {
85         if ("true".equals(input)) {
86             return "true";
87         } else if ("false".equals(input)) {
88             return "false";
89         } else {
90             return input;
91         }
92     }
93
94     public static @NonNull Boolean parseBoolean(final StmtContext<?, ?, ?> ctx, final String input) {
95         if ("true".equals(input)) {
96             return Boolean.TRUE;
97         } else if ("false".equals(input)) {
98             return Boolean.FALSE;
99         } else {
100             final StatementDefinition def = ctx.getPublicDefinition();
101             throw new SourceException(ctx.getStatementSourceReference(),
102                 "Invalid '%s' statement %s '%s', it can be either 'true' or 'false'",
103                 def.getStatementName(), def.getArgumentDefinition().get().getArgumentName(), input);
104         }
105     }
106
107     public static RevisionAwareXPath parseXPath(final StmtContext<?, ?, ?> ctx, final String path) {
108         final XPath xPath = XPATH_FACTORY.get().newXPath();
109         xPath.setNamespaceContext(StmtNamespaceContext.create(ctx,
110                 ImmutableBiMap.of(RFC6020_YANG_NAMESPACE_STRING, YANG_XPATH_FUNCTIONS_PREFIX)));
111
112         final String trimmed = trimSingleLastSlashFromXPath(path);
113         try {
114             // XPath extension functions have to be prefixed
115             // yang-specific XPath functions are in fact extended functions, therefore we have to add
116             // "yang" prefix to them so that they can be properly validated with the XPath.compile() method
117             // the "yang" prefix is bound to RFC6020 YANG namespace
118             final String prefixedXPath = addPrefixToYangXPathFunctions(trimmed, ctx);
119             // TODO: we could capture the result and expose its 'evaluate' method
120             xPath.compile(prefixedXPath);
121         } catch (final XPathExpressionException e) {
122             LOG.warn("Argument \"{}\" is not valid XPath string at \"{}\"", path, ctx.getStatementSourceReference(), e);
123         }
124
125         return new RevisionAwareXPathImpl(path, isAbsoluteXPath(path));
126     }
127
128     public static boolean isAbsoluteXPath(final String path) {
129         return PATH_ABS.matcher(path).matches();
130     }
131
132     @SuppressWarnings("checkstyle:illegalCatch")
133     public static SchemaNodeIdentifier nodeIdentifierFromPath(final StmtContext<?, ?, ?> ctx, final String path) {
134         // FIXME: is the path trimming really necessary??
135         final List<QName> qNames = new ArrayList<>();
136         for (final String nodeName : SLASH_SPLITTER.split(trimSingleLastSlashFromXPath(path))) {
137             try {
138                 qNames.add(StmtContextUtils.parseNodeIdentifier(ctx, nodeName));
139             } catch (final RuntimeException e) {
140                 throw new SourceException(ctx.getStatementSourceReference(), e,
141                         "Failed to parse node '%s' in path '%s'", nodeName, path);
142             }
143         }
144
145         return SchemaNodeIdentifier.create(qNames, PATH_ABS.matcher(path).matches());
146     }
147
148     /**
149      * Cleanup any resources attached to the current thread. Threads interacting with this class can cause thread-local
150      * caches to them. Invoke this method if you want to detach those resources.
151      */
152     public static void detachFromCurrentThread() {
153         XPATH_FACTORY.remove();
154     }
155
156     private static String addPrefixToYangXPathFunctions(final String path, final StmtContext<?, ?, ?> ctx) {
157         if (ctx.getRootVersion() == YangVersion.VERSION_1_1) {
158             final StringBuilder result = new StringBuilder();
159             final String prefix = YANG_XPATH_FUNCTIONS_PREFIX + ":";
160             final Matcher matcher = YANG_XPATH_FUNCTIONS_PATTERN.matcher(path);
161             while (matcher.find()) {
162                 matcher.appendReplacement(result, prefix + matcher.group());
163             }
164
165             matcher.appendTail(result);
166             return result.toString();
167         }
168
169         return path;
170     }
171
172     private static String trimSingleLastSlashFromXPath(final String path) {
173         return path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
174     }
175
176     private static BigDecimal yangConstraintToBigDecimal(final Number number) {
177         if (UnresolvedNumber.max().equals(number)) {
178             return YANG_MAX_NUM;
179         }
180         if (UnresolvedNumber.min().equals(number)) {
181             return YANG_MIN_NUM;
182         }
183
184         return new BigDecimal(number.toString());
185     }
186 }