ca.uqac.info.buffertannen.message.BitSequence.java Source code

Java tutorial

Introduction

Here is the source code for ca.uqac.info.buffertannen.message.BitSequence.java

Source

/*-------------------------------------------------------------------------
Buffer Tannen, a binary message protocol
Copyright (C) 2013  Sylvain Hall
    
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 ca.uqac.info.buffertannen.message;

import java.util.Vector;

import org.apache.commons.codec.binary.Base64;

/**
 * Representation of a sequence of bits. This sequence can be converted
 * to/from an array of bytes.
 * 
 * @author sylvain
 *
 */
public class BitSequence extends Vector<Boolean> {

    /**
     * Dummy UID
     */
    private static final long serialVersionUID = 1L;

    /**
     * Empty constructor. Constructs a bit sequence of length 0.
     */
    public BitSequence() {
        super();
    }

    /**
     * Constructs a bit sequence from an array of bytes. Since
     * the array of bytes may represent a sequence of bits that is
     * not a multiple of 8, the length of the bit sequence is also
     * provided.
     * 
     * @param array The array of bytes
     * @param length The length (in <em>bits</em> of the bit sequence
     *   contained in the array of bytes
     * @throws BitFormatException
     */
    public BitSequence(byte[] array, int length) throws BitFormatException {
        this();
        readFromBytes(array, length);
    }

    /**
     * Reads an array of bytes. Since
     * the array of bytes may represent a sequence of bits that is
     * not a multiple of 8, the length of the bit sequence is also
     * provided.
     * @param array The array of bytes
     * @param length The length (in <em>bits</em> of the bit sequence
     *   contained in the array of bytes
     * @throws BitFormatException
     */
    protected void readFromBytes(byte[] array, int length) throws BitFormatException {
        int num_bytes = (int) Math.ceil(((float) length) / 8f);
        int cur_length = 0;
        if (num_bytes > array.length) {
            // Error: length is longer than the length of the array
            throw new BitFormatException();
        }
        for (int i = 0; i < num_bytes && cur_length < length; i++) {
            byte b = array[i];
            for (int j = 7; j >= 0 && cur_length < length; j--) {
                int val = (b >> j) & 1;
                if (val == 1) {
                    this.add(true);
                } else {
                    this.add(false);
                }
                cur_length++;
            }
        }
    }

    /**
     * Constructs a bit sequence from an integer value. The value is converted
     * to a left-padded binary sequence
     * @param value The value to initialise the bit sequence from
     * @param length The length of the bit sequence
     * @throws FormatException Thrown if length is too short to accommodate
     *   the numerical value
     */
    public BitSequence(int value, int length) throws BitFormatException {
        this();
        int power_length = (int) Math.pow(2, length);
        if (value > power_length) {
            // Error: length is too short to accommodate value
            throw new BitFormatException();
        }
        for (int i = 1; i < power_length; i *= 2) {
            int b = value & i;
            if (b == 0) {
                this.insertElementAt(false, 0);
            } else {
                this.insertElementAt(true, 0);
                value -= b;
            }
        }
    }

    /**
     * Constructs a bit sequence from a string. All characters
     * other than 0 or 1 are silently ignored.
     * @param s The input string
     */
    public BitSequence(String s) {
        this();
        for (int i = 0; i < s.length(); i++) {
            String c = s.substring(i, i + 1);
            if (c.compareTo("0") == 0) {
                this.add(false);
            } else if (c.compareTo("1") == 0) {
                this.add(true);
            }
        }
    }

    /**
     * Displays the content of a byte as a string of 0 and 1
     * @param b The byte to display
     * @return
     */
    public static String byteToString(byte b) {
        byte[] a = new byte[1];
        return bytesToString(a);
    }

    /**
     * Displays the content of a byte array as a string of 0 and 1
     * @param b The bytes to display
     * @return
     */
    public static String bytesToString(byte[] a) {
        if (a == null)
            return null;
        BitSequence ba = null;
        try {
            ba = new BitSequence(a, 8 * a.length);
        } catch (BitFormatException e) {
            return "";
        }
        return ba.toString();
    }

    /**
     * Outputs the sequence of bits as an array of bytes.
     * The last byte is padded with zeros if the number of bits
     * in the array is not a multiple of 8.
     * 
     * @return The array of bytes
     */
    public byte[] toByteArray() {
        int byte_size = (int) Math.ceil(((float) this.size()) / 8f);
        byte[] out = new byte[byte_size];
        int byte_pos = 0;
        for (int i = 0; i < this.size(); i += 8) {
            byte b = 0;
            for (byte j = 0; j < 8 && i + j < this.size(); j++) {
                if (this.elementAt(i + j) == true) {
                    b |= 1 << (7 - j);
                }
            }
            out[byte_pos] = b;
            byte_pos++;
        }
        return out;
    }

