/*
 * @(#)StructParser.java  2.7.4  2011-08-18
 *
 * Copyright (c) 2000-2011 Werner Randelshofer, Immensee, Switzerland.
 * All rights reserved.
 *
 * You may not use, copy or modify this file, except in compliance with the
 * license agreement you entered into with Werner Randelshofer.
 * For details see accompanying license terms.
 */
package org.monte.media.binary;

import org.monte.media.math.ExtendedReal;
import org.monte.media.io.ByteArrayImageInputStream;
import java.io.*;
import org.monte.media.ParseException;
import org.monte.media.io.StreamPosTokenizer;
import java.util.*;
import java.text.*;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;

/**
 * Parses structured binary data using C-like data declarations.
 *
 * Syntax:
 * <pre><code>
 * Declarations ::= { MagicDeclaration | DescriptionDeclaration | EnumDeclaration | SetDeclaration | TypedefDeclaration } EOF
 * MagicDeclaration ::= "magic" identifier ("ushort" hexLiteral[".." hexLiteral] | stringLiteral) ";"
 * DescriptionDeclaration ::= "description" identifier stringLiteral "," stringLiteral ";"
 * EnumDeclaration ::= EnumSpecifier identifier ";"
 * SetDeclaration ::= SetSpecifier identifier ";"
 * TypedefDeclaration ::= "typedef" TypeSpecifier identifier ";"
 * EnumSpecifier ::= "enum" ( identifier | "{" identifier ["=" magicOrIntLiteral] {"," identifier ["=" intLiteral]} "}" )
 * SetSpecifier ::= "set" ( identifier | "{" identifier "=" intLiteral {"," identifier "=" intLiteral} "}" )
 * TypeSpecifier ::= ( StructSpecifier | (PrimitiveSpecifier [EnumSpecifier | SetSpecifier]) ) [ArrayList]
 * StructSpecifier ::= "struct (identifier | "{" MemberDeclaration {"," MemberDeclaration } "}" )
 * MemberDeclaration ::= TypeSpecifier identifier [ArrayList] ";"
 * PrimitiveSpecifier ::= "uint1" | "uint2" | "uint4" | "uint5" | "uint8" | "uint12" | "uint16" | "uint32"
 *                         | "int9" | "int16" | "int32"
 *                         | "ubyte" | "byte" | "short"
 *                         | "ushort" | "int" | "long" | "float" | "double"
 *                         | "extended"
 *                         | "char" | "charbyte" | "cstring" | "utf8" | "pstring"
 *                         | "pstring32"
 *                         | "utf16le" | "magic" | "mactimestamp" | "bcd2"
 *                         | "bcd4"
 *                         | "fixed16d16" | "fixed2d30" | "fixed8d8"
 *                         | "ataricolor"
 * ArrayList ::= "[" [ArraySize] "]" {"," identifier "[" [ArraySize] "]" }
 * ArraySize ::= () | intLiteral [("-"|"+"|"=="|"!=") intLiteral]
 * </code></pre>
 * @author Werner Randelshofer, Hausmatt 10, CH-6405 Immensee, Swityerland
 *
 * @version 2.7.3 2011-08-16 Fixes class comments.
 * <br>2.7.2 2011-01-12 Adds int32, int16.
 * <br>2.7.1 2011-01-04 Fixes reading of pstring32.
 * <br>2.7 2011-01-03 Adds uint5 and int9. Uses ImageInputStream for
 * reading data bitwise. Adds support for equality- and inequality-checks in
 * array indizes. Adds support for string literals in enum-names.
 * <br>2.6 2010-12-31 Return UBYTE as byte array because it can be displayed
 * better in the table.
 * <br>2.5 2010-12-28 Adds uint1, uint2.
 * <br>2.4.2 2010-10-02 Fixes Mactimestamp.
 * <br>2.4.1 2010-04-02 Added pstring32.
 * <br>2.4 2009-12-26 Added utf16le String.
 * <br>2.3.1 2009-11-19 MagicOrInt accepts now negative integers as well.
 * <br>2.3 2008-10-13 Added special treatment for ubyte- and charbyte-arrays.
 * Added support for uint4.
 * <br>2.2 2008-06-15 Added support for fixed decimal numbers. 
 * <br>2.1.1 2007-12-11 Don't fail when there are not enough bytes available
 * to fully read a pstring.  
 * <br>2.1 2007-12-08 Added support for BCD numbers.
 * <br>2.0 2006-12-29 Added support for short magics, character bytes and
 * improved presentation of struct arrays.
 * <br>1.4 2006-07-20 Reworked for Java 1.5.
 * <br>1.3 2005-01-09 Support for little-endian data types added. Support
 * for magic enumerations added.
 * <br>1.2.1 2003-04-01 Revised.
 * <br>1.2  2000-10-04 Support for set's added.
 * <br> 1.1  2000-10-03 Support for enm's added.
 * <br> 1.0 2000-06-11
 */
public class StructParser extends Object {

    protected static final long MAC_TIMESTAMP_EPOCH = new GregorianCalendar(1904, GregorianCalendar.JANUARY, 1).getTimeInMillis();
    private Declarations declarations;

    public StructParser() {
    }

    public StructParser(Reader r) throws IOException, ParseException {
        parse(r);
    }

    public String getName(String magic) {
        return getName((Object) magic);
    }

    public String getName(Object magic) {
        MagicDeclaration md = (MagicDeclaration) declarations.magics.get(magic);
        DescriptionDeclaration dd = (md == null) ? null : (DescriptionDeclaration) declarations.descriptions.get(md.identifier);
        return (dd == null) ? null : dd.name;
    }

    public String getIdentifierName(String magic) {
        MagicDeclaration md = (MagicDeclaration) declarations.magics.get(magic);
        DescriptionDeclaration dd = (md == null) ? null : (DescriptionDeclaration) declarations.descriptions.get(md.identifier);
        return (md == null) ? null : md.identifier;
    }

    public boolean isMagicDeclared(String magic) {
        return isMagicDeclared((Object) magic);
    }

    public boolean isMagicDeclared(Object magic) {
        MagicDeclaration md = (MagicDeclaration) declarations.magics.get(magic);
        return md != null;
    }

    public boolean isTypeDeclared(String magic) {
        return isTypeDeclared((Object) magic);
    }

    public boolean isTypeDeclared(Object magic) {
        TypedefDeclaration typedef = null;

        //DataInputStream in = new DataInputStream(new ByteArrayInputStream(data));
        MagicDeclaration magicdef = (MagicDeclaration) declarations.magics.get(magic);
        if (magicdef == null) {
            return false;
        }
        typedef = (TypedefDeclaration) declarations.typedefs.get(magicdef.identifier);
        return typedef != null;
    }

    public String getDescription(String magic) {
        return getDescription((Object) magic);
    }

    public String getDescription(Object magic) {
        MagicDeclaration md = (MagicDeclaration) declarations.magics.get(magic);
        DescriptionDeclaration dd = (md == null) ? null : (DescriptionDeclaration) declarations.descriptions.get(md.identifier);
        return (dd == null) ? null : dd.description;
    }

    public StructTableModel readStruct(String magic, byte[] data)
            throws IOException {
        return declarations.readStruct(magic, new ByteArrayImageInputStream(data));
    }

    public StructTableModel readStruct(int magic, byte[] data)
            throws IOException {
        return declarations.readStruct(magic, new ByteArrayImageInputStream(data));
    }

    public StructTableModel readStruct(String magic, InputStream data)
            throws IOException {
        if (data instanceof ImageInputStream) {
            return declarations.readStruct(magic, (ImageInputStream) data);
        } else {
            return declarations.readStruct(magic, new MemoryCacheImageInputStream(data));
        }
    }

    public StructTableModel readStruct(int magic, InputStream data)
            throws IOException {
        if (data instanceof ImageInputStream) {
            return declarations.readStruct(magic, (ImageInputStream) data);
        } else {
            return declarations.readStruct(magic, new MemoryCacheImageInputStream(data));
        }
    }

    /**
     * Consumes the number of bytes needed by the specified struct from the
     * input stream, and returns them as a byte array.
     */
    private static String errorMsg(String text, StreamPosTokenizer scanner) {
        StringBuffer b = new StringBuffer();
        b.append("line ");
        b.append(Integer.toString(scanner.lineno()));
        /*        b.append(' ');
        b.append(Integer.toString(scanner.getStartPosition()));
        b.append("..");
        b.append(Integer.toString(scanner.getEndPosition()));*/
        b.append(": ");
        b.append(text);
        b.append(" instead of ");
        switch (scanner.ttype) {
            case StreamPosTokenizer.TT_WORD:
                b.append('\'');
                b.append(scanner.sval);
                b.append('\'');
                break;
            case StreamPosTokenizer.TT_EOF:
                b.append("EOF");
                break;
            case StreamPosTokenizer.TT_EOL:
                b.append("EOL");
                break;
            case StreamPosTokenizer.TT_NUMBER:
                b.append(Double.toString(scanner.nval));
                break;
            case '"':
                b.append('"');
                b.append(scanner.sval);
                b.append('"');
                break;
            default:
                b.append('\'');
                b.append((char) scanner.ttype);
                b.append('\'');
                b.append("(0x");
                b.append(Integer.toHexString(scanner.ttype));
                b.append(')');
                break;
        }
        return b.toString();
    }

