1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.validator.routines;
18
19 import java.io.Serializable;
20 import java.util.List;
21 import java.util.regex.Matcher;
22 import java.util.regex.Pattern;
23
24 /**
25 * <strong>Regular Expression</strong> validation (using the JRE's regular expression support).
26 * <p>
27 * 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
28 * 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
29 * for a set of regular expressions:
30 * </p>
31 *
32 * <pre>
33 * <code>
34 * String[] regexs = new String[] {...};
35 * RegexValidator validator = new RegexValidator(regexs, false);
36 * </code>
37 * </pre>
38 *
39 * <ul>
40 * <li>Validate {@code true} or {@code false}:</li>
41 * <li>
42 * <ul>
43 * <li>{@code boolean valid = validator.isValid(value);}</li>
44 * </ul>
45 * </li>
46 * <li>Validate returning an aggregated String of the matched groups:</li>
47 * <li>
48 * <ul>
49 * <li>{@code String result = validator.validate(value);}</li>
50 * </ul>
51 * </li>
52 * <li>Validate returning the matched groups:</li>
53 * <li>
54 * <ul>
55 * <li>{@code String[] result = validator.match(value);}</li>
56 * </ul>
57 * </li>
58 * </ul>
59 *
60 * <strong>Note that patterns are matched against the entire input.</strong>
61 *
62 * <p>
63 * 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.
64 * </p>
65 *
66 * @since 1.4
67 */
68 public class RegexValidator implements Serializable {
69
70 private static final long serialVersionUID = -8832409930574867162L;
71
72 private static final int CASE_SENSITIVE = 0;
73
74 private static int toCompileFlags(final boolean caseSensitive) {
75 return caseSensitive ? CASE_SENSITIVE : Pattern.CASE_INSENSITIVE;
76 }
77
78 /**
79 * Compiled RE patterns from constructors.
80 */
81 private final Pattern[] patterns;
82
83 /**
84 * Constructs a new instance that matches any one of the set of regular expressions with the specified case sensitivity.
85 *
86 * @param regexs The set of regular expressions this validator will validate against
87 * @param flags See {@link Pattern#compile(String, int)}. sensitive</i>, otherwise matching is <em>case in-sensitive</em>
88 */
89 private RegexValidator(final int flags, final String... regexs) {
90 if (regexs == null || regexs.length == 0) {
91 throw new IllegalArgumentException("Regular expressions are missing");
92 }
93 patterns = new Pattern[regexs.length];
94 for (int i = 0; i < regexs.length; i++) {
95 final String regex = regexs[i];
96 if (regex == null || regex.isEmpty()) {
97 throw new IllegalArgumentException("Regular expression[" + i + "] is missing");
98 }
99 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 }