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