/*
* 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 com.github.fge.grappa.support;
import java.util.Arrays;
import java.util.Objects;
/**
* An immutable, set-like aggregation of (relatively few) characters that allows
* for an inverted semantic ("all chars except these few").
*/
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;
/**
* 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)
{
final int length = chars.length;
if (length == 0)
return NONE;
final char[] array = Arrays.copyOf(chars, length);
Arrays.sort(array);
return new Characters(false, array);
}
/**
* 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() ? NONE : of(chars.toCharArray());
}
/**
* 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)
{
final int length = chars.length;
if (length == 0)
return ALL;
final char[] array = Arrays.copyOf(chars, length);
Arrays.sort(array);
return new Characters(true, array);
}
private Characters(final boolean subtractive, final char[] chars)
{
this.subtractive = subtractive;
this.chars = Objects.requireNonNull(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 <b>not</b> in the set.
*
* @return the characters
*/
public char[] getChars()
{
return chars;
}
/**
* 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)
{
final int index = Arrays.binarySearch(chars, c);
return index < 0 == subtractive;
}
@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 obj)
{
if (this == obj)
return true;
if (!(obj instanceof Characters))
return false;
final Characters other = (Characters) obj;
return subtractive == other.subtractive
&& Arrays.equals(chars, other.chars);
}
@Override
public int hashCode()
{
return Arrays.hashCode(chars) + (subtractive ? 31 : 0);
}
}