    protected void parse(Reader r)
            throws IOException, ParseException {
        StreamPosTokenizer scanner = new StreamPosTokenizer(r);
        scanner.resetSyntax();
        scanner.wordChars('a', 'z');
        scanner.wordChars('A', 'Z');
        scanner.wordChars(128 + 32, 255);
        scanner.wordChars('_', '_');
        scanner.whitespaceChars(0, ' ');
        //scanner.commentChar('/');
        scanner.quoteChar('"');
        scanner.quoteChar('\'');
        scanner.parseNumbers();
        scanner.parseHexNumbers();
        scanner.slashSlashComments(true);
        scanner.slashStarComments(true);
        scanner.ordinaryChar(';');
        scanner.ordinaryChar('[');
        scanner.ordinaryChar(']');
        scanner.ordinaryChar('{');
        scanner.ordinaryChar('}');
        scanner.ordinaryChar('+');
        scanner.ordinaryChar('-');

        declarations = new Declarations(scanner);
    }

    /**
     * Declarations expression.
     *
     * <pre><code>
     * Declarations ::= { MagicDeclaration | DescriptionDeclaration | EnumDeclaration | SetDeclaration | TypedefDeclaration }
     * </code></pre>
     */
    protected static class Declarations {

        public Hashtable magics = new Hashtable();
        public Hashtable descriptions = new Hashtable();
        public Hashtable enums = new Hashtable();
        public Hashtable sets = new Hashtable();
        public Hashtable typedefs = new Hashtable();

        public Declarations(StreamPosTokenizer scanner)
                throws IOException, ParseException {
            while (scanner.nextToken() != StreamPosTokenizer.TT_EOF) {
                if (scanner.ttype == StreamPosTokenizer.TT_WORD) {
                    if (scanner.sval.equals("magic")) {
                        scanner.pushBack();
                        MagicDeclaration md = new MagicDeclaration(scanner);
                        if (md.magic != null) {
                            magics.put(md.magic, md);
                        } else {
                            for (int i = md.ushortMagicFrom; i <= md.ushortMagicTo; i++) {
                                magics.put(i, md);
                            }
                        }
                    } else if (scanner.sval.equals("description")) {
                        scanner.pushBack();
                        DescriptionDeclaration dd = new DescriptionDeclaration(scanner);
                        descriptions.put(dd.identifier, dd);
                    } else if (scanner.sval.equals("enum")) {
                        scanner.pushBack();
                        EnumDeclaration ed = new EnumDeclaration(scanner);
                        enums.put(ed.identifier, ed);
                    } else if (scanner.sval.equals("set")) {
                        scanner.pushBack();
                        SetDeclaration sd = new SetDeclaration(scanner);
                        sets.put(sd.identifier, sd);
                    } else if (scanner.sval.equals("typedef")) {
                        scanner.pushBack();
                        TypedefDeclaration td = new TypedefDeclaration(scanner);
                        typedefs.put(td.identifier, td);
                    } else {
                        throw new ParseException(errorMsg("Declarations: Expected 'magic', 'description', 'enum', 'set' or 'typedef' ", scanner));
                    }
                }
            }
        }

        private StructTableModel readStruct(Object magic, ImageInputStream in)
                throws IOException {
            Vector result = new Vector();
            TypedefDeclaration typedef = null;

            //DataInputStream in = new DataInputStream(new ByteArrayInputStream(data));
            MagicDeclaration magicdef = (MagicDeclaration) magics.get(magic);
            if (magicdef == null) {
                //System.out.println("StructParser.StructTableModel.readStruct() unknow magic:"+magic);
                //new Exception().printStackTrace();
                if (magic instanceof Integer) {
                    throw new IOException("unknown magic:" + Integer.toHexString((Integer) magic));
                } else {
                    throw new IOException("unknown magic:" + magic);
                }
            }
            typedef = (TypedefDeclaration) typedefs.get(magicdef.identifier);
            if (typedef == null) {
                //System.out.println("StructParser.StructTableModel.readStruct() typedef not found for magic:"+magic+" magicdef.identifier:"+magicdef.identifier);
                throw new IOException("unknown type:" + magicdef.identifier);
            }
            try {
                Object obj = typedef.read(in, typedef.identifier, this, result);
                if (obj != null) {
                    StructTableModel.Value value = new StructTableModel.Value();
                    value.declaration = typedef.identifier;
                    value.value = obj;
                    result.addElement(value);
                }
            } catch (EOFException e) {
            }
            return new StructTableModel(typedef, result);
        }
    }

    /**
     * MagicDeclaration expression.
     *
     * <pre><code>
     * MagicDeclaration ::= "magic" identifier ("ushort" hexLiteral[".." hexLiteral] | stringLiteral) ";"
     * </code></pre>
     */
    protected static class MagicDeclaration {

        public String identifier;
        /** If magic is null, then ushortMagicFrom/ushortMagicTo is used. */
        public String magic;
        public int ushortMagicFrom;
        public int ushortMagicTo;

        public MagicDeclaration(StreamPosTokenizer scanner)
                throws IOException, ParseException {
            if (scanner.nextToken() != StreamPosTokenizer.TT_WORD || !scanner.sval.equals("magic")) {
                throw new ParseException(errorMsg("MagicDeclaration: 'magic' expected", scanner));
            }
            if (scanner.nextToken() != StreamPosTokenizer.TT_WORD) {
                throw new ParseException(errorMsg("MagicDeclaration: identifier expected", scanner));
            }
            identifier = scanner.sval;

            if (scanner.nextToken() == StreamPosTokenizer.TT_WORD
                    && scanner.sval.equals("ushort")) {
                if (scanner.nextToken() != StreamPosTokenizer.TT_NUMBER) {
                    throw new ParseException(errorMsg("MagicDeclaration: ushort literal expected", scanner));
                }
                ushortMagicFrom = (int) scanner.nval;
                if (scanner.nextToken() == '.') {
                    if (scanner.nextToken() != '.') {
                        throw new ParseException(errorMsg("MagicDeclaration: interval literal expected", scanner));
                    }
                    if (scanner.nextToken() != StreamPosTokenizer.TT_NUMBER) {
                        throw new ParseException(errorMsg("MagicDeclaration: end interval literal expected", scanner));
                    }
                    ushortMagicTo = (int) scanner.nval;
                } else {
                    ushortMagicTo = ushortMagicFrom;
                    scanner.pushBack();
                }
            } else {
                if (scanner.ttype != '"') {
                    throw new ParseException(errorMsg("MagicDeclaration: string literal expected", scanner));
                }
                magic = scanner.sval;
            }
            if (scanner.nextToken() != ';') {
                throw new ParseException(errorMsg("MagicDeclaration: ';' expected", scanner));
            }
        }
    }
    /*
    private static int parseHexLiteral(StreamPosTokenizer scanner)
    throws IOException, ParseException {
    scanner.
    if (scanner.nextToken() != StreamPosTokenizer.TT_NUMBER || scanner.nval != 0) {
    throw new ParseException(errorMsg("MagicDeclaration: hex literal expected",scanner));
    }
    System.out.println("StructParser.MagicDeclaration nval::"+scanner.nval);
    System.out.println("StructParser.MagicDeclaration next TOken:"+scanner.nextToken());
    
    }*/

    /**
     * DescriptionDeclaration expression.
     *
     * <pre><code>
     * DescriptionDeclaration ::= "description" identifier stringLiteral "," stringLiteral ";"
     * </code></pre>
     */
    protected static class DescriptionDeclaration {

        public String identifier;
        public String name;
        public String description;

        public DescriptionDeclaration(StreamPosTokenizer scanner)
                throws IOException, ParseException {
            if (scanner.nextToken() != StreamPosTokenizer.TT_WORD || !scanner.sval.equals("description")) {
                throw new ParseException(errorMsg("DescriptionDeclaration: 'magic' expected", scanner));
            }
            if (scanner.nextToken() != StreamPosTokenizer.TT_WORD) {
                throw new ParseException(errorMsg("DescriptionDeclaration: identifier expected", scanner));
            }
            identifier = scanner.sval;
            if (scanner.nextToken() != '"') {
                throw new ParseException(errorMsg("DescriptionDeclaration: string literal (title) expected", scanner));
            }
            name = scanner.sval;
            if (scanner.nextToken() != ',') {
                throw new ParseException(errorMsg("DescriptionDeclaration: ',' expected", scanner));
            }
            if (scanner.nextToken() != '"') {
                throw new ParseException(errorMsg("DescriptionDeclaration: string literal (description) expected", scanner));
            }
            description = scanner.sval;
            while (scanner.nextToken() == '+') {
                if (scanner.nextToken() != '"') {
                    throw new ParseException(errorMsg("DescriptionDeclaration: string literal (description) after '+' expected", scanner));
                }
                description += scanner.sval;
            }
            scanner.pushBack();
            if (scanner.nextToken() != ';') {
                throw new ParseException(errorMsg("DescriptionDeclaration: ';' expected", scanner));
            }
        }
    }

    /**
     * EnumDeclaration expression.
     *
     * <pre><code>
     * EnumDeclaration ::= EnumSpecifier identifier ";"
     * </code></pre>
     */
    protected static class EnumDeclaration {

        public EnumSpecifier enumSpecifier;
        public String identifier;

        public EnumDeclaration(StreamPosTokenizer scanner)
                throws IOException, ParseException {
            enumSpecifier = new EnumSpecifier(scanner);

            if (scanner.nextToken() != StreamPosTokenizer.TT_WORD) {
                throw new ParseException(errorMsg("EnumDeclaration: identifier expected", scanner));
            }
            identifier = scanner.sval;

            if (scanner.nextToken() != ';') {
                throw new ParseException(errorMsg("EnumDeclaration: ';' expected", scanner));
            }
        }
    }

    /**
     * SetDeclaration expression.
     *
     * <pre><code>
     * SetDeclaration ::= SetSpecifier identifier ";"
     * </code></pre>
     */
    protected static class SetDeclaration {

        public SetSpecifier setSpecifier;
        public String identifier;

        public SetDeclaration(StreamPosTokenizer scanner)
                throws IOException, ParseException {
            setSpecifier = new SetSpecifier(scanner);

            if (scanner.nextToken() != StreamPosTokenizer.TT_WORD) {
                throw new ParseException(errorMsg("SetDeclaration: identifier expected", scanner));
            }
            identifier = scanner.sval;

            if (scanner.nextToken() != ';') {
                throw new ParseException(errorMsg("SetDeclaration: ';' expected", scanner));
            }
        }
    }

    /**
     * TypedefDeclaration expression.
     *
     * <pre><code>
     * TypedefDeclaration ::= "typedef" TypeSpecifier identifier ";"
     * </code></pre>
     */
    protected static class TypedefDeclaration {

        public TypeSpecifier typeSpecifier;
        public String identifier;

        public TypedefDeclaration(StreamPosTokenizer scanner)
                throws IOException, ParseException {
            if (scanner.nextToken() != StreamPosTokenizer.TT_WORD || !scanner.sval.equals("typedef")) {
                throw new ParseException(errorMsg("TypedefDeclaration: 'typedef' expected", scanner));
            }

            typeSpecifier = new TypeSpecifier(scanner);

            if (scanner.nextToken() != StreamPosTokenizer.TT_WORD) {
                throw new ParseException(errorMsg("TypedefDeclaration: identifier expected", scanner));
            }
            identifier = scanner.sval;

            if (scanner.nextToken() != ';') {
                throw new ParseException(errorMsg("TypedefDeclaration: ';' expected", scanner));
            }
            //System.out.println("typedef "+typeSpecifier+" "+identifier+";");
        }

        private int getResolvedType(Declarations declarations) throws IOException {
            return typeSpecifier.getResolvedPrimitiveType(declarations);
        }

        private Object read(ImageInputStream in, String parentIdentifier, Declarations declarations, Vector result)
                throws IOException {
            return typeSpecifier.read(in, parentIdentifier, declarations, identifier, result);
        }
    }

    /**
     * EnumSpecifier expression.
     *
     * <pre><code>
     * EnumSpecifier ::= "enum" ( identifier | "{" identifier ["=" intLiteral] {"," identifier ["=" intLiteral]} "}" )
     * </code></pre>
     */
    protected static class EnumSpecifier {

        public Hashtable members;
        public String identifier;
        public boolean isMagicEnum;

        public EnumSpecifier(StreamPosTokenizer scanner)
                throws IOException, ParseException {
            if (scanner.nextToken() != StreamPosTokenizer.TT_WORD || !scanner.sval.equals("enum")) {
                throw new ParseException(errorMsg("EnumSpecifier: 'enum' expected", scanner));
            }

            if (scanner.nextToken() == StreamPosTokenizer.TT_WORD) {
                identifier = scanner.sval;
            } else if (scanner.ttype == '{') {
                String name;
                int value = -1;
                members = new Hashtable();
                do {
                    if (scanner.nextToken() == '}') {
                        break;
                    }
                    if (scanner.ttype != StreamPosTokenizer.TT_WORD
                            && scanner.ttype != '"') {
                        throw new ParseException(errorMsg("EnumSpecifier: enumeration name expected", scanner));
                    }
                    name = scanner.sval;

                    if (scanner.nextToken() == '=') {
                        MagicOrIntLiteral literal = new MagicOrIntLiteral(scanner);
                        value = literal.intValue();
                        isMagicEnum = literal.isMagic();
                    } else {
                        value++;
                        scanner.pushBack();
                    }

                    members.put(new Integer(value), name);
                } while (scanner.nextToken() == ',');
                if (scanner.ttype != '}') {
                    throw new ParseException(errorMsg("EnumSpecifier: '}' expected", scanner));
                }
            } else {
                throw new ParseException(errorMsg("EnumSpecifier: identifier or '{' expected", scanner));
            }
            identifier = scanner.sval;
        }

        public String toEnumString(int value, Declarations declarations) {
            if (identifier != null) {
                EnumDeclaration enumDecl = (EnumDeclaration) declarations.enums.get(identifier);
                if (enumDecl == null) {
                    throw new InternalError("Enum Declaration missing for " + identifier);
                }
                return enumDecl.enumSpecifier.toEnumString(value, declarations);
            } else {
                StringBuffer buf = new StringBuffer();
                Integer intValue = new Integer(value);
                if (isMagicEnum) {
                    buf.append('"');
                    buf.append(MagicOrIntLiteral.toMagic(value));
                    buf.append('"');
                } else {
                    buf.append(Integer.toString(value));
                    /*
                    buf.append("0x");
                    buf.append(Integer.toHexString(value));*/
                }
                buf.append(" {");
                if (members.get(intValue) != null) {
                    buf.append(members.get(intValue).toString());
                }
                buf.append('}');
                return buf.toString();
            }
        }

        public String toEnumString(String value, Declarations declarations) {
            if (identifier != null) {
                EnumDeclaration enumDecl = (EnumDeclaration) declarations.enums.get(identifier);
                if (enumDecl == null) {
                    throw new InternalError("Enum Declaration missing for " + identifier);
                }
                return enumDecl.enumSpecifier.toEnumString(value, declarations);
            } else {
                if (value == null || value.length() != 4 || !isMagicEnum) {
                    return value;
                } else {
                    return toEnumString(MagicOrIntLiteral.toInt(value), declarations);
                }
            }
        }
    }

    /**
     * SetSpecifier expression.
     *
     * <pre><code>
     * SetSpecifier ::= "set" ( identifier | "{" identifier "=" intLiteral {"," identifier "=" intLiteral} "}" )
     * </code></pre>
     */
    protected static class SetSpecifier {

        public Hashtable members;
        public String identifier;

        public SetSpecifier(StreamPosTokenizer scanner)
                throws IOException, ParseException {
            if (scanner.nextToken() != StreamPosTokenizer.TT_WORD || !scanner.sval.equals("set")) {
                throw new ParseException(errorMsg("SetSpecifier: 'set' expected", scanner));
            }

            if (scanner.nextToken() == StreamPosTokenizer.TT_WORD) {
                identifier = scanner.sval;
            } else if (scanner.ttype == '{') {
                String name;
                int value = 0;
                members = new Hashtable();
                do {

                    if (scanner.nextToken() != StreamPosTokenizer.TT_WORD && scanner.ttype!='"') {
                        throw new ParseException(errorMsg("SetSpecifier: set name expected", scanner));
                    }
                    name = scanner.sval;

                    if (scanner.nextToken() == '=') {
                        value = (new IntLiteral(scanner)).intValue();
                    } else {
                        value = (value == 0) ? 1 : value << 1;
                        scanner.pushBack();
                    }

                    members.put(new Integer(value), name);
                } while (scanner.nextToken() == ',');
                if (scanner.ttype != '}') {
                    throw new ParseException(errorMsg("SetSpecifier: '}' expected", scanner));
                }
            } else {
                throw new ParseException(errorMsg("SetSpecifier: identifier or '{' expected", scanner));
            }
            identifier = scanner.sval;
        }

