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 *      http://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.openstreetmap.josm.data.validation.routines;
018
019import java.util.Arrays;
020import java.util.Objects;
021import java.util.regex.Matcher;
022import java.util.regex.Pattern;
023import java.util.stream.Collectors;
024import java.util.stream.IntStream;
025
026import org.openstreetmap.josm.tools.Utils;
027
028/**
029 * <b>Regular Expression</b> validation (using JDK 1.4+ regex support).
030 * <p>
031 * Construct the validator either for a single regular expression or a set (array) of
032 * regular expressions. By default validation is <i>case sensitive</i> but constructors
033 * are provided to allow  <i>case in-sensitive</i> validation. For example to create
034 * a validator which does <i>case in-sensitive</i> validation for a set of regular
035 * expressions:
036 * </p>
037 * <pre>
038 * <code>
039 * String[] regexs = new String[] {...};
040 * RegexValidator validator = new RegexValidator(regexs, false);
041 * </code>
042 * </pre>
043 *
044 * <ul>
045 *   <li>Validate <code>true</code> or <code>false</code>:</li>
046 *   <li>
047 *     <ul>
048 *       <li><code>boolean valid = validator.isValid(value);</code></li>
049 *     </ul>
050 *   </li>
051 *   <li>Validate returning an aggregated String of the matched groups:</li>
052 *   <li>
053 *     <ul>
054 *       <li><code>String result = validator.validate(value);</code></li>
055 *     </ul>
056 *   </li>
057 *   <li>Validate returning the matched groups:</li>
058 *   <li>
059 *     <ul>
060 *       <li><code>String[] result = validator.match(value);</code></li>
061 *     </ul>
062 *   </li>
063 * </ul>
064 *
065 * <b>Note that patterns are matched against the entire input.</b>
066 *
067 * <p>
068 * Cached instances pre-compile and re-use {@link Pattern}(s) - which according
069 * to the {@link Pattern} API are safe to use in a multi-threaded environment.
070 * </p>
071 *
072 * @version $Revision: 1741724 $
073 * @since Validator 1.4
074 */
075public class RegexValidator extends AbstractValidator {
076
077    private final Pattern[] patterns;
078
079    /**
080     * Construct a <i>case sensitive</i> validator for a single
081     * regular expression.
082     *
083     * @param regex The regular expression this validator will
084     * validate against
085     */
086    public RegexValidator(String regex) {
087        this(regex, true);
088    }
089
090    /**
091     * Construct a validator for a single regular expression
092     * with the specified case sensitivity.
093     *
094     * @param regex The regular expression this validator will
095     * validate against
096     * @param caseSensitive when <code>true</code> matching is <i>case
097     * sensitive</i>, otherwise matching is <i>case in-sensitive</i>
098     */
099    public RegexValidator(String regex, boolean caseSensitive) {
100        this(new String[] {regex}, caseSensitive);
101    }
102
103    /**
104     * Construct a <i>case sensitive</i> validator that matches any one
105     * of the set of regular expressions.
106     *
107     * @param regexs The set of regular expressions this validator will
108     * validate against
109     */
110    public RegexValidator(String... regexs) {
111        this(regexs, true);
112    }
113
114    /**
115     * Construct a validator that matches any one of the set of regular
116     * expressions with the specified case sensitivity.
117     *
118     * @param regexs The set of regular expressions this validator will
119     * validate against
120     * @param caseSensitive when <code>true</code> matching is <i>case
121     * sensitive</i>, otherwise matching is <i>case in-sensitive</i>
122     */
123    public RegexValidator(String[] regexs, boolean caseSensitive) {
124        if (regexs == null || regexs.length == 0) {
125            throw new IllegalArgumentException("Regular expressions are missing");
126        }
127        patterns = new Pattern[regexs.length];
128        int flags = caseSensitive ? 0 : Pattern.CASE_INSENSITIVE;
129        for (int i = 0; i < regexs.length; i++) {
130            if (Utils.isEmpty(regexs[i])) {
131                throw new IllegalArgumentException("Regular expression[" + i + "] is missing");
132            }
133            patterns[i] = Pattern.compile(regexs[i], flags);
134        }
135    }
136
137    /**
138     * Validate a value against the set of regular expressions.
139     *
140     * @param value The value to validate.
141     * @return <code>true</code> if the value is valid
142     * otherwise <code>false</code>.
143     */
144    @Override
145    public boolean isValid(String value) {
146        return value != null
147                && IntStream.range(0, patterns.length).anyMatch(i -> patterns[i].matcher(value).matches());
148    }
149
150    @Override
151    public String getValidatorName() {
152        return null;
153    }
154
155    /**
156     * Validate a value against the set of regular expressions
157     * returning the array of matched groups.
158     *
159     * @param value The value to validate.
160     * @return String array of the <i>groups</i> matched if
161     * valid or <code>null</code> if invalid
162     */
163    public String[] match(String value) {
164        if (value == null) {
165            return null;
166        }
167        for (Pattern pattern : patterns) {
168            Matcher matcher = pattern.matcher(value);
169            if (matcher.matches()) {
170                int count = matcher.groupCount();
171                return IntStream.range(0, count).mapToObj(j -> matcher.group(j + 1)).toArray(String[]::new);
172            }
173        }
174        return null;
175    }
176
177    /**
178     * Validate a value against the set of regular expressions
179     * returning a String value of the aggregated groups.
180     *
181     * @param value The value to validate.
182     * @return Aggregated String value comprised of the
183     * <i>groups</i> matched if valid or <code>null</code> if invalid
184     */
185    public String validate(String value) {
186        if (value == null) {
187            return null;
188        }
189        for (Pattern pattern : patterns) {
190            Matcher matcher = pattern.matcher(value);
191            if (matcher.matches()) {
192                int count = matcher.groupCount();
193                if (count == 1) {
194                    return matcher.group(1);
195                }
196                return IntStream.range(0, count).mapToObj(j -> matcher.group(j + 1)).filter(Objects::nonNull).collect(Collectors.joining());
197            }
198        }
199        return null;
200    }
201
202    /**
203     * Provide a String representation of this validator.
204     * @return A String representation of this validator
205     */
206    @Override
207    public String toString() {
208        return Arrays.stream(patterns).map(Pattern::pattern)
209                .collect(Collectors.joining(",", "RegexValidator{", "}"));
210    }
211}