package org.basex.util; import static org.basex.util.Token.*; import java.util.Arrays; import org.basex.util.list.ByteList; import org.basex.util.list.ElementList; /** * This class serves as an efficient constructor for byte arrays. * It bears some resemblance to Java's {@link StringBuilder}. * * @author BaseX Team 2005-12, BSD License * @author Christian Gruen */ public final class TokenBuilder { /** Half new line. */ public static final byte HLINE = 0x01; /** Bold flag. */ private static final byte BOLD = 0x02; /** Standard flag. */ private static final byte NORM = 0x03; /** Mark flag. */ public static final byte MARK = 0x04; /** New line. */ public static final byte NLINE = 0x0a; /** Character array. */ private byte[] chars; /** Current token size. */ private int size; /** * Empty constructor. */ public TokenBuilder() { this(ElementList.CAP); } /** * Constructor, specifying an initial array size. * @param i size */ public TokenBuilder(final int i) { chars = new byte[i]; } /** * Constructor, specifying an initial string. * @param str initial string */ public TokenBuilder(final String str) { this(token(str)); } /** * Constructor, specifying an initial array. * @param str initial string */ public TokenBuilder(final byte[] str) { chars = str; size = str.length; } /** * Returns the number of entries. * @return number of entries */ public int size() { return size; } /** * Sets the number of entries. * @param s number of entries */ public void size(final int s) { size = s; } /** * 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 addByte(BOLD); } /** * 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 addByte(NORM); } /** * 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 addByte(NLINE); } /** * 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 addByte(HLINE); } /** * Adds the specified UTF8 character. * @param ch the character to be added * @return self reference */ public TokenBuilder add(final int ch) { if(ch <= 0x7F) { addByte((byte) ch); } else if(ch <= 0x7FF) { addByte((byte) (ch >> 6 & 0x1F | 0xC0)); addByte((byte) (ch & 0x3F | 0x80)); } else if(ch <= 0xFFFF) { addByte((byte) (ch >> 12 & 0x0F | 0xE0)); addByte((byte) (ch >> 6 & 0x3F | 0x80)); addByte((byte) (ch & 0x3F | 0x80)); } else { addByte((byte) (ch >> 18 & 0x07 | 0xF0)); addByte((byte) (ch >> 12 & 0x3F | 0x80)); addByte((byte) (ch >> 6 & 0x3F | 0x80)); addByte((byte) (ch & 0x3F | 0x80)); } return this; } /** * Inserts the specified UTF8 character. * @param pos insertion index * @param ch the character to be added * @return self reference */ public TokenBuilder insert(final int pos, final int ch) { final int s = size; final int cl = chars.length; final int l = ch <= 0x7F ? 1 : ch <= 0x7FF ? 2 : ch <= 0xFFF ? 3 : 4; if(s + l > cl) { final int ns = Math.max(s + l, (int) (cl * Array.RESIZE)); chars = Arrays.copyOf(chars, ns); } Array.move(chars, pos, l, size - pos); size = pos; add(ch); size = s + l; return this; } /** * Returns the codepoint at the specified position. * @param p position * @return character */ public int cp(final int p) { return Token.cp(chars, p); } /** * Returns the codepoint length of the specified byte. * @param p position * @return character */ public int cl(final int p) { return Token.cl(chars, p); } /** * Returns the byte at the specified position. * @param p position * @return byte */ public byte get(final int p) { return chars[p]; } /** * Sets a byte at the specified position. * @param b byte to be set * @param p position */ public void set(final byte b, final int p) { chars[p] = b; } /** * Deletes bytes from the token. * @param p position * @param s number of bytes to be removed */ public void delete(final int p, final int s) { Array.move(chars, p + s, -s, size - p - s); size -= s; } /** * Adds a byte to the token. {@link ByteList} instances should be preferred * for the construction of pure byte arrays. * @param b the byte to be added * @return self reference */ public TokenBuilder addByte(final byte b) { if(size == chars.length) chars = Arrays.copyOf(chars, Array.newSize(size)); chars[size++] = b; return this; } /** * Adds a number to the token. * @param i the integer to be added * @return self reference */ public TokenBuilder addLong(final long i) { return add(token(i)); } /** * Adds a byte array to the token. * @param b the character array to be added * @return self reference */ public TokenBuilder add(final byte[] b) { return add(b, 0, b.length); } /** * Adds a partial byte array to the token. * @param b the character array to be added * @param s start position * @param e end position * @return self reference */ public TokenBuilder add(final byte[] b, final int s, final int e) { final int l = e - s; final int cl = chars.length; if(size + l > cl) { final int ns = Math.max(size + l, (int) (cl * Array.RESIZE)); chars = Arrays.copyOf(chars, ns); } System.arraycopy(b, s, chars, size, l); size += l; return this; } /** * Adds a string to the token. * @param s the string to be added * @return self reference */ public TokenBuilder add(final String s) { return add(token(s)); } /** * Adds multiple strings to the token, separated by the specified string. * @param s the string to be added * @param sep separator * @return self reference */ public TokenBuilder addSep(final Object[] s, final String sep) { for(int e = 0; e != s.length; ++e) { if(e != 0) add(sep); addExt(s[e]); } return this; } /** * Adds the string representation of an object to the token: * <ul> * <li> If extensions are specified, all characters {@code "%"} in the object * string are replaced by the specified extensions.</li> * <li> Extensions can either be tokens (byte arrays) or any other objects, * which will be represented as strings.</li> * <li> If a digit is found after the character {@code "%"}, it is interpreted * as insertion position.</li> * </ul> * @param str string to be extended * @param ext optional extensions * @return self reference */ public TokenBuilder addExt(final Object str, final Object... ext) { final byte[] t = str instanceof byte[] ? (byte[]) str : token(str == null ? "null" : str.toString()); for(int i = 0, e = 0; i < t.length; ++i) { if(t[i] != '%' || e == ext.length) { addByte(t[i]); } else { final byte c = i + 1 < t.length ? 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 < ext.length ? ext[n] : null; addExt(o instanceof byte[] ? (byte[]) o : o == null ? null : o.toString()); } } return this; } /** * Trims leading and trailing whitespaces. * @return self reference */ public TokenBuilder trim() { while(size > 0 && ws(chars[size - 1])) --size; int s = -1; while(++s < size && ws(chars[s])); if(s != 0 && s != size) Array.move(chars, s, -s, size - s); size -= s; return this; } /** * Returns the token as byte array. * @return character array */ public byte[] finish() { return Arrays.copyOf(chars, size); } @Override public String toString() { return string(chars, 0, size); } }