        public String toSetString(int value, Declarations declarations) {
            if (identifier != null) {
                SetDeclaration setDecl = (SetDeclaration) declarations.sets.get(identifier);
                if (setDecl == null) {
                    throw new InternalError("Set Declaration missing for " + identifier);
                }
                return setDecl.setSpecifier.toSetString(value, declarations);
            } else {
                StringBuffer buf = new StringBuffer();
                buf.append("0x");
                buf.append(Integer.toHexString(value));
                buf.append(" {");
                Enumeration enm = members.keys();
                boolean isFirst = true;
                while (enm.hasMoreElements()) {
                    Object key = enm.nextElement();
                    int intKey = ((Number) key).intValue();
                    if ((intKey == 0 && value == 0) || ((intKey & value) == intKey)) {
                        if (isFirst) {
                            isFirst = false;
                        } else {
                            buf.append(", ");
                        }
                        buf.append(members.get(key).toString());
                    }
                }
                buf.append('}');
                return buf.toString();
            }
        }
    }

    /**
     * TypeSpecifier expression.
     *
     * <pre><code>
     * TypeSpecifier ::= ( StructSpecifier | (PrimitiveSpecifier [EnumSpecifier | SetSpecifier]) ) [ArrayList]
     * ArrayList ::= "[" [ArraySize] "]" {"," identifier "[" [ArraySize] "]" }
     * </code></pre>
     */
    protected static class TypeSpecifier {

        public StructSpecifier structSpecifier;
        public PrimitiveSpecifier primitiveSpecifier;
        public EnumSpecifier enumSpecifier;
        public SetSpecifier setSpecifier;
        public Vector arrayList;

        public TypeSpecifier(StreamPosTokenizer scanner)
                throws IOException, ParseException {
            if (scanner.nextToken() == StreamPosTokenizer.TT_WORD && scanner.sval.equals("struct")) {
                scanner.pushBack();
                structSpecifier = new StructSpecifier(scanner);
            } else {
                scanner.pushBack();
                primitiveSpecifier = new PrimitiveSpecifier(scanner);

                scanner.nextToken();
                if (scanner.ttype == StreamPosTokenizer.TT_WORD && scanner.sval.equals("enum")) {
                    scanner.pushBack();
                    enumSpecifier = new EnumSpecifier(scanner);
                } else if (scanner.ttype == StreamPosTokenizer.TT_WORD && scanner.sval.equals("set")) {
                    scanner.pushBack();
                    setSpecifier = new SetSpecifier(scanner);
                } else {
                    scanner.pushBack();
                }
            }

            // ArrayList Begin
            if (scanner.nextToken() == '[') {
                arrayList = new Vector();
                do {
                    arrayList.addElement(new ArraySize(scanner));
                    if (scanner.nextToken() != ']') {
                        throw new ParseException(errorMsg("MemberDeclaration: ']' expected", scanner));
                    }
                } while (scanner.nextToken() == '[');
                scanner.pushBack();
            } else {
                scanner.pushBack();
            }
            // ArrayList End
        }

        private int getResolvedPrimitiveType(Declarations declarations) throws IOException {
            if (structSpecifier != null) {
                return PrimitiveSpecifier.NON_PRIMITIVE;
            } else if (primitiveSpecifier != null) {
                return primitiveSpecifier.getResolvedPrimitveType(declarations);
            } else {
                return PrimitiveSpecifier.NON_PRIMITIVE;
            }
        }

        private Object read(ImageInputStream in, String parentIdentifier, Declarations declarations, String identifier, Vector result)
                throws IOException {
            if (structSpecifier != null) {
                if (arrayList != null) {
                    // ArrayList Begin
                    Enumeration enm = arrayList.elements();
                    while (enm.hasMoreElements()) {
                        ArraySize arraySize = (ArraySize) enm.nextElement();
                        int size = arraySize.getArraySize(declarations, result);
                        if (size == ArraySize.REMAINDER) {
                            try {
                                for (int i = 0;; i++) {
                                    int loc = result.size();
                                    structSpecifier.read(in, parentIdentifier, declarations, result);

                                    for (int j = loc; j < result.size(); j++) {
                                        StructTableModel.Value arrayValue = (StructTableModel.Value) result.elementAt(j);
                                        arrayValue.index = (arrayValue.index == null) ? "[" + i + "]" : "[" + i + "]" + arrayValue.index;
                                    }
                                }
                            } catch (EOFException e) {
                            }
                        } else {
                            for (int i = 0; i < size; i++) {
                                int loc = result.size();
                                structSpecifier.read(in, parentIdentifier, declarations, result);

                                for (int j = loc; j < result.size(); j++) {
                                    StructTableModel.Value arrayValue = (StructTableModel.Value) result.elementAt(j);
                                    arrayValue.index = (arrayValue.index == null) ? "[" + i + "]" : "[" + i + "]" + arrayValue.index;
                                }
                            }
                        }
                    }
                    return null;
                } else {
                    structSpecifier.read(in, parentIdentifier, declarations, result);
                    return null;
                }
            } else if (primitiveSpecifier.getResolvedPrimitveType(declarations) == PrimitiveSpecifier.CHAR) {
                if (arrayList != null) {
                    StringBuffer buf = new StringBuffer();
                    buf.append('\"');
                    boolean hasValue = false;

                    // ArrayList Begin
                    Enumeration enm = arrayList.elements();
                    while (enm.hasMoreElements()) {
                        ArraySize arraySize = (ArraySize) enm.nextElement();
                        int size = arraySize.getArraySize(declarations, result);
                        if (size == ArraySize.REMAINDER) {
                            try {
                                for (int i = 0;; i++) {
                                    //if (i > 0) {
                                    //  buf.append(", ");
                                    //}
                                    Object value = readValue(in, parentIdentifier + "[" + i + "]", declarations, result);
                                    if (value != null) {
                                        hasValue = true;
                                        if (value.toString().equals("0x20")) {
                                            buf.append(' ');
                                        } else {
                                            buf.append(value.toString());
                                        }
                                    }
                                }
                            } catch (EOFException e) {
                            }
                        } else {
                            for (int i = 0; i < size; i++) {
                                //if (i > 0) {
                                //    buf.append(", ");
                                //}
                                Object value = readValue(in, parentIdentifier + "[" + i + "]", declarations, result);
                                if (value != null) {
                                    hasValue = true;
                                    if (value.toString().equals("0x20")) {
                                        buf.append(' ');
                                    } else {
                                        buf.append(value.toString());
                                    }
                                }
                            }
                        }
                    }
                    // ArrayList End
                    if (hasValue) {
                        buf.append('\"');
                        return buf.toString();
                    } else {
                        return null;
                    }
                } else {
                    return readValue(in, parentIdentifier, declarations, result);
                }
            } else if (primitiveSpecifier.getResolvedPrimitveType(declarations) == PrimitiveSpecifier.UBYTE) {
                if (arrayList != null) {
                    StringBuffer buf = new StringBuffer();
                    ByteArrayOutputStream bout = new ByteArrayOutputStream();
                    buf.append("0x");
                    boolean hasValue = false;

                    // ArrayList Begin
                    Enumeration enm = arrayList.elements();
                    while (enm.hasMoreElements()) {
                        ArraySize arraySize = (ArraySize) enm.nextElement();
                        int size = arraySize.getArraySize(declarations, result);
                        if (size == ArraySize.REMAINDER) {
                            try {
                                for (int i = 0;; i++) {
                                    //if (i > 0) {
                                    //  buf.append(", ");
                                    //}
                                    Integer value = (Integer) readValue(in, parentIdentifier + "[" + i + "]", declarations, result);
                                    if (value != null) {
                                        hasValue = true;
                                        String str = Integer.toHexString(value & 0xff);
                                        if (str.length() == 1) {
                                            buf.append('0');
                                        }
                                        buf.append(str);
                                        bout.write(value);
                                    }
                                }
                            } catch (EOFException e) {
                            }
                        } else {
                            for (int i = 0; i < size; i++) {
                                //if (i > 0) {
                                //    buf.append(", ");
                                //}
                                Integer value = (Integer) readValue(in, parentIdentifier + "[" + i + "]", declarations, result);
                                if (value != null) {
                                    hasValue = true;
                                    String str = Integer.toHexString(value & 0xff);
                                    if (str.length() == 1) {
                                        buf.append('0');
                                    }
                                    buf.append(str);
                                    bout.write(value);
                                }
                            }
                        }
                    }
                    // ArrayList End
                    if (hasValue) {
                        if (true) {
                            return bout.toByteArray();
                        }
                        //buf.append('}');
                        return buf.toString();
                    } else {
                        return null;
                    }
                } else {
                    return readValue(in, parentIdentifier, declarations, result);
                }
            } else {
                if (arrayList != null) {
                    StringBuffer buf = new StringBuffer();
                    buf.append('{');
                    boolean hasValue = false;

                    // ArrayList Begin
                    Enumeration enm = arrayList.elements();
                    while (enm.hasMoreElements()) {
                        ArraySize arraySize = (ArraySize) enm.nextElement();
                        int size = arraySize.getArraySize(declarations, result);
                        if (size == ArraySize.REMAINDER) {
                            try {
                                for (int i = 0;; i++) {
                                    if (i > 0) {
                                        buf.append(", ");
                                    }
                                    Object value = readValue(in, parentIdentifier + "[" + i + "]", declarations, result);
                                    if (value != null) {
                                        hasValue = true;
                                        buf.append(value.toString());
                                    }
                                }
                            } catch (EOFException e) {
                            }
                        } else {
                            for (int i = 0; i < size; i++) {
                                if (i > 0) {
                                    buf.append(", ");
                                }
                                Object value;
                                if (arraySize.type == ArraySize.EQUAL || arraySize.type == ArraySize.NOT_EQUAL) {
                                    value = readValue(in, parentIdentifier, declarations, result);
                                } else {
                                    value = readValue(in, parentIdentifier + "[" + i + "]", declarations, result);
                                }
                                if (value != null) {
                                    hasValue = true;
                                    buf.append(value.toString());
                                }
                            }
                        }
                    }
                    // ArrayList End
                    if (hasValue) {
                        buf.append('}');
                        return buf.toString();
                    } else {
                        return null;
                    }
                } else {
                    return readValue(in, parentIdentifier, declarations, result);
                }
            }
        }

