SerialWriter.java :  » Timer » timeriffic » com » alfray » timeriffic » core » serial » Android Open Source

Android Open Source » Timer » timeriffic 
timeriffic » com » alfray » timeriffic » core » serial » SerialWriter.java
/*
 * Project: NerdkillAndroid
 * Copyright (C) 2010 ralfoide gmail com,
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.alfray.timeriffic.core.serial;

import java.util.zip.CRC32;

import com.alfray.timeriffic.core.serial.SerialKey.DuplicateKey;

/**
 * Encoder/decoder for typed data.
 * Extracted from NerdkillAndroid.
 * <p/>
 *
 * A {@link SerialWriter} serialized typed key/values to an int array.
 * Each key is identified by a name -- however <em>only</em> the hashcode
 * of the key string is used as an id, not the exact string itself. <br/>
 * An exception {@link DuplicateKey} is thrown when trying to add a key
 * that has the same string hashcode than an already present key. <br/>
 * The assumption is made that string hashcodes are stable for a given
 * interpreting machine (e.g. JVM or Dalvik).
 * <p/>
 *
 * <p/>
 * Supported types: <br/>
 * - integer        <br/>
 * - long           <br/>
 * - bool           <br/>
 * - float          <br/>
 * - double         <br/>
 * - string         <br/>
 * - {@link SerialWriter}
 * <p/>
 *
 * The type {@link SerialWriter} can be used to embedded a struct within
 * a struct.
 * <p/>
 *
 * Users of the serialized data can either directly process the int[] array
 * using {@link #encodeAsArray()} or can transform it to a semi-compact hexa
 * string using {@link #encodeAsString()}. Both can later be decoded using
 * {@link SerialReader}.
 * <p/>
 *
 * A typical strategy is for a serializable struct to contain a
 * <code>saveInto(SerialWriter)</code> method. Whatever code that does
 * the saving can thus first create a {@link SerialWriter} and pass it
 * to the struct so that it can save itself into the given writer.
 * <br/>
 * Another strategy is for the struct to have a method that creates the
 * {@link SerialWriter}, adds all the fields and then return the writer.
 * <br/>
 * Both approaches are valid. The former is useful if the outer and inner
 * methods are working in collaboration, the later is useful if both sides
 * should be agnostic to each other.
 * <p/>
 *
 * Format of the encoded int array:
 * The serialized data starts with a header describing the data size,
 * followed by one entry per field added in the order of the addXyz() calls.
 * Finally there's a CRC and an EOF marker.
 *
 * <pre>
 * - header:
 *   - 1 int: number of ints to follow (including this and CRC + EOF marker)
 * - entries:
 *   - 1 int: type, ascii for I L B F D S or Z
 *   - 1 int: key
 *   - int, bool, float: 1 int, value
 *   - long, double: 2 int value (MSB + LSB)
 *   - string: 1 int = number of chars following, then int = c1 | c2 (0 padding as needed)
 *   - serializer: (self, starting with number of ints to follow)
 * - 1 int: CRC (of all previous ints including header, excluding this and EOF)
 * - 1 int: EOF
 * </pre>
 */
public class SerialWriter {

    public static class CantAddData extends RuntimeException {
        private static final long serialVersionUID = 8074293730213951679L;
        public CantAddData(String message) { super(message); }
    }

    private final SerialKey mKeyer = new SerialKey();

    private int[] mData;
    private int mSize;
    private boolean mCantAdd;

    static final int TYPE_INT    = 1;
    static final int TYPE_LONG   = 2;
    static final int TYPE_BOOL   = 3;
    static final int TYPE_FLOAT  = 4;
    static final int TYPE_DOUBLE = 5;
    static final int TYPE_STRING = 6;
    static final int TYPE_SERIAL = 7;
    static final int EOF = 0x0E0F;

    public SerialWriter() {
    }

    public int[] encodeAsArray() {
        close();

        // Resize the array to be of its exact size
        if (mData.length > mSize) {
            int[] newArray = new int[mSize];
            System.arraycopy(mData, 0, newArray, 0, mSize);
            mData = newArray;
        }

        return mData;
    }

    public String encodeAsString() {
        int[] a = encodeAsArray();
        int n = a.length;
        char[] cs = new char[n * 9];

        int j = 0;
        for (int i = 0; i < n; i++) {
            int v = a[i];

            // Write nibbles in MSB-LSB order, skipping leading zeros
            boolean skipZero = true;
            for (int k = 0; k < 8; k++) {
                int b = (v >> 28) & 0x0F;
                v = v << 4;
                if (skipZero) {
                    if (b == 0) {
                        if (k < 7) {
                            continue;
                        }
                    } else {
                        skipZero = false;
                    }
                }
                char c = b < 0x0A ? (char)('0' + b) : (char)('A' - 0x0A + b);
                cs[j++] = c;
            }

            cs[j++] = ' ';
        }

        return new String(cs, 0, j);
    }

    //--

    /**
     * @throws DuplicateKey if the key had already been registered.
     */
    public void addInt(String name, int intValue) {
        addInt(mKeyer.encodeUniqueKey(name), intValue);
    }

