
org.parboiled.support.Characters Maven / Gradle / Ivy
/*
* Copyright (C) 2009-2011 Mathias Doenitz
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.parboiled.support;
import com.google.common.base.Preconditions;
import java.util.Arrays;
/**
* An immutable, set-like aggregation of (relatively few) characters that allows
* for an inverted semantic ("all chars except these few").
*/
// TODO: replace with a sorted array
public final class Characters
{
private static final char[] NO_CHARS = new char[0];
/**
* The empty Characters set
*/
public static final Characters NONE = new Characters(false, NO_CHARS);
/**
* The Characters set including all character.
*/
public static final Characters ALL = new Characters(true, NO_CHARS);
// if the set is subtractive its semantics change from "includes all
// characters in the set" to "includes all characters not in the set"
private final boolean subtractive;
private final char[] chars;
private Characters(final boolean subtractive, final char[] chars)
{
this.subtractive = subtractive;
this.chars = Preconditions.checkNotNull(chars, "chars");
}
/**
* @return true if the set is subtractive
*/
public boolean isSubtractive()
{
return subtractive;
}
/**
* Returns the characters in this set, if it is additive.
* If the set is subtractive the method returns the characters not in the set.
*
* @return the characters
*/
public char[] getChars()
{
return chars;
}
/**
* Adds the given character to the set.
*
* @param c the character to add
* @return a new Characters object
*/
public Characters add(final char c)
{
return subtractive ? removeFromChars(c) : addToChars(c);
}
/**
* Removes the given character from the set.
*
* @param c the character to remove
* @return a new Characters object
*/
public Characters remove(final char c)
{
return subtractive ? addToChars(c) : removeFromChars(c);
}
/**
* Determines whether this instance contains the given character.
*
* @param c the character to check for
* @return true if this instance contains c
*/
public boolean contains(final char c)
{
return indexOf(chars, c) == -1 ? subtractive : !subtractive;
}
/**
* Returns a new Characters object containing all the characters of this instance plus all characters of the
* given instance.
*
* @param other the other Characters to add
* @return a new Characters object
*/
public Characters add(final Characters other)
{
Preconditions.checkNotNull(other, "other");
if (!subtractive && !other.subtractive) {
return addToChars(other.chars);
}
if (subtractive && other.subtractive) {
return retainAllChars(other.chars);
}
return subtractive ? removeFromChars(other.chars)
: other.removeFromChars(chars);
}
/**
* Returns a new Characters object containing all the characters of this instance minus all characters of the
* given instance.
*
* @param other the other Characters to remove
* @return a new Characters object
*/
public Characters remove(final Characters other)
{
Preconditions.checkNotNull(other, "other");
if (!subtractive && !other.subtractive) {
return removeFromChars(other.chars);
}
if (subtractive && other.subtractive) {
return new Characters(false, other.removeFromChars(chars).chars);
}
return subtractive ? addToChars(other.chars)
: retainAllChars(other.chars);
}
@Override
public String toString()
{
final StringBuilder sb = new StringBuilder();
sb.append(subtractive ? "![" : "[");
for (final char c : chars) {
sb.append(Chars.escape(c));
}
sb.append(']');
return sb.toString();
}
@Override
public boolean equals(final Object o)
{
if (this == o)
return true;
if (!(o instanceof Characters))
return false;
final Characters that = (Characters) o;
return subtractive == that.subtractive && equivalent(chars, that.chars);
}
@Override
public int hashCode()
{
return Arrays.hashCode(chars) + (subtractive ? 31 : 0);
}
private Characters addToChars(final char[] chs)
{
Characters characters = this;
for (final char c : chs) {
characters = characters.addToChars(c);
}
return characters;
}
private Characters addToChars(final char c)
{
if (indexOf(chars, c) != -1)
return this;
final char[] newChars = new char[chars.length + 1];
System.arraycopy(chars, 0, newChars, 0, chars.length);
newChars[chars.length] = c;
return new Characters(subtractive, newChars);
}
private Characters removeFromChars(final char[] chs)
{
Characters characters = this;
for (final char c : chs) {
characters = characters.removeFromChars(c);
}
return characters;
}
private Characters removeFromChars(final char c)
{
final int ix = indexOf(chars, c);
if (ix == -1)
return this;
if (chars.length == 1)
return subtractive ? Characters.ALL : Characters.NONE;
final char[] newChars = new char[chars.length - 1];
System.arraycopy(chars, 0, newChars, 0, ix);
System.arraycopy(chars, ix + 1, newChars, ix, chars.length - ix - 1);
return new Characters(subtractive, newChars);
}
private Characters retainAllChars(final char[] chs)
{
Characters characters = this;
for (final char c : chars) {
if (indexOf(chs, c) == -1) {
characters = characters.removeFromChars(c);
}
}
return characters;
}
private static int indexOf(final char[] chars, final char c)
{
for (int i = 0; i < chars.length; i++) {
if (chars[i] == c)
return i;
}
return -1;
}
// order independent Array.equals()
private static boolean equivalent(final char[] a, final char[] b)
{
Preconditions.checkNotNull(a, "a");
Preconditions.checkNotNull(b, "b");
if (a == b)
return true;
final int length = a.length;
if (b.length != length)
return false;
outer:
for (final char ac: a) {
for (int j = 0; j < length; j++) {
if (ac == b[j]) {
continue outer;
}
}
return false;
}
return true;
}
/**
* Creates a new Characters instance containing only the given char.
*
* @param c the char
* @return a new Characters object
*/
public static Characters of(final char c)
{
return new Characters(false, new char[]{ c });
}
/**
* Creates a new Characters instance containing only the given chars.
*
* @param chars the chars
* @return a new Characters object
*/
public static Characters of(final char... chars)
{
return chars.length == 0 ? Characters.NONE
: new Characters(false, chars.clone());
}
/**
* Creates a new Characters instance containing only the given chars.
*
* @param chars the chars
* @return a new Characters object
*/
public static Characters of(final String chars)
{
return chars.isEmpty() ? Characters.NONE
: new Characters(false, chars.toCharArray());
}
/**
* Creates a new Characters instance containing all characters minus the given one.
*
* @param c the char to NOT include
* @return a new Characters object
*/
public static Characters allBut(final char c)
{
return new Characters(true, new char[]{ c });
}
/**
* Creates a new Characters instance containing all characters minus the given ones.
*
* @param chars the chars to NOT include
* @return a new Characters object
*/
public static Characters allBut(final char... chars)
{
return chars.length == 0 ? Characters.ALL
: new Characters(true, chars.clone());
}
/**
* Creates a new Characters instance containing all characters minus the given ones.
*
* @param chars the chars to NOT include
* @return a new Characters object
*/
public static Characters allBut(final String chars)
{
return chars.isEmpty() ? Characters.ALL
: new Characters(true, chars.toCharArray());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy