public class BitBuffer {
    private byte[] byteArray;

    private Issues issues;
    
    private int currentOffset;

    private int bitOffset;

    private int currentByte;

    private int endOffset;

    private BitBuffer(byte[] byteArray, Issues issues) {
        this.byteArray = byteArray;
        this.issues = issues;
        
        currentOffset = 0;
        currentByte = byteArray[0];
        bitOffset = 0;

        // TODO: must set actual last bit here
        this.endOffset = byteArray.length * 8;
    }

    public static BitBuffer wrap(byte[] byteArray, Issues issues) {
        return new BitBuffer(byteArray, issues);
    }

    public void position(int offset) {
        this.currentOffset = offset / 8;
        currentByte = byteArray[currentOffset];
        this.bitOffset = (offset % 8);
    }

    public int position() {
        return currentOffset * 8 + bitOffset;
    }

    // TODO remove this method and set in constructor
    public void setEndOffset(int endOffset) {
        this.endOffset = endOffset;
    }

    /**
     * bit (1 or 0)
     *
     * @return
     */
    public boolean getB() {
        if (!hasMoreData()) {
            throw new RuntimeException("end of stream reached unexpectedly");
        }

        int mask = 0x80 >> bitOffset;
        boolean result = ((currentByte & mask) != 0);
        if (bitOffset == 7) {
            currentByte = byteArray[++currentOffset];
            bitOffset = 0;
        } else {
            bitOffset++;
        }
        return result;
    }

    public int getBB() {
        return getBitsUnsigned(2);
    }

    /**
     * raw char (not compressed)
     * @return
     */
    public int getRC() {
        boolean isNegative = getB();
        int value = getBitsUnsigned(7);

        return isNegative ? -value : value;
    }

    public int getUnsignedRC() {
        return getBitsUnsigned(8);
    }

    /**
     * raw short (not compressed)
     * @return
     */
    public int getRS() {
        int a = getBitsUnsigned(8);
        boolean isNegative = getB();
        int b = getBitsUnsigned(7);

        int value = a + (b << 8);
        return isNegative ? -value : value;
    }

    /**
     * raw long (not compressed)
     * @return
     */
    public int getRL() {
        int a = getBitsUnsigned(8);
        int b = getBitsUnsigned(8);
        int c = getBitsUnsigned(8);
        boolean isNegative = getB();
        int d = getBitsUnsigned(7);

        int value = a + (b << 8) + (c << 16) + (d << 24);
        return isNegative ? -value : value;
    }

    /**
     *   bitshort 16
     *   See 2.2 Bitshort
     *
     * @return
     */
    public int getBS() {
        int code = getBitsUnsigned(2);
        switch (code) {
        case 0:
            return getRS();
        case 1:
            return getBitsUnsigned(8);
        case 2:
            return 0;
        case 3:
            return 256;
        default:
            throw new RuntimeException("can't happen");
        }
    }

    /**
     *   bitlong 32
     *   See 2.3 Bitlong
     *
     * @return
     */
    public int getBL() {
        int code = getBitsUnsigned(2);
        switch (code) {
        case 0:
            return getRL();
        case 1:
            return getBitsUnsigned(8);
        case 2:
            return 0;
        case 3:
            throw new RuntimeException("unknown bitlong code of 0b11");
        default:
            throw new RuntimeException("can't happen");
        }
    }

    /**
     *   bitlonglong 64
     *   See 2.4 BITLONGLONG
     *
     * @return
     */
    public long getBLL() {
        int length = get3B();

        long result = 0;
        int shift = 0;
        while (length-- != 0) {
            result |= (getBitsUnsigned(8) << shift);
            shift += 8;
        }

        return result;
    }

    /**
     *   bitdouble
     *   See 2.5 BITDOUBLE
     *
     * @return
     */
    public double getBD() {
        int code = getBitsUnsigned(2);
        switch (code) {
        case 0:
            return getRD();
        case 1:
            return 1.0;
        case 2:
            return 0.0;
        case 3:
            throw new RuntimeException("unknown bitdouble code of 0b11");
        default:
            throw new RuntimeException("can't happen");
        }
    }

    public Point2D get2RD() {
        double x = getRD();
        double y = getRD();
        return new Point2D(x, y);
    }

    public Point2D get2DD(Point2D base) {
        double x = getDD(base.x);
        double y = getDD(base.y);
        return new Point2D(x, y);
    }

    public Point2D get2BD() {
        double x = getBD();
        double y = getBD();
        return new Point2D(x, y);
    }

    public Point3D get3BD() {
        double x = getBD();
        double y = getBD();
        double z = getBD();
        return new Point3D(x, y, z);
    }

    /**
     * raw double (not compressed)
     * @return
     */
    public double getRD() {
        long byte0 = getBitsUnsigned(8);
        long byte1 = getBitsUnsigned(8);
        long byte2 = getBitsUnsigned(8);
        long byte3 = getBitsUnsigned(8);
        long byte4 = getBitsUnsigned(8);
        long byte5 = getBitsUnsigned(8);
        long byte6 = getBitsUnsigned(8);
        long byte7 = getBitsUnsigned(8);

        return Double.longBitsToDouble((byte7 << 56)
                | (byte6 << 48)
                | (byte5 << 40)
                | (byte4 << 32)
                | (byte3 << 24)
                | (byte2 << 16)
                | (byte1 << 8)
                | (byte0));
    }

    /**
     * 3B - Paragraph 2.1
     *
     * @return
     */
    public int get3B() {
        if (getB()) {
            if (getB()) {
                if (getB()) {
                    return 7;
                } else {
                    return 6;
                }
            } else {
                return 2;
            }
        } else {
            return 0;
        }
    }

    /**
     * handle
     * see 2.13 Handle References
     *
     * @return
     */
    public Handle getHandle() {
        int code = getBitsUnsigned(4);
        int counter = getBitsUnsigned(4);

        int [] handle = new int[counter];
        for (int i = 0; i < counter; i++) {
            handle[i] = getBitsUnsigned(8);
        }

        return new Handle(code, handle);
    }

    public Handle getHandle(Handle baseHandle) {
        int code = getBitsUnsigned(4);
        int counter = getBitsUnsigned(4);

        int [] handle = new int[counter];
        for (int i = 0; i < counter; i++) {
            handle[i] = getBitsUnsigned(8);
        }

        Handle tempHandle = new Handle(code, handle);

        int thisOffset;
        switch (tempHandle.code) {
        case 2:
        case 3:
        case 4:
        case 5:
            thisOffset = tempHandle.offset;
            break;
        case 6:
            thisOffset = baseHandle.offset + 1;
            break;
        case 8:
            thisOffset = baseHandle.offset - 1;
            break;
        case 0xA:
            thisOffset = baseHandle.offset + tempHandle.offset;
            break;
        case 0xC:
            thisOffset = baseHandle.offset - tempHandle.offset;
            break;
        default:
            throw new RuntimeException("bad case");
        }

        return new Handle(5, thisOffset);
    }

