All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nl.pvanassen.bplist.parser.ElementParser Maven / Gradle / Ivy

The newest version!
package nl.pvanassen.bplist.parser;

import java.io.*;
import java.math.BigInteger;
import java.util.*;

import org.apache.commons.io.IOUtils;
import org.slf4j.*;

/**
 * Parser for reading the bplist
 * 
 * @author Paul van Assen
 */
public class ElementParser {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * Parse object table with a random access file. This method will not close
     * the file for you.
     *
     * @param file File object
     * @return List of objects parsed
     * @throws IOException
     *             In case of an error
     */
    public List> parseObjectTable(File file) throws IOException {
        RandomAccessFile raf = null;
        try {
            raf = new RandomAccessFile(file, "r");
            return parseObjectTable(raf);
        } finally {
            IOUtils.closeQuietly(raf);
        }
    }

    /**
     * Parse object table with a random access file. This method will not close
     * the file for you.
     *
     * @param raf
     *            Random access file
     * @return List of objects parsed
     * @throws IOException
     *             In case of an error
     */
    private List> parseObjectTable(RandomAccessFile raf) throws IOException {

        // Parse the HEADER
        // ----------------
        // magic number ("bplist")
        // file format version ("00")
        int bpli = raf.readInt();
        int st00 = raf.readInt();
        if ((bpli != 0x62706c69) || (st00 != 0x73743030)) {
            throw new IOException("parseHeader: File does not start with 'bplist00' magic.");
        }

        // Parse the TRAILER
        // ----------------
        // byte size of offset ints in offset table
        // byte size of object refs in arrays and dicts
        // number of offsets in offset table (also is number of objects)
        // element # in offset table which is top level object
        raf.seek(raf.length() - 32);
        raf.readLong();
        // count of object refs in arrays and dicts
        int refCount = (int) raf.readLong();
        raf.readLong();
        // element # in offset table which is top level object
        int topLevelOffset = (int) raf.readLong();
        raf.seek(8);

        // Read everything in memory hmmmm
        byte[] buf = new byte[topLevelOffset - 8];
        raf.readFully(buf);
        ByteArrayInputStream stream = new ByteArrayInputStream(buf);

        return parseObjectTable(new DataInputStream(stream), refCount);
    }

