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}