        private Object readValue(ImageInputStream in, String parentIdentifier, Declarations declarations, Vector result)
                throws IOException {
            Object value = primitiveSpecifier.read(in, parentIdentifier, declarations, result);
            if (value != null) {
                if (value instanceof Number) {
                    int intValue = ((Number) value).intValue();
                    if (enumSpecifier != null) {
                        value = enumSpecifier.toEnumString(intValue, declarations);
                    } else if (setSpecifier != null) {
                        value = setSpecifier.toSetString(intValue, declarations);
                    }
                } else if (value instanceof String) {
                    if (enumSpecifier != null) {
                        value = enumSpecifier.toEnumString((String) value, declarations);
                    }
                }
            }
            return value;
        }

        @Override
        public String toString() {
            StringBuffer buf = new StringBuffer();
            buf.append("TypeSpecifier ");
            buf.append("" + structSpecifier);
            buf.append("" + primitiveSpecifier);
            buf.append("" + enumSpecifier);
            buf.append("" + setSpecifier);
            buf.append("" + arrayList);
            return buf.toString();
        }
    }

    /**
     * StructSpecifier expression.
     *
     * <pre><code>
     * StructSpecifier ::= "struct (identifier | "{" MemberDeclaration { MemberDeclaration } "}" )
     * </code></pre>
     */
    protected static class StructSpecifier {

        public String identifier;
        public Vector members;

        public StructSpecifier(StreamPosTokenizer scanner)
                throws IOException, ParseException {
            if (scanner.nextToken() != StreamPosTokenizer.TT_WORD || !scanner.sval.equals("struct")) {
                throw new ParseException(errorMsg("StructSpecifier: 'struct' expected", scanner));
            }

            scanner.nextToken();
            if (scanner.ttype == StreamPosTokenizer.TT_WORD) {
                identifier = scanner.sval;
            } else if (scanner.ttype == '{') {
                members = new Vector();
                while (scanner.nextToken() != '}') {
                    scanner.pushBack();

                    members.addElement(new MemberDeclaration(scanner));
                }
            } else {
                throw new ParseException(errorMsg("StructSpecifier: identifier or '{' expected", scanner));
            }
        }

        private void read(ImageInputStream in, String parentIdentifier, Declarations declarations, Vector result)
                throws IOException {
            //try {
            Enumeration enm = members.elements();
            while (enm.hasMoreElements()) {
                MemberDeclaration aMember = (MemberDeclaration) enm.nextElement();
                aMember.read(in, parentIdentifier, declarations, result);
            }
            /*
            } catch (IOException e) {
            System.out.println("StructParser.StructSpecifier.read(...) IOException @ "+identifier);
            e.printStackTrace();
            throw e;
            }*/
        }
    }

    /**
     * MemberDeclaration expression.
     *
     * <pre><code>
     * MemberDeclaration ::= TypeSpecifier identifier [ArrayList] ";"
     * ArrayList ::= "[" [ArraySize] "]" {"," identifier "[" [ArraySize] "]" }
     * </code></pre>
     */
    protected static class MemberDeclaration {

        public TypeSpecifier typeSpecifier;
        public String identifier;
        public Vector arrayList;

        public MemberDeclaration(StreamPosTokenizer scanner)
                throws IOException, ParseException {
            typeSpecifier = new TypeSpecifier(scanner);

            if (scanner.nextToken() != StreamPosTokenizer.TT_WORD) {
                throw new ParseException(errorMsg("MemberDeclaration: identifier expected", scanner));
            }
            identifier = scanner.sval;

            // ArrayList Begin
            if (scanner.nextToken() == '[') {
                arrayList = new Vector();
                do {
                    arrayList.addElement(new ArraySize(scanner));
                    if (scanner.nextToken() != ']') {
                        throw new ParseException(errorMsg("MemberDeclaration: ']' expected", scanner));
                    }
                } while (scanner.nextToken() == '[');
                scanner.pushBack();
            } else {
                scanner.pushBack();
            }
            // ArrayList End

            if (scanner.nextToken() == ';') {
            } else if (scanner.ttype == '}') {
                scanner.pushBack();
            } else {
                throw new ParseException(errorMsg("MemberDeclaration: ';' expected", scanner));
            }
        }

        private void read(ImageInputStream in, String parentIdentifier, Declarations declarations, Vector result)
                throws IOException {
            if (arrayList != null) {
                // ArrayList Begin
                Enumeration enm = arrayList.elements();
                while (enm.hasMoreElements()) {
                    ArraySize arraySize = (ArraySize) enm.nextElement();
                    int size = arraySize.getArraySize(declarations, result);
                    if (size == ArraySize.REMAINDER) {
                        try {
                            for (int i = 0;; i++) {
                                int loc = result.size();
                                Object obj = typeSpecifier.read(in, parentIdentifier + "." + identifier + "[" + i + "]", declarations, identifier, result);
                                if (obj != null) {
                                    StructTableModel.Value value = new StructTableModel.Value();
                                    value.qualifiedIdentifier = parentIdentifier + "." + identifier;
                                    value.declaration = identifier;
                                    value.value = obj;
                                    result.addElement(value);
                                }
                            }
                        } catch (EOFException e) {
                        }
                    } else {
                        for (int i = 0; i < size; i++) {
                            int loc = result.size();
                            Object obj = typeSpecifier.read(in, parentIdentifier + "." + identifier + "[" + i + "]", declarations, identifier, result);
                            if (obj != null) {
                                StructTableModel.Value value = new StructTableModel.Value();
                                value.qualifiedIdentifier = parentIdentifier + "." + identifier+ "[" + i + "]";
                                value.declaration = identifier;
                                value.value = obj;
                                result.addElement(value);
                            }
                        }
                    }
                }
                // ArrayList End
            } else {
                Object obj = typeSpecifier.read(in, parentIdentifier + "." + identifier, declarations, identifier, result);
                if (obj != null) {
                    StructTableModel.Value value = new StructTableModel.Value();
                    value.qualifiedIdentifier = parentIdentifier + "." + identifier;
                    value.declaration = identifier;
                    value.value = obj;
                    result.addElement(value);
                }
            }

        }
    }

    /**
     * PrimitiveSpecifier expression.
     *
     * <pre><code>
     * PrimitiveSpecifier ::= "uint" n | "ubyte" | "byte" | "short" | "ushort" |
     *                         | "int" | "long" | "float" | "double" | "extended"
     *                         | "char" | "charbyte" | "cstring" | "utf8" | "pstring"
     *                         | "pstring32"
     *                         | "utf16le" |"magic" | "mactimestamp"
     *                         | "bcd2" | "bcd4"
     * </code></pre>
     */
    protected static class PrimitiveSpecifier {