    /**
     * Returns the binary content of the sequence as a string.
     * This method simply converts the result of {@link toByteArray}
     * into a String object. 
     * @return The binary content
     */
    public String toByteString() {
        byte[] array = toByteArray();
        String out = new String(array);
        return out;
    }

    /**
     * Converts a string into an array of bits. The method simply converts the
     * contents of the string into an array of bytes, whose binary
     * content is then read.
     * @param s The string to read from
     * @param length The number of bits
     * @throws BitFormatException 
     */
    public void fromByteString(String s, int length) throws BitFormatException {
        byte[] array = s.getBytes();
        this.clear();
        readFromBytes(array, length);
    }

    /**
     * Converts a string into an array of bits. The method simply converts the
     * contents of the string into an array of bytes, whose binary
     * content is then read.
     * @param s The string to read from
     * @throws BitFormatException 
     */
    public void fromByteString(String s) throws BitFormatException {
        byte[] array = s.getBytes();
        this.clear();
        readFromBytes(array, 8 * array.length);
    }

    /**
     * Converts a string into an array of bits. The method simply converts the
     * contents of the string into an array of bytes, whose binary
     * content is then read.
     * @param s The string to read from, encoded in Base64
     * @throws BitFormatException 
     * @throws Base64DecodingException 
     */
    public void fromBase64(String s) throws BitFormatException {
        byte[] data = Base64.decodeBase64(s);
        readFromBytes(data, data.length * 8);
    }

    /**
     * Returns the binary content of the sequence as a Base64 string.
     * @return The binary content in Base64
     */
    public String toBase64() {
        byte[] data = toByteArray();
        return Base64.encodeBase64String(data);
    }

    /**
     * Returns the contents of a bit sequence as an integer value,
     * with the most significant bit being the first of the sequence.
     * 
     * @return The integer value
     */
    public int intValue() {
        int out = 0;
        int pos = 0;
        for (int i = (int) Math.pow(2, this.size() - 1); i >= 1; i /= 2) {
            boolean b = this.get(pos);
            if (b == true) {
                out += i;
            }
            pos++;
        }
        return out;
    }

    /**
     * Creates a bit sequence from a part of the current bit sequence.
     * @param start The start position in the sequence
     * @param length The number of bits to retrieve from the start position
     * @return The sub-sequence, or null if the bounds are invalid
     */
    public BitSequence subSequence(int start, int length) {
        BitSequence bs = new BitSequence();
        if (start < 0 || start + length > this.size()) {
            // Invalid bounds
            return null;
        }
        for (int i = start; i < start + length; i++) {
            if (this.get(i)) {
                bs.add(true);
            } else {
                bs.add(false);
            }
        }
        return bs;
    }

    /**
     * Truncates the bit sequence off the first n bits
     * @param to The number of bits to remove from the beginning of the
     *   sequence. If this value is greater than the length of the sequence,
     *   the whole sequence is returned.
     */
    public BitSequence truncatePrefix(int to) {
        BitSequence out = null;
        if (to <= 0) {
            // Nothing to do
            return this;
        }
        if (to >= this.size()) {
            out = this.subSequence(0, this.size());
            this.clear();
        } else {
            out = this.subSequence(0, to);
            this.removeRange(0, to);
        }
        return out;
    }

    @Override
    public String toString() {
        return toString(0);
    }

    public String toString(int group) {
        StringBuilder out = new StringBuilder();
        for (int i = 0; i < this.size(); i++) {
            if (i > 1 && group > 0 && (i % group) == 0) {
                out.append(" ");
            }
            boolean b = this.get(i);
            if (b == true) {
                out.append("1");
            } else {
                out.append("0");
            }
        }
        return out.toString();
    }

    public static void main(String[] args) {
        BitSequence s1 = null;
        try {
            s1 = new BitSequence(14, 4);
        } catch (BitFormatException e) {
            e.printStackTrace();
        }
        System.out.println(s1);
        System.out.println(s1.intValue());
        byte[] b = s1.toByteArray();
        try {
            s1 = new BitSequence(b, 4);
        } catch (BitFormatException e) {
            e.printStackTrace();
        }
        System.out.println(s1);
    }
}