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.apache.juneau.commons.lang;
018
019import static org.apache.juneau.commons.utils.ThrowableUtils.*;
020
021import java.util.*;
022
023/**
024 * Stores a set of ASCII characters for quick lookup.
025 */
026public class AsciiSet {
027   /**
028    * Builder class.
029    */
030   public static class Builder {
031      final boolean[] store = new boolean[128];
032
033      /**
034       * Create a new {@link AsciiSet} object with the contents of this builder.
035       *
036       * @return A new {link AsciiSet} object.
037       */
038      public AsciiSet build() {
039         return new AsciiSet(store);
040      }
041
042      /**
043       * Adds a set of characters to this set.
044       *
045       * @param value The characters to keep in this store.
046       * @return This object.
047       */
048      public Builder chars(char...value) {
049         for (var i = 0; i < value.length; i++)
050            if (value[i] < 128)
051               store[value[i]] = true;
052         return this;
053      }
054
055      /**
056       * Adds a set of characters to this set.
057       *
058       * @param value The characters to keep in this store.
059       * @return This object.
060       */
061      public AsciiSet.Builder chars(String value) {
062         for (var i = 0; i < value.length(); i++) {
063            var c = value.charAt(i);
064            if (c < 128)
065               store[c] = true;
066         }
067         return this;
068      }
069
070      /**
071       * Adds a range of characters to this set.
072       *
073       * @param start The start character.
074       * @param end The end character.
075       * @return This object.
076       */
077      public AsciiSet.Builder range(char start, char end) {
078         for (var c = start; c <= end; c++)
079            if (c < 128)
080               store[c] = true;
081         return this;
082      }
083
084      /**
085       * Shortcut for calling multiple ranges.
086       *
087       * @param value Strings of the form "A-Z" where A and Z represent the first and last characters in the range.
088       * @return This object.
089       */
090      public AsciiSet.Builder ranges(String...value) {
091         for (var ss : value) {
092            if (ss.length() != 3 || ss.charAt(1) != '-')
093               throw illegalArg("Value passed to ranges() must be 3 characters");
094            range(ss.charAt(0), ss.charAt(2));
095         }
096         return this;
097      }
098   }
099
100   /**
101    * Creates a builder for an ASCII set.
102    *
103    * @return A new builder.
104    */
105   public static AsciiSet.Builder create() {
106      return new Builder();
107   }
108
109   /**
110    * Creates an ASCII set with the specified characters.
111    *
112    * @param value The characters to keep in this store.
113    * @return A new object.
114    */
115   public static AsciiSet of(String value) {
116      return new Builder().chars(value).build();
117   }
118
119   private final boolean[] store;
120
121   AsciiSet(boolean[] store) {
122      this.store = Arrays.copyOf(store, store.length);
123   }
124
125   /**
126    * Returns <jk>true</jk> if the specified character is in this store.
127    *
128    * @param value The character to check.
129    * @return <jk>true</jk> if the specified character is in this store.
130    */
131   public boolean contains(char value) {
132      if (value > 127)
133         return false;
134      return store[value];
135   }
136
137   /**
138    * Returns <jk>true</jk> if the specified string contains at least one character in this set.
139    *
140    * @param value The string to test.
141    * @return <jk>true</jk> if the string is not null and contains at least one character in this set.
142    */
143   public boolean contains(CharSequence value) {
144      if (value == null)
145         return false;
146      for (var i = 0; i < value.length(); i++)
147         if (contains(value.charAt(i)))
148            return true;
149      return false;
150   }
151
152   /**
153    * Returns <jk>true</jk> if the specified character is in this store.
154    *
155    * @param value The character to check.
156    * @return <jk>true</jk> if the specified character is in this store.
157    */
158   public boolean contains(int value) {
159      if (value < 0 || value > 127)
160         return false;
161      return store[value];
162   }
163
164   /**
165    * Returns <jk>true</jk> if the specified string contains only characters in this set.
166    *
167    * @param value The string to test.
168    * @return
169    *    <jk>true</jk> if the string contains only characters in this set.
170    *    <br>Nulls always return <jk>false</jk>.
171    *    <br>Blanks always return <jk>true</jk>.
172    */
173   public boolean containsOnly(String value) {
174      if (value == null)
175         return false;
176      for (var i = 0; i < value.length(); i++)
177         if (! contains(value.charAt(i)))
178            return false;
179      return true;
180   }
181
182   /**
183    * Copies an existing {@link AsciiSet} so that you can augment it with additional values.
184    *
185    * @return A builder initialized to the same characters in the copied set.
186    */
187   public AsciiSet.Builder copy() {
188      var b = new Builder();
189      System.arraycopy(store, 0, b.store, 0, 128);
190      return b;
191   }
192}