    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      https://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.validator.routines;
    018
    019import java.io.Serializable;
    020import java.util.List;
    021import java.util.regex.Matcher;
    022import java.util.regex.Pattern;
    023
    024/**
    025 * <strong>Regular Expression</strong> validation (using the JRE's regular expression support).
    026 * <p>
    027 * Constructs the validator either for a single regular expression or a set (array) of regular expressions. By default, validation is <em>case sensitive</em> but
    028 * constructors are provided to allow <em>case in-sensitive</em> validation. For example to create a validator which does <em>case in-sensitive</em> validation
    029 * for a set of regular expressions:
    030 * </p>
    031 *
    032 * <pre>
    033 * <code>
    034 * String[] regexs = new String[] {...};
    035 * RegexValidator validator = new RegexValidator(regexs, false);
    036 * </code>
    037 * </pre>
    038 *
    039 * <ul>
    040 * <li>Validate {@code true} or {@code false}:</li>
    041 * <li>
    042 * <ul>
    043 * <li>{@code boolean valid = validator.isValid(value);}</li>
    044 * </ul>
    045 * </li>
    046 * <li>Validate returning an aggregated String of the matched groups:</li>
    047 * <li>
    048 * <ul>
    049 * <li>{@code String result = validator.validate(value);}</li>
    050 * </ul>
    051 * </li>
    052 * <li>Validate returning the matched groups:</li>
    053 * <li>
    054 * <ul>
    055 * <li>{@code String[] result = validator.match(value);}</li>
    056 * </ul>
    057 * </li>
    058 * </ul>
    059 *
    060 * <strong>Note that patterns are matched against the entire input.</strong>
    061 *
    062 * <p>
    063 * Cached instances pre-compile and re-use {@link Pattern}(s) - which according to the {@link Pattern} API are safe to use in a multi-threaded environment.
    064 * </p>
    065 *
    066 * @since 1.4
    067 */
    068public class RegexValidator implements Serializable {
    069
    070    private static final long serialVersionUID = -8832409930574867162L;
    071
    072    private static final int CASE_SENSITIVE = 0;
    073
    074    private static int toCompileFlags(final boolean caseSensitive) {
    075        return caseSensitive ? CASE_SENSITIVE : Pattern.CASE_INSENSITIVE;
    076    }
    077
    078    /**
    079     * Compiled RE patterns from constructors.
    080     */
    081    private final Pattern[] patterns;
    082
    083    /**
    084     * Constructs a new instance that matches any one of the set of regular expressions with the specified case sensitivity.
    085     *
    086     * @param regexs The set of regular expressions this validator will validate against
    087     * @param flags  See {@link Pattern#compile(String, int)}. sensitive</i>, otherwise matching is <em>case in-sensitive</em>
    088     */
    089    private RegexValidator(final int flags, final String... regexs) {
    090        if (regexs == null || regexs.length == 0) {
    091            throw new IllegalArgumentException("Regular expressions are missing");
    092        }
    093        patterns = new Pattern[regexs.length];
    094        for (int i = 0; i < regexs.length; i++) {
    095            final String regex = regexs[i];
    096            if (regex == null || regex.isEmpty()) {
    097                throw new IllegalArgumentException("Regular expression[" + i + "] is missing");
    098            }
    099            patterns[i] = Pattern.compile(regex, flags);
    100        }
    101    }
    102
    103    /**
    104     * Constructs a new <em>case sensitive</em> instance that matches any one in the list of regular expressions.
    105     *
    106     * @param regexs The set of regular expressions this validator will validate against
    107     */
    108    RegexValidator(final List<String> regexs) {
    109        this(CASE_SENSITIVE, regexs.toArray(new String[] {}));
    110    }
    111
    112    /**
    113     * Constructs a new <em>case sensitive</em> instance for a single regular expression.
    114     *
    115     * @param regex The regular expression this validator will validate against
    116     */
    117    public RegexValidator(final String regex) {
    118        this(CASE_SENSITIVE, regex);
    119    }
    120
    121    /**
    122     * Constructs a new <em>case sensitive</em> instance that matches any one in the array of regular expressions.
    123     *
    124     * @param regexs The set of regular expressions this validator will validate against
    125     */
    126    public RegexValidator(final String... regexs) {
    127        this(CASE_SENSITIVE, regexs);
    128    }
    129
    130    /**
    131     * Constructs a new instance for a single regular expression with the specified case sensitivity.
    132     *
    133     * @param regex         The regular expression this validator will validate against
    134     * @param caseSensitive when {@code true} matching is <em>case sensitive</em>, otherwise matching is <em>case in-sensitive</em>
    135     */
    136    public RegexValidator(final String regex, final boolean caseSensitive) {
    137        this(toCompileFlags(caseSensitive), regex);
    138    }
    139
    140    /**
    141     * Constructs a new instance that matches any one of the set of regular expressions with the specified case sensitivity.
    142     *
    143     * @param regexs        The set of regular expressions this validator will validate against
    144     * @param caseSensitive when {@code true} matching is <em>case sensitive</em>, otherwise matching is <em>case in-sensitive</em>
    145     */
    146    public RegexValidator(final String[] regexs, final boolean caseSensitive) {
    147        this(toCompileFlags(caseSensitive), regexs);
    148    }
    149
    150    /**
    151     * Gets a copy of the Patterns.
    152     *
    153     * @return a copy of the Patterns.
    154     * @since 1.8
    155     */
    156    public Pattern[] getPatterns() {
    157        return patterns.clone();
    158    }
    159
    160    /**
    161     * Validates a value against the set of regular expressions.
    162     *
    163     * @param value The value to validate.
    164     * @return {@code true} if the value is valid otherwise {@code false}.
    165     */
    166    public boolean isValid(final String value) {
    167        if (value == null) {
    168            return false;
    169        }
    170        for (final Pattern pattern : patterns) {
    171            if (pattern.matcher(value).matches()) {
    172                return true;
    173            }
    174        }
    175        return false;
    176    }
    177
    178    /**
    179     * Validates a value against the set of regular expressions returning the array of matched groups.
    180     *
    181     * @param value The value to validate.
    182     * @return String array of the <em>groups</em> matched if valid or {@code null} if invalid
    183     */
    184    public String[] match(final String value) {
    185        if (value == null) {
    186            return null;
    187        }
    188        for (final Pattern pattern : patterns) {
    189            final Matcher matcher = pattern.matcher(value);
    190            if (matcher.matches()) {
    191                final int count = matcher.groupCount();
    192                final String[] groups = new String[count];
    193                for (int j = 0; j < count; j++) {
    194                    groups[j] = matcher.group(j + 1);
    195                }
    196                return groups;
    197            }
    198        }
    199        return null;
    200    }
    201
    202    /**
    203     * Provides a String representation of this validator.
    204     *
    205     * @return A String representation of this validator.
    206     */
    207    @Override
    208    public String toString() {
    209        final StringBuilder buffer = new StringBuilder();
    210        buffer.append("RegexValidator{");
    211        for (int i = 0; i < patterns.length; i++) {
    212            if (i > 0) {
    213                buffer.append(",");
    214            }
    215            buffer.append(patterns[i].pattern());
    216        }
    217        buffer.append("}");
    218        return buffer.toString();
    219    }
    220
    221    /**
    222     * Validates a value against the set of regular expressions returning a String value of the aggregated groups.
    223     *
    224     * @param value The value to validate.
    225     * @return Aggregated String value comprised of the <em>groups</em> matched if valid or {@code null} if invalid
    226     */
    227    public String validate(final String value) {
    228        if (value == null) {
    229            return null;
    230        }
    231        for (final Pattern pattern : patterns) {
    232            final Matcher matcher = pattern.matcher(value);
    233            if (matcher.matches()) {
    234                final int count = matcher.groupCount();
    235                if (count == 1) {
    236                    return matcher.group(1);
    237                }
    238                final StringBuilder buffer = new StringBuilder();
    239                for (int j = 0; j < count; j++) {
    240                    final String component = matcher.group(j + 1);
    241                    if (component != null) {
    242                        buffer.append(component);
    243                    }
    244                }
    245                return buffer.toString();
    246            }
    247        }
    248        return null;
    249    }
    250
    251}