| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.hadoop.chukwa.util; |
| |
| import java.util.regex.Pattern; |
| import java.util.regex.PatternSyntaxException; |
| |
| /*>>> |
| import checkers.nullness.quals.*; |
| import checkers.regex.quals.*; |
| */ |
| |
| /** |
| * Utility methods for regular expressions, most notably for testing whether |
| * a string is a regular expression. |
| */ |
| public class RegexUtil { |
| |
| /** |
| * A checked version of {@link PatternSyntaxException}. |
| * <p> |
| * This exception is useful when an illegal regex is detected but the |
| * contextual information to report a helpful error message is not available |
| * at the current depth in the call stack. By using a checked |
| * PatternSyntaxException the error must be handled up the call stack where |
| * a better error message can be reported. |
| * <p> |
| * |
| * Typical usage is: |
| * <pre> |
| * void myMethod(...) throws CheckedPatternSyntaxException { |
| * ... |
| * if (! isRegex(myString)) { |
| * throw new CheckedPatternSyntaxException(...); |
| * } |
| * ... Pattern.compile(myString) ... |
| * </pre> |
| * |
| * Simply calling <tt>Pattern.compile</tt> would have a similar effect, |
| * in that <tt>PatternSyntaxException</tt> would be thrown at run time if |
| * <tt>myString</tt> is not a regular expression. There are two problems |
| * with such an approach. First, a client of <tt>myMethod</tt> might |
| * forget to handle the exception, since <tt>PatternSyntaxException</tt> |
| * is not checked. Also, the Regex Checker would issue a warning about |
| * the call to <tt>Pattern.compile</tt> that might throw an exception. |
| * The above usage pattern avoids both problems. |
| * |
| * @see PatternSyntaxException |
| */ |
| public static class CheckedPatternSyntaxException extends Exception { |
| |
| private static final long serialVersionUID = 6266881831979001480L; |
| |
| private final PatternSyntaxException pse; |
| |
| /** |
| * Constructs a new CheckedPatternSyntaxException equivalent to the |
| * given {@link PatternSyntaxException}. |
| * <p> |
| * Consider calling this constructor with the result of |
| * {@link RegexUtil#regexError}. |
| * @param pse is PatternSyntaxException object |
| */ |
| public CheckedPatternSyntaxException(PatternSyntaxException pse) { |
| this.pse = pse; |
| } |
| |
| /** |
| * Constructs a new CheckedPatternSyntaxException. |
| * |
| * @param desc A description of the error |
| * @param regex The erroneous pattern |
| * @param index The approximate index in the pattern of the error, |
| * or {@code -1} if the index is not known |
| */ |
| public CheckedPatternSyntaxException(String desc, String regex, int index) { |
| this(new PatternSyntaxException(desc, regex, index)); |
| } |
| |
| /** |
| * Retrieves the description of the error. |
| * |
| * @return The description of the error |
| */ |
| public String getDescription() { |
| return pse.getDescription(); |
| } |
| |
| /** |
| * Retrieves the error index. |
| * |
| * @return The approximate index in the pattern of the error, or {@code -1} |
| * if the index is not known |
| */ |
| public int getIndex() { |
| return pse.getIndex(); |
| } |
| |
| /** |
| * Returns a multi-line string containing the description of the syntax |
| * error and its index, the erroneous regular-expression pattern, and a |
| * visual indication of the error index within the pattern. |
| * |
| * @return The full detail message |
| */ |
| public String getMessage() { |
| return pse.getMessage(); |
| } |
| |
| /** |
| * Retrieves the erroneous regular-expression pattern. |
| * |
| * @return The erroneous pattern |
| */ |
| public String getPattern() { |
| return pse.getPattern(); |
| } |
| } |
| |
| private RegexUtil() { |
| throw new AssertionError("Class RegexUtil shouldn't be instantiated"); |
| } |
| |
| /** |
| * Returns true if the argument is a syntactically valid regular |
| * expression. |
| * @param s is regular expression |
| * @return true if there is a match |
| */ |
| public static boolean isRegex(String s) { |
| return isRegex(s, 0); |
| } |
| |
| /** |
| * Returns true if the argument is a syntactically valid regular |
| * expression with at least the given number of groups. |
| * @param s is regular expression |
| * @param groups is number of groups to match |
| * @return true if there is a match |
| */ |
| /*>>> |
| @SuppressWarnings("regex") // RegexUtil |
| */ |
| /*@Pure*/ |
| public static boolean isRegex(String s, int groups) { |
| Pattern p; |
| try { |
| p = Pattern.compile(s); |
| } catch (PatternSyntaxException e) { |
| return false; |
| } |
| return getGroupCount(p) >= groups; |
| } |
| |
| /** |
| * Returns true if the argument is a syntactically valid regular |
| * expression. |
| * @param c is a character |
| * @return true if there is a match |
| */ |
| public static boolean isRegex(char c) { |
| return isRegex(Character.toString(c)); |
| } |
| |
| /** |
| * Returns null if the argument is a syntactically valid regular |
| * expression. Otherwise returns a string describing why the argument is |
| * not a regex. |
| * @param s is regular expression |
| * @return null if s is a regular expression |
| */ |
| public static String regexError(String s) { |
| return regexError(s, 0); |
| } |
| |
| /** |
| * Returns null if the argument is a syntactically valid regular |
| * expression with at least the given number of groups. Otherwise returns |
| * a string describing why the argument is not a regex. |
| * @param s is regular expression |
| * @param groups is number of groups to match |
| * @return null if s is a regular expression |
| */ |
| public static String regexError(String s, int groups) { |
| try { |
| Pattern p = Pattern.compile(s); |
| int actualGroups = getGroupCount(p); |
| if (actualGroups < groups) { |
| return regexErrorMessage(s, groups, actualGroups); |
| } |
| } catch (PatternSyntaxException e) { |
| return e.getMessage(); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns null if the argument is a syntactically valid regular |
| * expression. Otherwise returns a PatternSyntaxException describing |
| * why the argument is not a regex. |
| * @param s is regular expression |
| * @return null if s is a regular expression |
| */ |
| public static PatternSyntaxException regexException(String s) { |
| return regexException(s, 0); |
| } |
| |
| /** |
| * Returns null if the argument is a syntactically valid regular |
| * expression with at least the given number of groups. Otherwise returns a |
| * PatternSyntaxException describing why the argument is not a regex. |
| * @param s is regular expression |
| * @param groups is number of groups to match |
| * @return null if s is a regular expression |
| */ |
| public static PatternSyntaxException regexException(String s, int groups) { |
| try { |
| Pattern p = Pattern.compile(s); |
| int actualGroups = getGroupCount(p); |
| if (actualGroups < groups) { |
| return new PatternSyntaxException(regexErrorMessage(s, groups, actualGroups), s, -1); |
| } |
| } catch (PatternSyntaxException pse) { |
| return pse; |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the argument as a {@code @Regex String} if it is a regex, |
| * otherwise throws an error. The purpose of this method is to suppress Regex |
| * Checker warnings. Once the the Regex Checker supports flow-sensitivity, it |
| * should be very rarely needed. |
| * @param s is a regular expression |
| * @return null if s is a regular expression |
| */ |
| public static String asRegex(String s) { |
| return asRegex(s, 0); |
| } |
| |
| /** |
| * Returns the argument as a {@code @Regex(groups) String} if it is a regex |
| * with at least the given number of groups, otherwise throws an error. The |
| * purpose of this method is to suppress Regex Checker warnings. Once the the |
| * Regex Checker supports flow-sensitivity, it should be very rarely needed. |
| * @param s is a regular expression |
| * @param groups is number of group to match |
| * @return null if s is a regular expression |
| */ |
| public static String asRegex(String s, int groups) { |
| try { |
| Pattern p = Pattern.compile(s); |
| int actualGroups = getGroupCount(p); |
| if (actualGroups < groups) { |
| throw new Error(regexErrorMessage(s, groups, actualGroups)); |
| } |
| return s; |
| } catch (PatternSyntaxException e) { |
| throw new Error(e); |
| } |
| } |
| |
| /** |
| * Generates an error message for s when expectedGroups are needed, but s |
| * only has actualGroups. |
| */ |
| private static String regexErrorMessage(String s, int expectedGroups, int actualGroups) { |
| return "regex \"" + s + "\" has " + actualGroups + " groups, but " + |
| expectedGroups + " groups are needed."; |
| } |
| |
| /** |
| * Returns the count of groups in the argument. |
| */ |
| private static int getGroupCount(Pattern p) { |
| return p.matcher("").groupCount(); |
| } |
| } |