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