package org.basex.util;
import static org.basex.util.Token.*;
import java.util.*;
/**
* This class serves as an efficient constructor for {@link Token Tokens}.
* It bears some resemblance to Java's {@link StringBuilder}.
*
* @author BaseX Team 2005-17, BSD License
* @author Christian Gruen
*/
public final class TokenBuilder {
/** New line. */
public static final byte NLINE = 0x0A;
/** Unicode, private area (start). */
public static final int PRIVATE_START = 0xE000;
/** Unicode, private area (end). */
public static final int PRIVATE_END = 0xF8FF;
/** Half new line. */
public static final int HLINE = PRIVATE_END;
/** Bold flag. */
public static final int BOLD = PRIVATE_END - 1;
/** Standard flag. */
public static final int NORM = PRIVATE_END - 2;
/** Mark flag. */
public static final int MARK = PRIVATE_END - 3;
/** Underline flag. */
public static final int ULINE = PRIVATE_END - 4;
/** Byte array, storing all characters as UTF8. */
private byte[] chars;
/** Current token size. */
private int size;
/**
* Empty constructor.
*/
public TokenBuilder() {
this(Array.CAPACITY);
}
/**
* Constructor, specifying an initial internal array size.
* @param capacity initial array capacity
*/
public TokenBuilder(final int capacity) {
chars = new byte[capacity];
}
/**
* Constructor, specifying an initial string.
* @param string initial string
*/
public TokenBuilder(final String string) {
this(Token.token(string));
}
/**
* Constructor, specifying an initial token.
* @param token initial token
*/
public TokenBuilder(final byte[] token) {
this(token.length + Array.CAPACITY);
size = token.length;
System.arraycopy(token, 0, chars, 0, size);
}
/**
* Constructor, specifying initial codepoints.
* @param cps initial codepoints
*/
public TokenBuilder(final int[] cps) {
this(cps.length);
add(cps);
}
/**
* Returns the number of bytes.
* @return number of bytes
*/
public int size() {
return size;
}
/**
* Sets the number of bytes. Note that no bound check is performed by this method.
* @param sz number of bytes
*/
public void size(final int sz) {
size = sz;
}
/**
* Tests if the token is empty.
* @return result of check
*/
public boolean isEmpty() {
return size == 0;
}
/**
* Resets the token buffer.
* @return self reference
*/
public TokenBuilder reset() {
size = 0;
return this;
}
/**
* Adds a bold flag. This method should only be called to control text
* rendering in the visual front end.
* @return self reference
*/
public TokenBuilder bold() {
return add(BOLD);
}
/**
* Adds an underline toggle flag. This method should only be called to control text
* rendering in the visual front end.
* @return self reference
*/
public TokenBuilder uline() {
return add(ULINE);
}
/**
* Adds a norm flag. This method should only be called to control text
* rendering in the visual front end.
* @return self reference
*/
public TokenBuilder norm() {
return add(NORM);
}
/**
* Adds a half new line. This method should only be called to control text
* rendering in the visual front end.
* @return self reference
*/
public TokenBuilder hline() {
return add(HLINE);
}
/**
* Adds a new line. This method should only be called to control text
* rendering in the visual front end.
* @return self reference
*/
public TokenBuilder nline() {
return add(NLINE);
}
/**
* Adds the specified UTF8 codepoints.
* @param cps the codepoints to be added
* @return self reference
*/
public TokenBuilder add(final int[] cps) {
for(final int cp : cps) add(cp);
return this;
}
/**
* Adds the specified UTF8 codepoint.
* @param cp the codepoint to be added
* @return self reference
*/
public TokenBuilder add(final int cp) {
if(cp <= 0x7F) {
addByte((byte) cp);
} else if(cp <= 0x7FF) {
addByte((byte) (cp >> 6 & 0x1F | 0xC0));
addByte((byte) (cp & 0x3F | 0x80));
} else if(cp <= 0xFFFF) {
addByte((byte) (cp >> 12 & 0x0F | 0xE0));
addByte((byte) (cp >> 6 & 0x3F | 0x80));
addByte((byte) (cp & 0x3F | 0x80));
} else {
addByte((byte) (cp >> 18 & 0x07 | 0xF0));
addByte((byte) (cp >> 12 & 0x3F | 0x80));
addByte((byte) (cp >> 6 & 0x3F | 0x80));
addByte((byte) (cp & 0x3F | 0x80));
}
return this;
}
/**
* Returns the codepoint stored at the specified position.
* @param pos position
* @return character
*/
public int cp(final int pos) {
return Token.cp(chars, pos);
}
/**
* Returns the length of the codepoints stored at the specified position.
* @param pos position
* @return character
*/
public int cl(final int pos) {
return Token.cl(chars, pos);
}
/**
* Returns the byte stored at the specified position.
* @param pos position
* @return byte
*/
public byte get(final int pos) {
return chars[pos];
}
/**
* Sets a byte at the specified position.
* @param value byte to be set
* @param pos position
*/
public void set(final int pos, final byte value) {
chars[pos] = value;
}
/**
* Deletes bytes from the token.
* @param pos position
* @param length number of bytes to be removed
*/
public void delete(final int pos, final int length) {
Array.move(chars, pos + length, -length, size - pos - length);
size -= length;
}
/**
* Adds a single byte to the token.
* @param value the byte to be added
* @return self reference
*/
public TokenBuilder addByte(final byte value) {
byte[] chrs = chars;
final int s = size;
if(s == chrs.length) chrs = Arrays.copyOf(chrs, Array.newSize(s));
chrs[s] = value;
chars = chrs;
size = s + 1;
return this;
}
/**
* Adds an integer value to the token.
* @param value value to be added
* @return self reference
*/
public TokenBuilder addInt(final int value) {
return add(Token.token(value));
}
/**
* Adds a number to the token.
* @param value value to be added
* @return self reference
*/
public TokenBuilder addLong(final long value) {
return add(Token.token(value));
}
/**
* Adds a byte array to the token.
* @param value the byte array to be added
* @return self reference
*/
public TokenBuilder add(final byte[] value) {
return add(value, 0, value.length);
}
/**
* Adds part of a byte array to the token.
* @param value the byte array to be added
* @param start start position
* @param end end position
* @return self reference
*/
public TokenBuilder add(final byte[] value, final int start, final int end) {
byte[] chrs = chars;
final int cl = chrs.length, l = end - start, s = size, ns = s + l;
if(ns > cl) chrs = Arrays.copyOf(chrs, Array.newSize(ns));
System.arraycopy(value, start, chrs, s, l);
chars = chrs;
size = ns;
return this;
}
/**
* Adds a string to the token.
* @param string the string to be added
* @return self reference
*/
public TokenBuilder add(final String string) {
return add(Token.token(string));
}
/**
* Adds multiple strings to the token, separated by the specified string.
* @param objects the object to be added
* @param sep separator string
* @return self reference
*/
public TokenBuilder addSep(final Object[] objects, final String sep) {
final int ol = objects.length;
for(int o = 0; o < ol; o++) {
if(o != 0) add(sep);
addExt(objects[o]);
}
return this;
}
/**
* Adds the string representation of an object.
* The specified string may contain {@code "%"} characters as place holders.
* All place holders will be replaced by the specified extensions. If a digit is
* specified after the place holder character, it will be interpreted as insertion
* position.
*
* @param object object to be extended
* @param ext optional extensions
* @return self reference
*/
public TokenBuilder addExt(final Object object, final Object... ext) {
final byte[] t = token(object);
final int tl = t.length, el = ext.length;
for(int i = 0, e = 0; i < tl; ++i) {
if(t[i] != '%' || e == el) {
addByte(t[i]);
} else {
final byte c = i + 1 < tl ? t[i + 1] : 0;
final boolean d = c >= '1' && c <= '9';
if(d) ++i;
final int n = d ? c - '1' : e++;
final Object o = n < el ? ext[n] : null;
addExt(o);
}
}
return this;
}
/**
* Returns a token representation of the specified object.
* <ul>
* <li> byte arrays are returns as is.</li>
* <li> {@code null} references are replaced by the string "{@code null}".</li>
* <li> objects of type {@link Throwable} are converted to a string representation via
* {@link Util#message}.</li>
* <li> objects of type {@link Class} are converted via {@link Util#className(Class)}.</li>
* <li> for all other typer, {@link Object#toString} is called.</li>
* </ul>
* @param object object
* @return token
*/
public static byte[] token(final Object object) {
if(object instanceof byte[]) return (byte[]) object;
final String s;
if(object == null) {
s = "null";
} else if(object instanceof Throwable) {
s = Util.message((Throwable) object);
} else if(object instanceof Class<?>) {
s = Util.className((Class<?>) object);
} else {
s = object.toString();
}
return Token.token(s);
}
/**
* Trims leading and trailing whitespaces.
* @return self reference
*/
public TokenBuilder trim() {
final byte[] chrs = chars;
int s = size;
while(s > 0 && ws(chrs[s - 1])) --s;
int c = -1;
while(++c < s && ws(chrs[c]));
if(c != 0 && c != s) Array.move(chrs, c, -c, s - c);
size = s - c;
return this;
}
/**
* Returns the token as byte array.
* @return token
*/
public byte[] toArray() {
final int s = size;
return s == 0 ? EMPTY : Arrays.copyOf(chars, s);
}
/**
* Returns the token as byte array and resets the token buffer.
* The call of this function is identical to calling {@link #toArray} and {@link #reset}.
* @return token
*/
public byte[] next() {
final int s = size;
if(s == 0) return EMPTY;
size = 0;
return Arrays.copyOf(chars, s);
}
/**
* Returns the token as byte array, and invalidates the internal array.
* Warning: the function must only be called if the builder is discarded afterwards.
* @return token
*/
public byte[] finish() {
final byte[] chrs = chars;
chars = null;
final int s = size;
return s == 0 ? EMPTY : s == chrs.length ? chrs : Arrays.copyOf(chrs, s);
}
@Override
public String toString() {
return chars == null ? "" : string(chars, 0, size);
}
}