/*
* Copyright 2015 Skynav, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY SKYNAV, INC. AND ITS CONTRIBUTORS “AS IS” AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL SKYNAV, INC. OR ITS CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.skynav.ttv.model.value;
import java.util.BitSet;
public class CharacterClass {
public static final CharacterClass EMPTY = new CharacterClass();
public static final char OPEN_DELIMITER = '[';
public static final char CLOSE_DELIMITER = ']';
public static final char RANGE_DELIMITER = '-';
public static final char ESCAPE_DELIMITER = '\\';
public static final char UNICODE_DELIMITER = 'u';
private BitSet members;
private CharacterClass() {
this.members = new BitSet();
}
public CharacterClass(CharacterClass c) {
this.members = new BitSet();
this.add(c);
}
public boolean isEmpty() {
return members.isEmpty();
}
public boolean inClass(int c) {
return members.get(c);
}
/**
* Add code point range [C1,C2] to members.
* @param c1 start code point
* @param c2 end code point (inclusive)
*/
public void add(int c1, int c2) {
if (this == EMPTY)
throw new IllegalArgumentException();
members.set(c2, c2 + 1);
}
/**
* Add members of class C to this character class.
* @param c class from which members are to be added
*/
public void add(CharacterClass c) {
if (this == EMPTY)
throw new IllegalArgumentException();
members.or(c.members);
}
@Override
public int hashCode() {
return members.hashCode();
}
@Override
public boolean equals(Object o) {
if (o instanceof CharacterClass) {
CharacterClass other = (CharacterClass) o;
return other.members.equals(members);
} else
return false;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(OPEN_DELIMITER);
for (int i = members.nextSetBit(0); i >= 0; i = members.nextSetBit(i + 1))
sb.append(toUnicodeEscape(i));
sb.append(CLOSE_DELIMITER);
return sb.toString();
}
static public CharacterClass parse(String s) {
CharacterClass cc = new CharacterClass();
int[] c = new int[2];
int i = 0, k = parseDelimiter(s, i, OPEN_DELIMITER);
if (k <= 0)
throw new IllegalArgumentException();
else
i = k;
while ((k = parseClassCharacter(s, i, c, 0)) > i) {
i = k;
k = parseDelimiter(s, i, RANGE_DELIMITER);
if (k < 0)
throw new IllegalArgumentException();
else if (k == i)
c[1] = c[0];
else {
i = k;
if ((k = parseClassCharacter(s, i, c, 1)) <= 0)
throw new IllegalArgumentException();
i = k;
}
cc.add(c[0], c[1]);
}
if (parseDelimiter(s, i, CLOSE_DELIMITER) <= 0)
throw new IllegalArgumentException();
return cc.isEmpty() ? EMPTY : cc;
}
static private int parseClassCharacter(String s, int i, int[] ca, int ci) {
int j = parseDelimiter(s, i, ESCAPE_DELIMITER);
if (j < 0)
return -1;
else if (j > i)
return parseEscape(s, j, ca, ci);
else
return parseNonSpecial(s, j, ca, ci);
}
static private int parseEscape(String s, int i, int[] ca, int ci) {
int j = parseDelimiter(s, i, UNICODE_DELIMITER);
if (j < 0)
return -1;
else if (j > i)
return parseUnicodeEscape(s, j, ca, ci);
else
return parseNonUnicodeEscape(s, j, ca, ci);
}
static private int parseUnicodeEscape(String s, int i, int[] ca, int ci) {
int n = countHexDigits(s, i);
if ((n == 4) || (n == 6))
return parseHexDigits(s, i, n, ca, ci);
else
return -1;
}
static private String toUnicodeEscape(int c) {
StringBuffer sb = new StringBuffer();
sb.append(ESCAPE_DELIMITER);
sb.append(ESCAPE_DELIMITER);
sb.append(UNICODE_DELIMITER);
sb.append(toHexDigits(c, c > 65535 ? 6 : 4, '0', true));
return sb.toString();
}
static private int countHexDigits(String s, int i) {
int d = 0;
for (int j = i, n = s.length(); j < n; ++j) {
int c = s.charAt(j);
if (isHexDigit(c))
++d;
else
break;
}
return d;
}
static private int parseHexDigits(String s, int i, int d, int[] ca, int ci) {
int j = i, v = 0;
for (int k = i + d, n = s.length(); j < k; ++j) {
if (j < n)
v = (v << 4) | hexDigitValue(s, j);
}
ca[ci] = v;
return j;
}
static private String toHexDigits(int c, int n, char p, boolean upper) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < n; ++i, c >>>= 4) {
int d = c & 0xF;
int b = (d < 10) ? '0' : ((upper ? 'A' : 'a') - 10);
int h = b + d;
sb.append((char) h);
}
for (int i = 0, m = n - sb.length(); (m > 0) && (i < m); ++i)
sb.append(p);
sb.reverse();
return sb.toString();
}
static private boolean isHexDigit(int c) {
if ((c >= '0') && (c <= '9'))
return true;
else if ((c >= 'a') && (c <= 'f'))
return true;
else if ((c >= 'A') && (c <= 'F'))
return true;
else
return false;
}
static private int hexDigitValue(String s, int i) {
int c = s.charAt(i);
if ((c >= '0') && (c <= '9'))
return c - '0';
else if ((c >= 'a') && (c <= 'f'))
return (c - 'a') + 10;
else if ((c >= 'A') && (c <= 'F'))
return (c - 'A') + 10;
else
throw new IllegalArgumentException();
}
static private int parseNonUnicodeEscape(String s, int i, int[] ca, int ci) {
int n = s.length();
if (i >= n)
return -1;
else {
int c = s.charAt(i);
ca[ci] = c;
return i + 1;
}
}
static private int parseNonSpecial(String s, int i, int[] ca, int ci) {
int n = s.length();
if (i >= n)
return -1;
else {
int c = s.charAt(i);
if (isSpecial(c))
return -1;
else {
ca[ci] = c;
return i + 1;
}
}
}
static private int parseDelimiter(String s, int i, int d) {
int n = s.length();
if (i >= n)
return -1;
else {
int c = s.charAt(i);
if (c == d)
return i + 1;
else
return i;
}
}
static private boolean isSpecial(int c) {
if (c == '-')
return true;
else if (c == ']')
return true;
else if (c == '\'')
return true;
else if (c == '\"')
return true;
else if (c == ' ')
return true;
else if (c == '\t')
return true;
else if (c == '\r')
return true;
else if (c == '\n')
return true;
else
return false;
}
}