blob: 212610359c45a86a201c8558a1f5e55572055911 [file] [log] [blame]
/*
* 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();
}
}