Rev 4606 |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/*
* Copyright (C) 2006 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: 07-Dec-2006
*/
package uk.me.parabola.imgfmt.app;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import uk.me.parabola.imgfmt.MapTooBigException;
import uk.me.parabola.imgfmt.Sized;
import uk.me.parabola.imgfmt.fs.ImgChannel;
import uk.me.parabola.imgfmt.sys.FileLink;
import uk.me.parabola.log.Logger;
/**
* A straight forward implementation that just keeps all the data in a buffer
* until the file needs to be written to disk.
*
* @author Steve Ratcliffe
*/
public class BufferedImgFileWriter
implements ImgFileWriter, Sized
{
private static final Logger log =
Logger.
getLogger(BufferedImgFileWriter.
class);
private static final int KBYTE =
1024;
private static final int INIT_SIZE =
16 * KBYTE
;
private static final int GROW_SIZE =
128 * KBYTE
;
private static final int GUARD_SIZE = KBYTE
;
private final ImgChannel chan
;
private final String subfile
;
private ByteBuffer buf =
ByteBuffer.
allocate(INIT_SIZE
);
private int bufferSize = INIT_SIZE
;
// The size of the file. Note that for this to be set properly, the
// position must be set to a low value after the full file is written. This
// always happens because we go back and write the header after all is
// written.
// Always use getSize() which takes care of things.
private int maxSize
;
// The maximum allowed file size.
private long maxAllowedSize = 0xffffff
;
public BufferedImgFileWriter
(ImgChannel chan,
String subfile
) {
this.
chan = chan
;
this.
subfile = subfile
;
buf.
order(ByteOrder.
LITTLE_ENDIAN);
if (chan
instanceof FileLink
) {
((FileLink
) chan
).
link(this,
this);
}
}
/**
* Called to write out any saved buffers. The strategy may write
* directly to the file in which case this would have nothing or
* little to do.
*/
public void sync
() throws IOException {
buf.
limit((int) getSize
());
buf.
position(0);
log.
debug("syncing to pos", chan.
position(),
", size", buf.
limit());
chan.
write(buf
);
}
/**
* Get the position. Needed because may not be reflected in the underlying
* file if being buffered.
*
* @return The logical position within the file.
*/
public int position
() {
return buf.
position();
}
/**
* Set the position of the file.
*
* @param pos The new position in the file.
*/
public void position
(long pos
) {
int cur = position
();
if (cur
> maxSize
)
maxSize = cur
;
buf.
position((int) pos
);
}
/**
* Called when the stream is closed.
*/
public void close
() throws IOException {
sync
();
}
/**
* Write out a single byte.
* Should not be used for writing numbers, use put1s/u instead.
*
* @param b The byte to write.
*/
public void put
(byte b
) {
ensureSize
(1);
buf.
put(b
);
}
/**
* Write out int in range -128..127 as single byte.
* @param val The byte to write.
*/
public void put1s
(int val
) {
assert val
>= -
128 && val
<=
127 : val
;
ensureSize
(1);
buf.
put((byte)val
);
}
/**
* Write out int in range -32768..32767 as two bytes in little endian byte order.
* @param val The value to write.
*/
public void put2s
(int val
) {
assert val
>= -
32768 && val
<=
32767 : val
;
ensureSize
(2);
buf.
putShort((short)val
);
}
/**
* Write out int in range -0x800000..0x7fffff in little endian byte order.
* @param val The value to write.
*/
public void put3s
(int val
) {
assert val
>= -0x800000
&& val
<= 0x7fffff : val
;
ensureSize
(3);
buf.
put((byte)val
);
buf.
putShort((short)(val
>> 8));
}
/**
* Write out int in range 0..255 as single byte.
* @param val The value to write.
*/
public void put1u
(int val
) {
assert val
>=
0 && val
<=
255 : val
;
ensureSize
(1);
buf.
put((byte)val
);
}
/**
* Write out int in range 0..65535 as two bytes in little endian byte order.
* @param val The value to write.
*/
public void put2u
(int val
) {
assert val
>=
0 && val
<=
65535 : val
;
ensureSize
(2);
buf.
putChar((char)val
);
}
/**
* Write out int in range 0..0xffffff as three bytes in little endian byte order.
* @param val The value to write.
*/
public void put3u
(int val
) {
assert val
>=
0 && val
<= 0xffffff : val
;
ensureSize
(3);
buf.
put((byte)val
);
buf.
putChar((char)(val
>> 8));
}
/**
* Write out int as 1-4 bytes in little endian byte order.
*
* @param nBytes The number of bytes to write.
* @param val The value to write. Unsigned
*/
public void putNu
(int nBytes,
int val
) {
ensureSize
(nBytes
);
switch (nBytes
) {
case 1:
assert val
>=
0 && val
<=
255 : val
;
put
((byte)val
);
break;
case 2:
assert val
>=
0 && val
<=
65535 : val
;
buf.
putShort((short)val
);
break;
case 3:
assert val
>=
0 && val
<= 0xffffff : val
;
buf.
put((byte)val
);
buf.
putShort((short)(val
>> 8));
break;
case 4:
buf.
putInt(val
);
break;
default:
assert false : nBytes
;
}
}
/**
* Write out int (signed or unsigned) as 4 bytes.
*
* @param val The value to write.
*/
public void put4
(int val
) {
ensureSize
(4);
buf.
putInt(val
);
}
/**
* Write out an arbitrary length sequence of bytes.
*
* @param val The values to write.
*/
public void put
(byte[] val
) {
ensureSize
(val.
length);
buf.
put(val
);
}
/**
* Write out part of a byte array.
*
* @param src The array to take bytes from.
* @param start The start position.
* @param length The number of bytes to write.
*/
public void put
(byte[] src,
int start,
int length
) {
ensureSize
(length
);
buf.
put(src, start, length
);
}
public void put
(ByteBuffer src
) {
ensureSize
(src.
limit());
buf.
put(src
);
}
/**
* Get the size of the file as written.
*
* NOTE: that calling this is only valid at certain times.
*
* @return The size of the file, if it is available.
*/
public long getSize
() {
return Math.
max(maxSize, position
());
}
public ByteBuffer getBuffer
() {
return buf
;
}
/**
* Make sure there is enough room for the data we are about to write.
*
* @param length The amount of data.
*/
private void ensureSize
(int length
) {
int needed = buf.
position() + length
;
if (needed
> (bufferSize - GUARD_SIZE
)) {
while(needed
> (bufferSize - GUARD_SIZE
))
bufferSize += GROW_SIZE
;
if (bufferSize
> maxAllowedSize
) {
throw new MapTooBigException
(maxAllowedSize,
"The " + subfile +
" section of the map or tile ("
+ log.
threadTag()
+
") is too big.",
"Try splitting the map into smaller tiles or reducing the amount of information included in the map.");
}
ByteBuffer newb =
ByteBuffer.
allocate(bufferSize
);
newb.
order(ByteOrder.
LITTLE_ENDIAN);
buf.
flip();
newb.
put(buf
);
buf = newb
;
}
}
public void setMaxSize
(long maxSize
) {
this.
maxAllowedSize = maxSize
;
}
}