/* Formatter.java */ import java.io.DataInputStream; import java.io.IOException; import java.io.PrintStream; import java.io.StringBufferInputStream; import java.util.Hashtable; import java.util.Vector; /* This class holds a Format, and has methods for reading and writing data against it. */ public class Formatter { private Format format = null; private FormatMap format_map = null; public Formatter( String format ) throws InvalidFormatException { this( new Format(format) ); } public Formatter( Format format ) { this.format = format; } public void setFormatMap( FormatMap format_map ) { this.format_map = format_map; } public void write( Vector v, PrintStream out ) throws OutputFormatException { FormatOutputList vp = new VectorAndPointer( v ); this.format.write( vp, out ); } public void write( int i, PrintStream out ) throws OutputFormatException { write( new Integer(i), out ); } public void write( long l, PrintStream out ) throws OutputFormatException { write( new Long(l), out ); } public void write( float f, PrintStream out ) throws OutputFormatException { write( new Float(f), out ); } public void write( double d, PrintStream out ) throws OutputFormatException { write( new Double(d), out ); } public void write( Object o, PrintStream out ) throws OutputFormatException { Vector v = new Vector(); v.addElement( o ); write( v, out ); } public void read( Vector v, DataInputStream in ) throws InputFormatException { FormatInputList vp = new VectorAndPointer( v ); InputStreamAndBuffer inb = new InputStreamAndBuffer(in); this.format.read( vp, inb, this.format_map ); } public void read( Vector v, Hashtable ht, DataInputStream in ) throws InputFormatException { FormatInputList vp = new StringsHashtableAndPointer( v, ht ); InputStreamAndBuffer inb = new InputStreamAndBuffer(in); this.format.read( vp, inb, this.format_map ); } public void read( String[] s, Hashtable ht, DataInputStream in ) throws InputFormatException { Vector v = new Vector(); for ( int i = 0; i getWidth() ) throw new StringTooWideOnWriteException( s, vecptr, this.toString() ); else if ( s.length() < getWidth() ) { for ( int i=s.length()+1; i <= getWidth(); i++ ) { s = s + " "; } return s; } else return s; } else throw new IllegalObjectOnWriteException( o, vecptr, this.toString() ); } /* vp and in are used only in generating error messages. */ Object convertFromString( String s, FormatInputList vp, InputStreamAndBuffer in ) throws InvalidNumberOnReadException { /* We just return the slice read, as a string. */ return s; } public String toString() { return "A"+getWidth(); } } /* This class represents an Iw format element. */ class FormatI extends FormatIOElement { public FormatI( int w ) { setWidth( w ); } String convertToString( Object o, int vecptr ) throws IllegalObjectOnWriteException, NumberTooWideOnWriteException { String s; /* Convert the number to a string. */ if ( o instanceof Integer || o instanceof Long ) { CJFormat cjf = new CJFormat(); cjf.setWidth( getWidth() ); cjf.setPre( "" ); cjf.setPost( "" ); cjf.setLeadingZeroes( false ); cjf.setShowPlus( false ); cjf.setAlternate( false ); cjf.setShowSpace( false ); cjf.setLeftAlign( false ); cjf.setFmt( 'i' ); s = cjf.form( ((Number)o).longValue() ); /* Throw an exception if the string won't fit. */ if ( s.length() > getWidth() ) throw new NumberTooWideOnWriteException( (Number)o, vecptr, this.toString() ); else return s; } else throw new IllegalObjectOnWriteException( o, vecptr, this.toString() ); } /* vp and in are used only in generating error messages. */ Object convertFromString( String s, FormatInputList vp, InputStreamAndBuffer in ) throws InvalidNumberOnReadException { /* Parse the string to check it's a valid number, and convert if so. */ NumberParser np = Parsers.theParsers().number_parser; np.ReInit( new StringBufferInputStream(s) ); try { int start = np.Integer(); Long l = new Long( s.substring(start) ); return l; } catch ( ParseException e ) { throw new InvalidNumberOnReadException( s, vp.getPtr(), this.toString(), in.getLineErrorReport(), e.getMessage() ); } catch ( TokenMgrError e ) { throw new InvalidNumberOnReadException( s, vp.getPtr(), this.toString(), in.getLineErrorReport(), e.getMessage() ); } } public String toString() { return "I"+getWidth(); } } /* This class represents an Fw.d format element. Numbers should be output with d decimal places. */ class FormatF extends FormatIOElement { private int d; public FormatF( int w, int d ) { setWidth( w ); this.d = d; } String convertToString( Object o, int vecptr ) throws IllegalObjectOnWriteException, NumberTooWideOnWriteException { String s; /* Convert the number to a string. */ if ( o instanceof Integer || o instanceof Long || o instanceof Float || o instanceof Double ) { CJFormat cjf = new CJFormat(); cjf.setWidth( getWidth() ); cjf.setPrecision( this.d ); cjf.setPre( "" ); cjf.setPost( "" ); cjf.setLeadingZeroes( false ); cjf.setShowPlus( false ); cjf.setAlternate( false ); cjf.setShowSpace( false ); cjf.setLeftAlign( false ); cjf.setFmt( 'f' ); s = cjf.form( ((Number)o).doubleValue() ); /* Throw an exception if the string won't fit. */ if ( s.length() > getWidth() ) throw new NumberTooWideOnWriteException( (Number)o, vecptr, this.toString() ); else return s; } else throw new IllegalObjectOnWriteException( o, vecptr, this.toString() ); } /* vp and in are used only in generating error messages. */ Object convertFromString( String s, FormatInputList vp, InputStreamAndBuffer in ) throws InvalidNumberOnReadException { /* Parse the string to check it's a valid number, and convert if so. */ NumberParser np = Parsers.theParsers().number_parser; np.ReInit( new StringBufferInputStream(s) ); try { int start = np.Float(); Double d = new Double( s.substring(start) ); return d; } catch ( ParseException e ) { throw new InvalidNumberOnReadException( s, vp.getPtr(), this.toString(), in.getLineErrorReport(), e.getMessage() ); } catch ( TokenMgrError e ) { throw new InvalidNumberOnReadException( s, vp.getPtr(), this.toString(), in.getLineErrorReport(), e.getMessage() ); } } public String toString() { return "F"+getWidth()+"."+this.d; } } /* This class represents an Ew.d format element. Numbers should be output as s0.dd...ddEsdd where s is a sign. */ class FormatE extends FormatIOElement { int d; public FormatE( int w, int d ) { setWidth( w ); this.d = d; } String convertToString( Object o, int vecptr ) throws IllegalObjectOnWriteException, NumberTooWideOnWriteException { String s; /* Convert the number to a string. */ if ( o instanceof Integer || o instanceof Long || o instanceof Float || o instanceof Double ) { CJFormat cjf = new CJFormat(); cjf.setWidth( getWidth() ); cjf.setPrecision( this.d ); cjf.setPre( "" ); cjf.setPost( "" ); cjf.setLeadingZeroes( false ); cjf.setShowPlus( false ); cjf.setAlternate( false ); cjf.setShowSpace( false ); cjf.setLeftAlign( false ); cjf.setFmt( 'E' ); s = cjf.form( ((Number)o).doubleValue() ); /* Throw an exception if the string won't fit. */ if ( s.length() > getWidth() ) throw new NumberTooWideOnWriteException( (Number)o, vecptr, this.toString() ); else return s; } else throw new IllegalObjectOnWriteException( o, vecptr, this.toString() ); } /* vp and in are used only in generating error messages. */ Object convertFromString( String s, FormatInputList vp, InputStreamAndBuffer in ) throws InvalidNumberOnReadException { /* Parse the string to check it's a valid number, and convert if so. */ NumberParser np = Parsers.theParsers().number_parser; np.ReInit( new StringBufferInputStream(s) ); try { int start = np.Float(); Double d = new Double( s.substring(start) ); return d; } catch ( ParseException e ) { throw new InvalidNumberOnReadException( s, vp.getPtr(), this.toString(), in.getLineErrorReport(), e.getMessage() ); } catch ( TokenMgrError e ) { throw new InvalidNumberOnReadException( s, vp.getPtr(), this.toString(), in.getLineErrorReport(), e.getMessage() ); } } public String toString() { return "E"+getWidth()+"."+this.d; } } /* This class represents an / item. */ class FormatSlash extends FormatElement { public void write( FormatOutputList vp, PrintStream out ) { out.println(); } public void read( FormatInputList vp, InputStreamAndBuffer in, FormatMap format_map ) throws InputFormatException { in.readLine( vp.getPtr(), this ); } public String toString() { return "/"; } } /* This class represents an embedded literal, e.g. 'Title'. toString() does not yet handle embedded quotes. */ class FormatString extends FormatElement { private String s; public FormatString( String s ) { this.s = s; } public void write( FormatOutputList vp, PrintStream out ) { out.print(this.s); } public void read( FormatInputList vp, InputStreamAndBuffer in, FormatMap format_map ) throws InputFormatException { String s = in.getSlice( this.s.length(), vp.getPtr(), this ); if ( !( this.s.equals(s) ) ) throw new UnmatchedStringOnReadException( s, vp.getPtr(), this.toString(), in.getLineErrorReport() ); in.advance( this.s.length() ); } public String toString() { return "'" + this.s + "'"; } } /* This class represents a mapping from input data. We use it to specify, for example, that on input, an "X" should be replaced by a "0" before being interpreted by the formatted input routines. The user must provide an instance of this class, with getMapping defined. getMapping should return either null, if the input string is to be left as it is, or a replacement string. */ abstract class FormatMap { public abstract String getMapping( String in ); } interface FormatOutputList { boolean hasCurrentElement(); void checkCurrentElementForWrite( FormatElement format_element ) throws EndOfVectorOnWriteException; Object getCurrentElement(); Object getCurrentElementAndAdvance(); /* Returns the current pointer. Used only in generating error messages. */ int getPtr(); } interface FormatInputList { /* format_element and in are only for generating error messages. */ void checkCurrentElementForRead( FormatElement format_element, InputStreamAndBuffer in ) throws InputFormatException; // If the list is a VectorAndPointer, it won't throw an exception. // If it is a StringsHashtableAndPointer, it will throw a // EndOfKeyVectorOnReadException. /* Puts o into the input list and advances its pointer. Must be defined for each subclass. format_element and in are only for generating error messages. */ void putElementAndAdvance( Object o, FormatElement format_element, InputStreamAndBuffer in ) throws InputFormatException; /* Returns the current pointer. Used only in generating error messages. */ int getPtr(); } /* This class represents a Vector and a current-element pointer. We use it when outputting or inputting a Vector against a format: the pointer keeps track of the current element being output, and can be incremented by the format write and read methods. */ class VectorAndPointer implements FormatInputList, FormatOutputList { private Vector v = null; private int vecptr = 0; // On output, vecptr points at the next element to be used. // On input, it points at the next free slot to be filled. public VectorAndPointer( Vector v ) { this.v = v; } public VectorAndPointer() { this.v = new Vector(); } public boolean hasCurrentElement() { return ( this.vecptr < this.v.size() ); } public void checkCurrentElementForWrite( FormatElement format_element ) throws EndOfVectorOnWriteException { if ( !hasCurrentElement() ) throw new EndOfVectorOnWriteException( this.vecptr, format_element.toString() ); } /* Checks that the current element in the input list is OK and throws an exception if not. For this implementation of FormatInputList, there are no error conditions - we introduced the method for the StringHashtableAndPointer class, and need it here for compatibility. format_element and in are only for generating error messages. */ public void checkCurrentElementForRead( FormatElement format_element, InputStreamAndBuffer in ) { } public Object getCurrentElement() { return this.v.elementAt( this.vecptr ); } public Object getCurrentElementAndAdvance() { this.vecptr = this.vecptr+1; return this.v.elementAt( this.vecptr-1 ); } /* Puts o into the input list and advances its pointer. format_element and in are only for generating error messages, and not used in this implementation, since no error conditions can arise. */ public void putElementAndAdvance( Object o, FormatElement format_element, InputStreamAndBuffer in ) { this.v.addElement(o); this.vecptr = this.vecptr + 1; } public void advance() { this.vecptr = this.vecptr + 1; } /* Returns the current pointer. Used only in generating error messages. */ public int getPtr() { return this.vecptr; } } /* This class represents a Vector of Strings and a current-element pointer. We use it when inputting data against a format. */ class StringsHashtableAndPointer implements FormatInputList { private VectorAndPointer vp; private Hashtable ht; public StringsHashtableAndPointer( Vector strings, Hashtable ht ) { this.vp = new VectorAndPointer( strings ); this.ht = ht; } /* Checks that there is a current element in the key vector, and throws an exception if not. format_element and in are only for generating error messages. */ public void checkCurrentElementForRead( FormatElement format_element, InputStreamAndBuffer in ) throws EndOfKeyVectorOnReadException { if ( !(this.vp.hasCurrentElement() ) ) throw new EndOfKeyVectorOnReadException( this.vp.getPtr(), format_element.toString(), in.getLineErrorReport() ); } /* Puts o into the input list and advances its pointer. In this implementation, that means getting the current key, putting o into an appropriate hashtable slot, and advancing the pointer in the vector of keys. format_element and in are only for generating error messages. */ public void putElementAndAdvance( Object o, FormatElement format_element, InputStreamAndBuffer in ) throws KeyNotStringOnReadException { Object current_key = this.vp.getCurrentElement(); if ( current_key instanceof String ) { this.ht.put( (String)current_key, o ); this.vp.advance(); } else throw new KeyNotStringOnReadException( current_key, this.vp.getPtr(), format_element.toString(), in.getLineErrorReport() ); } /* Returns the current pointer. Used only in generating error messages. */ public int getPtr() { return this.vp.getPtr(); } } /* This class holds an input stream and a line buffer. */ class InputStreamAndBuffer { private DataInputStream in; // The stream we read from. private String line; // The line just read. private int ptr; // Initialised to 0 after reading a line. Index of the next // character to use in line. private int line_number; // Initially 0. Is incremented each time a line is read, so // the first line read is number 1. private boolean nothing_read; // Initially true. Is set false after reading a line. We // use this so that the first call of getSlice // knows to read a line. public InputStreamAndBuffer( DataInputStream in ) { this.in = in; this.ptr = 0; this.line = ""; this.line_number = 0; this.nothing_read = true; } /* Reads the next line into the line buffer. vecptr and format are used only in generating error messages. */ public void readLine( int vecptr, FormatElement format ) throws EndOfFileWhenStartingReadException, LineMissingOnReadException, IOExceptionOnReadException { try { String line = this.in.readLine(); if ( line == null ) { if ( this.nothing_read ) throw new EndOfFileWhenStartingReadException( vecptr, format.toString(), this.line, this.line_number ); else throw new LineMissingOnReadException( vecptr, format.toString(), this.line, this.line_number ); } else { this.ptr = 0; this.nothing_read = false; this.line_number = this.line_number + 1; this.line = line; // Don't do the assignment until we've checked for a null // line, because then we can then use this.line as the // previous value for error messages. } } catch ( IOException e ) { throw new IOExceptionOnReadException( this.line, this.line_number, e.getMessage() ); } } /* Returns a string consisting of the next width characters, and throws an exception if the line is not long enough. The 'vecptr' and 'format' parameters are used only in generating error messages. */ public String getSlice( int width, int vecptr, FormatElement format ) throws DataMissingOnReadException, LineMissingOnReadException, EndOfFileWhenStartingReadException, IOExceptionOnReadException { if ( this.nothing_read ) readLine( vecptr, format ); if ( this.ptr+width > this.line.length() ) throw new DataMissingOnReadException( vecptr, format.toString(), getLineErrorReport() ); else { return this.line.substring( this.ptr, this.ptr+width ); } } /* Advances the pointer by width. */ public void advance( int width ) { this.ptr = this.ptr + width; } /* Generates an error report showing the line, character pointer ptr and line number. */ public String getLineErrorReport() { StringBuffer s = new StringBuffer(); /* Report the line number. */ s.append( " Line number = " + this.line_number + ":\n" ); /* Show the line. */ s.append( this.line + "\n" ); /* Show an arrow under ptr. */ for ( int i=0; i