    // Public for instrumentation only
    public int getBitsUnsigned(int numberOfBits) {
        assert numberOfBits <= 31;
        int result = 0;
        for (int i = 0; i < numberOfBits; i++) {
            result <<= 1;
            if (this.getB()) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * Unicode text
     *
     * @return
     */
    public String getTU() {
        int length = getBS();

        byte[] x = new byte[length*2];
        for (int i = 0; i < length; i++) {
            x[2*i+1] = (byte)this.getBitsUnsigned(8);
            x[2*i] = (byte)this.getBitsUnsigned(8);
        }
        try {
            return new String(x, "UTF-16");
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return "";
        }  // TODO
    }

    public boolean hasMoreData() {
        return currentOffset*8 + bitOffset < endOffset;
    }

    public void B(Value<Boolean> value) {
        value.set(getB());
    }

    public void BS(Value<Integer> value) {
        value.set(getBS());
    }

    public void BL(Value<Integer> value) {
        value.set(getBL());
    }

    public void BD(Value<Double> value) {
        value.set(getBD());
    }

    public void RC(Value<Integer> value) {
        value.set(getRC());
    }

    public void expectB(boolean expected)
    {
        boolean actual = getB();
        if (actual != expected) {
            issues.addWarning(MessageFormat.format("Unknown bit value: {0} expected but {1} found. Investigation needed.", expected, actual));
        }
    }

    public void expectRC(int expected)
    {
        int actual = getRC();
        if (actual != expected) {
            issues.addWarning(MessageFormat.format("Unknown RC value: {0} expected but {1} found. Investigation needed.", expected, actual));
        }
    }

    public void expectBD(double expected) {
        double actual = getBD();
        if (actual != expected) {
            issues.addWarning(MessageFormat.format("Unknown bit-double value: {0} expected but {1} found. Investigation needed.", expected, actual));
        }
    }

    public void CMC(Value<CmColor> color) {
        int expectZero = getBS();
        int rgbValue = getBL();
        int colorByte = getRC();
        if ((colorByte & 0x01) != 0) {
            // Read color name
        }
        if ((colorByte & 0x02) != 0) {
            // Read book name
        }

        color.set(new CmColor(rgbValue, colorByte));
    }

    public void H(Value<Handle> value) {
        Handle handle = getHandle();
        value.set(handle);
    }

    public void H(Value<Handle> value, HandleType type) {
        Handle handle = getHandle();
        value.set(handle);
    }

    public void threeBD(Value<Point3D> field) {
        double x = getBD();
        double y = getBD();
        double z = getBD();
        field.set(new Point3D(x, y, z));
    }

    // Two raw doubles
    public void twoRD(Value<Point2D> field) {
        double x = getRD();
        double y = getRD();
        field.set(new Point2D(x, y));
    }

    public void TU(Value<String> field) {
        String text = getTU();
        field.set(text);
    }

    /**
     * Paragraph 2.12 Object Type
     * 
     * @return
     */
    public int getOT() {
        int code = getBitsUnsigned(2);
        switch (code) {
        case 0:
            return getBitsUnsigned(8);
        case 1:
            return getBitsUnsigned(8) + 0x01F0;
        case 3:
            // TODO issue warning and fall thru to case 2
        case 2:
            int loByte = getBitsUnsigned(8);
            int hiByte = getBitsUnsigned(8);
            return hiByte << 8 | loByte;
        default:
            throw new RuntimeException("cannot happen");
        }
    }

    public void advanceToByteBoundary() {
        if (bitOffset != 0) {
            currentByte = byteArray[++currentOffset];
            bitOffset = 0;
        }
    }

    public void assertEndOfStream() {
        int currentBitOffset = currentOffset*8 + bitOffset;
        if (currentBitOffset != endOffset) {
            throw new RuntimeException("not at end of stream");
        }
    }

    public CmColor getCMC()
    {
        int colorIndex = getBS();
        if (colorIndex == 0) {
            int rgbValue = getBL();
            int colorByte = getRC();
            return new CmColor(rgbValue, colorByte);
        } else {
            // no more fields
            // TODO complete this...
            return new CmColor(0, 0);
        }
    }

    public CmColor getENC()
    {
        // 2.1.1 page 13
        int colorIndex = getBS();
        if ((colorIndex & 0x2000) != 0) {
            int transparency = getBL();
        }
        // no more fields
        // TODO complete this...
        return new CmColor(0, 0);
    }

    public double getDD(double defaultValue)
    {
        int code = getBitsUnsigned(2);
        switch (code) {
        case 0:
            return defaultValue;
        case 1:
        {
            long b = Double.doubleToLongBits(defaultValue);

            long byte1 = getBitsUnsigned(8);
            long byte2 = getBitsUnsigned(8);
            long byte3 = getBitsUnsigned(8);
            long byte4 = getBitsUnsigned(8);

            return Double.longBitsToDouble((byte4 << 24)
                    | (byte3 << 16)
                    | (byte2 << 8)
                    | (byte1 << 0)
                    | (b & 0xFFFFFFFF00000000L));
        }
        case 2:
        {
            long b = Double.doubleToLongBits(defaultValue);

            long byte1 = getBitsUnsigned(8);
            long byte2 = getBitsUnsigned(8);
            long byte3 = getBitsUnsigned(8);
            long byte4 = getBitsUnsigned(8);
            long byte5 = getBitsUnsigned(8);
            long byte6 = getBitsUnsigned(8);

            return Double.longBitsToDouble((byte2 << 40)
                    | (byte1 << 32)
                    | (byte6 << 24)
                    | (byte5 << 16)
                    | (byte4 << 8)
                    | (byte3 << 0)
                    | (b & 0xFFFF000000000000L));
        }
        case 3:
            return getRD();
        default:
            throw new RuntimeException("cannot happen");
        }
    }

    public int[] getBytes(int length) {
        int [] result = new int[length]; 
        for (int i = 0; i < length; i++) {
            result[i] = this.getBitsUnsigned(8);
        }
        return result;
    }

    public Point3D getBE() {
        boolean extrusionBit = getB();
        if (extrusionBit) {
            return new Point3D(0.0, 0.0, 1.0);
        } else {
            return get3BD();
        }
    }

    public double getBT() {
        boolean thicknessBit = getB();
        if (thicknessBit) {
            return 0.0;
        } else {
            return getBD();
        }
    }

} public class BitStreams {

    private byte[] byteArray;
    private Issues issues;
    private int dataStreamStart;
    private int stringStreamStart;
    private int stringStreamEnd;
    private int endDataPosition;
    private int handleStreamEnd;

    public BitStreams(byte[] byteArray, byte[] signature, FileVersion fileVersion, Issues issues) {
        this.byteArray = byteArray;
        this.issues = issues;

        ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);

        // The signature
        byte [] actualSignature = new byte[16];
        byteBuffer.get(actualSignature);
        if (!Arrays.equals(actualSignature, signature)) {
            throw new RuntimeException("bad signature: ");
        }

        int sizeOfDataArea = byteBuffer.getInt();
        int unknown75 = byteBuffer.getInt();
        int totalSizeInBits = byteBuffer.getInt();

        // Position in bit buffer at same place as where we got to
        // in the byte buffer.
        int position = byteBuffer.position();
        dataStreamStart = position * 8;

        endDataPosition = 24*8 + totalSizeInBits;

        /*
         * Find the string section. We do this by reading the buffer
         * backwards from the end. The size of the string data area is
         * stored as either a 15 bit number or a 31 bit number at the
         * end of the buffer. Once we have the size, move back from
         * there to get the start of the string data area.
         */

        BitBuffer bitBuffer = BitBuffer.wrap(byteArray, issues);
        identifyStringStream(bitBuffer);

        handleStreamEnd = byteArray.length * 8;
    }

    public BitStreams(byte[] objectBuffer, int byteOffset, Issues issues) {
        this.byteArray = objectBuffer;
        this.issues = issues;

        ByteBuffer objectsBuffer = ByteBuffer.wrap(objectBuffer);
        objectsBuffer.order(ByteOrder.LITTLE_ENDIAN);
        objectsBuffer.position(byteOffset);

        int sizeOfObject = getMS(objectsBuffer);
        int bitSizeOfHandleStream = getUnsignedMC(objectsBuffer);

        dataStreamStart = objectsBuffer.position() * 8;

        int bitSizeOfObjectData = sizeOfObject * 8 - bitSizeOfHandleStream;

        endDataPosition = dataStreamStart + bitSizeOfObjectData;

        handleStreamEnd = endDataPosition + bitSizeOfHandleStream;


        /*
         * Find the string section. We do this by reading the buffer
         * backwards from the end. The size of the string data area is
         * stored as either a 15 bit number or a 31 bit number at the
         * end of the buffer. Once we have the size, move back from
         * there to get the start of the string data area.
         */

        BitBuffer bitBuffer = BitBuffer.wrap(byteArray, issues);
        identifyStringStream(bitBuffer);
    }

    /**
     * Given the position of the end of the combined data and string streams, being also
     * the start of the handle stream, we work backwards to identify the start and end of
     * the string stream.
     * <P>
     * <code>stringStreamStart</code> and <code>stringStreamEnd</code> are set by this method.
     *  
     * @param bitBuffer
     */
    private void identifyStringStream(BitBuffer bitBuffer) {
        /*
         * The last bit indicates if there is a string stream.
         * All versions 2007+ have a string stream, and we don't support
         * prior versions, so this bit should always be set, except in the
         * objects in the objects section where it may not be set.
         */
        int position = endDataPosition;
        position -= 1;
        bitBuffer.position(position);
        boolean endBit = bitBuffer.getB();

        int strDataSize;

        if (endBit) {

            position -= 16;
            bitBuffer.position(position);
            strDataSize = bitBuffer.getRS();
            if ((strDataSize & 0x8000) != 0) {
                position -= 16;
                bitBuffer.position(position);
                int hiSize = bitBuffer.getRS();
                int loSize = -strDataSize;
                strDataSize = (hiSize << 15) | loSize;
            }
        } else {
            strDataSize = 0;
        }
        stringStreamEnd = position;
        stringStreamStart = position - strDataSize;
    }

    public BitBuffer getDataStream() {
        BitBuffer dataStream = BitBuffer.wrap(byteArray, issues);
        dataStream.position(dataStreamStart);
        dataStream.setEndOffset(stringStreamStart);
        return dataStream;
    }

    public BitBuffer getStringStream() {
        BitBuffer stringStream = BitBuffer.wrap(byteArray, issues);
        stringStream.position(stringStreamStart);
        stringStream.setEndOffset(stringStreamEnd);
        return stringStream;
    }

    public BitBuffer getHandleStream() {
        BitBuffer handleStream = BitBuffer.wrap(byteArray, issues);
        handleStream.position(endDataPosition);
        handleStream.setEndOffset(handleStreamEnd);
        return handleStream;
    }

    public static int getMC(ByteBuffer buffer) {
        int result = 0;
        int shift = 0;

        byte b = buffer.get();
        while ((b & 0x80) != 0) {
            int byteValue = (b & 0x7F);
            result |= (byteValue << shift);
            shift += 7;
            b = buffer.get();
        }

        boolean signBit = (b & 0x40) != 0;
        int byteValue = (b & 0x3F);
        result |= (byteValue << shift);

        if (signBit) {
            result = -result;
        }

        return result;
    }

    public static int getUnsignedMC(ByteBuffer buffer) {
        int result = 0;
        int shift = 0;

        boolean highBit;
        do {
            byte b = buffer.get();
            highBit = (b & 0x80) != 0;
            int byteValue = (b & 0x7F);
            result |= (byteValue << shift);
            shift += 7;
        } while (highBit);

        return result;
    }

    public static int getMS(ByteBuffer buffer) {
        int result = 0;
        int shift = 0;

        assert buffer.order() == ByteOrder.LITTLE_ENDIAN;

        boolean highBit;
        do {
            short word = buffer.getShort();
            highBit = (word & 0x8000) != 0;
            int wordValue = (word & 0x7FFF);
            result |= (wordValue << shift);
            shift += 15;
        } while (highBit);

        return result;
    }

} public class Handle
{

    public final int code;

    public final int offset;

    public Handle(int code, int[] handle)
    {
        this.code = code;
        
        int value = 0;
        int shift = 0;
        for (int i = handle.length-1; i >= 0; i--) {
        	value |= handle[i] << shift;
        	shift += 8;
        }
        
        this.offset = value;
    }

    public Handle(int code, int value)
    {
        this.code = code;
        this.offset = value;
    }

    public String toString() {
    	return Integer.toString(code) + ": " + offset + "(" + Integer.toHexString(offset) + ")";
    }
} public class AcdbDictionaryWithDefault extends Dictionary {

    private Handle defaultEntryHandle;

    public AcdbDictionaryWithDefault(ObjectMap objectMap) {
        super(objectMap);
    }
    
    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.43 ACDBDICTIONARYWDFLT page 146

        super.readCommonDictionaryData(dataStream, stringStream, handleStream);

        defaultEntryHandle = handleStream.getHandle();
        
        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public CadObject getDefaultEntry() {
        CadObject result = objectMap.parseObject(defaultEntryHandle);
        return (CadObject) result;
    }

	public String toString() {
		return "ACDBDICTIONARYWDFLT";
	}

} public class AcdbPlaceHolder extends NonEntityObject {

    public AcdbPlaceHolder(ObjectMap objectMap) {
        super(objectMap);
    }
    
	public String toString() {
		return "ACDBPLACEHOLDER";
	}

} public class Appid extends NonEntityObject {

    public String entryName;

    private Handle externalRefBlockHandle;

    public Appid(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.64 APPID (67) page 173

        entryName = stringStream.getTU();
        boolean sixtyFourFlag = dataStream.getB();

        // This is documented in the spec but does not appear to
        // be in the data.
//      int xRefOrdinal = dataStream.getBS();
        boolean xDep = dataStream.getB();
        int unknown = dataStream.getRC();

        externalRefBlockHandle = handleStream.getHandle(handleOfThisObject);

        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public String toString() {
        return "APPID";
    }

    public CadObject getExternalRefBlock() {
        CadObject result = objectMap.parseObjectPossiblyNull(externalRefBlockHandle);
        return (CadObject) result;
    }

} public class AppidControlObj extends NonEntityObject {

    public Handle[] appidObjectHandles;

    public AppidControlObj(ObjectMap objectMap) {
	super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
	// 19.4.63 APPID CONTROL (66) page 172

	int numentries = dataStream.getBL();

	// The handles

	appidObjectHandles = new Handle[numentries];
	for (int i = 0; i < numentries; i++) {
	    appidObjectHandles[i] = handleStream.getHandle(handleOfThisObject);
	}

	handleStream.advanceToByteBoundary();

	dataStream.assertEndOfStream();
	stringStream.assertEndOfStream();
	handleStream.assertEndOfStream();
    }

    @Override
    public String toString() {
	return "APPID CONTROL OBJ";
    }

    public List<Appid> getAppids()
    {
	return new AbstractList<Appid>() {
	    @Override
	    public Appid get(int index)
	    {
		CadObject result = objectMap.parseObjectPossiblyNull(appidObjectHandles[index]);
		return (Appid) result;
	    }

	    @Override
	    public int size()
	    {
		return appidObjectHandles.length;
	    }
	};
    }

} public class Arc extends EntityObject {

    public Point3D center;
    public double radius;
    public double thickness;
    public Point3D extrusion;
    public double startAngle;
    public double endAngle;

    public Arc(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.18 ARC (17) page 117    

        center = dataStream.get3BD();
        radius = dataStream.getBD();
        thickness = dataStream.getBT();
        extrusion = dataStream.getBE();
        startAngle = dataStream.getBD();
        endAngle = dataStream.getBD();

        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public String toString() {
        return "ARC";
    }
} public class Attdef extends EntityObject {

    public double elevation;

    public Point2D insertionPoint;

    public Point2D alignPoint;

    public Point3D extrusion;

    public double thickness;

    public double obliqueAngle;

    public double rotationAngle;

    public double height;

    public double widthFactor;

    public String defaultValue;

    public int generation;

    public int horizontalAlignment;

    public int verticalAlignment;

    public String tag;

    public int fieldLength;

    public int flags;

    public boolean lockPosition;

    public int version;

    public String prompt;

    private Handle styleHandle;

    public Attdef(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.5 ATTDEF (3) page 108

        int dataFlags = dataStream.getUnsignedRC();
        if ((dataFlags & 0x01) == 0) {
            elevation = dataStream.getRD();
        }
        insertionPoint = dataStream.get2RD();
        if ((dataFlags & 0x02) == 0) {
            alignPoint = dataStream.get2DD(insertionPoint);
        }
        extrusion = dataStream.getBE();
        thickness = dataStream.getBT();
        if ((dataFlags & 0x04) == 0) {
            obliqueAngle = dataStream.getRD();
        }
        if ((dataFlags & 0x08) == 0) {
            rotationAngle = dataStream.getRD();
        }
        height = dataStream.getRD();
        if ((dataFlags & 0x10) == 0) {
            widthFactor = dataStream.getRD();
        }
        defaultValue = stringStream.getTU();
        if ((dataFlags & 0x20) == 0) {
            generation = dataStream.getBS();
        }
        if ((dataFlags & 0x40) == 0) {
            horizontalAlignment = dataStream.getBS();
        }
        if ((dataFlags & 0x80) == 0) {
            verticalAlignment = dataStream.getBS();
        }
        tag = stringStream.getTU();
        fieldLength = dataStream.getBS();
        flags = dataStream.getRC();
        lockPosition = dataStream.getB();
        
        // Version appears to be missing.
        // Version makes sense for ATTRIB but not ATTDEF anyway.
//        version = dataStream.getRC();
        
        prompt = stringStream.getTU();
        styleHandle = handleStream.getHandle(handleOfThisObject);
        
        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

	public String toString() {
		return "ATTDEF";
	}

    public CadObject getStyle() {
        CadObject result = objectMap.parseObject(styleHandle);
        return (CadObject) result;
    }

} public class Attrib extends EntityObject {

    public double elevation;

    public Point2D insertionPoint;

    public Point2D alignPoint;

    public Point3D extrusion;

    public double thickness;

    public double obliqueAngle;

    public double rotationAngle;

    public double height;

    public double widthFactor;

    public String value;

    public int generation;

    public int horizontalAlignment;

    public int verticalAlignment;

    public String tag;

    public int fieldLength;

    public int flags;

    public boolean lockPosition;

    public int version;

    private Handle styleHandle;

    public Attrib(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.4 ATTRIB (2) page 107

        int dataFlags = dataStream.getUnsignedRC();
        if ((dataFlags & 0x01) == 0) {
            elevation = dataStream.getRD();
        }
        insertionPoint = dataStream.get2RD();
        if ((dataFlags & 0x02) == 0) {
            alignPoint = dataStream.get2DD(insertionPoint);
        }
        extrusion = dataStream.getBE();
        thickness = dataStream.getBT();
        if ((dataFlags & 0x04) == 0) {
            obliqueAngle = dataStream.getRD();
        }
        if ((dataFlags & 0x08) == 0) {
            rotationAngle = dataStream.getRD();
        }
        height = dataStream.getRD();
        if ((dataFlags & 0x10) == 0) {
        widthFactor = dataStream.getRD();
        }
        value = stringStream.getTU();
        if ((dataFlags & 0x20) == 0) {
        generation = dataStream.getBS();
        }
        if ((dataFlags & 0x40) == 0) {
            horizontalAlignment = dataStream.getBS();
        }
        if ((dataFlags & 0x80) == 0) {
            verticalAlignment = dataStream.getBS();
        }
        version = dataStream.getRC();
        tag = stringStream.getTU();
        fieldLength = dataStream.getBS();
        flags = dataStream.getRC();
        lockPosition = dataStream.getB();

        styleHandle = handleStream.getHandle(handleOfThisObject);
        
        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

	public String toString() {
		return "ATTRIB";
	}

    public CadObject getStyle() {
        CadObject result = objectMap.parseObject(styleHandle);
        return (CadObject) result;
    }

} public class Block extends EntityObject {

    public String blockName;

    public Block(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.6 BLOCK (4) page 110

        blockName = stringStream.getTU();

        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

	public String toString() {
		return "BLOCK";
	}

} public class BlockControlObj extends NonEntityObject {

    private Handle[] blockObjectHandles;
    private Handle modelSpaceHandle;
    private Handle paperSpaceHandle;

	public BlockControlObj(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.49 BLOCK CONTROL (48) page 154

        int numentries = dataStream.getBL();
        
        // The handles
        
        blockObjectHandles = new Handle[numentries];
        for (int i = 0; i < numentries; i++) {
            blockObjectHandles[i] = handleStream.getHandle(handleOfThisObject);
        }
        modelSpaceHandle = handleStream.getHandle();
        paperSpaceHandle = handleStream.getHandle();
        
        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public String toString() {
		return "BLOCK CONTROL OBJ";
	}

    public List<BlockHeader> getBlockHeaders()
    {
        return new AbstractList<BlockHeader>() {

            @Override
            public BlockHeader get(int index)
            {
                CadObject result = objectMap.parseObjectPossiblyNull(blockObjectHandles[index]);
                return (BlockHeader) result;
            }

            @Override
            public int size()
            {
                return blockObjectHandles.length;
            }
        };
    }

    public CadObject getModelSpace() {
        CadObject result = objectMap.parseObjectPossiblyNull(modelSpaceHandle);
        return (CadObject) result;
    }

    public CadObject getPaperSpace() {
        CadObject result = objectMap.parseObjectPossiblyNull(paperSpaceHandle);
        return (CadObject) result;
    }

} public class BlockHeader extends NonEntityObject {

    public String entryName;
    public boolean sixtyFourFlag;
    private boolean blockIsXref;
    private boolean xrefOverlaid;
    public boolean loadedBit;
    public Point3D basePoint;
    public String xrefPName;
    public String blockDescription;
    private int[] previewData;
    public int insertUnits;
    public boolean explodable;
    public int blockScaling;
    private Handle firstEntityHandle;
    private Handle lastEntityHandle;
    private Handle[] ownedObjectHandles;
    private Handle endBlockHandle;
    private List<List<Handle>> insertHandles;
    private Handle layoutHandle;

    public BlockHeader(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream,
            FileVersion fileVersion) {
        // 19.4.50 BLOCK HEADER (49) page 155

        entryName = stringStream.getTU();
        sixtyFourFlag = dataStream.getB();
        /*
         * At this point we always seem to be three bits prior to a word that
         * best matches the expected flags. We get there reliably if we don't
         * read the xrefindex value. TODO investigate this as this code may not
         * be correct.
         */
        // int xrefordinal = dataStream.getBS();
        boolean xdep = dataStream.getB();
        boolean anonymous = dataStream.getB();
        boolean hasAttributes = dataStream.getB();
        blockIsXref = dataStream.getB();
        xrefOverlaid = dataStream.getB();
        loadedBit = dataStream.getB();
        int ownedObjectCount = dataStream.getBL();
        basePoint = dataStream.get3BD();
        xrefPName = stringStream.getTU();

        List<Integer> insertCounts = new ArrayList<>();
        int thisInsertCount = dataStream.getRC();
        while (thisInsertCount != 0) {
            insertCounts.add(thisInsertCount);
            thisInsertCount = dataStream.getRC();
        }

        blockDescription = stringStream.getTU();
        int sizeOfPreviewData = dataStream.getBL();
        previewData = dataStream.getBytes(sizeOfPreviewData);
        insertUnits = dataStream.getBS();
        explodable = dataStream.getB();
        blockScaling = dataStream.getRC();

        // The handles

        if (!blockIsXref && !xrefOverlaid) {
            firstEntityHandle = handleStream.getHandle(handleOfThisObject);
            lastEntityHandle = handleStream.getHandle(handleOfThisObject);
        }

        ownedObjectHandles = new Handle[ownedObjectCount];
        for (int i = 0; i < ownedObjectCount; i++) {
            ownedObjectHandles[i] = handleStream.getHandle();
        }

        endBlockHandle = handleStream.getHandle();

        insertHandles = new ArrayList<>();
        for (Integer insertCount : insertCounts) {
            List<Handle> thisList = new ArrayList<>();
            for (int i = 0; i < insertCount; i++) {
                Handle insertHandle = handleStream.getHandle();
                thisList.add(insertHandle);
            }
            insertHandles.add(thisList);
        }

        layoutHandle = handleStream.getHandle();

        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    @Override
    public String toString() {
        return "BLOCK HEADER";
    }

    public CadObject getFirstEntity() {
        if (firstEntityHandle == null) {
            return null;
        } else {
            CadObject result = objectMap.parseObjectPossiblyNull(firstEntityHandle);
            return result;
        }
    }

    public CadObject getLastEntity() {
        if (lastEntityHandle == null) {
            return null;
        } else {
            CadObject result = objectMap.parseObjectPossiblyNull(lastEntityHandle);
            return result;
        }
    }

    public List<CadObject> getOwnedObjects() {
        return new AbstractList<CadObject>() {

            @Override
            public CadObject get(int index) {
                CadObject result = objectMap.parseObject(ownedObjectHandles[index]);
                return result;
            }

            @Override
            public int size() {
                return ownedObjectHandles.length;
            }
        };
    }

    public CadObject getEndBlock() {
        CadObject result = objectMap.parseObject(endBlockHandle);
        return result;
    }

    /**
     * This method returns all inserts in a flat list. This no doubt needs to be
     * changed to return a two dimensional list in case consumers need the extra
     * information.
     */
    public List<Insert> getInserts() {
        List<Insert> result = new ArrayList<>();

        for (List<Handle> x : insertHandles) {
            for (Handle y : x) {
                CadObject insert = objectMap.parseObjectPossiblyOrphaned(y);
                result.add((Insert) insert);
            }
        }
        return result;
    }

    public CadObject getLayout() {
        CadObject result = objectMap.parseObjectPossiblyNull(layoutHandle);
        return result;
    }

} public abstract class CadObject {

    protected final ObjectMap objectMap;

    public Handle handleOfThisObject;

    // Defined in this class but always set in derived classes
    protected Handle[] reactorHandles;

    private List<Handle> genericHandles = new ArrayList<>();

    private Map<Handle, Object[]> extendedEntityData = new HashMap<>();

    // Defined in this class but always set in derived classes
    protected Handle xdicobjhandle;

    public CadObject(ObjectMap objectMap) {
        this.objectMap = objectMap;
    }

    public void readFromStreams(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream,
            FileVersion fileVersion) {
        handleOfThisObject = dataStream.getHandle();

        // Page 254 Chapter 27 Extended Entity Data

        int sizeOfExtendedObjectData = dataStream.getBS();
        while (sizeOfExtendedObjectData != 0) {
            Handle appHandle = dataStream.getHandle();

            List<Object> components = new ArrayList<>();
            Stack<List<Object>> componentStack = new Stack<>();

            int expected = dataStream.position() + sizeOfExtendedObjectData * 8;

            while (dataStream.position() < expected) {
                int dxfGroupCode = dataStream.getRC();

                switch (dxfGroupCode) {
                case 0: {
                    int length = dataStream.getRC();
                    byte[] x = new byte[length * 2];
                    for (int i = 0; i < length * 2; i++) {
                        x[i] = (byte) dataStream.getRC();
                    }
                    try {
                        String text = new String(x, "UTF-16");
                        components.add(text);
                    } catch (UnsupportedEncodingException e) {
                        throw new RuntimeException("Should not happen");
                    }

                    int unknown = dataStream.getRC();

                    break;
                }

                case 2: {
                    int x = dataStream.getRC();
                    switch (x) {
                    case 0:
                        componentStack.push(components);
                        components = new ArrayList<>();
                        break;
                    case 1:
                        List<Object> justCompletedList = components;
                        components = componentStack.pop();
                        components.add(justCompletedList.toArray());
                        break;
                    }
                }
                break;

                case 3:
                case 5: {
                    int[] handleBytes = dataStream.getBytes(8);
                    Handle handle = new Handle(5, handleBytes);
                    // TODO distinguish between 3 and 5, otherwise
                    // the information is lost.
                    components.add(handle);
                }
                break;

                case 4: {
                    int length = dataStream.getRC();
                    byte[] x = new byte[length];
                    for (int i = 0; i < length; i++) {
                        x[i] = (byte) dataStream.getRC();
                    }
                    components.add(x);
                }
                break;

                case 10:
                case 11:
                case 12:
                case 13: {
                    double x = dataStream.getRD();
                    double y = dataStream.getRD();
                    double z = dataStream.getRD();
                    Point3D point = new Point3D(x, y, z);
                    components.add(point);
                }
                break;

                case 40:
                case 41:
                case 42: {
                    double x = dataStream.getRD();
                    components.add(x);
                }
                break;

                case 70: {
                    int x = dataStream.getRS();
                    components.add(Short.valueOf((short) x));
                }
                break;

                case 71: {
                    int x = dataStream.getRL();
                    components.add(Long.valueOf(x));
                }
                break;

                default:
                    throw new RuntimeException("Unexpected case");
                }
            }

            assert dataStream.position() == expected;

            extendedEntityData.put(appHandle, components.toArray());

            sizeOfExtendedObjectData = dataStream.getBS();
        }

        readPostCommonFields(dataStream, stringStream, handleStream, fileVersion);
    }

    abstract protected void readPostCommonFields(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream,
            FileVersion fileVersion);

    protected void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream,
            FileVersion fileVersion) {
        // For the time being, provide this default implementation that
        // just reads all the handles.
        // Ultimately this should be an abstract method.

        try {
            do {
                Handle referencedHandle = handleStream.getHandle(handleOfThisObject);
                genericHandles.add(referencedHandle);
            } while (true);
        } catch (RuntimeException e) {

        }
        handleStream.advanceToByteBoundary();
        handleStream.assertEndOfStream();
    }

    public List<CadObject> getReactors() {
        return new AbstractList<CadObject>() {

            @Override
            public CadObject get(int index) {
                if (reactorHandles[index] == null) {
                    return null;
                } else {
                    CadObject result = objectMap.parseObject(reactorHandles[index]);
                    return result;
                }
            }

            @Override
            public int size() {
                return reactorHandles.length;
            }
        };
    }

    public Dictionary getXDictionary() {
        if (xdicobjhandle == null) {
            return null;
        } else {
            CadObject result = objectMap.parseObject(xdicobjhandle);
            return (Dictionary) result;
        }
    }

    public Map<Appid, Object[]> getExtendedEntityData() {
        return new AbstractMap<Appid, Object[]>() {

            @Override
            public Set<java.util.Map.Entry<Appid, Object[]>> entrySet() {
                return new AbstractSet<java.util.Map.Entry<Appid, Object[]>>() {

                    @Override
                    public Iterator<java.util.Map.Entry<Appid, Object[]>> iterator() {
                        final Iterator<java.util.Map.Entry<Handle, Object[]>> iter = extendedEntityData.entrySet()
                                .iterator();
                        return new Iterator<java.util.Map.Entry<Appid, Object[]>>() {

                            @Override
                            public boolean hasNext() {
                                return iter.hasNext();
                            }

                            @Override
                            public java.util.Map.Entry<Appid, Object[]> next() {
                                final java.util.Map.Entry<Handle, Object[]> e = iter.next();
                                return new java.util.Map.Entry<Appid, Object[]>() {

                                    @Override
                                    public Appid getKey() {
                                        return (Appid) objectMap.parseObject(e.getKey());
                                    }

                                    @Override
                                    public Object[] getValue() {
                                        return e.getValue();
                                    }

                                    @Override
                                    public Object[] setValue(Object[] arg0) {
                                        throw new UnsupportedOperationException();
                                    }
                                };
                            }

                            @Override
                            public void remove() {
                                throw new UnsupportedOperationException();
                            }
                        };
                    }

                    @Override
                    public int size() {
                        return extendedEntityData.size();
                    }
                };
            }
        };
    }

    public Dictionary getXdicobj() {
        if (xdicobjhandle == null) {
            return null;
        } else {
            CadObject result = objectMap.parseObject(xdicobjhandle);
            return (Dictionary) result;
        }
    }

    public List<CadObject> getGenericObjects() {
        return new AbstractList<CadObject>() {

            @Override
            public CadObject get(int index) {
                CadObject result = objectMap.parseObjectPossiblyNullOrOrphaned(genericHandles.get(index));
                return result;
            }

            @Override
            public int size() {
                return genericHandles.size();
            }
        };
    }
} public class Circle extends EntityObject {

    public Point3D center;
    public double radius;
    public double thickness;
    public Point3D extrusion;

    public Circle(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.19 CIRCLE (18) page 118    

        center = dataStream.get3BD();
        radius = dataStream.getBD();
        thickness = dataStream.getBT();
        extrusion = dataStream.getBE();

        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public String toString() {
        return "CIRCLE";
    }
} public class Dictionary extends NonEntityObject {

    public Map<String, Handle> dictionaryMap;

    public Dictionary(ObjectMap objectMap) {
        super(objectMap);
    }
    
    @Override
	public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.42 DICTIONARY (42)

        readCommonDictionaryData(dataStream, stringStream, handleStream);
        
        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
	}

    protected void readCommonDictionaryData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream) {
        int numItems = dataStream.getBL();

        int cloningFlag = dataStream.getBS();
        int hardOwnerFlag = dataStream.getRC();
    
        // The handles
        
        dictionaryMap = new HashMap<>();
        for (int i = 0; i < numItems; i++) {
            String key = stringStream.getTU();
            Handle handle = handleStream.getHandle(handleOfThisObject);
            dictionaryMap.put(key, handle);
        }
    }

	public String toString() {
		return "DICTIONARY";
	}

    public CadObject lookupObject(String key)
    {
        Handle handle = dictionaryMap.get(key);
        return objectMap.parseObjectPossiblyNull(handle);
    }

    public Set<String> getKeys() {
        return dictionaryMap.keySet();
    }

} public class DimStyle extends NonEntityObject {

	public DimStyle(ObjectMap objectMap) {
        super(objectMap);
    }

    public String toString() {
		return "DIMSTYLE";
	}

} public class DimensionLinear extends Dimension {

    public DimensionLinear(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readDimensionSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {

        // 19.4.23 DIMENSION (LINEAR) (21) page 121

        Point3D thirteenPoint = dataStream.get3BD();
        Point3D fourteenPoint = dataStream.get3BD();
        Point3D tenPoint = dataStream.get3BD();

        double extensionLineRotation = dataStream.getBD();
        double dimensionRotation = dataStream.getBD();

        // Read all handles (until we figure out what they are)

        Handle dimstyleHandle = handleStream.getHandle();
        Handle anonymousBlockHandle = handleStream.getHandle();

        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public String toString() {
        return "DIMENSION (LINEAR)";
    }
} public class DimstyleControlObj extends NonEntityObject {

	public DimstyleControlObj(ObjectMap objectMap) {
        super(objectMap);
    }

    public String toString() {
		return "DIMSTYLE CONTROL OBJ";
	}

} public class Endblk extends EntityObject {

	public Endblk(ObjectMap objectMap) {
        super(objectMap);
    }

    public String toString() {
		return "ENDBLK";
	}

} public class GenericObject extends NonEntityObject {

	public String objectType;

	public GenericObject(ObjectMap objectMap, int objectType) {
        super(objectMap);
		this.objectType = Integer.toString(objectType);
	}

	public GenericObject(ObjectMap objectMap, String classdxfname) {
        super(objectMap);
		this.objectType = classdxfname;
	}

	public String toString() {
		return "unknown object of type " + objectType;
	}

} public class Hatch extends EntityObject {

    public static class PolylinePathSegment {
        public Point2D pt0;
        public double bulge;
    }

    public class PolylinePath extends Path {
        public boolean bulgesPresent;
        public boolean closed;
        public PolylinePathSegment[] pathSegments;

        @Override
        public void readFromDataStream(BitBuffer dataStream, BitBuffer handleStream, FileVersion fileVersion) {
            bulgesPresent = dataStream.getB();
            closed = dataStream.getB();
            int numPathSegs = dataStream.getBL();
            pathSegments = new PolylinePathSegment[numPathSegs];
            for (int j = 0; j < numPathSegs; j++) {
                pathSegments[j].pt0 = dataStream.get2RD();
                if (bulgesPresent) {
                    pathSegments[j].bulge = dataStream.getBD();
                }
            }

            super.readBoundaryItemCountAndHandles(dataStream, handleStream);
        }
    }

    public class SegmentedPath extends Path {
        public PathType[] pathSegments;

        @Override
        public void readFromDataStream(BitBuffer dataStream, BitBuffer handleStream, FileVersion fileVersion) {
            int numPathSegs = dataStream.getBL();
            pathSegments = new PathType[numPathSegs];
            for (int j = 0; j < numPathSegs; j++) {
                int pathTypeStatus = dataStream.getRC();
                switch (pathTypeStatus) {
                case 1: // LINE
                    pathSegments[j] = new LinePathType();
                    break;
                case 2: // CIRCULAR ARC
                    pathSegments[j] = new CircularArcPathType();
                    break;
                case 3: // ELIPTICAL ARC
                    pathSegments[j] = new ElipticalArcPathType();
                    break;
                case 4: // SPLINE
                    pathSegments[j] = new SplinePathType();
                    break;
                default:
                    throw new RuntimeException("unexpected case");
                }

                pathSegments[j].readFromDataStream(dataStream, fileVersion);
            }

            super.readBoundaryItemCountAndHandles(dataStream, handleStream);
        }
    }

    public abstract class Path {
        private Handle[] boundaryObjHandles;

        public abstract void readFromDataStream(BitBuffer dataStream, BitBuffer handleStream, FileVersion fileVersion);

        public void readBoundaryItemCountAndHandles(BitBuffer dataStream, BitBuffer handleStream) {
            int numboundaryobjhandles = dataStream.getBL();
            boundaryObjHandles = new Handle[numboundaryobjhandles];
            for (int i = 0; i < numboundaryobjhandles; i++) {
                boundaryObjHandles[i] = handleStream.getHandle(handleOfThisObject);
            }
        }
    }

    public static class DefLine {

        public double angle;
        public Point2D pt0;
        public Point2D offset;
        public double[] dashLengths;

        public void readFromDataStream(BitBuffer dataStream) {
            angle = dataStream.getBD();
            pt0 = dataStream.get2RD();
            offset = dataStream.get2BD();
            int numDashes = dataStream.getBL();
            dashLengths = new double[numDashes];
            for (int i = 0; i < numDashes; i++) {
                dashLengths[i] = dataStream.getBD();
            }
        }
    }

    public static class Fill {

        public double angle;
        public Point2D pt0;
        public double scaleOrSpacing;
        public boolean doubleHatch;
        public DefLine[] defLines;

        public void readFromDataStream(BitBuffer dataStream) {
            angle = dataStream.getBD();
            pt0 = dataStream.get2RD();
            scaleOrSpacing = dataStream.getBD();
            doubleHatch = dataStream.getB();
            int numDefLines = dataStream.getBS();
            defLines = new DefLine[numDefLines];
            for (int i = 0; i < numDefLines; i++) {
                defLines[i] = new DefLine();
                defLines[i].readFromDataStream(dataStream);
            }
        }

    }

    public static class GradientColor {
        public double unknownDouble;
        public int unknownShort;
        public int rgbColor;
        public int ignoredColorByte;
    }

    public static class ControlPoint {
        public Point2D pt0;
        public double weight;
    }

    public abstract class PathType {
        public abstract void readFromDataStream(BitBuffer dataStream, FileVersion fileVersion);
    }

    public class LinePathType extends PathType {
        public double pt0;
        public double pt1;

        @Override
        public void readFromDataStream(BitBuffer dataStream, FileVersion fileVersion) {
            pt0 = dataStream.getRD();
            pt1 = dataStream.getRD();
        }
    }

    public class CircularArcPathType extends PathType {
        public double pt0;
        public double radius;
        public double startAngle;
        public double endAngle;
        public boolean isCcw;

        @Override
        public void readFromDataStream(BitBuffer dataStream, FileVersion fileVersion) {
            pt0 = dataStream.getRD();
            radius = dataStream.getBD();
            startAngle = dataStream.getBD();
            endAngle = dataStream.getBD();
            isCcw = dataStream.getB();
        }
    }

    public class ElipticalArcPathType extends PathType {
        public Point2D pt0;
        public Point2D endPoint;
        public double minorMajorRatio;
        public double startAngle;
        public double endAngle;
        public boolean isCcw;

        @Override
        public void readFromDataStream(BitBuffer dataStream, FileVersion fileVersion) {
            pt0 = dataStream.get2RD();
            endPoint = dataStream.get2RD();
            minorMajorRatio = dataStream.getBD();
            startAngle = dataStream.getBD();
            endAngle = dataStream.getBD();
            isCcw = dataStream.getB();
        }
    }

    public class SplinePathType extends PathType {

        public int degree;
        public boolean isPeriodic;
        public double[] knots;
        public ControlPoint[] controlPoints;
        public Point2D[] fitPoints;
        public Point2D startTangent;
        public Point2D endTanget;

        @Override
        public void readFromDataStream(BitBuffer dataStream, FileVersion fileVersion) {
            degree = dataStream.getBL();
            boolean isRational = dataStream.getB();
            isPeriodic = dataStream.getB();
            int numKnots = dataStream.getBL();
            int numControlPoints = dataStream.getBL();
            knots = new double[numKnots];
            for (int i = 0; i < numKnots; i++) {
                knots[i] = dataStream.getBD();
            }
            controlPoints = new ControlPoint[numControlPoints];
            for (int i = 0; i < numControlPoints; i++) {
                controlPoints[i] = new ControlPoint();
                controlPoints[i].pt0 = dataStream.get2RD();
                if (isRational) {
                    controlPoints[i].weight = dataStream.getBD();
                }
            }
            if (!fileVersion.is2013OrLater()) {
                int numFitPoints = dataStream.getBL();
                fitPoints = new Point2D[numFitPoints];
                for (int i = 0; i < numFitPoints; i++) {
                    fitPoints[i] = dataStream.get2RD();
                }
                startTangent = dataStream.get2RD();
                endTanget = dataStream.get2RD();
            }
        }
    }

    public Point3D start;
    public Point3D end;
    public double thickness;
    public Point3D extrusion;
    public int isGradientFill;
    public int reserved;
    public double gradientAngle;
    public double gradientShift;
    public int singleColorGradient;
    public double gradientTint;
    public GradientColor[] gradientColors;
    public String gradientName;
    public double zCoordinate;
    public Point3D extrusion2;
    public String name;
    public boolean associative;
    public Path[] paths;
    public int style;
    public int patternType;
    public Fill fill;
    public double pixelSize;
    public Point2D[] seedPoints;

    public Hatch(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream,
            FileVersion fileVersion) {

        // 19.4.73 HATCH (78) page 184

        isGradientFill = dataStream.getBL();
        reserved = dataStream.getBL();
        gradientAngle = dataStream.getBD();
        gradientShift = dataStream.getBD();
        singleColorGradient = dataStream.getBL();
        gradientTint = dataStream.getBD();
        int numberOfGradientColors = dataStream.getBL();
        gradientColors = new GradientColor[numberOfGradientColors];
        for (int i = 0; i < numberOfGradientColors; i++) {
            gradientColors[i].unknownDouble = dataStream.getBD();
            gradientColors[i].unknownShort = dataStream.getBS();
            gradientColors[i].rgbColor = dataStream.getBL();
            gradientColors[i].ignoredColorByte = dataStream.getRC();
        }
        gradientName = stringStream.getTU();
        zCoordinate = dataStream.getBD();
        extrusion2 = dataStream.get3BD();
        name = stringStream.getTU();
        boolean solidFill = dataStream.getB();
        associative = dataStream.getB();

        boolean pixelSizePresent = false;

        int numPaths = dataStream.getBL();
        paths = new Path[numPaths];
        for (int i = 0; i < numPaths; i++) {
            int pathFlag = dataStream.getBL();
            if ((pathFlag & 0x02) == 0) {
                paths[i] = new SegmentedPath();
            } else {
                // Polyline path
                paths[i] = new PolylinePath();
            }

            paths[i].readFromDataStream(dataStream, handleStream, fileVersion);

            // Extra, not documented in spec.
            // This field is not present when numPaths = 0 so
            // this is assumed to be a field that exists in each path.
            // known to appear in R27.
            int unknown = dataStream.getBL();

            pixelSizePresent |= (pathFlag & 0x04) != 0;
        }

        style = dataStream.getBS();
        patternType = dataStream.getBS();
        if (!solidFill) {
            fill = new Fill();
            fill.readFromDataStream(dataStream);
        }

        if (pixelSizePresent) {
            pixelSize = dataStream.getBD();
        }

        int numSeedPoints = dataStream.getBL();
        seedPoints = new Point2D[numSeedPoints];
        for (int i = 0; i < numSeedPoints; i++) {
            seedPoints[i] = dataStream.get2RD();
        }

        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    @Override
    public String toString() {
        return "HATCH";
    }
} public class Insert extends EntityObject {

    public Point3D insertPoint;
    public double xScaleFactor;
    public double yScaleFactor;
    public double zScaleFactor;
    public double rotation;
    public Point3D extrusion;
    private Handle blockHeaderHandle;
    private Handle[] ownedObjectHandles;
    private Handle seqEndHandle;

    public Insert(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.9 INSERT (7) page 111

        insertPoint = dataStream.get3BD();
        int dataFlags = dataStream.getBB();
        switch (dataFlags) {
        case 0:
            xScaleFactor = dataStream.getRD();
            yScaleFactor = dataStream.getDD(xScaleFactor);
            zScaleFactor = dataStream.getDD(xScaleFactor);
            break;
        case 1:
            xScaleFactor = 1.0;
            yScaleFactor = dataStream.getDD(xScaleFactor);
            zScaleFactor = dataStream.getDD(xScaleFactor);
            break;
        case 2:
            xScaleFactor = dataStream.getRD();
            yScaleFactor = xScaleFactor;
            zScaleFactor = xScaleFactor;
            break;
        case 3:
            xScaleFactor = 1.0;
            yScaleFactor = 1.0;
            zScaleFactor = 1.0;
            break;
        }

        rotation = dataStream.getBD();
        extrusion = dataStream.get3BD();
        boolean hasAttributes = dataStream.getB();
        int ownedObjectCount = 0;
        if (hasAttributes) {
            ownedObjectCount = dataStream.getBL();
        }

        // The handles

        blockHeaderHandle = handleStream.getHandle();

        ownedObjectHandles = new Handle[ownedObjectCount];
        for (int i = 0; i< ownedObjectCount; i++) {
            ownedObjectHandles[i] = handleStream.getHandle();
        }

        if (hasAttributes) {
            seqEndHandle = handleStream.getHandle();
        }

        handleStream.advanceToByteBoundary();
        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    @Override
    public String toString() {
        return "INSERT";
    }

    public BlockHeader getBlockHeader() {
        CadObject result = objectMap.parseObject(blockHeaderHandle);
        return (BlockHeader) result;
    }

    public List<CadObject> getOwnedObjects()
    {
        return new AbstractList<CadObject>() {

            @Override
            public CadObject get(int index)
            {
                CadObject result = objectMap.parseObject(ownedObjectHandles[index]);
                return result;
            }

            @Override
            public int size()
            {
                return ownedObjectHandles.length;
            }
        };
    }

    public CadObject getSeqEnd() {
        if (seqEndHandle == null) {
            return null;
        } else {
            CadObject result = objectMap.parseObject(seqEndHandle);
            return result;
        }
    }

} public class LType extends NonEntityObject {

    public class Dash
    {
        public double dashLength;
        public int complexShapecode;
        public double xOffset;
        public double yOffset;
        public double scale;
        public double rotation;
        public int shapeFlag;
        private Handle shapefileForDashHandle;
        public String textToBeDrawn;

        public void readFromDataStream(BitBuffer dataStream) {
            dashLength = dataStream.getBD();
            complexShapecode = dataStream.getBS();
            xOffset = dataStream.getRD();
            yOffset = dataStream.getRD();
            scale = dataStream.getBD();
            rotation = dataStream.getBD();
            shapeFlag = dataStream.getBS();
        }
        
        public void readFromHandleStream(BitBuffer handleStream) {
            shapefileForDashHandle = handleStream.getHandle();
        }

        public CadObject getShapefileForDash() {
            CadObject result = objectMap.parseObjectPossiblyNull(shapefileForDashHandle);
            return (CadObject) result;
        }

        public void extractTextFromTextArea(int[] textArea) {
            int textLength = 0;
            for (int i = complexShapecode; i < textArea.length && textArea[i] != 0; i++) {
                textLength++;
            }
            byte[] extractedBytes = new byte[textLength];
            for (int j = 0; j < textLength; j++) {
                extractedBytes[j] = (byte)textArea[complexShapecode+j];
            }
            try {
                textToBeDrawn = new String(extractedBytes, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                objectMap.getIssues().addWarning("Invalid text in LType text area.  The text is not valid UTF-8.");
            }
        }
    }

    public String entryName;
    public boolean sixtyFourFlag;
    public boolean xDep;
    public String description;
    public double patternLen;
    public Dash[] dashes;
    private Handle externalReferenceBlockHandle;

    public LType(ObjectMap objectMap) {
        super(objectMap);
    }
    
    @Override
	public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
		// 19.4.56 LTYPE 57 page 162  

		entryName = stringStream.getTU();
		
		sixtyFourFlag = dataStream.getB();
		xDep = dataStream.getB();
		description = stringStream.getTU();
		patternLen = dataStream.getBD();
		dataStream.expectRC(65);
		int numDashes = dataStream.getRC();

		boolean isTextAreaPresent = false;
		
		dashes = new Dash[numDashes];
        for (int i = 0; i < numDashes; i++) {
            dashes[i] = new Dash();
            dashes[i].readFromDataStream(dataStream);
            
            isTextAreaPresent = isTextAreaPresent || ((dashes[i].shapeFlag & 0x02) != 0);
        }
		
		// The 512 byte area
        if (isTextAreaPresent) {
            int[] textArea = dataStream.getBytes(512);
            
            for (int i = 0; i < numDashes; i++) {
                if (((dashes[i].shapeFlag & 0x02) != 0)) {
                    dashes[i].extractTextFromTextArea(textArea);
                }
            }
        }

		// The handles.
		
		externalReferenceBlockHandle = handleStream.getHandle();
		for (int i = 0; i < numDashes; i++) {
            dashes[i].readFromHandleStream(handleStream);
        }
		
		handleStream.advanceToByteBoundary();
        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
		handleStream.assertEndOfStream();
	}

	public String toString() {
		return "LTYPE";
	}

    public CadObject getExternalReferenceBlock() {
        CadObject result = objectMap.parseObjectPossiblyNull(externalReferenceBlockHandle);
        return (CadObject) result;
    }

} public class LTypeControlObj extends NonEntityObject {

	private Handle[] lineTypeHandles;
	private Handle bylayerLinetypeHandle;
	private Handle byblockLinetypeHandle;

	public LTypeControlObj(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
	public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
		// 19.4.55 LINETYPE CONTROL (56)
		
		int numEntries = dataStream.getBL();

		dataStream.assertEndOfStream();
		
		// Here starts the handle area
		
		lineTypeHandles = new Handle[numEntries];
		for (int i = 0; i < numEntries; i++){
		    lineTypeHandles[i] = handleStream.getHandle(handleOfThisObject);
		}

		bylayerLinetypeHandle = handleStream.getHandle();
		byblockLinetypeHandle = handleStream.getHandle();
		
		handleStream.advanceToByteBoundary();
		handleStream.assertEndOfStream();
	}

	public String toString() {
		return "LTYPE CONTROL OBJ";
	}

    public List<LType> getLineTypes() {
        return new AbstractList<LType>() {

            @Override
            public LType get(int index)
            {
                return (LType)objectMap.parseObjectPossiblyNull(lineTypeHandles[index]);
            }

            @Override
            public int size()
            {
                return lineTypeHandles.length;
            }
        };
    }

    public LType getBylayerLinetype() {
        CadObject result = objectMap.parseObject(bylayerLinetypeHandle);
        return (LType) result;
    }

    public LType getByblockLinetype() {
        CadObject result = objectMap.parseObject(byblockLinetypeHandle);
        return (LType) result;
    }

} public class Layer extends NonEntityObject {

    public String entryName;
    public CmColor color;
    private Handle externalReferenceBlockHandle;
    private Handle plotStyleHandle;
    private Handle lineTypeHandle;
    private Handle materialHandle;

    public Layer(ObjectMap objectMap) {
        super(objectMap);
    }
    
    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.52 LAYER (51) page 158

        entryName = stringStream.getTU();
        boolean sixtyFourFlag = dataStream.getB();
        /*
         * At this point we always seem to be three bits prior to a word
         * that best matches the expected flags.  We get there reliably if
         * we don't read the xrefindex value.  TODO investigate this as this
         * code may not be correct.
         */
//        int xrefordinal = dataStream.getBS();
        boolean xdep = dataStream.getB();
        int flags = dataStream.getBS();
        color = dataStream.getCMC();
        
        // The handles
        
        externalReferenceBlockHandle = handleStream.getHandle();
        plotStyleHandle = handleStream.getHandle();
        materialHandle = handleStream.getHandle(handleOfThisObject);
        lineTypeHandle = handleStream.getHandle(handleOfThisObject);

        // It appears that, contrary to the specification, this handle is not present in 2010 (R24).
        if (fileVersion.is2013OrLater()) {
            Handle nullHandle = handleStream.getHandle();
        }

        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public String toString() {
        return "LAYER";
    }

    public CadObject getExternalReferenceBlock() {
        CadObject result = objectMap.parseObjectPossiblyNull(externalReferenceBlockHandle);
        return (CadObject) result;
    }

    public AcdbPlaceHolder getPlotStyle() {
        CadObject result = objectMap.parseObject(plotStyleHandle);
        return (AcdbPlaceHolder) result;
    }

    public CadObject getLineType() {
        CadObject result = objectMap.parseObject(lineTypeHandle);
        return (CadObject) result;
    }

    public CadObject getMaterial() {
        CadObject result = objectMap.parseObject(materialHandle);
        return (CadObject) result;
    }

} public class LayerControlObj extends NonEntityObject {

    public Handle[] layerObjectHandles;

    public LayerControlObj(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream,
            FileVersion fileVersion) {
        // 19.4.51 LAYER CONTROL (50) page 157

        int numentries = dataStream.getBL();

        // The handles

        layerObjectHandles = new Handle[numentries];
        for (int i = 0; i < numentries; i++) {
            layerObjectHandles[i] = handleStream.getHandle(handleOfThisObject);
        }

        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    @Override
    public String toString() {
        return "LAYER CONTROL OBJ";
    }

    public List<Layer> getLayers() {
        return new AbstractList<Layer>() {

            @Override
            public Layer get(int index) {
                CadObject result = objectMap.parseObjectPossiblyNull(layerObjectHandles[index]);
                return (Layer) result;
            }

            @Override
            public int size() {
                return layerObjectHandles.length;
            }
        };
    }

} public class Layout extends NonEntityObject {

    public String pageSetupName;
    public String printerOrConfig;
    public int plotLayoutFlags;
    public double leftMargin;
    public double bottomMargin;
    public double rightMargin;
    public double topMargin;
    public double paperWidth;
    public double paperHeight;
    public String paperSize;
    public Point2D plotOrigin;
    public int paperUnits;
    public int plotRotation;
    public int plotType;
    public Point2D windowMinimum;
    public Point2D windowMaximum;
    public double realWorldUnits;
    public double drawingUnits;
    public String currentStyleSheet;
    public int scaleType;
    public double scaleFactor;
    public Point2D paperImageOrigin;
    public int shadePlotMode;
    public int shadePlotResLevel;
    public int shadePlotCustomDpi;
    public String layoutName;
    public int tabOrder;
    public int flag;
    public Point3D ucsOrigin;
    public Point2D limitMinimum;
    public Point2D limitMaximum;
    public Point3D insertionPoint;
    public Point3D ucsXAxis;
    public Point3D ucsYAxis;
    public double elevation;
    public int orthographicViewType;
    public Point3D extentMinimum;
    public Point3D extentMaximum;
    private Handle plotViewHandle;
    private Handle visualStyleHandle;
    private Handle paperspaceBlockRecordHandle;
    private Handle lastActiveViewportHandle;
    private Handle baseUcsHandle;
    private Handle namedUcsHandle;
    private Handle[] viewportHandles;

    public Layout(ObjectMap objectMap) {
        super(objectMap);
    }
    
    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.81 LAYOUT (82)  page 196

        pageSetupName = stringStream.getTU();
        printerOrConfig = stringStream.getTU();
        plotLayoutFlags = dataStream.getBS();
        leftMargin = dataStream.getBD();
        bottomMargin = dataStream.getBD();
        rightMargin = dataStream.getBD();
        topMargin = dataStream.getBD();
        paperWidth = dataStream.getBD();
        paperHeight = dataStream.getBD();
        paperSize = stringStream.getTU();
        plotOrigin = dataStream.get2BD();
        paperUnits = dataStream.getBS();
        plotRotation = dataStream.getBS();
        plotType = dataStream.getBS();
        windowMinimum = dataStream.get2BD();
        windowMaximum = dataStream.get2BD();
        realWorldUnits = dataStream.getBD();
        drawingUnits = dataStream.getBD();
        currentStyleSheet = stringStream.getTU();
        scaleType = dataStream.getBS();
        scaleFactor = dataStream.getBD();
        paperImageOrigin = dataStream.get2BD();
        shadePlotMode = dataStream.getBS();
        shadePlotResLevel = dataStream.getBS();
        shadePlotCustomDpi = dataStream.getBS();
        layoutName = stringStream.getTU();
        tabOrder = dataStream.getBS();
        flag = dataStream.getBS();
        ucsOrigin = dataStream.get3BD();
        limitMinimum = dataStream.get2RD();
        limitMaximum = dataStream.get2RD();
        insertionPoint = dataStream.get3BD();
        ucsXAxis = dataStream.get3BD();
        ucsYAxis = dataStream.get3BD();
        elevation = dataStream.getBD();
        orthographicViewType = dataStream.getBS();
        extentMinimum = dataStream.get3BD();
        extentMaximum = dataStream.get3BD();
        
        // Only 2 bits left, so certainly not a raw long!
        // Bit long more likely.
        int viewportCount = dataStream.getBL();
    
        // The handles

        plotViewHandle = handleStream.getHandle();
        visualStyleHandle = handleStream.getHandle(handleOfThisObject);
        paperspaceBlockRecordHandle = handleStream.getHandle(handleOfThisObject);
        lastActiveViewportHandle = handleStream.getHandle(handleOfThisObject);
        baseUcsHandle = handleStream.getHandle();
        namedUcsHandle = handleStream.getHandle();
        viewportHandles = new Handle[viewportCount];
        for (int i = 0; i < viewportCount; i++) {
            viewportHandles[i] = handleStream.getHandle(handleOfThisObject);
        }
        
        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public CadObject getPlotView() {
        CadObject result = objectMap.parseObjectPossiblyNull(plotViewHandle);
        return result;
    }

    public CadObject getVisualStyle() {
        CadObject result = objectMap.parseObjectPossiblyNull(visualStyleHandle);
        return result;
    }

    public BlockHeader getPaperspaceBlockRecord() {
        CadObject result = objectMap.parseObject(paperspaceBlockRecordHandle);
        return (BlockHeader)result;
    }

    public CadObject getLastActiveViewport() {
        CadObject result = objectMap.parseObjectPossiblyNull(lastActiveViewportHandle);
        if (!(result == null || result instanceof VPort || result instanceof ViewPort)) {
            throw new RuntimeException("unexpected object class");
        }
        return result;
    }

    public CadObject getBaseUcs() {
        CadObject result = objectMap.parseObjectPossiblyNull(baseUcsHandle);
        return result;
    }

    public CadObject getNamedUcs() {
        CadObject result = objectMap.parseObjectPossiblyNull(namedUcsHandle);
        return result;
    }

    // TODO common base for ViewPort and VPort ????
    public List<CadObject> getViewPorts()
    {
        return new AbstractList<CadObject>() {

            @Override
            public CadObject get(int index)
            {
                CadObject result = objectMap.parseObject(viewportHandles[index]);
                if (!(result instanceof VPort || result instanceof ViewPort)) {
                    throw new RuntimeException("unexpected object class");
                }
                return (CadObject) result;
            }

            @Override
            public int size()
            {
                return viewportHandles.length;
            }
        };
    }

	public String toString() {
		return "LAYOUT";
	}

} public class Leader extends EntityObject {

    public int annotType;
    public int pathType;
    public Point3D[] points;
    public Point3D endPointProj;
    public Point3D extrusion;
    public Point3D xDirection;
    public Point3D offsetToBlockInsPt;
    public double unknown1;
    /** possibly boxWidth */
    public double unknown2;
    /** possibly boxHeight */
    public double unknown3;
    public boolean hooklineOnXDir;
    public boolean arrowHeadOn;
    public int unknownInt;
    public boolean unknownBit1;
    public boolean unknownBit2;
    private Handle annotationHandle;
    private Handle dimStyleHandle;

    public Leader(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.45 LEADER (45) page 147    

        dataStream.expectB(false);
        annotType = dataStream.getBS();
        pathType = dataStream.getBS();
        int numberOfPoints = dataStream.getBL();
        
        points = new Point3D[numberOfPoints];
        for (int i = 0; i < numberOfPoints; i++) {
            points[i] = dataStream.get3BD();
        }

        endPointProj = dataStream.get3BD();
        extrusion = dataStream.get3BD();
        xDirection = dataStream.get3BD();
        offsetToBlockInsPt = dataStream.get3BD();
        /*
         * There now appear to be three bit-decimals.
         * The specification say to expect 3 bit-decimals
         * followed by boxHeight and boxWidth.  So it is
         * unknown whether we have a single bit-decimal
         * followed by boxHeight and boxWidth, or whether
         * the specification is wrong in including the box height
         * and width.  To avoid guessing wrong we treat as three
         * separate unknown decimals.
         */
        unknown1 = dataStream.getBD();
        unknown2 = dataStream.getBD();
        unknown3 = dataStream.getBD();
        
        hooklineOnXDir = dataStream.getB();
        arrowHeadOn = dataStream.getB();
        unknownInt = dataStream.getBS();
        unknownBit1 = dataStream.getB();
        unknownBit2 = dataStream.getB();

        // The Handles
        
        annotationHandle = handleStream.getHandle();
        dimStyleHandle = handleStream.getHandle();
        
        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public String toString() {
        return "LEADER";
    }

    public CadObject getAnnotation() {
        CadObject result = objectMap.parseObject(annotationHandle);
        return (CadObject) result;
    }

    public CadObject getDimStyle() {
        CadObject result = objectMap.parseObject(dimStyleHandle);
        return (CadObject) result;
    }

} public class Line extends EntityObject {

    public Point3D start;
    public Point3D end;
    public double thickness;
    public Point3D extrusion;

    public Line(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {

        // 19.4.20 LINE (19) page 118

        boolean zAreZero = dataStream.getB();

        double startX = dataStream.getRD();
        double endX = dataStream.getDD(startX);
        double startY = dataStream.getRD();
        double endY = dataStream.getDD(startY);
        double startZ;
        double endZ;
        if (zAreZero) {
            startZ = 0;
            endZ = 0;
        } else {
            startZ = dataStream.getRD();
            endZ = dataStream.getDD(startZ);
        }
        start = new Point3D(startX, startY, startZ);
        end = new Point3D(endX, endY, endZ);
        
        thickness = dataStream.getBT();
        extrusion = dataStream.getBE();

        // Read all handles (until we figure out what they are)

//        try {
//            do {
//                Handle referencedHandle = handleStream.getHandle(handleOfThisObject);
//                genericHandles.add(referencedHandle);
//            } while (true);
//        } catch (RuntimeException e) {
//
//        }


        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public String toString() {
        return "LINE";
    }
} public class LwPolyline extends EntityObject {

    public Handle referencedHandle;

    public List<VertexOfLwPolyline> points;

    public Double constantWidth;

    public boolean isClosed;

    public LwPolyline(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {

        boolean b1 = dataStream.getB();
        boolean b2 = dataStream.getB();

        int numberOfPoints;
        int numberOfVariableWidths = 0;
        int numberOfBulges = 0;

        if (b1) {
            // It seems that the polyline is always closed when b1 is set.
            isClosed = true;

            numberOfPoints = dataStream.getBS();
        } else {
            boolean bit1 = dataStream.getB();
            boolean bit2 = dataStream.getB();
            boolean areVariableWidthsPresent = dataStream.getB();
            boolean areBulgesPresent = dataStream.getB();
            dataStream.expectB(false);
            boolean bitC = dataStream.getB();
            dataStream.expectB(false);
            dataStream.expectB(false);

            assert !bit1;
            assert !bit2;

            if (!areVariableWidthsPresent) {

                if (areBulgesPresent != bitC) {
                    System.out.println("different");
                }

                boolean hasWidth = areBulgesPresent;

                if (!b2) {
                    int flags17 = dataStream.getBitsUnsigned(8);
                    if (flags17 != 2) {
                        throw new RuntimeException("new case");
                    }

                    isClosed = true;

                    if (hasWidth) {
                        constantWidth = dataStream.getBD();
                        numberOfPoints = dataStream.getBS();
                        if (numberOfPoints == 0) {
                            System.out.println("it's gone wrong");
                        }
                        if (areVariableWidthsPresent == areBulgesPresent) {
                            System.out.println("here");
                        }
                        if (areVariableWidthsPresent) {
                            numberOfVariableWidths = dataStream.getBS();
                            if (numberOfPoints != numberOfVariableWidths) {
                                throw new RuntimeException();
                            }
                        }
                        if (areBulgesPresent) {
                            numberOfBulges = dataStream.getBS();
                            if (numberOfPoints != numberOfBulges) {
                                throw new RuntimeException();
                            }
                        }
                    } else {
                        if (bitC) {
                            // When this bit is set there appears to be an extra
                            // bit-double.  TODO compare to DXF to determine what
                            // this is.
                            double x = dataStream.getBD();
                        }
                        numberOfPoints = dataStream.getBS();
                        if (numberOfPoints == 0) {
                            System.out.println("it's gone wrong");
                        }
                        numberOfVariableWidths = 0;
                        numberOfBulges = 0;
                    }
                } else {
                    isClosed = false;

                    constantWidth = dataStream.getBD();
                    numberOfPoints = dataStream.getBS();
                    if (numberOfPoints == 0) {
                        System.out.println("it's gone wrong");
                    }

                    numberOfVariableWidths = 0;
                    numberOfBulges = 0;
                    if (hasWidth) {
                        if (areVariableWidthsPresent == areBulgesPresent) {
                            System.out.println("");
                        }
                        if (areVariableWidthsPresent) {
                            numberOfVariableWidths = dataStream.getBS();
                            if (numberOfVariableWidths != numberOfPoints) {
                                throw new RuntimeException();
                            }
                        }
                        if (areBulgesPresent) {
                            numberOfBulges = dataStream.getBS();
                            if (numberOfBulges != numberOfPoints) {
                                throw new RuntimeException();
                            }
                        }
                    }
                }
            } else {
                isClosed = false;

                if (!b2) {
                    // Actually this path is never hit in the test files.
                    System.out.println("this case fails");
                }

                if (areBulgesPresent || bitC) {
                    System.out.println("here");
                }

                numberOfPoints = dataStream.getBS();
                if (areVariableWidthsPresent) {
                    numberOfVariableWidths = dataStream.getBS();
                    if (numberOfVariableWidths != numberOfPoints) {
                        throw new RuntimeException();
                    }
                } else {
                    numberOfBulges = dataStream.getBS();
                    if (numberOfBulges != numberOfPoints) {
                        throw new RuntimeException();
                    }
                }
                if (numberOfPoints != 2 || numberOfVariableWidths != 2) {
                    System.out.println("here");
                }
            }
        }

        points = new ArrayList<>(numberOfPoints);

        double x = dataStream.getRD();
        double y = dataStream.getRD();
        points.add(new VertexOfLwPolyline(x, y));
        for (int index = 1; index < numberOfPoints; index++) {
            x = dataStream.getDD(x);
            y = dataStream.getDD(y);
            points.add(new VertexOfLwPolyline(x, y));
        }

        for (int index = 0; index < numberOfVariableWidths; index++) {
            double startingWidth = dataStream.getBD();
            double endingWidth = dataStream.getBD();
            points.get(index).setVariableWidth(startingWidth, endingWidth);
        }

        for (int index = 0; index < numberOfBulges; index++) {
            double bulge = dataStream.getBD();
            points.get(index).setBulge(bulge);
        }

        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    @Override
    public String toString() {
        return "LWPOLYLINE";
    }

    public static class VertexOfLwPolyline {

        public final Point2D vertex;
        public double startingWidth;
        public double endingWidth;
        public double bulge;

        public VertexOfLwPolyline(double x, double y) {
            vertex = new Point2D(x, y);
        }

        @Override
        public String toString() {
            return vertex.toString();
        }

        public void setVariableWidth(double startingWidth, double endingWidth) {
            this.startingWidth = startingWidth;
            this.endingWidth = endingWidth;
        }

        public void setBulge(double bulge) {
            this.bulge = bulge;
        }
    }
} public class MLineStyle extends NonEntityObject {

	public MLineStyle(ObjectMap objectMap) {
        super(objectMap);
    }

    public String toString() {
		return "MLINESTYLE";
	}

} public class MText extends EntityObject {

    public Point3D insertionPoint;
    public Point3D extrusion;
    public Point3D xAxisDir;
    public double rectWidth;
    public double rectHeight;
    public double textHeight;
    public int attachment;
    public int drawingDir;
    public double extents;
    public double extentsWid;
    public String text;
    public int linespacingStyle;
    public double linespacingFactor;
    public boolean unknownBit;
    public double backgroundScaleFactor;
    public CmColor backgroundColor;
    public int backgroundTransparency;
    private Handle styleHandle;

    public MText(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.44 MTEXT (44) page 146    

        insertionPoint = dataStream.get3BD();
        extrusion = dataStream.get3BD();
        xAxisDir = dataStream.get3BD();
        rectWidth = dataStream.getBD();
        rectHeight = dataStream.getBD();
        textHeight = dataStream.getBD();
        attachment = dataStream.getBS();
        drawingDir = dataStream.getBS();
        extents = dataStream.getBD();
        extentsWid = dataStream.getBD();
        text = stringStream.getTU();
        linespacingStyle = dataStream.getBS();
        linespacingFactor = dataStream.getBD();
        unknownBit = dataStream.getB();
        int backgroundFlags = dataStream.getBL();
        
        if (backgroundFlags == 1) {
            backgroundScaleFactor = dataStream.getBD();
            backgroundColor = dataStream.getCMC();
            backgroundTransparency = dataStream.getBL();
        } else {
            backgroundScaleFactor = 1.5;
        }

        // The Handles
        
        styleHandle = handleStream.getHandle();
        
        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public String toString() {
        return "MTEXT";
    }

    public CadObject getStyle() {
        CadObject result = objectMap.parseObject(styleHandle);
        return (CadObject) result;
    }

} public class ObjectMap
{
    private Reader reader;

    public ObjectMap(Reader reader) {
        this.reader = reader;
    }

    public Issues getIssues() {
        return reader.getIssues();
    }

    public CadObject parseObject(Handle handle)
    {
        return reader.parseObject(handle);
    }

    public CadObject parseObjectPossiblyNull(Handle handle)
    {
        if (handle.offset == 0) {
            return null;
        } else {
            return reader.parseObject(handle);
        }
    }

    public CadObject parseObjectPossiblyOrphaned(Handle handle)
    {
        return reader.parseObjectPossiblyOrphaned(handle);
    }

    public CadObject parseObjectPossiblyNullOrOrphaned(Handle handle)
    {
        if (handle.offset == 0)
        {
            return null;
        }
        else
        {
            return reader.parseObjectPossiblyOrphaned(handle);
        }
    }

} public class PlaneSurface extends NonEntityObject {

    public PlaneSurface(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public String toString() {
        return "PLANESURFACE";
    }

} public class Point extends EntityObject {

    public Point3D point;
    
    public double thickness;
    
    public Point3D extrusion;
    
    public double xAxisAngle;

    public Point(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {

        // 19.4.29 POINT (27) page 125

        point = dataStream.get3BD();
        thickness = dataStream.getBT();
        extrusion = dataStream.getBE();
        xAxisAngle = dataStream.getBD();

        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public String toString() {
        return "POINT";
    }
} public class PolylineMesh extends EntityObject {

    public int flags;

    public int curveType;

    public int mVertexCount;

    public int nVertexCount;
    
    public int mDensity;

    public int nDensity;

    private Handle[] vertexHandles;

    private Handle seqEndHandle;

	public PolylineMesh(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.32 POLYLINE (MESH) (30) page 128

        flags = dataStream.getBS();
        curveType = dataStream.getBS();
        mVertexCount = dataStream.getBS();
        nVertexCount = dataStream.getBS();
        mDensity = dataStream.getBS();
        nDensity = dataStream.getBS();
        int ownedObjectCount = dataStream.getBL();
        
        // The handles
        
        vertexHandles = new Handle[ownedObjectCount];
        for (int i = 0; i < ownedObjectCount; i++) {
            vertexHandles[i] = handleStream.getHandle(handleOfThisObject);
        }
        seqEndHandle = handleStream.getHandle();
        
        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public String toString() {
		return "POLYLINE (MESH)";
	}

    public List<EntityObject> getOwnedObjects()
    {
        return new AbstractList<EntityObject>() {

            @Override
            public EntityObject get(int index)
            {
                CadObject result = objectMap.parseObject(vertexHandles[index]);
                return (EntityObject) result;
            }

            @Override
            public int size()
            {
                return vertexHandles.length;
            }
        };
    }

    public CadObject getSeqEnd() {
        CadObject result = objectMap.parseObjectPossiblyNull(seqEndHandle);
        return (CadObject) result;
    }

} public class PolylinePFace extends EntityObject {

    public int numVertexes;

    public int numFaces;
    
    private Handle[] vertexHandles;

    private Handle seqEndHandle;

	public PolylinePFace(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.31 POLYLINE (PFACE) (29) page 127

        numVertexes = dataStream.getBS();
        numFaces = dataStream.getBS();
        int ownedObjectCount = dataStream.getBL();
        
        // The handles
        
        vertexHandles = new Handle[ownedObjectCount];
        for (int i = 0; i < ownedObjectCount; i++) {
            vertexHandles[i] = handleStream.getHandle(handleOfThisObject);
        }
        seqEndHandle = handleStream.getHandle();
        
        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public String toString() {
		return "POLYLINE (PFACE)";
	}

    public List<EntityObject> getOwnedObjects()
    {
        return new AbstractList<EntityObject>() {

            @Override
            public EntityObject get(int index)
            {
                CadObject result = objectMap.parseObject(vertexHandles[index]);
                return (EntityObject) result;
            }

            @Override
            public int size()
            {
                return vertexHandles.length;
            }
        };
    }

    public CadObject getSeqEnd() {
        CadObject result = objectMap.parseObjectPossiblyNull(seqEndHandle);
        return (CadObject) result;
    }

} public class SeqEnd extends EntityObject {

	public SeqEnd(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.8 SEQEND (6) page 111

        // These objects contain no data specific to the object type.

        // Occasionally a SEQEND will have a null handle.  The meaning is unknown.
        try {
        Handle nullHandle = handleStream.getHandle();
        if (nullHandle.offset != 0) {
            throw new RuntimeException("Unexpected handle in SeqEnd object, and it is not null.");
        }
        } catch (Exception e) {
            // This is the normal case.
        }
        
        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public String toString() {
		return "SEQEND";
	}
} public class Solid extends EntityObject {

    public double thickness;
    public double elevation;
    public Point2D corner1;
    public Point2D corner2;
    public Point2D corner3;
    public Point2D corner4;
    public Point3D extrusion;

    public Solid(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.33 SOLID (31) page 129    

        thickness = dataStream.getBT();
        elevation = dataStream.getBD();
        corner1 = dataStream.get2RD();
        corner2 = dataStream.get2RD();
        corner3 = dataStream.get2RD();
        corner4 = dataStream.get2RD();
        extrusion = dataStream.getBE();

        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public String toString() {
        return "SOLID";
    }
} public class SortEntsTable extends NonEntityObject {

    private Handle[] sortHandles;
    private Handle ownerHandle;
    private Handle[] entityHandles;

    public SortEntsTable(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.90 SORTENTSTABLE page 207

        int numEntries = dataStream.getBL();

        sortHandles = new Handle[numEntries];
        for (int i = 0; i < numEntries; i++) {
            sortHandles[i] = dataStream.getHandle();
        }

        // The handles

        ownerHandle = handleStream.getHandle(handleOfThisObject);

        entityHandles = new Handle[numEntries];
        for (int i = 0; i < numEntries; i++) {
            entityHandles[i] = handleStream.getHandle(handleOfThisObject);
        }

        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public List<CadObject> getSortObjects()
    {
        return new AbstractList<CadObject>() {

            @Override
            public CadObject get(int index)
            {
                /*
                 * The sort values may contain handles that actually do not
                 * exist any more in the handle table.
                 */
                CadObject result = objectMap.parseObjectPossiblyOrphaned(sortHandles[index]);
                return result;
            }

            @Override
            public int size()
            {
                return sortHandles.length;
            }
        };
    }

    public CadObject getOwner() {
        CadObject result = objectMap.parseObject(ownerHandle);
        return result;
    }

    public List<CadObject> getEntities()
    {
        return new AbstractList<CadObject>() {

            @Override
            public CadObject get(int index)
            {
                /*
                 * The sort entities may contain handles that actually do not
                 * exist any more in the handle table.
                 */
                CadObject result = objectMap.parseObjectPossiblyOrphaned(entityHandles[index]);
                return result;
            }

            @Override
            public int size()
            {
                return entityHandles.length;
            }
        };
    }

    @Override
    public String toString() {
        return "SORTENTSTABLE";
    }

} public class Spline extends EntityObject {

    public class CtrlPoint {

        private Point3D controlPoint;
        private Double weight;

        public void read(BitBuffer dataStream) {
            controlPoint = dataStream.get3BD();
            if (weightsPresent) {
                weight = dataStream.getBD();
            }
        }
    }

    public Point3D center;
    public double radius;
    public double thickness;
    public Point3D extrusion;

    private int scenario;
    private int splineFlags1;
    private int knotParameter;
    private int degree;

    private double fitTolerance;
    private Point3D beginningTangentVector;
    private Point3D endingTangentVector;

    private boolean rational;
    private boolean closed;
    private boolean periodic;
    private double knotTolerance;
    private double ctrlTolerance;
    private boolean weightsPresent;
    private double[] knotValues;
    private CtrlPoint[] ctrlPoints;
    private Point3D[] fitPoints;

    public Spline(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.38 SPLINE (36) page 134

        scenario = dataStream.getBL();
        if (fileVersion.is2013OrLater()) {
            splineFlags1 = dataStream.getBL();
            knotParameter = dataStream.getBL();

            switch(knotParameter) {
            case 0:
            case 1:
            case 2:
                scenario = 1;
                break;
            case 15:
                scenario = 1;
                break;
            default:
                scenario = 1;
                knotParameter = 15;
                break;
            }
        }

        degree = dataStream.getBL();

        if (scenario == 2) {
            fitTolerance = dataStream.getBD();
            beginningTangentVector = dataStream.get3BD();
            endingTangentVector = dataStream.get3BD();
            int numberOfFitPoints = dataStream.getBL();

            fitPoints = new Point3D[numberOfFitPoints];
            for (int i = 0; i < numberOfFitPoints; i++) {
                fitPoints[i] = dataStream.get3BD();
            }
        }

        if (scenario == 1) {
            rational = dataStream.getB();
            closed = dataStream.getB();
            periodic = dataStream.getB();
            knotTolerance = dataStream.getBD();
            ctrlTolerance = dataStream.getBD();
            int numberOfKnots = dataStream.getBL();
            int numberOfCtrlPoints = dataStream.getBL();
            weightsPresent = dataStream.getB();

            knotValues = new double[numberOfKnots];
            for (int i = 0; i < numberOfKnots; i++) {
                knotValues[i] = dataStream.getBD();
            }

            ctrlPoints = new CtrlPoint[numberOfCtrlPoints];
            for (int i = 0; i < numberOfCtrlPoints; i++) {
                ctrlPoints[i] = new CtrlPoint();
                ctrlPoints[i].read(dataStream);
            }
        }

        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    @Override
    public String toString() {
        return "SPLINE";
    }
} public class Style extends NonEntityObject {

    public Style(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
	public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.54 SHAPEFILE (53)
        
        
        
        String entryName = stringStream.getTU();
        String fontName = stringStream.getTU();
        String bigFontName = stringStream.getTU();
	}

	public String toString() {
		return "STYLE";
	}

} public class StyleControlObj extends NonEntityObject {

	public StyleControlObj(ObjectMap objectMap) {
        super(objectMap);
    }

    public String toString() {
		return "STYLE CONTROL OBJ";
	}

} public class Text extends EntityObject {

    public double elevation;
    public Point2D insertionPoint;
    public Point2D alignmentPoint;
    public Point3D extrusion;
    public double thickness;
    public double obliqueAngle;
    public double rotationAngle;
    public double height;
    public double widthFactor;
    public String textValue;
    public int generation;
    public int horizontalAlignment;
    public int verticalAlignment;
    public Handle styleHandle;

    public Text(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.3 TEXT (1) page 106    

        int dataFlags = dataStream.getUnsignedRC();
        if ((dataFlags & 0x01) == 0) {
            elevation = dataStream.getRD();
        }
        insertionPoint = dataStream.get2RD();
        if ((dataFlags & 0x02) == 0) {
            alignmentPoint = dataStream.get2DD(insertionPoint);
        }
        extrusion = dataStream.getBE();
        thickness = dataStream.getBT();
        if ((dataFlags & 0x04) == 0) {
            obliqueAngle = dataStream.getRD();
        }
        if ((dataFlags & 0x08) == 0) {
            rotationAngle = dataStream.getRD();
        }
        height = dataStream.getRD();
        if ((dataFlags & 0x10) == 0) {
            widthFactor = dataStream.getRD();
        }
        textValue = stringStream.getTU();
        if ((dataFlags & 0x20) == 0) {
            generation = dataStream.getBS();
        }
        if ((dataFlags & 0x40) == 0) {
            horizontalAlignment = dataStream.getBS();
        }
        if ((dataFlags & 0x80) == 0) {
            verticalAlignment = dataStream.getBS();
        }

        // The Handles
        
        styleHandle = handleStream.getHandle();
        
        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public String toString() {
        return "TEXT";
    }
} public class ThreeDSolid extends EntityObject {

    public static class Wire {

        public int wireType;
        public int wireSelectionMarker;
        public int wireColor;
        public int wireAcisIndex;
        public Point3D[] points;

        public void readFromStream(BitBuffer dataStream) {
            wireType = dataStream.getRC();
            wireSelectionMarker = dataStream.getBL();
            wireColor = dataStream.getBS();
            wireAcisIndex = dataStream.getBL();
            int wireNumberOfPoints = dataStream.getBL();
            points = new Point3D[wireNumberOfPoints];
            for (int j = 0; j < wireNumberOfPoints; j++) {
                points[j] = dataStream.get3BD();
            }
            boolean transformPresent = dataStream.getB();
            if (transformPresent) {
                Point3D xAxis = dataStream.get3BD();
                Point3D yAxis = dataStream.get3BD();
                Point3D zAxis = dataStream.get3BD();
                Point3D translation = dataStream.get3BD();
                double scale = dataStream.getBD();
                boolean hasRotation = dataStream.getB();
                boolean hasReflection = dataStream.getB();
                boolean hasShear = dataStream.getB();
            }
        }

    }

    public Point3D point;

    public Handle historyId;

    public Wire [] wires;

    public ThreeDSolid(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.39 REGION (37), 3DSOLID (38), BODY (39) page 137

        // TODO need to read as Common Entity Data is described in 19.4.1 page 104
        // (Common Entity Format read above)

        boolean acisEmptyBit = dataStream.getB();
        boolean unknownBit = dataStream.getB();

        int version = dataStream.getBS();
        if (version == 1) {
            int blockSize = dataStream.getBL();
            for (int i = 0; i < blockSize; i++) {
                dataStream.getRC();
            }
        } else if (version == 2) {
            // process ACIS file

            StringBuilder header = new StringBuilder();
            byte [] h2 = new byte[15];
            for (int i = 0; i < 15; i++) {
                h2[i] = (byte)dataStream.getRC();
            }

            String str;
            try
            {
                str = new String(h2, "UTF-8");
            } catch (UnsupportedEncodingException e)
            {
                throw new RuntimeException(e);
            }

            if (str.equals("ACIS BinaryFile")) {
                byte[] endMarker;
                try
                {
                    endMarker = "End..of..ACIS..data".getBytes("UTF-8");
                } catch (UnsupportedEncodingException e)
                {
                    throw new RuntimeException(e);
                }

                endMarker[3] = 0x0E;
                endMarker[4] = 0x02;
                endMarker[7] = 0x0E;
                endMarker[8] = 0x04;
                endMarker[13] = 0x0D;
                endMarker[14] = 0x04;

                byte sourceByte = (byte)dataStream.getRC();
                do {
                    int numMatches = 0;
                    for (byte endMarkerByte : endMarker) {
                        if (sourceByte != endMarkerByte) {
                            break;
                        }
                        numMatches++;
                        if (numMatches < endMarker.length) {
                            sourceByte = (byte)dataStream.getRC();
                        }
                    }

                    if (numMatches == endMarker.length) {
                        break;
                    }

                    if (numMatches == 0) {
                        sourceByte = (byte)dataStream.getRC();
                    }
                } while (true);

                // We should return a ByteBuffer for the binary data.
                //                ByteBuffer.wrap(bigArray, offset, length).slice()

            } else {
                objectMap.getIssues().addWarning("text file present but not yet supported");
            }
        } else {
            throw new RuntimeException("Version must be 1 or 2");
        }

        // TODO check range of fields covered by this flag:
        // (need sample file with flag set off)
        boolean wireframeDataPresent = dataStream.getB();
        if (wireframeDataPresent) {
            boolean pointPresent = dataStream.getB();
            if (pointPresent) {
                point = dataStream.get3BD();
            }
            int numIsoLines = dataStream.getBL();
            boolean isoPresent = dataStream.getB();
            int numWires = dataStream.getBL();

            wires = new Wire[numWires];
            for (int i = 0; i < numWires; i++) {
                wires[i] = new Wire();
                wires[i].readFromStream(dataStream);
            }

            int numberOfSilhouettes = dataStream.getBL();
            for (int i = 0; i < numberOfSilhouettes; i++) {
                // TODO complete this.
                // (file reads without this because numberOfSilhouettes is zero)

            }

            for (int i = 0; i < numWires; i++) {
                // TODO complete this.
                // why num wires again?  And this data does not exist anyway.  Despite loads of wires
                // there are just enough bits left for a B and BL.  Probably this should be nested in the silhouettes
                // loop but need file with silhouettes to confirm.

            }

            boolean acisEmptyBit2 = dataStream.getB();
            int unknown = dataStream.getBL();
        }

        historyId = handleStream.getHandle();

        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    @Override
    public String toString() {
        return "3DSOLID";
    }

    private class ByteBufferAsInputStream extends InputStream {

        private ByteBuffer byteBuffer;

        ByteBufferAsInputStream(ByteBuffer byteBuffer) {
            this.byteBuffer = byteBuffer;
        }

        @Override
        public int read() throws IOException {
            if (!byteBuffer.hasRemaining()) {
                return -1;
            }
            return byteBuffer.get();
        }

        @Override
        public int read(byte[] bytes, int offset, int length) throws IOException {
            length = Math.min(byteBuffer.remaining(), length);
            byteBuffer.get(bytes, offset, length);
            return length;
        }
    }
} public class TwoDPolyline extends EntityObject {

    public boolean isClosed;
    public boolean curveFitVerticesAdded;
    public boolean splineFitVerticesAdded;
    public boolean is3DPolyline;
    public boolean is3DPolygonMesh;
    public boolean isMeshClosedInNDirection;
    public boolean isPolyfaceMesh;
    public boolean continuousLinetypePattern;
    public int curveType;
    public double startWidth;
    public double endWidth;
    public double thickness;
    public double elevation;
    public Point3D extrusion;
    private Handle[] ownedObjectHandles;
    private Handle seqEndHandle;

    public TwoDPolyline(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.16 2D POLYLINE (15) page 115

        int flags = dataStream.getBS();

        isClosed = (flags & 0x01) != 0;
        curveFitVerticesAdded = (flags & 0x02) != 0;
        splineFitVerticesAdded = (flags & 0x04) != 0;
        is3DPolyline = (flags & 0x08) != 0;
        is3DPolygonMesh = (flags & 0x10) != 0;
        isMeshClosedInNDirection = (flags & 0x20) != 0;
        isPolyfaceMesh = (flags & 0x40) != 0;
        continuousLinetypePattern = (flags & 0x80) != 0;

        curveType = dataStream.getBS();
        startWidth = dataStream.getBD();
        endWidth = dataStream.getBD();
        thickness = dataStream.getBT();
        elevation = dataStream.getBD();
        extrusion = dataStream.getBE();
        int ownedObjectCount = dataStream.getBL();

        // The Handles

        ownedObjectHandles = new Handle[ownedObjectCount];
        for (int i = 0; i< ownedObjectCount; i++) {
            ownedObjectHandles[i] = handleStream.getHandle();
        }

        seqEndHandle = handleStream.getHandle();

        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public String toString() {
        return "2D POLYLINE";
    }

    public List<EntityObject> getOwnedObjects()
    {
        return new AbstractList<EntityObject>() {

            @Override
            public EntityObject get(int index)
            {
                CadObject result = objectMap.parseObject(ownedObjectHandles[index]);
                return (EntityObject) result;
            }

            @Override
            public int size()
            {
                return ownedObjectHandles.length;
            }
        };
    }

    public SeqEnd getSeqEnd() {
        CadObject result = objectMap.parseObjectPossiblyNull(seqEndHandle);
        return (SeqEnd) result;
    }

} public class Ucs extends NonEntityObject {

	public Ucs(ObjectMap objectMap) {
        super(objectMap);
    }

    public String toString() {
		return "UCS";
	}

} public class UcsControlObj extends NonEntityObject {

	public UcsControlObj(ObjectMap objectMap) {
        super(objectMap);
    }

    public String toString() {
		return "UCS CONTROL OBJ";
	}

} public class VPort extends NonEntityObject {

    public VPort(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
	public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.62 VPORT 65 page 169    

	    // Similar to LTYPE 57
	    
        String entryName = stringStream.getTU();
        
        boolean sixtyFourFlag = dataStream.getB();
//        int xRefOrdinal = dataStream.getBS();
        boolean xDep = dataStream.getB();
      double viewHeight = dataStream.getBD();
      double aspectRatio = dataStream.getBD();
      double viewCenter1 = dataStream.getRD();
      double viewCenter2 = dataStream.getRD();
      double viewTarget1 = dataStream.getBD();
      double viewTarget2 = dataStream.getBD();
      double viewTarget3 = dataStream.getBD();
      double viewDir1 = dataStream.getBD();
      double viewDir2 = dataStream.getBD();
      double viewDir3 = dataStream.getBD();
      double viewTwist = dataStream.getBD();
      double lensLength = dataStream.getBD();
      double frontClip = dataStream.getBD();
      double backClip = dataStream.getBD();
      boolean viewMode0 = dataStream.getB();
      boolean viewMode1 = dataStream.getB();
      boolean viewMode2 = dataStream.getB();
      boolean viewMode3 = dataStream.getB();
      int renderMode = dataStream.getRC();
      boolean useDefaultLights = dataStream.getB();
      int defaultLightingType = dataStream.getRC();
      double brightness = dataStream.getBD();
      double contrast = dataStream.getBD();
      CmColor ambientColor = dataStream.getCMC();
      double lowerLeft1 = dataStream.getRD(); 
      double lowerLeft2 = dataStream.getRD(); 
      double upperRight1 = dataStream.getRD(); 
      double upperRight2 = dataStream.getRD(); 
      boolean UCSFOLLOW = dataStream.getB();
      int circleZoom = dataStream.getBS(); 
      boolean fastZoom = dataStream.getB();
      boolean ucsIcon1 = dataStream.getB();
      boolean ucsIcon2 = dataStream.getB();
      boolean gridFlag = dataStream.getB();
      double gridSpacing1 = dataStream.getRD(); 
      double gridSpacing2 = dataStream.getRD(); 
      boolean snapFlag = dataStream.getB();
      boolean snapStyle = dataStream.getB();
      int snapIsopair = dataStream.getBS(); 
      double snapRot = dataStream.getBD(); 
      double snapBase1 = dataStream.getRD(); 
      double snapBase2 = dataStream.getRD(); 
      double snapSpacing1 = dataStream.getRD(); 
      double snapSpacing2 = dataStream.getRD(); 
      boolean unknown = dataStream.getB();
      boolean ucsPerViewport = dataStream.getB();
      double ucsOrigin1 = dataStream.getBD(); 
      double ucsOrigin2 = dataStream.getBD(); 
      double ucsOrigin3 = dataStream.getBD(); 
      double ucsXAxis1 = dataStream.getBD(); 
      double ucsXAxis2 = dataStream.getBD(); 
      double ucsXAxis3 = dataStream.getBD(); 
      double ucsYAxis1 = dataStream.getBD(); 
      double ucsYAxis2 = dataStream.getBD(); 
      double ucsYAxis3 = dataStream.getBD(); 
      double ucsElevation = dataStream.getBD(); 
      int ucsOrthographicType = dataStream.getBS(); 
      int gridFlags = dataStream.getBS(); 
      int gridMajor = dataStream.getBS(); 
      
      dataStream.assertEndOfStream();
	}

	public String toString() {
		return "VPORT";
	}

} public class VPortControlObj extends NonEntityObject {

	public VPortControlObj(ObjectMap objectMap) {
        super(objectMap);
    }

    public String toString() {
		return "VPORT CONTROL OBJ";
	}

} public class Vertex2D extends EntityObject {

    public int flags;
    public Point3D point;
    public double startWidth;
    public double endWidth;
    public double bulge;
    public int vertexId;
    public double tangentDir;

    public Vertex2D(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {
        // 19.4.11 VERTEX (2D) (10) page 113

        flags = dataStream.getRC();
        point = dataStream.get3BD();
        double width = dataStream.getBD();
        if (width < 0.0) {
            startWidth = -width;
            endWidth = -width;
        } else {
            startWidth = width;
            endWidth = dataStream.getBD();
        }
        bulge = dataStream.getBD();
        vertexId = dataStream.getBL();
        tangentDir = dataStream.getBD();

        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
        handleStream.assertEndOfStream();
    }

    public String toString() {
        return "VERTEX (2D)";
    }

} public class View extends NonEntityObject {

	public View(ObjectMap objectMap) {
        super(objectMap);
    }

    public String toString() {
		return "VIEW";
	}

} public class ViewControlObj extends NonEntityObject {

	public ViewControlObj(ObjectMap objectMap) {
        super(objectMap);
    }

    public String toString() {
		return "VIEW CONTROL OBJ";
	}

} public class ViewPort extends EntityObject {

    public Point3D point;
    
    public double thickness;
    
    public Point3D extrusion;
    
    public double xAxisAngle;

    public Point3D center;

    public double width;

    public double height;

    public Point3D viewTarget;

    public Point3D viewDirection;

    public double viewTwistAngle;

    public double viewHeight;

    public double lensLength;

    public double frontClipZ;

    public double backClipZ;

    public double snapAngle;

    public Point2D viewCenter;

    public Point2D snapBase;

    public Point2D snapSpacing;

    public Point2D gridSpacing;

    public int circleZoom;

    public int gridMajor;

    public String styleSheet;

    public int renderMode;

    public boolean ucsAtOrigin;

    public boolean ucsPerViewport;

    public Point3D ucsOrigin;

    public Point3D ucsXAxis;

    public Point3D ucsYAxis;

    public double ucsElevation;

    public int ucsOrthoViewType;

    public int shadePlotMode;

    public boolean useDefLights;

    public int defLightingType;

    public double brightness;

    public double contrast;

    public CmColor ambientLightColor;

    public ViewPort(ObjectMap objectMap) {
        super(objectMap);
    }

    @Override
    public void readObjectTypeSpecificData(BitBuffer dataStream, BitBuffer stringStream, BitBuffer handleStream, FileVersion fileVersion) {

        // 19.4.36 VIEWPORT (34) page 131

        center = dataStream.get3BD();
        width = dataStream.getBD();
        height = dataStream.getBD();
        viewTarget = dataStream.get3BD();
        viewDirection = dataStream.get3BD();
        viewTwistAngle = dataStream.getBD();
        viewHeight = dataStream.getBD();
        lensLength = dataStream.getBD();
        frontClipZ = dataStream.getBD();
        backClipZ = dataStream.getBD();
        snapAngle = dataStream.getBD();
        viewCenter = dataStream.get2RD();
        snapBase = dataStream.get2RD();
        snapSpacing = dataStream.get2RD();
        gridSpacing = dataStream.get2RD();
        circleZoom = dataStream.getBS();
        gridMajor = dataStream.getBS();
        int frozenLayerCount = dataStream.getBL();
        int statusFlags = dataStream.getBL();
        styleSheet = stringStream.getTU();
        renderMode = dataStream.getRC();
        ucsAtOrigin = dataStream.getB();
        ucsPerViewport = dataStream.getB();
        ucsOrigin = dataStream.get3BD();
        ucsXAxis = dataStream.get3BD();
        ucsYAxis = dataStream.get3BD();
        ucsElevation = dataStream.getBD();
        ucsOrthoViewType = dataStream.getBS();
        shadePlotMode = dataStream.getBS();
        useDefLights = dataStream.getB();
        defLightingType = dataStream.getRC();
        brightness = dataStream.getBD();
        contrast = dataStream.getBD();
        ambientLightColor = dataStream.getCMC();

        handleStream.advanceToByteBoundary();

        dataStream.assertEndOfStream();
        stringStream.assertEndOfStream();
//        handleStream.assertEndOfStream();
    }

    public String toString() {
        return "VIEWPORT";
    }
} public class XRecord extends NonEntityObject {

	public XRecord(ObjectMap objectMap) {
        super(objectMap);
    }

    public String toString() {
		return "XRECORD";
	}

}
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.onespatial.dwglib.bitstreams.BitBuffer;
import com.onespatial.dwglib.bitstreams.BitStreams;
import com.onespatial.dwglib.bitstreams.Handle;
import com.onespatial.dwglib.objects.AcdbDictionaryWithDefault;
import com.onespatial.dwglib.objects.AcdbPlaceHolder;
import com.onespatial.dwglib.objects.Appid;
import com.onespatial.dwglib.objects.AppidControlObj;
import com.onespatial.dwglib.objects.Arc;
import com.onespatial.dwglib.objects.Attdef;
import com.onespatial.dwglib.objects.Attrib;
import com.onespatial.dwglib.objects.Block;
import com.onespatial.dwglib.objects.BlockControlObj;
import com.onespatial.dwglib.objects.BlockHeader;
import com.onespatial.dwglib.objects.CadObject;
import com.onespatial.dwglib.objects.Circle;
import com.onespatial.dwglib.objects.Dictionary;
import com.onespatial.dwglib.objects.DimStyle;
import com.onespatial.dwglib.objects.DimensionLinear;
import com.onespatial.dwglib.objects.DimstyleControlObj;
import com.onespatial.dwglib.objects.Endblk;
import com.onespatial.dwglib.objects.GenericObject;
import com.onespatial.dwglib.objects.Hatch;
import com.onespatial.dwglib.objects.Insert;
import com.onespatial.dwglib.objects.LType;
import com.onespatial.dwglib.objects.LTypeControlObj;
import com.onespatial.dwglib.objects.Layer;
import com.onespatial.dwglib.objects.LayerControlObj;
import com.onespatial.dwglib.objects.Layout;
import com.onespatial.dwglib.objects.Leader;
import com.onespatial.dwglib.objects.Line;
import com.onespatial.dwglib.objects.LwPolyline;
import com.onespatial.dwglib.objects.MLineStyle;
import com.onespatial.dwglib.objects.MText;
import com.onespatial.dwglib.objects.ObjectMap;
import com.onespatial.dwglib.objects.PlaneSurface;
import com.onespatial.dwglib.objects.Point;
import com.onespatial.dwglib.objects.PolylineMesh;
import com.onespatial.dwglib.objects.PolylinePFace;
import com.onespatial.dwglib.objects.SeqEnd;
import com.onespatial.dwglib.objects.Solid;
import com.onespatial.dwglib.objects.SortEntsTable;
import com.onespatial.dwglib.objects.Spline;
import com.onespatial.dwglib.objects.Style;
import com.onespatial.dwglib.objects.StyleControlObj;
import com.onespatial.dwglib.objects.Text;
import com.onespatial.dwglib.objects.ThreeDSolid;
import com.onespatial.dwglib.objects.TwoDPolyline;
import com.onespatial.dwglib.objects.Ucs;
import com.onespatial.dwglib.objects.UcsControlObj;
import com.onespatial.dwglib.objects.VPort;
import com.onespatial.dwglib.objects.VPortControlObj;
import com.onespatial.dwglib.objects.Vertex2D;
import com.onespatial.dwglib.objects.View;
import com.onespatial.dwglib.objects.ViewControlObj;
import com.onespatial.dwglib.objects.ViewPort;
import com.onespatial.dwglib.objects.XRecord;
        Long offsetIntoObjectMap = getOffsetIntoObjectMap(h);

        if (offsetIntoObjectMap == null) {
            throw new RuntimeException("remove me");
        }
        assert offsetIntoObjectMap != null;

        CadObject cadObject = parseObjectAtGivenOffset(offsetIntoObjectMap);

        return cadObject;
    }

    // Public for time being. Should be moved to embedded object so hidden from
    // user.
    // Perhaps into a class representing the object map.
    public CadObject parseObjectPossiblyOrphaned(Handle h) {
        Long offsetIntoObjectMap = getOffsetIntoObjectMap(h);

        if (offsetIntoObjectMap == null) {
            return null;
        }

        CadObject cadObject = parseObjectAtGivenOffset(offsetIntoObjectMap);

        return cadObject;
    }

    protected CadObject parseObjectAtGivenOffset(long offsetIntoObjectMap) {
        if (doneObjects.containsKey(offsetIntoObjectMap)) {
            return doneObjects.get(offsetIntoObjectMap);
        }

        if (offsetIntoObjectMap > Integer.MAX_VALUE) {
            throw new RuntimeException("overflow");
        }
        BitStreams bitStreams = new BitStreams(objectBuffer, (int) offsetIntoObjectMap, issues);
        BitBuffer dataStream = bitStreams.getDataStream();
        BitBuffer stringStream = bitStreams.getStringStream();
        BitBuffer handleStream = bitStreams.getHandleStream();

        int objectType = dataStream.getOT();

        CadObject cadObject;

        // Create placeholder class.
        objectMap = new ObjectMap(this);

        if (objectType >= 500) {
            int classIndex = objectType - 500;
            ClassData thisClass = classes.get(classIndex);

            switch (thisClass.classdxfname) {
            case "ACDBDICTIONARYWDFLT":
                cadObject = new AcdbDictionaryWithDefault(objectMap);
                break;
            case "PLANESURFACE":
                cadObject = new PlaneSurface(objectMap);
                break;
            case "SORTENTSTABLE":
                cadObject = new SortEntsTable(objectMap);
                break;
            default:
                cadObject = new GenericObject(objectMap, thisClass.classdxfname);
                break;
            }

        } else {
            switch (objectType) {
            case 1:
                cadObject = new Text(objectMap);
                break;
            case 2:
                cadObject = new Attrib(objectMap);
                break;
            case 3:
                cadObject = new Attdef(objectMap);
                break;
            case 4:
                cadObject = new Block(objectMap);
                break;
            case 5:
                cadObject = new Endblk(objectMap);
                break;
            case 6:
                cadObject = new SeqEnd(objectMap);
                break;
            case 7:
                cadObject = new Insert(objectMap);
                break;
            case 10:
                cadObject = new Vertex2D(objectMap);
                break;
            case 15:
                cadObject = new TwoDPolyline(objectMap);
                break;
            case 17:
                cadObject = new Arc(objectMap);
                break;
            case 18:
                cadObject = new Circle(objectMap);
                break;
            case 19:
                cadObject = new Line(objectMap);
                break;
            case 21:
                cadObject = new DimensionLinear(objectMap);
                break;
            case 27:
                cadObject = new Point(objectMap);
                break;
            case 29:
                cadObject = new PolylinePFace(objectMap);
                break;
            case 30:
                cadObject = new PolylineMesh(objectMap);
                break;
            case 31:
                cadObject = new Solid(objectMap);
                break;
            case 34:
                cadObject = new ViewPort(objectMap);