package com.ximple.io.dgn7;
|
|
import com.ximple.util.DgnUtility;
|
import org.apache.log4j.LogManager;
|
import org.apache.log4j.Logger;
|
|
import java.io.EOFException;
|
import java.io.IOException;
|
import java.nio.ByteBuffer;
|
import java.nio.ByteOrder;
|
import java.nio.MappedByteBuffer;
|
import java.nio.ShortBuffer;
|
import java.nio.channels.FileChannel;
|
import java.nio.channels.ReadableByteChannel;
|
import java.nio.channels.WritableByteChannel;
|
|
public class Dgn7fileWriter {
|
private static final Logger logger = LogManager.getLogger(Dgn7fileWriter.class);
|
|
private Dgn7fileHeader header;
|
private FileChannel channel;
|
ByteBuffer buffer;
|
private ElementType fileElementType = ElementType.UNDEFINED;
|
private ByteBuffer headerTransfer;
|
private final Element.FileRecord record = new Element.FileRecord();
|
private final boolean randomAccessEnabled;
|
private Lock lock;
|
private boolean useMemoryMappedBuffer;
|
private long currentOffset = 0L;
|
private StreamLogging streamLogger = new StreamLogging("Dgn7 Writer");
|
private int maxElementId = 0;
|
|
public Dgn7fileWriter(FileChannel channel, boolean strict, boolean useMemoryMapped, Lock lock)
|
throws IOException, Dgn7fileException {
|
this.channel = channel;
|
this.useMemoryMappedBuffer = useMemoryMapped;
|
streamLogger.open();
|
randomAccessEnabled = channel instanceof FileChannel;
|
this.lock = lock;
|
lock.lockRead();
|
lock.lockWrite();
|
// init(strict);
|
}
|
|
public Dgn7fileWriter(FileChannel channel, Lock lock) throws IOException, Dgn7fileException {
|
this(channel, true, true, lock);
|
}
|
|
protected boolean hasNext() throws IOException {
|
// mark current position
|
int position = buffer.position();
|
|
// ensure the proper position, regardless of read or handler behavior
|
try {
|
buffer.position(this.toBufferOffset(record.end));
|
} catch (IllegalArgumentException e) {
|
logger.warn("position=" + this.toBufferOffset(record.end), e);
|
|
return false;
|
}
|
|
// no more data left
|
if (buffer.remaining() < 4) {
|
return false;
|
}
|
|
// looks good
|
boolean hasNext = true;
|
short type = buffer.getShort();
|
|
if (type == -1) {
|
hasNext = false;
|
}
|
|
// reset things to as they were
|
buffer.position(position);
|
|
return hasNext;
|
}
|
|
protected Element.FileRecord nextElement() throws IOException, Dgn7fileException {
|
// need to update position
|
buffer.position(this.toBufferOffset(record.end));
|
|
// record header is big endian
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
|
// read shape record header
|
int recordNumber = ++maxElementId;
|
short signature = buffer.getShort();
|
|
// byte type = (byte) (buffer.get() & 0x7f);
|
byte type = (byte) ((signature >>> 8) & 0x007f);
|
|
// silly Bentley say contentLength is in 2-byte words
|
// and ByteByffer uses bytes.
|
// track the record location
|
int elementLength = (buffer.getShort() * 2) + 4;
|
|
if (!buffer.isReadOnly() && !useMemoryMappedBuffer) {
|
// capacity is less than required for the record
|
// copy the old into the newly allocated
|
if (buffer.capacity() < elementLength) {
|
this.currentOffset += buffer.position();
|
|
ByteBuffer old = buffer;
|
|
// ensure enough capacity for one more record header
|
buffer = Dgn7fileReader.ensureCapacity(buffer, elementLength, useMemoryMappedBuffer);
|
buffer.put(old);
|
fill(buffer, channel);
|
buffer.position(0);
|
} else
|
|
// remaining is less than record length
|
// compact the remaining data and read again,
|
// allowing enough room for one more record header
|
if (buffer.remaining() < elementLength) {
|
this.currentOffset += buffer.position();
|
buffer.compact();
|
fill(buffer, channel);
|
buffer.position(0);
|
}
|
}
|
|
// shape record is all little endian
|
// buffer.order(ByteOrder.LITTLE_ENDIAN);
|
// read the type, handlers don't need it
|
ElementType recordType = ElementType.forID(type);
|
|
logger.debug("nextElement at " + this.toBufferOffset(record.end) + ":type=" + type);
|
|
// this usually happens if the handler logic is bunk,
|
// but bad files could exist as well...
|
|
/*
|
* if (recordType != ElementType.NULL && recordType != fileElementType)
|
* {
|
* throw new IllegalStateException("ShapeType changed illegally from " + fileElementType + " to " + recordType);
|
* }
|
*/
|
|
// peek at bounds, then reset for handler
|
// many handler's may ignore bounds reading, but we don't want to
|
// second guess them...
|
buffer.mark();
|
|
if (recordType.isMultiPoint()) {
|
int lowCoorX = buffer.getInt();
|
|
lowCoorX = DgnUtility.convertFromDGN(lowCoorX);
|
record.minX = DgnUtility.converUnitToCoord(lowCoorX);
|
|
int lowCoorY = buffer.getInt();
|
|
lowCoorY = DgnUtility.convertFromDGN(lowCoorY);
|
record.minY = DgnUtility.converUnitToCoord(lowCoorY);
|
|
int lowCoorZ = buffer.getInt();
|
|
lowCoorZ = DgnUtility.convertFromDGN(lowCoorZ);
|
record.minZ = DgnUtility.converUnitToCoord(lowCoorZ);
|
|
int highCoorX = buffer.getInt();
|
|
highCoorX = DgnUtility.convertFromDGN(highCoorX);
|
record.maxX = DgnUtility.converUnitToCoord(highCoorX);
|
|
int highCoorY = buffer.getInt();
|
|
highCoorY = DgnUtility.convertFromDGN(highCoorY);
|
record.maxY = DgnUtility.converUnitToCoord(highCoorY);
|
|
int highCoorZ = buffer.getInt();
|
|
highCoorZ = DgnUtility.convertFromDGN(highCoorZ);
|
record.maxZ = DgnUtility.converUnitToCoord(highCoorZ);
|
}
|
|
buffer.reset();
|
record.offset = record.end;
|
|
// update all the record info.
|
record.length = elementLength;
|
record.signature = signature;
|
record.number = recordNumber;
|
record.buffer = buffer;
|
|
// remember, we read one int already...
|
record.end = this.toFileOffset(buffer.position()) + elementLength - 4;
|
// record.end = this.toFileOffset(buffer.position()) + elementLength;
|
|
// mark this position for the reader
|
record.start = buffer.position();
|
|
// clear any cached record
|
record.handler = recordType.getElementHandler();
|
record.element = null;
|
|
return record;
|
}
|
|
private void init(boolean strict) throws IOException, Dgn7fileException {
|
header = readHeader(channel, strict);
|
|
if (useMemoryMappedBuffer) {
|
FileChannel fc = channel;
|
|
buffer = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size());
|
|
// buffer.position(100);
|
buffer.position(header.size());
|
this.currentOffset = 0;
|
} else {
|
// force useMemoryMappedBuffer to false
|
this.useMemoryMappedBuffer = false;
|
|
// start with 8K buffer
|
buffer = ByteBuffer.allocateDirect(8 * 1024);
|
fill(buffer, channel);
|
buffer.flip();
|
this.currentOffset = header.size();
|
}
|
|
headerTransfer = ByteBuffer.allocate(4);
|
headerTransfer.order(ByteOrder.LITTLE_ENDIAN);
|
|
// make sure the record end is set now...
|
record.end = toFileOffset(buffer.position());
|
}
|
|
public static Dgn7fileHeader readHeader(FileChannel channel, boolean strict) throws IOException {
|
ByteBuffer buffer = ByteBuffer.allocateDirect(4);
|
|
if (fill(buffer, channel) == -1) {
|
throw new EOFException("Premature end of header");
|
}
|
|
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
|
int length = buffer.getShort(2) * 2;
|
ByteBuffer old = buffer;
|
|
old.position(0);
|
|
// ensure enough capacity for one more record header
|
buffer = ByteBuffer.allocateDirect(length + 4);
|
buffer.put(old);
|
|
if (fill(buffer, channel) == -1) {
|
throw new EOFException("Premature end of header");
|
}
|
|
buffer.position(0);
|
|
Dgn7fileHeader header = new Dgn7fileHeader();
|
|
header.read(buffer, strict);
|
|
return header;
|
}
|
|
protected static int fill(ByteBuffer buffer, FileChannel channel) throws IOException {
|
int r = buffer.remaining();
|
|
// channel reads return -1 when EOF or other error
|
// because they a non-blocking reads, 0 is a valid return value!!
|
while ((buffer.remaining() > 0) && (r != -1)) {
|
r = channel.read(buffer);
|
}
|
|
if (r == -1) {
|
buffer.limit(buffer.position());
|
}
|
|
return r;
|
}
|
|
private void allocateBuffers() {
|
buffer = ByteBuffer.allocateDirect(16 * 1024);
|
}
|
|
private void checkShapeBuffer(int size) {
|
if (buffer.capacity() < size) {
|
if (buffer != null)
|
NIOUtilities.clean(buffer);
|
buffer = ByteBuffer.allocateDirect(size);
|
}
|
}
|
|
private void drain() throws IOException {
|
buffer.flip();
|
while (buffer.remaining() > 0)
|
channel.write(buffer);
|
buffer.flip().limit(buffer.capacity());
|
}
|
|
private int toBufferOffset(int offset) {
|
return (int) (offset - currentOffset);
|
}
|
|
private int toFileOffset(int offset) {
|
return (int) (currentOffset + offset);
|
}
|
|
public void writeElement(Element element) throws IOException {
|
// element.raw
|
if (element == null) return;
|
ByteBuffer writeBuffer = ByteBuffer.allocateDirect(element.raw.length * 2);
|
writeBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
for (short word : element.raw) {
|
writeBuffer.putShort(word);
|
}
|
writeBuffer.rewind();
|
|
channel.write(writeBuffer);
|
}
|
|
public void toEnd() throws IOException, Dgn7fileException {
|
while (hasNext()) {
|
nextElement();
|
}
|
}
|
|
public void close() throws IOException {
|
lock.unlockWrite();
|
lock.unlockRead();
|
|
if (channel.isOpen()) {
|
channel.close();
|
streamLogger.close();
|
}
|
|
if (buffer instanceof MappedByteBuffer) {
|
NIOUtilities.clean(buffer);
|
}
|
|
channel = null;
|
header = null;
|
}
|
|
public void writeEOF() throws IOException {
|
ByteBuffer writeBuffer = ByteBuffer.allocateDirect(2);
|
writeBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
writeBuffer.putShort((short) -1);
|
channel.write(writeBuffer);
|
|
}
|
}
|