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}