package org.kefirsf.bb.proc;
import org.kefirsf.bb.util.ArrayCharSequence;
import java.util.Arrays;
import java.util.Set;
import java.util.TreeSet;
/**
* Класс источник для парсинга BB-кодов
*
* @author Kefir
*/
public class Source implements CharSequence {
/**
* Source text content
*/
private final char[] text;
/**
* Source text length
*/
private final int textLength;
/**
* Current offset
*/
private int offset = 0;
/**
* Set of constants. All constants in configuration.
*/
private Set<PatternConstant> constantSet;
/**
* First chars of constants of configuration. It's needed for fast check constants on current position.
*/
private char[] constantChars;
/**
* Check if the current sub sequence can be a constant.
* @return true if next sub sequence may be a constant.
*/
public boolean nextMayBeConstant() {
return Arrays.binarySearch(constantChars, text[offset]) >= 0;
}
/**
* Create source class.
*
* @param text source text.
*/
public Source(CharSequence text) {
textLength = text.length();
this.text = new char[textLength];
if (text instanceof String) {
((String) text).getChars(0, textLength, this.text, 0);
} else if (text instanceof StringBuilder) {
((StringBuilder) text).getChars(0, textLength, this.text, 0);
} else if (text instanceof StringBuffer) {
((StringBuffer) text).getChars(0, textLength, this.text, 0);
} else {
text.toString().getChars(0, textLength, this.text, 0);
}
}
/**
* Set constant set from configuration. For fast search.
* @param constantSet set of constants in the source
*/
public void setConstantSet(Set<PatternConstant> constantSet) {
this.constantSet = constantSet;
this.constantChars = getConstantChars();
}
/**
* Collect first chars of constants of configuration.
*
* @return array of first chars of constants.
*/
private char[] getConstantChars() {
Set<Character> chars = new TreeSet<Character>();
chars.add('\n');
chars.add('\r');
for (PatternConstant constant : constantSet) {
char c = constant.getValue().charAt(0);
if (constant.isIgnoreCase()) {
chars.add(Character.toLowerCase(c));
chars.add(Character.toUpperCase(c));
} else {
chars.add(c);
}
}
char[] cs = new char[chars.size()];
int j = 0;
for (Character c : chars) {
cs[j] = c;
j++;
}
Arrays.sort(cs);
return cs;
}
/**
* Test id next sequence the constant?
*
* @param constant constant pattern element
* @return true if next sub sequence is constant.
*/
public boolean nextIs(PatternConstant constant) {
char[] cs = constant.getCharArray();
int length = cs.length;
if (length > textLength - offset) {
return false;
}
if (!constant.isIgnoreCase()) {
int i;
//noinspection StatementWithEmptyBody
for (i = 0; i < length && text[offset + i] == cs[i]; i++);
return i == length;
} else {
for (int i = 0; i < length; i++) {
char ct = text[offset + i];
char cv = cs[i];
if (
ct == cv ||
Character.toUpperCase(ct) == Character.toUpperCase(cv) ||
Character.toLowerCase(ct) == Character.toLowerCase(cv)
) {
continue;
}
return false;
}
return true;
}
}
/**
* Find constant in source text.
*
* @param constant constant pattern element
* @return index of constant of negative if don't find.
*/
public int find(PatternConstant constant) {
char[] cs = constant.getCharArray();
boolean ignoreCase = constant.isIgnoreCase();
return find(cs, ignoreCase);
}
public int find(char[] chars, boolean ignoreCase) {
return findFrom(offset, chars, ignoreCase);
}
public int findFrom(int index, char[] chars, boolean ignoreCase) {
int length = chars.length;
for (int i = index; i < textLength - length + 1; i++) {
boolean flag = true;
for (int j = 0; j < length && flag; j++) {
char ct = text[i + j];
char cv = chars[j];
flag = (
ct == cv ||
(
ignoreCase &&
(
Character.toUpperCase(ct) == Character.toUpperCase(cv) ||
Character.toLowerCase(ct) == Character.toLowerCase(cv)
)
)
);
}
if (flag) {
return i;
}
}
return -1;
}
/**
* Return next character and increment offset.
*
* @return character.
*/
public char next() {
char c = text[offset];
incOffset();
return c;
}
/**
* Return a character by index
*/
public char charAt(int index){
return text[index];
}
/**
* Return current offset.
*
* @return offset from begin.
*/
public int getOffset() {
return offset;
}
/**
* Increment offset.
*/
public void incOffset() {
offset++;
}
/**
* Increment offset.
*
* @param increment increment size.
*/
public void incOffset(int increment) {
offset += increment;
}
/**
* Set offset.
*
* @param offset new offset value.
*/
public void setOffset(int offset) {
this.offset = offset;
}
/**
* Есть ли еще что-то в строке?
*
* @return true - если есть
* false если достигнут конец строки
*/
public boolean hasNext() {
return offset < textLength;
}
/**
* Return length of source text
*
* @return length of source text
*/
public int length() {
return textLength;
}
/**
* Получает строку от текущего смещения до значения <code>end</code>
*
* @param end последний индекс
* @return подстрока
*/
public CharSequence sub(int end) {
return new ArrayCharSequence(text, offset, end - offset);
}
/**
* Get String from offset to end.
*
* @return char sequence
*/
public CharSequence subToEnd() {
return sub(textLength);
}
public String toString() {
return String.valueOf(textLength);
}
public CharSequence subSequence(int start, int end) {
return new ArrayCharSequence(text, start, end - start);
}
}