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}