        private final static int NON_PRIMITIVE = -1;
        private final static char[] HEX = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
        /* 8 bits signed. */
        public final static int BYTE = 0;
        /* 8 bits unsigned. */
        public final static int UBYTE = 1;
        /* 16 bits signed big endian. */
        public final static int SHORT = 2;
        /* 16 bits unsigned big endian. */
        public final static int USHORT = 3;
        /* 32 bits signed big endian. */
        public final static int INT = 4;
        /* 32 bits unsigned big endian. */
        public final static int UINT = 5;
        /* 64 bits signed big endian. */
        public final static int LONG = 6;
        /* 32 bits IEEE 754. */
        public final static int FLOAT = 7;
        /* 64 bits IEEE 754. */
        public final static int DOUBLE = 8;
        /* 80 bits IEEE 754. */
        public final static int EXTENDED = 9;
        /* 8 bits ASCII. */
        public final static int CHARBYTE = 10;
        /* 16 bits UTF-16. */
        public final static int CHAR = 11;
        /* C-Style Zero terminated ASCII String. */
        public final static int CSTRING = 12;
        /* Pascal Style String. Starting with an 8 or 16 bit long length value. */
        public final static int PSTRING = 13;
        /* Pascal Style String. Starting with an 8 or 16 bit long length value.
        padded to 32 bytes. */
        public final static int PSTRING32 = 30;
        /* UTF-8 zero terminated string. */
        public final static int UTF8 = 32;
        /* UTF-16 little endian zero terminated string. */
        public final static int UTF16LE = 29;
        /* 4 ASCII characters in 4 subsequent bytes. */
        public final static int MAGIC = 14;
        /* 32 bit signed count of seconds since 1904. */
        public final static int MAC_TIMESTAMP = 15;
        /* A typedef type. */
        public final static int TYPEDEF_TYPE = 16;
        /* 16 bits signed little endian. */
        public final static int SHORTLE = 17;
        /* 16 bits unsigned little endian. */
        public final static int USHORTLE = 18;
        /* 32 bits signed little endian. */
        public final static int INTLE = 19;
        /* 32 bits unsigned little endian. */
        public final static int UINTLE = 20;
        /* 64 bits signed little endian. */
        public final static int LONGLE = 21;
        /* 2 Digits Binary Coded Decimal (=1 byte). */
        public final static int BCD2 = 22;
        /* 4 Digits Binary Coded Decimal (=2 bytes). */
        public final static int BCD4 = 23;
        /* 32-bit fixed decimal divided by 16.16 (=4 bytes). */
        public final static int FIXED_16D16 = 24;
        /* 32-bit fixed decimal divided by 2.30 (=4 bytes). */
        public final static int FIXED_2D30 = 25;
        /* 16-bit fixed decimal divided by 8.8 (=2 bytes). */
        public final static int FIXED_8D8 = 26;
        /* 1 bit unsigned. Does not need to start at byte boundaries. */
        public final static int UINT1 = 34;
        /* 2 bits unsigned. Does not need to start at byte boundaries. */
        public final static int UINT2 = 35;
        /* 3 bits unsigned. Does not need to start at byte boundaries. */
        public final static int UINT3 = 36;
        /* 4 bits unsigned. Does not need to start at byte boundaries. */
        public final static int UINT4 = 27;
        /* 5 bits unsigned. Does not need to start at byte boundaries. */
        public final static int UINT5 = 37;
        /* 5 bits unsigned. Does not need to start at byte boundaries. */
        public final static int UINT9 = 41;
        /* 8 bits unsigned. Does not need to start at byte boundaries. */
        public final static int UINT8 = 33;
        /* 16 bits unsigned. Does not need to start at byte boundaries. */
        public final static int UINT12 = 38;
        /* 16 bits unsigned. Does not need to start at byte boundaries. */
        public final static int UINT16 = 39;
        /* 9 bits signed. Does not need to start at byte boundaries. */
        public final static int INT9 = 40;
        /* Atari 16 bit color value with 3 bits per channel:
         *      RGB 00000rrr0ggg0bbb
         */
        public final static int ATARI_COLOR = 31;
        public int type;
        public String typedef;

        public PrimitiveSpecifier(StreamPosTokenizer scanner)
                throws IOException, ParseException {

            if (scanner.nextToken() != StreamPosTokenizer.TT_WORD) {
                throw new ParseException(errorMsg("PrimitiveSpecifier: primitive type name expected", scanner));
            }

            if (scanner.sval.equals("ataricolor")) {
                type = ATARI_COLOR;
            } else if (scanner.sval.equals("byte")) {
                type = BYTE;
            } else if (scanner.sval.equals("ubyte")) {
                type = UBYTE;
            } else if (scanner.sval.equals("uint8")) {
                type = UINT8;
            } else if (scanner.sval.equals("short")) {
                type = SHORT;
            } else if (scanner.sval.equals("int16")) {
                type = SHORT;
            } else if (scanner.sval.equals("ushort")) {
                type = USHORT;
            } else if (scanner.sval.equals("uint16")) {
                type = UINT16;
            } else if (scanner.sval.equals("int")) {
                type = INT;
            } else if (scanner.sval.equals("uint")) {
                type = UINT;
            } else if (scanner.sval.equals("uint32")) {
                type = UINT;
            } else if (scanner.sval.equals("long")) {
                type = LONG;
            } else if (scanner.sval.equals("float")) {
                type = FLOAT;
            } else if (scanner.sval.equals("double")) {
                type = DOUBLE;
            } else if (scanner.sval.equals("extended")) {
                type = EXTENDED;
            } else if (scanner.sval.equals("char")) {
                type = CHAR;
            } else if (scanner.sval.equals("charbyte")) {
                type = CHARBYTE;
            } else if (scanner.sval.equals("cstring")) {
                type = CSTRING;
            } else if (scanner.sval.equals("utf8")) {
                type = UTF8;
            } else if (scanner.sval.equals("utf16le")) {
                type = UTF16LE;
            } else if (scanner.sval.equals("pstring")) {
                type = PSTRING;
            } else if (scanner.sval.equals("pstring32")) {
                type = PSTRING32;
            } else if (scanner.sval.equals("magic")) {
                type = MAGIC;
            } else if (scanner.sval.equals("mactimestamp")) {
                type = MAC_TIMESTAMP;
            } else if (scanner.sval.equals("shortLE")) {
                type = SHORTLE;
            } else if (scanner.sval.equals("ushortLE")) {
                type = USHORTLE;
            } else if (scanner.sval.equals("intLE")) {
                type = INTLE;
            } else if (scanner.sval.equals("uintLE")) {
                type = UINTLE;
            } else if (scanner.sval.equals("longLE")) {
                type = LONGLE;
            } else if (scanner.sval.equals("bcd2")) {
                type = BCD2;
            } else if (scanner.sval.equals("bcd4")) {
                type = BCD4;
            } else if (scanner.sval.equals("fixed16d16")) {
                type = FIXED_16D16;
            } else if (scanner.sval.equals("fixed2d30")) {
                type = FIXED_2D30;
            } else if (scanner.sval.equals("fixed8d8")) {
                type = FIXED_8D8;
            } else if (scanner.sval.equals("uint1")) {
                type = UINT1;
            } else if (scanner.sval.equals("uint2")) {
                type = UINT2;
            } else if (scanner.sval.equals("uint3")) {
                type = UINT3;
            } else if (scanner.sval.equals("uint4")) {
                type = UINT4;
            } else if (scanner.sval.equals("uint5")) {
                type = UINT5;
            } else if (scanner.sval.equals("uint9")) {
                type = UINT9;
            } else if (scanner.sval.equals("uint12")) {
                type = UINT12;
            } else if (scanner.sval.equals("int9")) {
                type = INT9;
            } else {
                type = TYPEDEF_TYPE;
                typedef = scanner.sval;
            }
        }

        private int getResolvedPrimitveType(Declarations declarations) throws IOException {
            switch (type) {
                case TYPEDEF_TYPE:
                    TypedefDeclaration typedefDeclaration = (TypedefDeclaration) declarations.typedefs.get(typedef);
                    if (typedefDeclaration == null) {
                        throw new IOException("typedef not found for:" + typedef);
                    }
                    return typedefDeclaration.getResolvedType(declarations);
                default:
                    return type;
            }
        }

