/* * `gnu.iou' * Copyright (C) 2006 John Pritchard. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA */ package gnu.iou ; import java.io.DataInputStream; import java.io.IOException; import java.util.Enumeration; import java.util.NoSuchElementException; import java.util.StringTokenizer; /** * <p> Line oriented string buffer, or dynamic string array container. * Plus string utility methods. For an array of strings this is * faster than a Vector which is synchronized and generalized (not * String). * * <p> This uses character arrays internally because more intensive * use of the line buffer transforms Strings to character arrays and * back into String objects and then back into character arrays. And * in the very common <code>`toString()'</code> case a String will be * concatenated as character arrays, too. * * @author John Pritchard (john@syntelos.org) */ public class linebuf { public final static char[] default_line_separator = {'\r','\n'}; protected char[][] buf ; protected int p = 0; private int f ; private char[] output_line_sep = default_line_separator; private char[] output_prefix = null; /** * @param growthfactor Set internal buffer allocation unit length * (number of lines). */ public linebuf ( int growthfactor){ super(); if ( 0 < growthfactor){ this.f = growthfactor; buf = new char[f][]; } else throw new IllegalArgumentException("Growth factor must be greater than zero."); } /** * Default growth factor of ten. */ public linebuf(){ this(10); } /** * Append argument line. * * @param line One line to append. */ public linebuf ( String line){ this(10); append(line); } public linebuf ( Object obj){ this(10); append(obj); } /** * Append argument lines. * * @param lineset Lines to append. */ public linebuf ( String[] lineset){ this((null != lineset && 0 < lineset.length)?(lineset.length):(10)); append(lineset); } /** * Append argument lines. * * @param lineset Lines to append. * * @param line_sep Linebuf string element (line) separator. */ public linebuf ( String[] lineset, String line_sep){ this(null,lineset,line_sep); } /** * Append argument lines. * * @param prefix Output prefix. * * @param lineset Lines to append using * <code>Object.toString().</code> * * @param line_sep Linebuf string element (line) separator. */ public linebuf ( String prefix, Object[] lineset, String line_sep){ this((null != lineset && 0 < lineset.length)?(lineset.length):(10)); if ( null != lineset){ Object ob; int many = lineset.length; for ( int lc = 0; lc < many; lc++){ ob = lineset[lc]; if ( null != ob) append(ob); else println(); } } if ( null != line_sep) this.output_line_sep = line_sep.toCharArray(); if ( null != prefix) this.output_prefix = prefix.toCharArray(); } /** * Append argument lines. * * @param prefix Output prefix. * * @param lineset Lines to append. * * @param line_sep Linebuf string element (line) separator. */ public linebuf ( String prefix, String[] lineset, String line_sep){ this((null != lineset && 0 < lineset.length)?(lineset.length):(10)); append(lineset); if ( null != line_sep) this.output_line_sep = line_sep.toCharArray(); if ( null != prefix) this.output_prefix = prefix.toCharArray(); } /** * Append argument lines. * * @param prefix Output prefix. * * @param lineset Lines to append using * <code>Object.toString().</code> * * @param line_sep Linebuf string element (line) separator. */ public linebuf ( String prefix, Enumeration lineset, String line_sep){ this(10); if ( null != lineset){ Object ob; while (lineset.hasMoreElements()){ ob = lineset.nextElement(); if ( null != ob) append(ob); else println(); } } if ( null != line_sep) this.output_line_sep = line_sep.toCharArray(); if ( null != prefix) this.output_prefix = prefix.toCharArray(); } /** * @returns Index of last line used, or zero. */ public int index(){ if ( 0 < p) return p-1; else return 0; } /** * @param idx Line index in this buffer. */ public String index ( int idx){ if ( idx >= 0 && idx < buf.length) return new String(buf[idx]); else return null; } /** * @param idx Line index in this buffer. */ public char[] index_cary ( int idx){ if ( idx >= 0 && idx < buf.length) return buf[idx]; else return null; } /** * @param idx Line index in this buffer. * * @param val Line text */ public void index ( int idx, String val){ if ( idx >= 0 ){ if ( idx < buf.length) buf[idx] = detab(val); else { while ( idx >= buf.length){ char[][] copier = new char[buf.length+f][]; System.arraycopy(buf,0,copier,0,buf.length); buf = copier; } buf[idx] = detab(val); } if ( idx >= p) p = idx+1; } else throw new ArrayIndexOutOfBoundsException("Index `"+idx+"' is invalid."); } /** * @param idx Line index in this buffer. * * @param val Line text */ public void index ( int idx, char[] val){ if ( idx >= 0 ){ if ( idx < buf.length) buf[idx] = val; else { while ( idx >= buf.length){ char[][] copier = new char[buf.length+f][]; System.arraycopy(buf,0,copier,0,buf.length); buf = copier; } buf[idx] = val; } if ( idx >= p) p = idx+1; } else throw new ArrayIndexOutOfBoundsException("Index `"+idx+"' is invalid."); } /** * @param idx Line index in this buffer. * * @param val Line text to prepend to any current text in line */ public void index_prepend ( int idx, String val){ if ( idx >= 0 ){ while ( idx >= buf.length){ char[][] copier = new char[buf.length+f][]; System.arraycopy(buf,0,copier,0,buf.length); buf = copier; } if ( null != buf[idx]) buf[idx] = chbuf.cat(detab(val),buf[idx]); else buf[idx] = detab(val); if ( idx >= p) p = idx+1; } else throw new ArrayIndexOutOfBoundsException("Index `"+idx+"' is invalid."); } /** * @param idx Line index in this buffer. * * @param val Line text */ public void index_append ( int idx, String val){ index_append(idx,detab(val)); } /** * @param idx Line index in this buffer. * * @param val Line text */ public void index_append ( int idx, char[] val){ if ( idx >= 0){ while ( idx >= buf.length){ char[][] copier = new char[buf.length+f][]; System.arraycopy(buf,0,copier,0,buf.length); buf = copier; } if ( null != buf[idx]) buf[idx] = chbuf.cat(buf[idx],val); else buf[idx] = val; if ( idx >= p) p = idx+1; } else throw new ArrayIndexOutOfBoundsException("Index `"+idx+"' is invalid."); } /** * <p> Not as fast as chbuf for many chars.</p> * @param idx Line index in this buffer. * @param ch Line character */ public void index_append ( int idx, char ch){ if ( idx >= 0){ while ( idx >= buf.length){ char[][] copier = new char[buf.length+f][]; System.arraycopy(buf,0,copier,0,buf.length); buf = copier; } if ( null != buf[idx]){ char[] bul = buf[idx]; int blen = bul.length; char[] copier = new char[blen+1]; System.arraycopy(bul,0,copier,0,blen); copier[blen] = ch; buf[idx] = copier; } else buf[idx] = new char[]{ch}; if ( idx >= p) p = idx+1; } else throw new ArrayIndexOutOfBoundsException("Index `"+idx+"' is invalid."); } public void line_append ( String s){ index_append( p-1, s.toCharArray()); } /** * Use the first <code>`colw'</code> characters of * <code>`col'</code> to overwrite the <code>"[col0,colw)"</code> * segment of the line. * * @param idx Line index in this buffer. * * @param col Column text * * @param col0 Column specification first column index * * @param colw Column specification width * */ public void index_column_overwrite ( int idx, String col, int col0, int colw){ index_column_overwrite(idx,detab(col),col0,colw); } /** * Use the first <code>`colw'</code> characters of * <code>`col'</code> to overwrite the <code>"[col0,colw)"</code> * segment of the line. * * @param idx Line index in this buffer. * * @param col Column text * * @param col0 Column specification first column index * * @param colw Column specification width * */ public void index_column_overwrite ( int idx, char[] col, int col0, int colw){ if ( null == col) return; else { // Setup destination char[] tcary = index_cary(idx); int linlen = col0+1+colw; if ( null != tcary){ if ( tcary.length < linlen){ char[] copier = new char[linlen]; for ( int cc = tcary.length; cc < linlen; cc++) copier [cc] = ' '; System.arraycopy(tcary,0,copier,0,tcary.length); tcary = copier; } } else { tcary = new char[linlen]; for ( int cc = 0; cc < col0; cc++) tcary [cc] = ' '; } // Write column from `col' if ( col.length < colw) colw = col.length; System.arraycopy(col,0,tcary,col0,colw); index( idx, tcary); return; } } /** * Convenience method to prepend string value to elements * currently in the line buffer. Has no effect on elements * subsequently appended to line buffer, or any other future * operations. * * <p> Makes null elements non- null! * * @param val String value to prepend to existing elements of the * buffer. */ public linebuf lines_prepend ( String val){ char[] valc = detab(val); for ( int cc = 0; cc < p; cc++){ if ( null != buf[cc]) buf[cc] = chbuf.cat(valc,buf[cc]); else buf[cc] = chbuf.copy(valc); } return this; } /** * Convenience method to append string value to elements * currently in the line buffer. Has no effect on elements * subsequently appended to line buffer, or any other future * operations. * * <p> Makes null elements non- null! * * @param val String value to append to existing elements of the * buffer. */ public linebuf lines_append ( String val){ char[] valc = detab(val); for ( int cc = 0; cc < p; cc++){ if ( null != buf[cc]) buf[cc] = chbuf.cat(buf[cc],valc); else buf[cc] = chbuf.copy(valc); } return this; } /** * Append null line, return this. */ public linebuf println (){ final String line = null; return append( line); } /** * Append line, return this. * * <p> Will insert null lines. */ public linebuf append ( char[] line){ if ( p >= buf.length){ char[][] copier = new char[buf.length+f][]; System.arraycopy(buf,0,copier,0,buf.length); buf = copier; } buf[p++] = detab(line); return this; } /** * Append line, return this. * * <p> Will insert null lines. */ public linebuf append ( char[] line, int ofs, int len){ if ( p >= buf.length){ char[][] copier = new char[buf.length+f][]; System.arraycopy(buf,0,copier,0,buf.length); buf = copier; } buf[p++] = detab(line,ofs,len); return this; } /** * Append line, return this. * * <p> Will insert null lines. */ public linebuf append ( String line){ if ( p >= buf.length){ char[][] copier = new char[buf.length+f][]; System.arraycopy(buf,0,copier,0,buf.length); buf = copier; } buf[p++] = detab(line); return this; } /** * Will insert null line elements. */ public linebuf append ( String[] lines){ if ( null == lines || 0 >= lines.length) return this; else { int len = lines.length; for ( int cc = 0; cc < len; cc++){ append(lines[cc]); } return this; } } private final static String nil = null; /** * Will insert null line elements. */ public linebuf append ( Object[] oary){ if ( null == oary || 0 >= oary.length) return this; else { int len = oary.length; for ( int cc = 0; cc < len; cc++){ append( oary[cc]); } return this; } } public linebuf append ( Object o){ if ( null == o){ append( nil); return this; } else if ( o instanceof String){ append( (String)o); return this; } else { append(new chbuf(o).toString()); return this; } } /** * Split input string `s' on delimiter characters, and add each * resulting token as an element- line. */ public linebuf append ( String s, String delimiters){ StringTokenizer strtok = new StringTokenizer(s,delimiters); while(strtok.hasMoreTokens()) append(strtok.nextToken()); return this; } /** * Returns lines or null. */ public String[] toStringArray(){ if ( 0 < p){ String[] ret = new String[p]; char[] line; for ( int cc = 0; cc < p; cc++){ line = buf[cc]; if ( null != line) ret[cc] = new String(line); } return ret; } else return null; } /** * Returns lines or null. */ public char[][] toCharCharArray(){ if ( 0 < p){ int len = p; char[][] bbuf = this.buf; char[][] copier = new char[len][]; System.arraycopy(bbuf,0,copier,0,len); return copier; } else return null; } /** * Reset the line separator used by `toString'. By default it is * the local platform line separator, typically CRLF for MacOs or * Windows, and LF for Unix, Linux, Be, Inferno, Plan9, NeXt or * MacOsX. */ public linebuf line_separator( String sep){ if ( null != sep) this.output_line_sep = sep.toCharArray(); return this; } public String line_separator(){ if ( null != this.output_line_sep) return new String(this.output_line_sep); else return null; } /** * Invert (reverse) the order of strings in the linebuffer. * * <p> <b>Reminder: Not MT SAFE,</b> although MT robust. Works on * a copy of the internal buffer, then replaces the internal * buffer with the inverted one. Changes made to the internal * buffer by another thread during this operation will be lost. */ public linebuf invert(){ if ( 0 >= p) return this; else { int len = p; char[][] copier = new char[len][]; System.arraycopy(buf,0,copier,0,p); char[] s; for ( int top = (len-1), bot = 0; bot < len; bot++, top--){ if ( bot >= top) break; else { s = copier[bot]; copier[bot] = copier[top]; copier[top] = s; } } buf = copier; return this; } } /** * Terminate each line with the line separator string, by default * this is the local platform newline (eg, CRLF (Mac/ Win) or LF * (Unix)). */ public String toString(){ char[] cary = toCharArray(); if ( null == cary) return null; else return new String(cary); } /** * Terminate each line with the line separator string, by default * this is the local platform newline (eg, CRLF (Mac/ Win) or LF * (Unix)). */ public char[] toCharArray(){ if ( 0 < p){ char[][] bbuf = buf; int len = p; chbuf strbuf = new chbuf(len*50); if ( null != output_prefix) strbuf.append(output_prefix); for ( int cc = 0; cc < len; cc++){ if ( 0 < cc) strbuf.append(output_line_sep); strbuf.append(buf[cc]); } return strbuf.toCary(); } else return null; } /** * Discard content, reuse buffer. */ public linebuf reset(){ for ( int cc = 0; cc < p; cc++){ buf[cc] = null; } p = 0; return this; } /** * Number of lines. */ public int length(){ return p; } public void backspace( int rowidx, int colidx){ char[] line = index_cary(rowidx); if ( null != line){ int linlen = line.length-1; if ( 0 < linlen){ char[] copier = new char[linlen]; System.arraycopy(line, 0, copier, 0, linlen); colidx += 1; if ( colidx < linlen) System.arraycopy(line, colidx, copier, colidx, (linlen-colidx)); index( rowidx, copier); } else { index( rowidx, (char[])null); } } } /** * Delete the first line */ public void pop (){ if ( 0 < p){ System.arraycopy( buf, 1, buf, 0, p-1); p -= 1; } } public void delete ( int rowidx, int colidx){ char[] line = index_cary(rowidx); if ( null != line){ int linlen = line.length-1; if ( 0 < linlen){ char[] copier = new char[linlen]; System.arraycopy(line, 0, copier, 0, linlen); colidx += 2; if ( colidx < linlen) System.arraycopy(line, colidx, copier, colidx, (linlen-colidx)); index(rowidx,copier); } else { index(rowidx, (char[])null); } } } public void insert ( char key, int rowidx, int colidx){ char[] line = index_cary(rowidx); if ( null != line){ int linlen = line.length; if (colidx < linlen){ line[colidx] = key; } else { if ( colidx > linlen){ char[] copier = new char[linlen+1]; System.arraycopy(line,0,copier,0,linlen); line = copier; line[linlen] = key; } else { char[] copier = new char[linlen+1]; System.arraycopy(line,0,copier,0,linlen); line = copier; line[colidx] = key; } index(rowidx,line); } } else { line = new char[1]; line[0] = key; index(rowidx,line); } } public final static String toString ( Object[] oary){ if ( null == oary) return null; else if ( 1 == oary.length){ Object obj = oary[0]; if ( obj instanceof String) return (String)obj; else return new linebuf(obj).toString(); } else return new linebuf(null,oary,null).toString(); } public final static String toString ( Object obj){ if ( obj instanceof String) return (String)obj; else return new linebuf(obj).toString(); } public final static String[] toStringArray ( Object o){ if ( o instanceof String){ String[] ret = {(String)o}; return ret; } else { linebuf lb = new linebuf(); lb.append(o); return lb.toStringArray(); } } public final static String[] toStringArray ( DataInputStream din) throws IOException { linebuf lb = new linebuf(); String line; while (null != (line = din.readLine())){ lb.append(line); } return lb.toStringArray(); } public final static String[] toStringArray ( Enumeration en){ linebuf lb = toStringArray(en,null); if ( null == lb) return null; else return lb.toStringArray(); } public final static linebuf toStringArray ( Enumeration en, linebuf lb){ if ( null == en) return lb; if ( null == lb) lb = new linebuf(); try { String str; while ( en.hasMoreElements()){ str = en.nextElement().toString(); lb.append(str); } } catch ( NoSuchElementException nsx){ nsx.printStackTrace(); } return lb; } /** * Parses input using default delimiters of carriage return and * newline (CR and LF). */ public final static String[] toStringArray ( String s){ linebuf lb = toStringArray(s,null,"\r\n"); return lb.toStringArray(); } public final static String[] toStringArray ( String s, String delimiters){ if ( null == s) return null; else { linebuf lb = toStringArray(s,null,delimiters); return lb.toStringArray(); } } /** * Uses default delimiters of carriage return and newline (CR and * LF). */ public final static linebuf toStringArray ( String s, linebuf lb){ return toStringArray(s,lb,"\r\n"); } public final static linebuf toStringArray ( String s, linebuf lb, String delimiters){ if ( null == s) return lb; else if ( null == lb) lb = new linebuf(); if ( null == delimiters){ lb.append(s); return lb; } else { StringTokenizer strtok = new StringTokenizer(s,delimiters); while(strtok.hasMoreTokens()){ lb.append(strtok.nextToken()); } return lb; } } public final static String[] toStringArray( String s, char sep){ if ( null == s) return null; else { char[] cary = s.toCharArray(); String[] lb = null; int lbx = 0; for ( int mark = 0, len = cary.length, cc = 0; cc < len; cc++){ if ( sep == cary[cc]){ if ( 0 < (cc-mark)){ //lb.append( cary, mark, cc) if ( null == lb) lb = new String[1]; else { lbx = lb.length; String[] copier = new String[lbx+1]; System.arraycopy(lb,0,copier,0,lbx); } lb[lbx] = new String(cary,mark,cc); } mark = cc+1; } } return lb; } } /** * Use the `term' argument literally, not as a set of delimiter * characters but as a whole term delimiting substrings in `s'. * * @param s String source * * @param term Substring delimiter */ public final static String[] toStringArray_term ( String s, String term){ if ( null == s) return null; else { linebuf lb = new linebuf(); char[] src_cary = s.toCharArray(), term_cary = term.toCharArray(); int idx0 = 0, idx1 = chbuf.indexOf(src_cary,term_cary); if ( 0 < idx1){ int next_term = term_cary.length; while (true){ lb.append(chbuf.substring(src_cary,idx0,idx1)); idx1 += next_term; idx0 = idx1; idx1 = chbuf.indexOf(src_cary,term_cary,idx0); if ( 0 < idx1) continue; else { idx1 = src_cary.length; if ( idx0 < idx1) lb.append(chbuf.substring(src_cary,idx0,idx1)); break; } } } else lb.append(s); return lb.toStringArray(); } } public final static String[] invert ( String s, String delim){ linebuf lb = invert(s,null,delim); return lb.toStringArray(); } public final static linebuf invert ( String s, linebuf lb, String delimiters){ if ( null == s) return lb; if ( null == lb) lb = new linebuf(); StringTokenizer strtok = new StringTokenizer(s,delimiters); while(strtok.hasMoreTokens()){ lb.append(strtok.nextToken()); } return lb.invert(); } /** * Insert 'lvl' tabs before each line of a multiline text, else * return text (use leveltabs for a single line). */ public final static String indent ( int lvl, String text){ linebuf lb = toStringArray(text,null,"\r\n"); String tabs = null; if ( 0 < lvl){ char[] tabsary = new char[lvl]; for ( int c = 0; c < lvl; c++) tabsary[c] = '\t'; tabs = new String(tabsary); } lb.lines_prepend(tabs); return lb.toString(); } /** * Parse series of space separated, optionally quoted strings. */ public final static String[] space_quoted ( String line){ if ( null == line) return null; line = line.trim(); linebuf lb = new linebuf(); chbuf strbuf = new chbuf(); StringTokenizer strtok = new StringTokenizer( line, " \t`'\"", true); String sok; char cok = 0, lc; char quote = (char)0; loop: while(strtok.hasMoreTokens()){ sok = strtok.nextToken(); if ( 1 == sok.length()){ lc = cok; cok = sok.charAt(0); switch(cok){ case ' ': case '\t': if ( 0 < quote) strbuf.append(cok); continue loop; case '"': case '`': case '\'': if ( '\\' == lc){ // backslash- quoted quote strbuf.popChar(); // pop backslash strbuf.append(cok); // push quote } else if ( quote == cok){ lb.append(strbuf.toString()); strbuf.reset(); quote = (char)0; } else if ( 0 == quote) quote = cok; else strbuf.append(cok); continue loop; default: break; } if ( 0 < quote) strbuf.append(sok); else { strbuf.append(sok); lb.append(strbuf.toString()); strbuf.reset(); } } else if ( 0 < quote){ strbuf.append(sok); } else { strbuf.append(sok); lb.append(strbuf.toString()); strbuf.reset(); } } return lb.toStringArray(); } /** * Convert multiline object descriptor onto a single line. */ public final static String toStringline ( Object o){ if ( null == o) return null; else if ( o instanceof String){ linebuf lb = toStringArray( (String)o, null, "\t\r\n"); lb.line_separator("; "); return lb.toString(); } else { linebuf lb = new linebuf(); lb.line_separator("; "); lb.append(o); return lb.toString(); } } /** * Convert each tab to five spaces. * * @param s String to detabify */ public final static char[] detab ( String s){ return detab( (char[])((null != s)?(s.toCharArray()):(null))); } public final static char[] detab ( char[] scary){ if ( null == scary) return null; else return detab(scary,0,scary.length); } private final static char[] tabs = {' ',' ',' ',' ',' '}; public final static char[] detab ( char[] scary, int ofs, int len){ if ( null == scary) return null; else { chbuf cb = new chbuf(); char ch; for ( int cc = ofs; cc < len; cc++){ ch = scary[cc]; if ( '\t' == ch) cb.append(tabs); else cb.append(ch); } return cb.toCary(); } } /** * Convert each tab to a space. Used by * <code>`index_column_overwrite'</code> which in the context of * column formatting with spaces, truncates any tabs for "belt * & braces" safety. * * @param s String to detabify * * @see #index_column_overwrite */ public final static char[] sptab ( String s){ if ( null == s) return null; else { char[] scary = s.toCharArray(); int slen = scary.length; boolean mod = false; for ( int cc = 0; cc < slen; cc++){ if ( '\t' == scary[cc]){ mod = true; scary[cc] = ' '; } } return scary; } } }