2 * Copyright (c) 2017 Pantheon Technologies, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.yang.parser.rfc7950.stmt;
10 import static org.opendaylight.yangtools.yang.common.YangConstants.RFC6020_YANG_NAMESPACE;
11 import static org.opendaylight.yangtools.yang.common.YangConstants.YANG_XPATH_FUNCTIONS_PREFIX;
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.annotation.Nonnull;
22 import javax.annotation.RegEx;
23 import javax.xml.xpath.XPath;
24 import javax.xml.xpath.XPathExpressionException;
25 import javax.xml.xpath.XPathFactory;
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.stmt.SchemaNodeIdentifier;
30 import org.opendaylight.yangtools.yang.model.api.stmt.UnresolvedNumber;
31 import org.opendaylight.yangtools.yang.model.util.RevisionAwareXPathImpl;
32 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
33 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
34 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
39 * Utility class for dealing with arguments encountered by StatementSupport classes. Note that using this class may
40 * result in thread-local state getting attached. To clean up this state, please invoke
41 * {@link #detachFromCurrentThread()} when appropriate.
44 public final class ArgumentUtils {
45 public static final Splitter PIPE_SPLITTER = Splitter.on('|').trimResults();
46 public static final Splitter TWO_DOTS_SPLITTER = Splitter.on("..").trimResults();
48 private static final Logger LOG = LoggerFactory.getLogger(ArgumentUtils.class);
51 private static final String YANG_XPATH_FUNCTIONS_STRING =
52 "(re-match|deref|derived-from(-or-self)?|enum-value|bit-is-set)([ \t\r\n]*)(\\()";
53 private static final Pattern YANG_XPATH_FUNCTIONS_PATTERN = Pattern.compile(YANG_XPATH_FUNCTIONS_STRING);
56 private static final String PATH_ABS_STR = "/[^/].*";
57 private static final Pattern PATH_ABS = Pattern.compile(PATH_ABS_STR);
58 private static final Splitter SLASH_SPLITTER = Splitter.on('/').omitEmptyStrings().trimResults();
60 // XPathFactory is not thread-safe, rather than locking around a shared instance, we use a thread-local one.
61 private static final ThreadLocal<XPathFactory> XPATH_FACTORY = new ThreadLocal<XPathFactory>() {
63 protected XPathFactory initialValue() {
64 return XPathFactory.newInstance();
68 // these objects are to compare whether range has MAX or MIN value
69 // none of these values should appear as Yang number according to spec so they are safe to use
70 private static final BigDecimal YANG_MIN_NUM = BigDecimal.valueOf(-Double.MAX_VALUE);
71 private static final BigDecimal YANG_MAX_NUM = BigDecimal.valueOf(Double.MAX_VALUE);
73 private ArgumentUtils() {
74 throw new UnsupportedOperationException();
77 public static int compareNumbers(final Number n1, final Number n2) {
78 final BigDecimal num1 = yangConstraintToBigDecimal(n1);
79 final BigDecimal num2 = yangConstraintToBigDecimal(n2);
80 return new BigDecimal(num1.toString()).compareTo(new BigDecimal(num2.toString()));
83 public static String internBoolean(final String input) {
84 if ("true".equals(input)) {
86 } else if ("false".equals(input)) {
93 public static @Nonnull Boolean parseBoolean(final StmtContext<?, ?, ?> ctx, final String input) {
94 if ("true".equals(input)) {
96 } else if ("false".equals(input)) {
99 throw new SourceException(ctx.getStatementSourceReference(),
100 "Invalid '%s' statement %s '%s', it can be either 'true' or 'false'",
101 ctx.getPublicDefinition().getStatementName(), ctx.getPublicDefinition().getArgumentName(), input);
105 public static RevisionAwareXPath parseXPath(final StmtContext<?, ?, ?> ctx, final String path) {
106 final XPath xPath = XPATH_FACTORY.get().newXPath();
107 xPath.setNamespaceContext(StmtNamespaceContext.create(ctx,
108 ImmutableBiMap.of(RFC6020_YANG_NAMESPACE.toString(), YANG_XPATH_FUNCTIONS_PREFIX)));
110 final String trimmed = trimSingleLastSlashFromXPath(path);
112 // XPath extension functions have to be prefixed
113 // yang-specific XPath functions are in fact extended functions, therefore we have to add
114 // "yang" prefix to them so that they can be properly validated with the XPath.compile() method
115 // the "yang" prefix is bound to RFC6020 YANG namespace
116 final String prefixedXPath = addPrefixToYangXPathFunctions(trimmed, ctx);
117 // TODO: we could capture the result and expose its 'evaluate' method
118 xPath.compile(prefixedXPath);
119 } catch (final XPathExpressionException e) {
120 LOG.warn("Argument \"{}\" is not valid XPath string at \"{}\"", path, ctx.getStatementSourceReference(), e);
123 return new RevisionAwareXPathImpl(path, PATH_ABS.matcher(path).matches());
126 @SuppressWarnings("checkstyle:illegalCatch")
127 public static SchemaNodeIdentifier nodeIdentifierFromPath(final StmtContext<?, ?, ?> ctx, final String path) {
128 // FIXME: is the path trimming really necessary??
129 final List<QName> qNames = new ArrayList<>();
130 for (final String nodeName : SLASH_SPLITTER.split(trimSingleLastSlashFromXPath(path))) {
132 final QName qName = StmtContextUtils.qnameFromArgument(ctx, nodeName);
134 } catch (final RuntimeException e) {
135 throw new SourceException(ctx.getStatementSourceReference(), e,
136 "Failed to parse node '%s' in path '%s'", nodeName, path);
140 return SchemaNodeIdentifier.create(qNames, PATH_ABS.matcher(path).matches());
144 * Cleanup any resources attached to the current thread. Threads interacting with this class can cause thread-local
145 * caches to them. Invoke this method if you want to detach those resources.
147 public static void detachFromCurrentThread() {
148 XPATH_FACTORY.remove();
151 private static String addPrefixToYangXPathFunctions(final String path, final StmtContext<?, ?, ?> ctx) {
152 if (ctx.getRootVersion() == YangVersion.VERSION_1_1) {
153 // FIXME once Java 9 is available, change this to StringBuilder as Matcher.appendReplacement() and
154 // Matcher.appendTail() will accept StringBuilder parameter in Java 9
155 final StringBuffer result = new StringBuffer();
156 final String prefix = YANG_XPATH_FUNCTIONS_PREFIX + ":";
157 final Matcher matcher = YANG_XPATH_FUNCTIONS_PATTERN.matcher(path);
158 while (matcher.find()) {
159 matcher.appendReplacement(result, prefix + matcher.group());
162 matcher.appendTail(result);
163 return result.toString();
169 private static String trimSingleLastSlashFromXPath(final String path) {
170 return path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
173 private static BigDecimal yangConstraintToBigDecimal(final Number number) {
174 if (UnresolvedNumber.max().equals(number)) {
177 if (UnresolvedNumber.min().equals(number)) {
181 return new BigDecimal(number.toString());