    /**
     * Object Formats (marker byte followed by additional info in some cases)
     * 
    *
  • null 0000 0000
  • *
  • bool 0000 1000 // false
  • *
  • bool 0000 1001 // true
  • *
  • fill 0000 1111 // fill byte
  • *
  • int 0001 nnnn ... // # of bytes is 2^nnnn, big-endian bytes
  • *
  • real 0010 nnnn ... // # of bytes is 2^nnnn, big-endian bytes
  • *
  • date 0011 0011 ... // 8 byte float follows, big-endian bytes
  • *
  • data 0100 nnnn [int] ... // nnnn is number of bytes unless 1111 then int count follows, followed by bytes
  • *
  • string 0101 nnnn [int] ... // ASCII string, nnnn is # of chars, if 1111 then int count, else bytes
  • *
  • string 0110 nnnn [int] ... // Unicode string, nnnn is # of chars, else 1111 then int count, then big-endian 2-byte shorts
  • *
  • 0111 xxxx // unused
  • *
  • uid 1000 nnnn ... // nnnn+1 is # of bytes
  • *
  • 1001 xxxx // unused
  • *
  • array 1010 nnnn [int] objref* // nnnn is count, unless '1111', then int count follows
  • *
  • 1011 xxxx // unused
  • *
  • 1100 xxxx // unused
  • *
  • dict 1101 nnnn [int] keyref* objref* // nnnn is count, unless '1111', then int count follows
  • *
  • 1110 xxxx // unused
  • *
  • 1111 xxxx // unused
  • *
*/ private List> parseObjectTable(DataInputStream in, int refCount) throws IOException { List> objectTable = new LinkedList>(); int marker; while ((marker = in.read()) != -1) { // System.err.println("parseObjectTable marker=" + // Integer.toBinaryString(marker)+" 0x"+Integer.toHexString(marker)+" @0x"+Long.toHexString(getPosition())); switch ((marker & 0xf0) >> 4) { case 0: { parseBoolean(marker & 0xf, objectTable); break; } case 1: { int count = 1 << (marker & 0xf); parseInt(in, count, objectTable); break; } case 2: { int count = 1 << (marker & 0xf); parseReal(in, count, objectTable); break; } case 3: { switch (marker & 0xf) { case 3: parseDate(in, objectTable); break; default: throw new IOException("parseObjectTable: illegal marker " + Integer.toBinaryString(marker)); } break; } case 4: { int count = marker & 0xf; if (count == 15) { count = readCount(in); } parseData(in, count, objectTable); break; } case 5: { int count = marker & 0xf; if (count == 15) { count = readCount(in); } parseAsciiString(in, count, objectTable); break; } case 6: { int count = marker & 0xf; if (count == 15) { count = readCount(in); } parseUnicodeString(in, count, objectTable); break; } case 7: { if (logger.isDebugEnabled()) { logger.debug("parseObjectTable: illegal marker " + Integer.toBinaryString(marker)); } return objectTable; // throw new // IOException("parseObjectTable: illegal marker "+Integer.toBinaryString(marker)); // break; } case 8: { int count = (marker & 0xf) + 1; if (logger.isDebugEnabled()) { logger.debug("uid " + count); } parseUID(in, count, objectTable); break; } case 9: { throw new IOException("parseObjectTable: illegal marker " + Integer.toBinaryString(marker)); // break; } case 10: { int count = marker & 0xf; if (count == 15) { count = readCount(in); } if (refCount > 255) { parseShortArray(in, count, objectTable); } else { parseByteArray(in, count, objectTable); } break; } case 11: { throw new IOException("parseObjectTable: illegal marker " + Integer.toBinaryString(marker)); // break; } case 12: { throw new IOException("parseObjectTable: illegal marker " + Integer.toBinaryString(marker)); // break; } case 13: { int count = marker & 0xf; if (count == 15) { count = readCount(in); } if (refCount > 256) { parseShortDict(in, count, objectTable); } else { parseByteDict(in, count, objectTable); } break; } case 14: { throw new IOException("parseObjectTable: illegal marker " + Integer.toBinaryString(marker)); // break; } case 15: { throw new IOException("parseObjectTable: illegal marker " + Integer.toBinaryString(marker)); // break; } } } return objectTable; } /** * Reads a count value from the object table. Count values are encoded using * the following scheme: * int 0001 nnnn ... // # of bytes is 2^nnnn, big-endian bytes */ private int readCount(DataInputStream in) throws IOException { int marker = in.read(); if (marker == -1) { throw new IOException("variableLengthInt: Illegal EOF in marker"); } if (((marker & 0xf0) >> 4) != 1) { throw new IOException("variableLengthInt: Illegal marker " + Integer.toBinaryString(marker)); } int count = 1 << (marker & 0xf); int value = 0; for (int i = 0; i < count; i++) { int b = in.read(); if (b == -1) { throw new IOException("variableLengthInt: Illegal EOF in value"); } value = (value << 8) | b; } return value; } /** * null 0000 0000 bool 0000 1000 // false bool 0000 1001 // true fill 0000 * 1111 // fill byte */ private void parseBoolean(int primitive, List> objectTable) throws IOException { switch (primitive) { case 0: objectTable.add(null); break; case 8: objectTable.add(BPListBoolean.FALSE); break; case 9: objectTable.add(BPListBoolean.TRUE); break; case 15: // fill byte: don't add to object table break; default: throw new IOException("parsePrimitive: illegal primitive " + Integer.toBinaryString(primitive)); } } /** * array 1010 nnnn [int] objref* // nnnn is count, unless '1111', then int * count follows */ private void parseByteArray(DataInputStream in, int count, List> objectTable) throws IOException { int[] objref = new int[count]; for (int i = 0; i < count; i++) { objref[i] = in.readByte() & 0xff; if (objref[i] == -1) { throw new IOException("parseByteArray: illegal EOF in objref*"); } } objectTable.add(new BPLArray(objectTable, objref, BPListType.BYTE_ARRAY)); } /** * array 1010 nnnn [int] objref* // nnnn is count, unless '1111', then int * count follows */ private void parseShortArray(DataInputStream in, int count, List> objectTable) throws IOException { int[] objref = new int[count]; for (int i = 0; i < count; i++) { objref[i] = in.readShort() & 0xffff; if (objref[i] == -1) { throw new IOException("parseShortArray: illegal EOF in objref*"); } } objectTable.add(new BPLArray(objectTable, objref, BPListType.SHORT_ARRAY)); } /* * data 0100 nnnn [int] ... // nnnn is number of bytes unless 1111 then int * count follows, followed by bytes */ private void parseData(DataInputStream in, int count, List> objectTable) throws IOException { byte[] data = new byte[count]; in.readFully(data); objectTable.add(new BPListData(data)); } /** * byte dict 1101 nnnn keyref* objref* // nnnn is less than '1111' */ private void parseByteDict(DataInputStream in, int count, List> objectTable) throws IOException { int[]keyref = new int[count]; int[]objref = new int[count]; for (int i = 0; i < count; i++) { keyref[i] = in.readByte() & 0xff; } for (int i = 0; i < count; i++) { objref[i] = in.readByte() & 0xff; } objectTable.add(new BPLDict(objectTable, keyref, objref, BPListType.BYTE_DICT)); } /** * short dict 1101 ffff int keyref* objref* // int is count */ private void parseShortDict(DataInputStream in, int count, List> objectTable) throws IOException { int[]keyref = new int[count]; int[]objref = new int[count]; for (int i = 0; i < count; i++) { keyref[i] = in.readShort() & 0xffff; } for (int i = 0; i < count; i++) { objref[i] = in.readShort() & 0xffff; } objectTable.add(new BPLDict(objectTable, keyref, objref, BPListType.SHORT_DICT)); } /** * string 0101 nnnn [int] ... // ASCII string, nnnn is # of chars, else 1111 * then int count, then bytes */ private void parseAsciiString(DataInputStream in, int count, List> objectTable) throws IOException { byte[] buf = new byte[count]; in.readFully(buf); objectTable.add(new BPListString(buf)); } private void parseUID(DataInputStream in, int count, List> objectTable) throws IOException { if (count > 4) { throw new IOException("parseUID: unsupported byte count: " + count); } byte[] uid = new byte[count]; in.readFully(uid); objectTable.add(new BPLUid(new BigInteger(uid).intValue())); } /** * int 0001 nnnn ... // # of bytes is 2^nnnn, big-endian bytes */ private void parseInt(DataInputStream in, int count, List> objectTable) throws IOException { if (count > 8) { throw new IOException("parseInt: unsupported byte count: " + count); } long value = 0; for (int i = 0; i < count; i++) { int b = in.read(); if (b == -1) { throw new IOException("parseInt: Illegal EOF in value"); } value = (value << 8) | b; } objectTable.add(new BPListLong(value)); } /** * real 0010 nnnn ... // # of bytes is 2^nnnn, big-endian bytes */ private void parseReal(DataInputStream in, int count, List> objectTable) throws IOException { switch (count) { case 4: objectTable.add(new BPListFloat(in.readFloat())); break; case 8: objectTable.add(new BPListDouble(in.readDouble())); break; default: throw new IOException("parseReal: unsupported byte count:" + count); } } /** * unknown 0011 0000 ... // 8 byte float follows, big-endian bytes */ /* * private void parseUnknown(DataInputStream in) throws IOException { * in.skipBytes(1); objectTable.add("unknown"); } */ /** * date 0011 0011 ... // 8 byte float follows, big-endian bytes */ private void parseDate(DataInputStream in, List> objectTable) throws IOException { objectTable.add(new BPListDate(in.readDouble())); } /** * string 0110 nnnn [int] ... // Unicode string, nnnn is # of chars, else * 1111 then int count, then big-endian 2-byte shorts */ private void parseUnicodeString(DataInputStream in, int count, List> objectTable) throws IOException { char[] buf = new char[count]; for (int i = 0; i < count; i++) { buf[i] = in.readChar(); } objectTable.add(new BPListString(buf)); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy