001/* 002 * MIT License 003 * 004 * Copyright (c) 2016 Michael Angstadt 005 * 006 * Permission is hereby granted, free of charge, to any person obtaining a copy 007 * of this software and associated documentation files (the "Software"), to deal 008 * in the Software without restriction, including without limitation the rights 009 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 010 * copies of the Software, and to permit persons to whom the Software is 011 * furnished to do so, subject to the following conditions: 012 * 013 * The above copyright notice and this permission notice shall be included in 014 * all copies or substantial portions of the Software. 015 * 016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 017 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 018 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 019 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 020 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 021 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 022 * SOFTWARE. 023 */ 024 025package com.github.mangstadt.vinnie.validate; 026 027import java.util.BitSet; 028 029/** 030 * Validates whether or not strings contain only certain characters. 031 * @author Michael Angstadt 032 */ 033public class AllowedCharacters { 034 private static final int LENGTH = 128; 035 private final BitSet bitSet; 036 private final boolean allowNonAscii; 037 038 /** 039 * Creates an allowed character list based on the given {@link BitSet}. 040 * @param bitSet the bit set 041 * @param allowNonAscii true to allow characters outside of the 7-bit ASCII 042 * character set (character codes greater than 127), false to only allow 043 * 7-bit ASCII 044 */ 045 public AllowedCharacters(BitSet bitSet, boolean allowNonAscii) { 046 this.bitSet = bitSet; 047 this.allowNonAscii = allowNonAscii; 048 } 049 050 /** 051 * Gets the underlying {@link BitSet} object. 052 * @return the {@link BitSet} object 053 */ 054 public BitSet bitSet() { 055 return bitSet; 056 } 057 058 /** 059 * Determines if this allowed character list permits characters that are not 060 * part of 7-bit ASCII (character codes greater than 127). 061 * @return true if non-ASCII characters are allowed, false if not 062 */ 063 public boolean isNonAsciiAllowed() { 064 return allowNonAscii; 065 } 066 067 /** 068 * Determines if a string only contains allowed characters. 069 * @param string the string 070 * @return true if the string only contains allowed characters, false if not 071 */ 072 public boolean check(String string) { 073 for (int i = 0; i < string.length(); i++) { 074 char c = string.charAt(i); 075 if (c >= LENGTH) { 076 if (!allowNonAscii) { 077 return false; 078 } 079 continue; 080 } 081 082 if (!bitSet.get(c)) { 083 return false; 084 } 085 } 086 return true; 087 } 088 089 /** 090 * Returns an allowed character list that is the opposite of this allowed 091 * character list (in other words, characters that are NOT allowed). 092 * @return the reverse list 093 */ 094 public AllowedCharacters flip() { 095 BitSet bitSet = (BitSet) this.bitSet.clone(); 096 bitSet.flip(0, LENGTH); 097 return new AllowedCharacters(bitSet, !allowNonAscii); 098 } 099 100 /** 101 * Generates a string representation of this allowed character list. 102 * Non-printable characters are represented by their character codes. 103 * @return the string 104 */ 105 @Override 106 public String toString() { 107 return toString(false); 108 } 109 110 /** 111 * Generates a string representation of this allowed character list. 112 * Non-printable characters are represented by their character codes. 113 * @param printableOnly true to only include printable characters in the 114 * string, false to include all characters 115 * @return the string 116 */ 117 public String toString(boolean printableOnly) { 118 StringBuilder sb = new StringBuilder(); 119 sb.append('['); 120 for (int i = 0; i < LENGTH; i++) { 121 if (!bitSet.get(i)) { 122 continue; 123 } 124 125 String toPrint = null; 126 127 char c = (char) i; 128 switch (c) { 129 case ' ': 130 toPrint = "<space>"; 131 break; 132 case '\r': 133 toPrint = "\\r"; 134 break; 135 case '\n': 136 toPrint = "\\n"; 137 break; 138 case '\t': 139 toPrint = "\\t"; 140 break; 141 default: 142 if (i < 32 || i == 127) { 143 if (printableOnly) { 144 continue; 145 } 146 toPrint = "(" + i + ")"; 147 } 148 } 149 150 sb.append(' '); 151 if (toPrint == null) { 152 sb.append(c); 153 } else { 154 sb.append(toPrint); 155 } 156 } 157 sb.append(" ]"); 158 return sb.toString(); 159 } 160 161 /** 162 * Builder class for creating new instances of {@link AllowedCharacters}. 163 */ 164 public static class Builder { 165 private final BitSet bitSet; 166 private boolean allowNonAscii; 167 168 /** 169 * Creates a new builder. 170 */ 171 public Builder() { 172 bitSet = new BitSet(LENGTH); 173 allowNonAscii = false; 174 } 175 176 /** 177 * Initializes the builder with an existing {@link AllowedCharacters} 178 * object. 179 * @param original the object to copy 180 */ 181 public Builder(AllowedCharacters original) { 182 bitSet = (BitSet) original.bitSet.clone(); 183 allowNonAscii = original.allowNonAscii; 184 } 185 186 /** 187 * Allow all characters. 188 * @return this 189 */ 190 public Builder allowAll() { 191 bitSet.set(0, LENGTH); 192 allowNonAscii = true; 193 return this; 194 } 195 196 /** 197 * Allow characters within the given range. 198 * @param from the character to start at 199 * @param to the character to end at (inclusive) 200 * @return this 201 */ 202 public Builder allow(int from, int to) { 203 bitSet.set(from, to + 1); 204 return this; 205 } 206 207 /** 208 * Allow all the characters in the given string. 209 * @param characters the string containing the allowable characters 210 * @return this 211 */ 212 public Builder allow(String characters) { 213 setAll(characters, true); 214 return this; 215 } 216 217 /** 218 * Allow the given character. 219 * @param c the character 220 * @return this 221 */ 222 public Builder allow(char c) { 223 bitSet.set(c); 224 return this; 225 } 226 227 /** 228 * Allows all characters that are considered "printable" (32-126 229 * inclusive). This does NOT include tabs, carriage returns, or line 230 * feeds. This DOES include spaces. 231 * @return this 232 */ 233 public Builder allowPrintable() { 234 return allow(32, 126); 235 } 236 237 /** 238 * Allows all characters outside the range of 7-bit ASCII. 239 * @return this 240 */ 241 public Builder allowNonAscii() { 242 allowNonAscii = true; 243 return this; 244 } 245 246 /** 247 * Reject all the characters in the given string. 248 * @param characters the string containing the illegal characters 249 * @return this 250 */ 251 public Builder except(String characters) { 252 setAll(characters, false); 253 return this; 254 } 255 256 /** 257 * Reject the given character. 258 * @param c the character 259 * @return this 260 */ 261 public Builder except(char c) { 262 bitSet.set(c, false); 263 return this; 264 } 265 266 /** 267 * Constructs the final {@link AllowedCharacters} object. 268 * @return the object 269 */ 270 public AllowedCharacters build() { 271 return new AllowedCharacters(bitSet, allowNonAscii); 272 } 273 274 private void setAll(String characters, boolean value) { 275 for (int i = 0; i < characters.length(); i++) { 276 char c = characters.charAt(i); 277 bitSet.set(c, value); 278 } 279 } 280 } 281}