001/* 002 * Copyright (C) 2009-2011 Mathias Doenitz 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.parboiled.support; 018 019import org.parboiled.common.StringUtils; 020import org.parboiled.common.Preconditions; 021import org.parboiled.common.StringUtils; 022 023import java.util.Arrays; 024 025import static org.parboiled.common.Preconditions.checkArgNotNull; 026 027/** 028 * An immutable, set-like aggregation of (relatively few) characters that allows for an inverted semantic 029 * ("all chars except these few"). 030 */ 031public class Characters { 032 033 private static final char[] NO_CHARS = new char[0]; 034 035 /** 036 * The empty Characters set 037 */ 038 public static final Characters NONE = new Characters(false, NO_CHARS); 039 040 /** 041 * The Characters set including all character. 042 */ 043 public static final Characters ALL = new Characters(true, NO_CHARS); 044 045 // if the set is subtractive its semantics change from "includes all characters in the set" to 046 // "includes all characters not in the set" 047 private final boolean subtractive; 048 private final char[] chars; 049 050 private Characters(boolean subtractive, char[] chars) { 051 this.subtractive = subtractive; 052 this.chars = Preconditions.checkArgNotNull(chars, "chars").clone(); // wilbur: clone and then sort for fast lookup 053 Arrays.sort(this.chars); 054 } 055 056 /** 057 * @return true if the set is subtractive 058 */ 059 public boolean isSubtractive() { 060 return subtractive; 061 } 062 063 /** 064 * Returns the characters in this set, if it is additive. 065 * If the set is subtractive the method returns the characters <b>not</b> in the set. 066 * 067 * @return the characters 068 */ 069 public char[] getChars() { 070 return chars; 071 } 072 073 /** 074 * Adds the given character to the set. 075 * 076 * @param c the character to add 077 * @return a new Characters object 078 */ 079 public Characters add(char c) { 080 return subtractive ? removeFromChars(c) : addToChars(c); 081 } 082 083 /** 084 * Removes the given character from the set. 085 * 086 * @param c the character to remove 087 * @return a new Characters object 088 */ 089 public Characters remove(char c) { 090 return subtractive ? addToChars(c) : removeFromChars(c); 091 } 092 093 /** 094 * Determines whether this instance contains the given character. 095 * 096 * @param c the character to check for 097 * @return true if this instance contains c 098 */ 099 public boolean contains(char c) { 100 // return indexOf(chars, c) == -1 ? subtractive : !subtractive; 101 return (Arrays.binarySearch(chars, c) < 0) ? subtractive : !subtractive; // wilbur: faster lookup 102 } 103 104 /** 105 * Returns a new Characters object containing all the characters of this instance plus all characters of the 106 * given instance. 107 * 108 * @param other the other Characters to add 109 * @return a new Characters object 110 */ 111 public Characters add(Characters other) { 112 Preconditions.checkArgNotNull(other, "other"); 113 if (!subtractive && !other.subtractive) { 114 return addToChars(other.chars); 115 } 116 if (subtractive && other.subtractive) { 117 return retainAllChars(other.chars); 118 } 119 return subtractive ? removeFromChars(other.chars) : other.removeFromChars(chars); 120 } 121 122 /** 123 * Returns a new Characters object containing all the characters of this instance minus all characters of the 124 * given instance. 125 * 126 * @param other the other Characters to remove 127 * @return a new Characters object 128 */ 129 public Characters remove(Characters other) { 130 Preconditions.checkArgNotNull(other, "other"); 131 if (!subtractive && !other.subtractive) { 132 return removeFromChars(other.chars); 133 } 134 if (subtractive && other.subtractive) { 135 return new Characters(false, other.removeFromChars(chars).chars); 136 } 137 return subtractive ? addToChars(other.chars) : retainAllChars(other.chars); 138 } 139 140 @Override 141 public String toString() { 142 StringBuilder sb = new StringBuilder(); 143 sb.append(subtractive ? "![" : "["); 144 for (char c : chars) { 145 sb.append(StringUtils.escape(c)); 146 } 147 sb.append(']'); 148 return sb.toString(); 149 } 150 151 @Override 152 public boolean equals(Object o) { 153 if (this == o) return true; 154 if (!(o instanceof Characters)) return false; 155 Characters that = (Characters) o; 156 return subtractive == that.subtractive && equivalent(chars, that.chars); 157 } 158 159 @Override 160 public int hashCode() { 161 int result = (subtractive ? 1 : 0); 162 result = 31 * result + Arrays.hashCode(chars); 163 return result; 164 } 165 166 private Characters addToChars(char[] chs) { 167 Characters characters = this; 168 for (char c : chs) { 169 characters = characters.addToChars(c); 170 } 171 return characters; 172 } 173 174 private Characters addToChars(char c) { 175 if (indexOf(chars, c) != -1) return this; 176 char[] newChars = new char[chars.length + 1]; 177 System.arraycopy(chars, 0, newChars, 0, chars.length); 178 newChars[chars.length] = c; 179 return new Characters(subtractive, newChars); 180 } 181 182 private Characters removeFromChars(char[] chs) { 183 Characters characters = this; 184 for (char c : chs) { 185 characters = characters.removeFromChars(c); 186 } 187 return characters; 188 } 189 190 private Characters removeFromChars(char c) { 191 int ix = indexOf(chars, c); 192 if (ix == -1) return this; 193 if (chars.length == 1) return subtractive ? Characters.ALL : Characters.NONE; 194 char[] newChars = new char[chars.length - 1]; 195 System.arraycopy(chars, 0, newChars, 0, ix); 196 System.arraycopy(chars, ix + 1, newChars, ix, chars.length - ix - 1); 197 return new Characters(subtractive, newChars); 198 } 199 200 private Characters retainAllChars(char[] chs) { 201 Characters characters = this; 202 for (char c : chars) { 203 if (indexOf(chs, c) == -1) { 204 characters = characters.removeFromChars(c); 205 } 206 } 207 return characters; 208 } 209 210 private static int indexOf(char[] chars, char c) { 211 for (int i = 0; i < chars.length; i++) { 212 if (chars[i] == c) return i; 213 } 214 return -1; 215 } 216 217 // order independent Array.equals() 218 private static boolean equivalent(char[] a, char[] b) { 219 Preconditions.checkArgNotNull(a, "a"); 220 Preconditions.checkArgNotNull(b, "b"); 221 if (a == b) return true; 222 int length = a.length; 223 if (b.length != length) return false; 224 225 outer: 226 for (int i = 0; i < length; i++) { 227 char ac = a[i]; 228 for (int j = 0; j < length; j++) { 229 if (ac == b[j]) continue outer; 230 } 231 return false; 232 } 233 return true; 234 } 235 236 /** 237 * Creates a new Characters instance containing only the given char. 238 * 239 * @param c the char 240 * @return a new Characters object 241 */ 242 public static Characters of(char c) { 243 return new Characters(false, new char[] {c}); 244 } 245 246 /** 247 * Creates a new Characters instance containing only the given chars. 248 * 249 * @param chars the chars 250 * @return a new Characters object 251 */ 252 public static Characters of(char... chars) { 253 return chars.length == 0 ? Characters.NONE : new Characters(false, chars.clone()); 254 } 255 256 /** 257 * Creates a new Characters instance containing only the given chars. 258 * 259 * @param chars the chars 260 * @return a new Characters object 261 */ 262 public static Characters of(String chars) { 263 return StringUtils.isEmpty(chars) ? Characters.NONE : new Characters(false, chars.toCharArray()); 264 } 265 266 /** 267 * Creates a new Characters instance containing all characters minus the given one. 268 * 269 * @param c the char to NOT include 270 * @return a new Characters object 271 */ 272 public static Characters allBut(char c) { 273 return new Characters(true, new char[] {c}); 274 } 275 276 /** 277 * Creates a new Characters instance containing all characters minus the given ones. 278 * 279 * @param chars the chars to NOT include 280 * @return a new Characters object 281 */ 282 public static Characters allBut(char... chars) { 283 return chars.length == 0 ? Characters.ALL : new Characters(true, chars.clone()); 284 } 285 286 /** 287 * Creates a new Characters instance containing all characters minus the given ones. 288 * 289 * @param chars the chars to NOT include 290 * @return a new Characters object 291 */ 292 public static Characters allBut(String chars) { 293 return StringUtils.isEmpty(chars) ? Characters.ALL : new Characters(true, chars.toCharArray()); 294 } 295 296}