Seal BaseNotification
[mdsal.git] / binding / yang-binding / src / main / java / org / opendaylight / yangtools / yang / binding / RegexPatterns.java
1 /*
2  * Copyright (c) 2018 Pantheon Technologies, s.r.o.  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.binding;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11
12 import com.google.common.annotations.Beta;
13 import java.util.regex.Pattern;
14
15 @Beta
16 public final class RegexPatterns {
17     private static final String NEGATED_PATTERN_PREFIX = "^(?!";
18     private static final String NEGATED_PATTERN_SUFFIX = ").*$";
19
20     private RegexPatterns() {
21
22     }
23
24     /**
25      * Check if the specified {@link Pattern} is the result of {@link #negatePatternString(String)}. This method
26      * assumes the pattern was not hand-coded but rather was automatically-generated, such that its non-automated
27      * parts come from XSD regular expressions. If this constraint is violated, this method may result false positives.
28      *
29      * @param pattern Pattern to check
30      * @return True if this pattern is a negation.
31      * @throws NullPointerException if pattern is null
32      * @throws IllegalArgumentException if the pattern does not conform to expected structure
33      */
34     public static boolean isNegatedPattern(final Pattern pattern) {
35         final String str = pattern.toString();
36         return str.startsWith(RegexPatterns.NEGATED_PATTERN_PREFIX)
37                 && str.endsWith(RegexPatterns.NEGATED_PATTERN_SUFFIX);
38     }
39
40     /**
41      * Create a {@link Pattern} expression which performs inverted match to the specified pattern. The input pattern
42      * is expected to be a valid regular expression passing {@link Pattern#compile(String)} and to have both start and
43      * end of string anchors as the first and last characters.
44      *
45      * @param pattern Pattern regular expression to negate
46      * @return Negated regular expression
47      * @throws IllegalArgumentException if the pattern does not conform to expected structure
48      * @throws NullPointerException if pattern is null
49      */
50     public static String negatePatternString(final String pattern) {
51         checkArgument(pattern.charAt(0) == '^' && pattern.charAt(pattern.length() - 1) == '$',
52                 "Pattern '%s' does not have expected format", pattern);
53
54         /*
55          * Converting the expression into a negation is tricky. For example, when we have:
56          *
57          *   pattern "a|b" { modifier invert-match; }
58          *
59          * this gets escaped into either "^a|b$" or "^(?:a|b)$". Either format can occur, as the non-capturing group
60          * strictly needed only in some cases. From that we want to arrive at:
61          *   "^(?!(?:a|b)$).*$".
62          *
63          *           ^^^         original expression
64          *        ^^^^^^^^       tail of a grouped expression (without head anchor)
65          *    ^^^^        ^^^^   inversion of match
66          *
67          * Inversion works by explicitly anchoring at the start of the string and then:
68          * - specifying a negative lookahead until the end of string
69          * - matching any string
70          * - anchoring at the end of the string
71          */
72         final boolean hasGroup = pattern.startsWith("^(?:") && pattern.endsWith(")$");
73         final int len = pattern.length();
74         final StringBuilder sb = new StringBuilder(len + (hasGroup ? 7 : 11))
75                 .append(NEGATED_PATTERN_PREFIX);
76
77         if (hasGroup) {
78             sb.append(pattern, 1, len);
79         } else {
80             sb.append("(?:").append(pattern, 1, len - 1).append(")$");
81         }
82         return sb.append(NEGATED_PATTERN_SUFFIX).toString();
83     }
84 }