Save This Page
Home » poi-src-3.2-FINAL-20081019 » org.apache » poi » hssf » record » [javadoc | source]
    1   
    2   /* ====================================================================
    3      Licensed to the Apache Software Foundation (ASF) under one or more
    4      contributor license agreements.  See the NOTICE file distributed with
    5      this work for additional information regarding copyright ownership.
    6      The ASF licenses this file to You under the Apache License, Version 2.0
    7      (the "License"); you may not use this file except in compliance with
    8      the License.  You may obtain a copy of the License at
    9   
   10          http://www.apache.org/licenses/LICENSE-2.0
   11   
   12      Unless required by applicable law or agreed to in writing, software
   13      distributed under the License is distributed on an "AS IS" BASIS,
   14      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   15      See the License for the specific language governing permissions and
   16      limitations under the License.
   17   ==================================================================== */
   18   
   19   
   20   package org.apache.poi.hssf.record;
   21   
   22   import org.apache.poi.util.LittleEndian;
   23   
   24   import java.io.IOException;
   25   import java.io.InputStream;
   26   import java.io.ByteArrayOutputStream;
   27   
   28   /**
   29    * Title:  Record Input Stream<P>
   30    * Description:  Wraps a stream and provides helper methods for the construction of records.<P>
   31    *
   32    * @author Jason Height (jheight @ apache dot org)
   33    */
   34   
   35   public class RecordInputStream extends InputStream
   36   {
   37     /** Maximum size of a single record (minus the 4 byte header) without a continue*/
   38     public final static short MAX_RECORD_DATA_SIZE = 8224;
   39   
   40     private InputStream in;
   41     protected short currentSid;
   42     protected short currentLength = -1;
   43     protected short nextSid = -1;
   44   
   45     protected byte[] data = new byte[MAX_RECORD_DATA_SIZE];
   46     protected short recordOffset;
   47     protected long pos;
   48     
   49     private boolean autoContinue = true;
   50   
   51     public RecordInputStream(InputStream in) throws RecordFormatException  {
   52       this.in = in;
   53       try {
   54         nextSid = LittleEndian.readShort(in);
   55         //Dont increment the pos just yet (technically we are at the start of
   56         //the record stream until nextRecord is called).      
   57       } catch (IOException ex) {
   58         throw new RecordFormatException("Error reading bytes", ex);
   59       }
   60     }
   61     
   62     /** This method will read a byte from the current record*/
   63     public int read() throws IOException {
   64       checkRecordPosition();
   65   
   66       byte result = data[recordOffset];
   67       recordOffset += 1;
   68       pos += 1;
   69       return result;    
   70     }
   71     
   72     public short getSid() {
   73       return currentSid;
   74     }
   75     
   76     public short getLength() {
   77       return currentLength;
   78     }
   79   
   80     public short getRecordOffset() {
   81       return recordOffset;
   82     }
   83   
   84     public long getPos() {
   85       return pos;
   86     }
   87   
   88     public boolean hasNextRecord() {
   89       return (nextSid != 0);
   90     }
   91     
   92     /** Moves to the next record in the stream.
   93      * 
   94      * <i>Note: The auto continue flag is reset to true</i>
   95      */
   96     
   97     public void nextRecord() throws RecordFormatException {
   98       if ((currentLength != -1) && (currentLength != recordOffset)) {
   99         System.out.println("WARN. Unread "+remaining()+" bytes of record 0x"+Integer.toHexString(currentSid));
  100       }
  101       currentSid = nextSid;
  102       pos += LittleEndian.SHORT_SIZE;
  103       autoContinue = true;
  104       try {
  105         recordOffset = 0;
  106         currentLength = LittleEndian.readShort(in);     
  107         if (currentLength > MAX_RECORD_DATA_SIZE)
  108           throw new RecordFormatException("The content of an excel record cannot exceed "+MAX_RECORD_DATA_SIZE+" bytes");
  109         pos += LittleEndian.SHORT_SIZE;
  110         in.read(data, 0, currentLength);
  111   
  112         //Read the Sid of the next record
  113         nextSid = LittleEndian.readShort(in);
  114       } catch (IOException ex) {
  115         throw new RecordFormatException("Error reading bytes", ex);
  116       }
  117     }
  118     
  119     public void setAutoContinue(boolean enable) {
  120       this.autoContinue = enable;    
  121     }
  122     
  123     public boolean getAutoContinue() {
  124       return autoContinue;
  125     }
  126     
  127     protected void checkRecordPosition() {
  128       if (remaining() <= 0) {
  129         if (isContinueNext() && autoContinue) {
  130           nextRecord();
  131         }
  132         else throw new ArrayIndexOutOfBoundsException();
  133       }    
  134     }
  135     
  136     /**
  137      * Reads an 8 bit, signed value
  138      */
  139     public byte readByte() {
  140       checkRecordPosition();
  141       
  142       byte result = data[recordOffset];
  143       recordOffset += 1;
  144       pos += 1;
  145       return result;
  146     }
  147     
  148     /**
  149      * Reads a 16 bit, signed value
  150      */
  151     public short readShort() {
  152       checkRecordPosition();
  153       
  154       short result = LittleEndian.getShort(data, recordOffset);
  155       recordOffset += LittleEndian.SHORT_SIZE;
  156       pos += LittleEndian.SHORT_SIZE;
  157       return result;
  158     }
  159   
  160     public int readInt() {
  161       checkRecordPosition();
  162       
  163       int result = LittleEndian.getInt(data, recordOffset);
  164       recordOffset += LittleEndian.INT_SIZE;
  165       pos += LittleEndian.INT_SIZE;
  166       return result;
  167     }
  168   
  169     public long readLong() {
  170       checkRecordPosition();    
  171       
  172       long result = LittleEndian.getLong(data, recordOffset);
  173       recordOffset += LittleEndian.LONG_SIZE;
  174       pos += LittleEndian.LONG_SIZE;
  175       return result;
  176     }
  177   
  178     /**
  179      * Reads an 8 bit, unsigned value
  180      */
  181     public short readUByte() {
  182   	  short s = readByte();
  183   	  if(s < 0) {
  184   		  s += 256;
  185   	  }
  186   	  return s;
  187     }
  188   
  189     /**
  190      * Reads a 16 bit,un- signed value.
  191      * @return
  192      */
  193     public int readUShort() {
  194       checkRecordPosition();    
  195       
  196       int result = LittleEndian.getUShort(data, recordOffset);
  197       recordOffset += LittleEndian.SHORT_SIZE;
  198       pos += LittleEndian.SHORT_SIZE;
  199       return result;
  200     }
  201   
  202     byte[] NAN_data = null;
  203     public double readDouble() {
  204       checkRecordPosition();    
  205       //Reset NAN data
  206       NAN_data = null;
  207       double result = LittleEndian.getDouble(data, recordOffset);
  208       //Excel represents NAN in several ways, at this point in time we do not often
  209       //know the sequence of bytes, so as a hack we store the NAN byte sequence
  210       //so that it is not corrupted.
  211       if (Double.isNaN(result)) {
  212         NAN_data = new byte[8];
  213         System.arraycopy(data, recordOffset, NAN_data, 0, 8);
  214       }
  215       
  216       recordOffset += LittleEndian.DOUBLE_SIZE;
  217       pos += LittleEndian.DOUBLE_SIZE;
  218       return result;
  219     }
  220     
  221     public byte[] getNANData() {
  222       if (NAN_data == null)
  223         throw new RecordFormatException("Do NOT call getNANData without calling readDouble that returns NaN");
  224       return NAN_data;
  225     }
  226     
  227     public short[] readShortArray() {
  228       checkRecordPosition();
  229       
  230       short[] arr = LittleEndian.getShortArray(data, recordOffset);
  231       final int size = (2 * (arr.length +1));
  232       recordOffset += size;
  233       pos += size;
  234       
  235       return arr;
  236     }
  237     
  238     /**     
  239      *  given a byte array of 16-bit unicode characters, compress to 8-bit and     
  240      *  return a string     
  241      *     
  242      * { 0x16, 0x00 } -0x16     
  243      *      
  244      * @param length the length of the final string
  245      * @return                                     the converted string
  246      * @exception  IllegalArgumentException        if len is too large (i.e.,
  247      *      there is not enough data in string to create a String of that     
  248      *      length)     
  249      */  
  250     public String readUnicodeLEString(int length) {
  251       if ((length < 0) || (((remaining() / 2) < length) && !isContinueNext())) {
  252               throw new IllegalArgumentException("Illegal length - asked for " + length + " but only " + (remaining()/2) + " left!");
  253       }
  254   
  255       StringBuffer buf = new StringBuffer(length);
  256       for (int i=0;i<length;i++) {
  257         if ((remaining() == 0) && (isContinueNext())){
  258           nextRecord();
  259           int compressByte = readByte();
  260           if(compressByte != 1) throw new IllegalArgumentException("compressByte in continue records must be 1 while reading unicode LE string");
  261         }
  262         char ch = (char)readShort();
  263         buf.append(ch); 
  264       }
  265       return buf.toString();
  266     }
  267       
  268     public String readCompressedUnicode(int length) {
  269   	if(length == 0) {
  270   		return "";
  271   	}
  272       if ((length < 0) || ((remaining() < length) && !isContinueNext())) {
  273               throw new IllegalArgumentException("Illegal length " + length);
  274       }
  275   
  276       StringBuffer buf = new StringBuffer(length);
  277       for (int i=0;i<length;i++) {
  278         if ((remaining() == 0) && (isContinueNext())) {
  279             nextRecord();
  280             int compressByte = readByte();
  281             if(compressByte != 0) throw new IllegalArgumentException("compressByte in continue records must be 0 while reading compressed unicode");
  282         }
  283         byte b = readByte();
  284         //Typecast direct to char from byte with high bit set causes all ones
  285         //in the high byte of the char (which is of course incorrect)
  286         char ch = (char)( (short)0xff & (short)b );
  287         buf.append(ch); 
  288       }
  289       return buf.toString();    
  290     }
  291     
  292     /** Returns an excel style unicode string from the bytes reminaing in the record.
  293      * <i>Note:</i> Unicode strings differ from <b>normal</b> strings due to the addition of
  294      * formatting information.
  295      * 
  296      * @return The unicode string representation of the remaining bytes.
  297      */
  298     public UnicodeString readUnicodeString() {
  299       return new UnicodeString(this);
  300     }
  301     
  302     /** Returns the remaining bytes for the current record.
  303      * 
  304      * @return The remaining bytes of the current record.
  305      */
  306     public byte[] readRemainder() {
  307       int size = remaining();
  308       byte[] result = new byte[size];
  309       System.arraycopy(data, recordOffset, result, 0, size);
  310       recordOffset += size;
  311       pos += size;
  312       return result;
  313     }
  314     
  315     /** Reads all byte data for the current record, including any
  316      *  that overlaps into any following continue records.
  317      * 
  318      *  @deprecated Best to write a input stream that wraps this one where there is
  319      *  special sub record that may overlap continue records.
  320      */  
  321     public byte[] readAllContinuedRemainder() {
  322       //Using a ByteArrayOutputStream is just an easy way to get a
  323       //growable array of the data.
  324       ByteArrayOutputStream out = new ByteArrayOutputStream(2*MAX_RECORD_DATA_SIZE);
  325   
  326       while (isContinueNext()) {
  327         byte[] b = readRemainder();      
  328         out.write(b, 0, b.length);
  329         nextRecord();
  330       }
  331       byte[] b = readRemainder();      
  332       out.write(b, 0, b.length);    
  333       
  334       return out.toByteArray();
  335     }
  336   
  337     /** The remaining number of bytes in the <i>current</i> record.
  338      * 
  339      * @return The number of bytes remaining in the current record
  340      */
  341     public int remaining() {
  342       return (currentLength - recordOffset);
  343     }
  344   
  345     /** Returns true iif a Continue record is next in the excel stream 
  346      * 
  347      * @return True when a ContinueRecord is next.
  348      */
  349     public boolean isContinueNext() {
  350       return (nextSid == ContinueRecord.sid);
  351     }
  352   }

Save This Page
Home » poi-src-3.2-FINAL-20081019 » org.apache » poi » hssf » record » [javadoc | source]