    /**
     * @throws DuplicateKey if the key had already been registered.
     */
    public void addLong(String name, long longValue) {
        addLong(mKeyer.encodeUniqueKey(name), longValue);
    }

    /**
     * @throws DuplicateKey if the key had already been registered.
     */
    public void addBool(String name, boolean boolValue) {
        addBool(mKeyer.encodeUniqueKey(name), boolValue);
    }

    /**
     * @throws DuplicateKey if the key had already been registered.
     */
    public void addFloat(String name, float floatValue) {
        addFloat(mKeyer.encodeUniqueKey(name), floatValue);
    }

    /**
     * @throws DuplicateKey if the key had already been registered.
     */
    public void addDouble(String name, double doubleValue) {
        addDouble(mKeyer.encodeUniqueKey(name), doubleValue);
    }

    /** Add a string. Doesn't add a null value.
     * @throws DuplicateKey if the key had already been registered.
     */
    public void addString(String name, String strValue) {
        addString(mKeyer.encodeUniqueKey(name), strValue);
    }

    /** Add a Serial. Doesn't add a null value. */
    public void addSerial(String name, SerialWriter serialValue) {
        addSerial(mKeyer.encodeUniqueKey(name), serialValue);
    }

    //--

    public void addInt(int key, int intValue) {
        _addInt(key, TYPE_INT, intValue);
    }

    public void addLong(int key, long longValue) {
        _addLong(key, TYPE_LONG, longValue);
    }

    public void addBool(int key, boolean boolValue) {
        _addInt(key, TYPE_BOOL, boolValue ? 1 : 0);
    }

    public void addFloat(int key, float floatValue) {
        _addInt(key, TYPE_FLOAT, Float.floatToIntBits(floatValue));
    }

    public void addDouble(int key, double doubleValue) {
        _addLong(key, TYPE_DOUBLE, Double.doubleToLongBits(doubleValue));
    }

    /** Add a string. Doesn't add a null value. */
    public void addString(int key, String strValue) {
        if (strValue == null) return;

        int n = strValue.length();
        int m = (n + 1) / 2;

        int pos = alloc(2 + m + 1);
        mData[pos++] = TYPE_STRING;
        mData[pos++] = key;
        mData[pos++] = n;

        for (int i = 0; i < n;) {
            char c1 = strValue.charAt(i++);
            char c2 = i >= n ? 0 : strValue.charAt(i++);
            int j = (c1 << 16) | (c2 & 0x0FFFF);
            mData[pos++] = j;
        }

        mSize = pos;
    }

    /** Add a Serial. Doesn't add a null value. */
    public void addSerial(int key, SerialWriter serialValue) {
        if (serialValue == null) return;

        int[] a = serialValue.encodeAsArray();
        int n = a.length;

        int pos = alloc(2 + n);
        mData[pos++] = TYPE_SERIAL;
        mData[pos++] = key;

        System.arraycopy(a, 0, mData, pos, n);
        pos += n;

        mSize = pos;
    }

    //---

    private int alloc(int numInts) {

        if (mCantAdd) {
            throw new CantAddData("Serial data has been closed by a call to encode(). Can't add anymore.");
        }

        if (mData == null) {
            // Reserve the header int
            mData = new int[numInts + 1];
            mSize = 1;
            return mSize;
        }

        int s = mSize;
        int n = s + numInts;

        if (mData.length < n) {
            // need to realloc
            int[] newArray = new int[s+n];
            System.arraycopy(mData, 0, newArray, 0, s);
            mData = newArray;
        }
        return mSize;
    }

    private void _addInt(int key, int type, int value) {
        int pos = alloc(2+1);
        mData[pos++] = type;
        mData[pos++] = key;
        mData[pos++] = value;
        mSize = pos;
    }

    private void _addLong(int key, int type, long value) {
        int pos = alloc(2+2);
        mData[pos++] = type;
        mData[pos++] = key;
        // MSB first
        mData[pos++] = (int) (value >> 32);
        // LSB next
        mData[pos++] = (int) (value & 0xFFFFFFFF);
        mSize = pos;
    }

    /** Closing the array adds the CRC and the EOF. Can't add anymore once this is done. */
    private void close() {
        // can't close the array twice
        if (mCantAdd) return;

        int pos = alloc(2);

        // write the header
        mData[0] = pos + 2;
        // write the CRC and EOF footer
        mData[pos  ] = computeCrc(mData, 0, pos);
        mData[pos+1] = EOF;
        mSize += 2;
        mCantAdd = true;
    }

    /* This is static so that we can reuse it as-is in the reader class. */
    static int computeCrc(int[] data, int offset, int length) {
        CRC32 crc = new CRC32();

        for (; length > 0; length--) {
            int v = data[offset++];
            crc.update(v & 0x0FF);
            v = v >> 8;
            crc.update(v & 0x0FF);
            v = v >> 8;
            crc.update(v & 0x0FF);
            v = v >> 8;
            crc.update(v & 0x0FF);
        }

        return (int) crc.getValue();
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.