package com.ximple.io.dgn7; //~--- JDK imports ------------------------------------------------------------ import javax.swing.JFileChooser; import javax.swing.JFrame; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import com.vividsolutions.jts.geom.GeometryFactory; import com.ximple.util.DgnUtility; /** * Dgn7fileReader * * @author Ulysses * @version 0.1 * @since 2006/5/17 下午 01:24:10 */ public class Dgn7fileReader { private static final Logger logger = LogManager.getLogger(Dgn7fileReader.class); private Dgn7fileHeader header; private ReadableByteChannel channel; ByteBuffer buffer; private ElementType fileElementType = ElementType.UNDEFINED; private ByteBuffer headerTransfer; private final Record record = new Record(); private final boolean randomAccessEnabled; private Lock lock; private boolean useMemoryMappedBuffer; private long currentOffset = 0L; private StreamLogging streamLogger = new StreamLogging("Shapefile Reader"); private int maxElementId = 0; public Dgn7fileReader(ReadableByteChannel 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(); init(strict); } public Dgn7fileReader(ReadableByteChannel channel, Lock lock) throws IOException, Dgn7fileException { this(channel, true, true, lock); } // ensure the capacity of the buffer is of size by doubling the original // capacity until it is big enough // this may be naiive and result in out of MemoryError as implemented... public static ByteBuffer ensureCapacity(ByteBuffer buffer, int size, boolean useMemoryMappedBuffer) { // This sucks if you accidentally pass is a MemoryMappedBuffer of size // 80M // like I did while messing around, within moments I had 1 gig of // swap... if (buffer.isReadOnly() || useMemoryMappedBuffer) { return buffer; } int limit = buffer.limit(); while (limit < size) { limit *= 2; } if (limit != buffer.limit()) { // if (record.ready) { buffer = ByteBuffer.allocateDirect(limit); // } // else { // throw new IllegalArgumentException("next before hasNext"); // } } return buffer; } // for filling a ReadableByteChannel public static int fill(ByteBuffer buffer, ReadableByteChannel 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; } public static Dgn7fileHeader readHeader(ReadableByteChannel 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; } public Dgn7fileHeader getHeader() { return header; } public void close() throws IOException { lock.unlockRead(); if (channel.isOpen()) { channel.close(); streamLogger.close(); } if (buffer instanceof MappedByteBuffer) { NIOUtilities.clean(buffer); } channel = null; header = null; } public boolean supportsRandomAccess() { return randomAccessEnabled; } public Record 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 = 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; // 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; } public void goTo(int offset) throws IOException, UnsupportedOperationException { if (randomAccessEnabled) { if (this.useMemoryMappedBuffer) { buffer.position(offset); } else { /* * Check to see if requested offset is already loaded; ensure * that record header is in the buffer */ if ((this.currentOffset <= offset) && (this.currentOffset + buffer.limit() >= offset + 4)) { buffer.position(this.toBufferOffset(offset)); } else { FileChannel fc = (FileChannel) this.channel; fc.position(offset); this.currentOffset = offset; buffer.position(0); fill(buffer, fc); buffer.position(0); } } int oldRecordOffset = record.end; record.end = offset; try { hasNext(); } catch (IOException ioe) { record.end = oldRecordOffset; throw ioe; } } else { throw new UnsupportedOperationException("Random Access not enabled"); } } public Record elementAt(int offset) throws IOException, UnsupportedOperationException, Dgn7fileException { if (randomAccessEnabled) { this.goTo(offset); return nextElement(); } throw new UnsupportedOperationException("Random Access not enabled"); } public 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; } private void init(boolean strict) throws IOException, Dgn7fileException { header = readHeader(channel, strict); // fileElementType = header.getElementType(); // handler = fileElementType.getElementHandler(); // recordHeader = ByteBuffer.allocateDirect(4); // recordHeader.order(ByteOrder.BIG_ENDIAN); // if (handler == null) // { // throw new IOException("Unsuported shape type:" + fileElementType); // } if ((channel instanceof FileChannel) && useMemoryMappedBuffer) { FileChannel fc = (FileChannel) channel; buffer = fc.map(FileChannel.MapMode.READ_ONLY, 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()); } private int toBufferOffset(int offset) { return (int) (offset - currentOffset); } private int toFileOffset(int offset) { return (int) (currentOffset + offset); } public int getCount(int count) throws Dgn7fileException { try { if (channel == null) { return -1; } count = 0; for (int tmp = readElement(); tmp != -1; tmp = readElement()) { count += tmp; } } catch (IOException ioe) { count = -1; // What now? This seems arbitrarily appropriate ! throw new Dgn7fileException("Problem reading dgnfile record", ioe); } return count; } public int getCount() throws Dgn7fileException { return getCount(0); } private int readElement() throws IOException { if (!fillBuffer()) { return -1; } // burn the record number buffer.getInt(); if (!fillBuffer()) { return -1; } int recordlength = buffer.getInt() * 2; // Going to read the first 4 bytes of the record so // subtract that from the record length recordlength -= 4; if (!fillBuffer()) { return -1; } // read record type (used to determine if record is a null record) int type = buffer.getInt(); // go to end of record while (buffer.limit() < buffer.position() + recordlength) { recordlength -= buffer.limit() - buffer.position(); buffer.clear(); if (channel.read(buffer) < 1) { return -1; } } buffer.position(buffer.position() + recordlength); // return 0 if record is null. Null records should be counted. if (type == 0) { // this is a null feature return 0; } return 1; } private boolean fillBuffer() throws IOException { int result = 1; if (buffer.limit() <= buffer.position() + 4) { result = fill(buffer, channel); } return result > 0; } public static void main(String[] args) { JFileChooser jfc = new JFileChooser("D:/TEMP"); File f = null; int r = jfc.showOpenDialog(new JFrame()); if (r == JFileChooser.APPROVE_OPTION) { try { f = jfc.getSelectedFile(); FileChannel channel = new FileInputStream(f).getChannel(); Dgn7fileReader reader = new Dgn7fileReader(channel, new Lock()); System.out.println(reader.getHeader().toString()); GeometryFactory factory = new GeometryFactory(); int count, size; count = 0; size = 0; try { Element lastComplex = null; while (reader.hasNext()) { size++; Dgn7fileReader.Record record = reader.nextElement(); if (record.element() != null) { Element element = (Element) record.element(); ElementType type = element.getElementType(); if ((!type.isComplexElement()) && (!element.isComponentElement())) { if (lastComplex != null) { // @todo add process in here count++; lastComplex = null; } // @todo add process in here count++; } else if (element.isComponentElement()) { if (lastComplex != null) { ((ComplexElement) lastComplex).add(element); } } else if (type.isComplexElement()) { if (lastComplex == null) { lastComplex = element; } else { // @todo add process in here count++; lastComplex = element; } } } } } catch (IOException e) { logger.warn("Stop read dgn file", e); } catch (Dgn7fileException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } finally { reader.close(); } System.out.println("count=" + count + " size=" + size); // reader.close(); } catch (IOException ioe) { System.out.println(ioe); ioe.printStackTrace(); } catch (Dgn7fileException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } } System.exit(0); } public final class Record { int length; int number = 0; int offset; // Relative to the whole file int start = 0; // Relative to the current loaded buffer short signature = 0; /** * The minimum X value. */ public double minX; /** * The minimum Y value. */ public double minY; /** * The minimum Z value. */ public double minZ; /** * The maximum X value. */ public double maxX; /** * The maximum Y value. */ public double maxY; /** * The maximum Z value. */ public double maxZ; // ElementType type; int end = 0; // Relative to the whole file Object element = null; IElementHandler handler; public Object element() { if (element == null) { buffer.position(start); buffer.order(ByteOrder.LITTLE_ENDIAN); if (handler == null) { return null; } element = handler.read(buffer, signature, length); } return element; } public int offset() { return offset; } /** * A summary of the record. */ public String toString() { return "Record " + number + " length " + length + " bounds " + minX + "," + minY + " " + maxX + "," + maxY; } } }