        private Object read(ImageInputStream in, String parentIdentifier, Declarations declarations, Vector result)
                throws IOException {
            switch (type) {
                case BYTE:
                    return new Byte(in.readByte());
                case UBYTE: {
                    int b = in.read();
                    if (b == -1) {
                        throw new EOFException();
                    }
                    return new Integer(b);
                }
                case SHORT:
                    return new Short(in.readShort());
                case USHORT:
                    return new Integer(in.readShort() & 0xffff);
                case INT:
                    return new Integer(in.readInt());
                case UINT:
                    return new Long(in.readInt() & 0xffffffffL);
                case LONG:
                    return new Long(in.readLong());
                case FLOAT:
                    return new Float(in.readFloat());
                case DOUBLE:
                    return new Double(in.readDouble());
                case EXTENDED: {
                    byte[] bits = new byte[10];
                    in.readFully(bits);
                    return new ExtendedReal(bits);
                }
                case CHARBYTE: {
                    char ch = (char) (in.readByte() & 0xff);
                    if (ch <= 0x20 || Character.isIdentifierIgnorable(ch)) {
                        return "0x" + Integer.toHexString(ch);
                    } else {
                        return new Character(ch);
                    }
                }
                case CHAR:
                    return new Character(in.readChar());
                case CSTRING: {
                    StringBuffer buf = new StringBuffer();
                    int ch;
                    while ((ch = in.read()) > 0) {
                        buf.append((char) ch);
                    }
                    return buf.toString();
                }
                case UTF8: {
                    ByteArrayOutputStream buf = new ByteArrayOutputStream();
                    int ch1;
                    while ((ch1 = in.read()) > 0) {
                        buf.write(ch1);
                    }
                    return new String(buf.toByteArray(), "UTF-8");
                }
                case UTF16LE: {
                    StringBuffer buf = new StringBuffer();
                    int ch1;
                    while ((ch1 = in.read()) > 0) {
                        int ch2 = in.read();
                        buf.append((char) (ch1 | (ch2 << 8)));
                    }
                    return buf.toString();
                }
                case PSTRING: {
                    int size = in.read();
                    if (size == 0) {
                        size = in.read();
                        in.read(); // why do we skip two bytes here?
                        in.read();
                    }
                    if (size < 0) {
                        return "";
                    }
                    byte[] bytes = new byte[size];
                    //in.readFully(bytes);
                    int n = 0;
                    while (n < size) {
                        int count = in.read(bytes, n, size - n);
                        if (count < 0) {
                            System.out.println("StructParser.PrimitiveSpecifier.read not enough bytes for pstring. Expected size:" + size + " actual size:" + n);
                            break;
                            //throw new EOFException();
                        }
                        n += count;
                    }

                    return new String(bytes);
                }
                case PSTRING32: {
                    int used = 1;
                    int size = in.read();
                    if (size == 0) {
                        size = in.read();
                        in.read();
                        in.read();
                        used += 2;
                    }
                    if (size > 32 - used) {
                        size = 32 - used;
                    }
                    byte[] bytes = new byte[size];
                    //in.readFully(bytes);
                    int n = 0;
                    while (n < size) {
                        int count = in.read(bytes, n, size - n);
                        if (count < 0) {
                            System.out.println("StructParser.PrimitiveSpecifier.read not enough bytes for pstring. Expected size:" + size + " actual size:" + n);
                            break;
                            //throw new EOFException();
                        }
                        n += count;
                    }
                    used += size;
                    if (used < 32) {
                        in.skipBytes(32 - used);
                    }

                    return new String(bytes);
                }
                case MAGIC: {
                    int magic = in.readInt();
                    byte[] bytes = new byte[4];
                    bytes[0] = (byte) (magic >>> 24);
                    bytes[1] = (byte) (magic >>> 16);
                    bytes[2] = (byte) (magic >>> 8);
                    bytes[3] = (byte) (magic >>> 0);
                    return new String(bytes);
                }
                case MAC_TIMESTAMP: {
                    long timestamp = ((long) in.readInt()) & 0xffffffffL;
                    DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    Date date = new Date(MAC_TIMESTAMP_EPOCH + timestamp * 1000);
                    return fmt.format(date);
                }
                case TYPEDEF_TYPE: {
                    TypedefDeclaration typedefDeclaration = (TypedefDeclaration) declarations.typedefs.get(typedef);
                    if (typedefDeclaration == null) {
                        throw new IOException("typedef not found for:" + typedef);
                    }
                    return typedefDeclaration.read(in, parentIdentifier, declarations, result);
                }
                case SHORTLE: {
                    int b0 = in.read();
                    int b1 = in.read();
                    if (b1 == -1) {
                        throw new EOFException();
                    }
                    return new Short((short) (b0 & 0xff | (b1 & 0xff) << 8));
                }
                case USHORTLE: {
                    int b0 = in.read();
                    int b1 = in.read();
                    if (b1 == -1) {
                        throw new EOFException();
                    }
                    return new Integer(b0 & 0xff | ((b1 & 0xff) << 8));
                }
                case INTLE: {
                    int b0 = in.read();
                    int b1 = in.read();
                    int b2 = in.read();
                    int b3 = in.read();
                    if (b3 == -1) {
                        throw new EOFException();
                    }
                    return new Integer(b0 & 0xff | ((b1 & 0xff) << 8) | ((b2 & 0xff) << 16) | ((b3 & 0xff) << 24));
                }
                case UINTLE: {
                    int b0 = in.read();
                    int b1 = in.read();
                    int b2 = in.read();
                    int b3 = in.read();
                    if (b3 == -1) {
                        throw new EOFException();
                    }
                    return new Long(((long) (b0 & 0xff | ((b1 & 0xff) << 8) | ((b2 & 0xff) << 16) | ((b3 & 0xff) << 24))) & 0x00ffffffff);
                }
                case LONGLE: {
                    int b0 = in.read();
                    int b1 = in.read();
                    int b2 = in.read();
                    int b3 = in.read();
                    int b4 = in.read();
                    int b5 = in.read();
                    int b6 = in.read();
                    int b7 = in.read();
                    if (b7 == -1) {
                        throw new EOFException();
                    }
                    return new Long(
                            (long) b0 & 0xff | ((long) (b1 & 0xff) << 8) | ((long) (b2 & 0xff) << 16) | ((long) (b3 & 0xff) << 24) | ((long) (b4 & 0xff) << 32) | ((long) (b5 & 0xff) << 40) | ((long) (b6 & 0xff) << 48) | ((long) (b7 & 0xff) << 56));
                }
                case BCD2: {
                    int b0 = in.read();

                    return new Integer(
                            ((b0 & 0xf0) >> 4) * 10
                            + (b0 & 0x0f));
                }
                case BCD4: {
                    int b0 = in.read();
                    int b1 = in.read();

                    return new Integer(
                            ((b0 & 0xf0) >> 4) * 1000
                            + (b0 & 0x0f) * 100
                            + ((b1 & 0xf0) >> 4) * 10
                            + (b1 & 0x0f));
                }
                case FIXED_16D16: {
                    int wholePart = in.readUnsignedShort();
                    int fractionPart = in.readUnsignedShort();

                    return new Double(wholePart + fractionPart / 65536.0);
                }
                case FIXED_2D30: {
                    int fixed = in.readInt();
                    int wholePart = fixed >>> 30;
                    int fractionPart = fixed & 0x3fffffff;

                    return new Double(wholePart + fractionPart / (double) 0x3fffffff);
                }
                case FIXED_8D8: {
                    int fixed = in.readUnsignedShort();
                    int wholePart = fixed >>> 8;
                    int fractionPart = fixed & 0xff;

                    return new Float(wholePart + fractionPart / 256f);
                }
                case UINT1: {
                    return in.readBit();
                }
                case UINT2: {
                    return (int) in.readBits(2);
                }
                case UINT3: {
                    return (int) in.readBits(3);
                }
                case UINT4: {
                    return (int) in.readBits(4);
                }
                case UINT5: {
                    return (int) in.readBits(5);
                }
                case UINT9: {
                    return (int) in.readBits(9);
                }
                case UINT8: {
                    return (int) in.readBits(8);
                }
                case UINT12: {
                    return (int) in.readBits(12);
                }
                case UINT16: {
                    return (int) in.readBits(16);
                }
                case INT9: {
                    int value = (int) in.readBits(9);
                    if ((value & 0x256) != 0) {
                        value |= 0xffffff00;
                    }
                    return value;
                }
                case ATARI_COLOR: {
                    int clr = in.readUnsignedShort();
                    int red = (clr & 0x700) >> 8;
                    int green = (clr & 0x70) >> 4;
                    int blue = (clr & 0x7);
                    StringBuilder buf = new StringBuilder();
                    String hex = Integer.toHexString(clr);
                    buf.append("0x");
                    for (int i = 0; i < 4 - hex.length(); i++) {
                        buf.append('0');
                    }
                    buf.append(hex);
                    buf.append(" {rgb:0x");
                    int rgb = ((red << 5) | (red << 2) | (red >>> 1)) << 16//
                            | ((green << 5) | (green << 2) | (green >>> 1)) << 8
                            | ((blue << 5) | (blue << 2) | (blue >>> 1));
                    hex = Integer.toHexString(rgb);
                    for (int i = 0; i < 6 - hex.length(); i++) {
                        buf.append('0');
                    }
                    buf.append(hex);
                    buf.append(", rgb:");
                    buf.append((red << 5) | (red << 2) | (red >>> 1));
                    buf.append(' ');
                    buf.append((green << 5) | (green << 2) | (green >>> 1));
                    buf.append(' ');
                    buf.append((blue << 5) | (blue << 2) | (blue >>> 1));
                    buf.append('}');
                    return new String(buf);
                }
                default:
                    throw new InternalError("invalid type:" + type);
            }
        }
    }

    /**
     * ArraySize expression.
     *
     * <pre><code>
     * ArraySize ::= () | intLiteral ["-" intLiteral]
     * </code></pre>
     */
    protected static class ArraySize {

        public final static int REMAINDER = -1;
        public final static int LITERAL = 0;
        public final static int VARIABLE = 1;
        public final static int NEGATIVE_VARIABLE = 2;
        public final static int EQUAL = 3;
        public final static int NOT_EQUAL = 4;
        public int type = LITERAL;
        public int size;
        public String variable;
        public int offset;
        public Object equal;

