Subversion Repositories mkgmap

Rev

Rev 3408 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * 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.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.
         * @return A string
         * @throws ReadFailedException For failures.
         */

        public String getZString() throws ReadFailedException {
                StringBuffer sb = new StringBuffer();

                // Slow but sure implementation.
                for (byte b = get(); b != 0; b = get()) {
                        sb.append((char) b);
                }
                return sb.toString();
        }

        /**
         * 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");
                }
        }
}