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