        public ArraySize(StreamPosTokenizer scanner)
                throws IOException, ParseException {
            if (scanner.nextToken() == ']') {
                scanner.pushBack();
                type = REMAINDER;
            } else if (scanner.ttype == StreamPosTokenizer.TT_WORD) {
                variable = scanner.sval;
                if (scanner.nextToken() == '-') {
                    offset = new IntLiteral(scanner).intValue() * -1;
                    type = VARIABLE;
                } else if (scanner.ttype == '+') {
                    offset = new IntLiteral(scanner).intValue();
                    type = VARIABLE;
                } else if (scanner.ttype == '=') {
                    if (scanner.nextToken() != '=') {
                        throw new ParseException(errorMsg("== expected", scanner));
                    }
                    if (scanner.nextToken() == '"') {
                        equal = scanner.sval;
                    } else {
                        scanner.pushBack();
                        equal = new IntLiteral(scanner).intValue();
                    }
                    type = EQUAL;
                } else if (scanner.ttype == '!') {
                    if (scanner.nextToken() != '=') {
                        throw new ParseException(errorMsg("!= expected", scanner));
                    }
                    if (scanner.nextToken() == '"') {
                        equal = scanner.sval;
                    } else {
                        int sign;
                        if (scanner.ttype=='-') {
                            sign=-1;
                        }else {
                            sign=1;
                        scanner.pushBack();
                        }
                        equal = new IntLiteral(scanner).intValue()*sign;
                    }
                    type = NOT_EQUAL;
                } else {
                    type = VARIABLE;
                    scanner.pushBack();
                }
            } else {
                scanner.pushBack();
                size = new IntLiteral(scanner).intValue();
                if (scanner.nextToken() == '-') {
                    offset = size;
                    size = 0;
                    if (scanner.nextToken() != StreamPosTokenizer.TT_WORD) {
                        throw new ParseException(errorMsg("Variable name expected", scanner));
                    }
                    variable = scanner.sval;
                    type = NEGATIVE_VARIABLE;
                } else if (scanner.ttype == '+') {
                    offset = size;
                    size = 0;
                    if (scanner.nextToken() != StreamPosTokenizer.TT_WORD) {
                        throw new ParseException(errorMsg("Variable name expected", scanner));
                    }
                    variable = scanner.sval;
                    type = VARIABLE;
                } else if (scanner.ttype == '=') {
                    if (scanner.nextToken() != '=') {
                        throw new ParseException(errorMsg("== expected", scanner));
                    }
                    offset = size;
                    size = 0;
                    if (scanner.nextToken() != StreamPosTokenizer.TT_WORD) {
                        throw new ParseException(errorMsg("Variable name expected", scanner));
                    }
                    variable = scanner.sval;
                    type = EQUAL;
                } else if (scanner.ttype == '!') {
                    if (scanner.nextToken() != '=') {
                        throw new ParseException(errorMsg("== expected", scanner));
                    }
                    offset = size;
                    size = 0;
                    if (scanner.nextToken() != StreamPosTokenizer.TT_WORD) {
                        throw new ParseException(errorMsg("Variable name expected", scanner));
                    }
                    variable = scanner.sval;
                    type = NOT_EQUAL;
                } else {
                    type = LITERAL;
                    scanner.pushBack();
                }
            }
        }

        public int getArraySize(Declarations declarations, Vector result) {
            switch (type) {
                case LITERAL:
                    return size;
                case VARIABLE:
                case NEGATIVE_VARIABLE:
                    int sign = type == NEGATIVE_VARIABLE ? -1 : 1;

                    // Search for variable with the same name
                    for (int i = result.size() - 1; i > -1; i--) {
                        StructTableModel.Value value = (StructTableModel.Value) result.elementAt(i);
                        if (value.declaration.equals(variable)) {
                            int variableValue = ((Number) value.value).intValue();
                            return variableValue * sign + offset;
                        }
                    }
                    // Search for fully qualified variable with the same name
                    for (int i = result.size() - 1; i > -1; i--) {
                        StructTableModel.Value value = (StructTableModel.Value) result.elementAt(i);
                        if (value.qualifiedIdentifier.equals(variable)) {
                            int variableValue = ((Number) value.value).intValue();
                            return variableValue * sign + offset;
                        }
                    }
                    // Search for fully qualified variable with the same name ending
                    for (int i = result.size() - 1; i > -1; i--) {
                        StructTableModel.Value value = (StructTableModel.Value) result.elementAt(i);
                        if (value.qualifiedIdentifier.endsWith(variable)) {
                            int variableValue = ((Number) value.value).intValue();
                            return variableValue * sign + offset;
                        }
                    }
                    throw new InternalError("Invalid ArraySize variable:" + variable);
                case EQUAL:
                case NOT_EQUAL:
                    int trueValue = type == EQUAL ? 1 : 0;
                    int falseValue = 1 - trueValue;

                    // Search for variable with the same name
                    for (int i = result.size() - 1; i > -1; i--) {
                        StructTableModel.Value value = (StructTableModel.Value) result.elementAt(i);
                        if (value.declaration.equals(variable)) {
                            return ((Number)equal).intValue()==((Number)value.value).intValue() ? trueValue : falseValue;
                        }
                    }
                    // Search for fully qualified variable with the same name
                    for (int i = result.size() - 1; i > -1; i--) {
                        StructTableModel.Value value = (StructTableModel.Value) result.elementAt(i);
                        if (value.qualifiedIdentifier.equals(variable)) {
                            return ((Number)equal).intValue()==((Number)value.value).intValue() ? trueValue : falseValue;
                        }
                    }
                    // Search for fully qualified variable with the same name ending
                    for (int i = result.size() - 1; i > -1; i--) {
                        StructTableModel.Value value = (StructTableModel.Value) result.elementAt(i);
                        if (value.qualifiedIdentifier.endsWith(variable)) {
                            return ((Number)equal).intValue()==((Number)value.value).intValue() ? trueValue : falseValue;
                        }
                    }
                    throw new InternalError("Invalid ArraySize variable:" + variable);
                case REMAINDER:
                    return -1;
                default:
                    throw new InternalError("Invalid ArraySize type:" + type);
            }
        }
    }

    /**
     * IntLiteral expression.
     *
     * <pre><code>
     * IntLiteral ::= intLiteral | hexLiteral
     * </code></pre>
     */
    protected static class IntLiteral {

        public int value;

        public IntLiteral(StreamPosTokenizer scanner)
                throws IOException, ParseException {
            if (scanner.nextToken() != StreamPosTokenizer.TT_NUMBER) {
                throw new ParseException(errorMsg("IntLiteral: numeric value expected", scanner));
            }
            value = (int) scanner.nval;
            if (scanner.nval == 0.0) {
                if (scanner.nextToken() == StreamPosTokenizer.TT_WORD && scanner.sval.startsWith("x")) {
                    value = Integer.valueOf(scanner.sval.substring(1), 16).intValue();
                } else {
                    scanner.pushBack();
                }
            }
        }

        public int intValue() {
            return value;
        }
    }

    /**
     * MagicOrIntLiteral expression.
     *
     * <pre><code>
     * MagicOrIntLiteral ::= magicLiteral | intLiteral | hexLiteral
     * </code></pre>
     */
    protected static class MagicOrIntLiteral {

        public int intValue;
        public String magicValue;

        public MagicOrIntLiteral(StreamPosTokenizer scanner)
                throws IOException, ParseException {
            switch (scanner.nextToken()) {
                case StreamPosTokenizer.TT_NUMBER:
                    intValue = (int) scanner.nval;
                    if (scanner.nval == 0.0) {
                        if (scanner.nextToken() == StreamPosTokenizer.TT_WORD && scanner.sval.startsWith("x")) {
                            intValue = Integer.valueOf(scanner.sval.substring(1), 16).intValue();
                        } else {
                            scanner.pushBack();
                        }
                    }
                    break;
                case '-':
                    if (scanner.nextToken() != StreamPosTokenizer.TT_NUMBER) {
                        throw new ParseException(errorMsg("MagicOrIntLiteral: numeric value expected", scanner));
                    }
                    intValue = -(int) scanner.nval;
                    break;
                case '"':
                    if (scanner.sval.length() != 4) {
                        throw new ParseException(errorMsg("MagicOrIntLiteral: magic with length of 4 characters expected", scanner));
                    }
                    magicValue = scanner.sval;
                    intValue = toInt(magicValue);
                    break;
                default:
                    throw new ParseException(errorMsg("MagicOrIntLiteral: numeric value expected", scanner));
            }
        }

        public boolean isMagic() {
            return magicValue != null;
        }

        public int intValue() {
            return intValue;
        }

        public String stringValue() {
            return (magicValue == null) ? Integer.toString(intValue) : magicValue;
        }

        public static int toInt(String magic) {
            return ((magic.charAt(0) & 0xff) << 24) | ((magic.charAt(1) & 0xff) << 16) | ((magic.charAt(2) & 0xff) << 8) | ((magic.charAt(3) & 0xff) << 0);
        }

        public static String toMagic(int value) {
            byte[] buf = new byte[4];
            buf[0] = (byte) ((value & 0xff000000) >>> 24);
            buf[1] = (byte) ((value & 0xff0000) >>> 16);
            buf[2] = (byte) ((value & 0xff00) >>> 8);
            buf[3] = (byte) (value & 0xff);
            try {
                return new String(buf, "ASCII");
            } catch (UnsupportedEncodingException e) {
                InternalError error = new InternalError(e.getMessage());
                error.initCause(e);
                throw error;
            }
        }
    }
}
