All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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