/* * Copyright (C) 2007 Steve Ratcliffe * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 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. * * * Author: Steve Ratcliffe * Create date: Dec 14, 2007 */ package uk.me.parabola.imgfmt.app; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import uk.me.parabola.imgfmt.MapFailedException; import uk.me.parabola.imgfmt.ReadFailedException; import uk.me.parabola.imgfmt.fs.ImgChannel; import uk.me.parabola.log.Logger; /** * Read from an img file via a buffer. * * @author Steve Ratcliffe */ public class BufferedImgFileReader implements ImgFileReader { private static final Logger log = Logger.getLogger(BufferedImgFileReader.class); // Buffer size, must be a power of 2 private static final int BUF_SIZE = 0x1000; private final ImgChannel chan; // The buffer that we read out of private final ByteBuffer buf = ByteBuffer.allocate(BUF_SIZE); private long bufStart; private int bufSize = -1; // We keep our own idea of the file position. private long position; public BufferedImgFileReader(ImgChannel chan) { this.chan = chan; } /** * Called when the stream is closed. Any resources can be freed. * * @throws IOException When there is an error in closing. */ public void close() throws IOException { chan.close(); } /** * Get the position. Needed because may not be reflected in the underlying * file if being buffered. * * @return The logical position within the file. */ public long position() { return position; } /** * Set the position of the file. * * @param pos The new position in the file. */ public void position(long pos) { position = pos; } /** * Read in a single byte from the current position. * * @return The byte that was read. */ public byte get() throws ReadFailedException { // Check if the current position is within the buffer fillBuffer(); int pos = (int) (position - bufStart); if (pos >= bufSize) return 0; // XXX do something else position++; return buf.get(pos); } /** * Read in two bytes. Done in the correct byte order. * * @return The 2 byte integer that was read. */ public char getChar() throws ReadFailedException { // Slow but sure implementation byte b1 = get(); byte b2 = get(); return (char) (((b2 & 0xff) << 8) + (b1 & 0xff)); } /** * Read a three byte signed quantity. * @return The read value. * @throws ReadFailedException */ public int get3() throws ReadFailedException { // Slow but sure implementation byte b1 = get(); byte b2 = get(); byte b3 = get(); return (b1 & 0xff) | ((b2 & 0xff) << 8) | (b3 << 16) ; } public int getu3() throws ReadFailedException { return get3() & 0xffffff; } /** * Read in a 4 byte value. * * @return A 4 byte integer. */ public int getInt() throws ReadFailedException { // Slow but sure implementation byte b1 = get(); byte b2 = get(); byte b3 = get(); byte b4 = get(); return (b1 & 0xff) | ((b2 & 0xff) << 8) | ((b3 & 0xff) << 16) | ((b4 & 0xff) << 24) ; } public int getUint(int n) throws ReadFailedException { switch (n) { case 1: return get() & 0xff; case 2: return getChar(); case 3: return getu3(); case 4: return getInt(); default: // this is a programming error so exit throw new MapFailedException("bad integer size " + n); } } /** * Read in an arbitrary length sequence of bytes. * * @param len The number of bytes to read. */ public byte[] get(int len) throws ReadFailedException { byte[] bytes = new byte[len]; // Slow but sure implementation. for (int i = 0; i < len; i++) { bytes[i] = get(); } return bytes; } /** * Read a zero terminated string from the file, still as raw bytes. * * @return A byte array containing the encoded representation of the string. * @throws ReadFailedException For failures. */ public byte[] getZString() throws ReadFailedException { ByteArrayOutputStream out = new ByteArrayOutputStream(); // Slow but sure implementation. for (byte b = get(); b != 0; b = get()) { out.write(b); } return out.toByteArray(); } /** * Read in a string of digits in the compressed base 11 format that is used * for phone numbers in the POI section. * @param delimiter This will replace all digit 11 characters. Usually a * '-' to separate numbers in a telephone. No doubt there is a different * standard in each country. * @return A phone number possibly containing the delimiter character. */ public String getBase11str(byte firstChar, char delimiter) { // NB totally untested. StringBuilder str11 = new StringBuilder(); int term = 2; int ch = firstChar & 0xff; do { assert !(str11.length() == 0 && (ch & 0x80) == 0); if ((ch & 0x80) != 0) --term; str11.append(base(ch & 0x7F, 11, 2)); if (term != 0) ch = get(); } while (term != 0); // Remove any trailing delimiters while (str11.length() > 0 && str11.charAt(str11.length()-1) == 'A') str11.setLength(str11.length()-1); // Convert in-line delimiters to the char delimiter int len = str11.length(); for (int i = 0; i < len; i++) { if (str11.charAt(i) == 'A') str11.setCharAt(i, delimiter); } return str11.toString(); } private String base(int inNum, int base, int width) { int num = inNum; StringBuilder val = new StringBuilder(); if (base < 2 || base > 36 || width < 1) return ""; String digit = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; while (num != 0) { val.append(digit.charAt(num % base)); num /= base; } while (val.length() < width) val.append('0'); val.reverse(); return val.toString(); } /** * Check to see if the buffer contains the byte at the current position. * If not then it is re-read so that it does. * * @throws ReadFailedException If the buffer needs filling and the file cannot be * read. */ private void fillBuffer() throws ReadFailedException { // If we are no longer inside the buffer, then re-read it. if (position < bufStart || position >= bufStart + bufSize) { // Get channel position on a block boundary. bufStart = position & ~(BUF_SIZE - 1); chan.position(bufStart); log.debug("reading in a buffer start=", bufStart); // Fill buffer buf.clear(); bufSize = 0; try { bufSize = chan.read(buf); } catch (IOException e) { throw new ReadFailedException("failed to fill buffer", e); } log.debug("there were", bufSize, "bytes read"); } } }