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

craterdog.primitives.BinaryString Maven / Gradle / Ivy

/************************************************************************
 * Copyright (c) Crater Dog Technologies(TM).  All Rights Reserved.     *
 ************************************************************************
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.        *
 *                                                                      *
 * This code is free software; you can redistribute it and/or modify it *
 * under the terms of The MIT License (MIT), as published by the Open   *
 * Source Initiative. (See http://opensource.org/licenses/MIT)          *
 ************************************************************************/
package craterdog.primitives;

import craterdog.core.Composite;
import craterdog.core.Iterator;
import craterdog.core.Primitive;
import craterdog.core.Sequential;
import craterdog.utils.Base02Utils;
import craterdog.utils.Base16Utils;
import craterdog.utils.Base32Utils;
import craterdog.utils.Base64Utils;
import craterdog.utils.ByteUtils;
import java.util.Arrays;
import java.util.BitSet;
import java.util.NoSuchElementException;


/**
 * This class implements a binary string in a way that makes it easy to encode and decode it
 * in various numeric bases (2, 16, 32, 64). The class also supports the
 * java.lang.Iterable interface so that it can be used by the java language
 * for each loops. Binary strings are immutable.
 *
 * @author Derk Norton
 */
public final class BinaryString extends Primitive implements Sequential, Composite {

    private final byte[] bytes;


    /**
     * This default constructor creates an empty binary string.
     */
    public BinaryString() {
        this.bytes = new byte[0];
    }


    /**
     * This constructor creates a binary string containing the specified bytes.
     *
     * @param bytes The bytes to be used to create the binary string.
     */
    public BinaryString(byte[] bytes) {
        this.bytes = Arrays.copyOf(bytes, bytes.length);
    }


    /**
     * This constructor creates a binary string containing the specified bytes.
     *
     * @param bytes The bytes to be used to create the binary string.
     */
    public BinaryString(Byte[] bytes) {
        int size = bytes.length;
        this.bytes = new byte[size];
        for (int i = 0; i < size; i++) {
            this.bytes[i] = bytes[i];
        }
    }


    /**
     * This constructor decodes the specified string using base 64 to extract the
     * corresponding binary string.
     *
     * @param string The encoded string containing the bytes to be used to create the binary string.
     */
    public BinaryString(String string) {
        this.bytes = Base64Utils.decode(string);
    }


    /**
     * This constructor decodes the specified string using the specified base to extract the
     * corresponding binary string.
     *
     * @param string The encoded string containing the bytes to be used to create the binary string.
     * @param base The numeric base (2, 16, 32, or 64) that was used to encode the string.
     */
    public BinaryString(String string, int base) {
        switch (base) {
            case 2:
                this.bytes = Base02Utils.decode(string);
                break;
            case 16:
                this.bytes = Base16Utils.decode(string);
                break;
            case 32:
                this.bytes = Base32Utils.decode(string);
                break;
            case 64:
                this.bytes = Base64Utils.decode(string);
                break;
            default:
                throw new NumberFormatException("Base " + base + " binary strings not supported.");
        }
    }


    /**
     * This constructor creates a binary string containing the specified bit set.
     *
     * @param bits The bit set to be used to create the binary string.
     */
    public BinaryString(BitSet bits) {
        this.bytes = bits.toByteArray();
    }


    @Override
    public Iterator createIterator() {
        return new BinaryIterator();
    }


    @Override
    public int compareTo(BinaryString that) {
        if (that == null) return 1;
        if (this == that) return 0;  // same object
        int result = 0;
        Iterator thisIterator = this.createIterator();
        Iterator thatIterator = that.createIterator();
        while (thisIterator.hasNext() && thatIterator.hasNext()) {
            Byte thisElement = thisIterator.next();
            Byte thatElement = thatIterator.next();
            result = Integer.compare(
                    ByteUtils.byteToUnsigned(thisElement),
                    ByteUtils.byteToUnsigned(thatElement)
            );
            if (result != 0) break;
        }
        if (result == 0) {
            // same so far, check for different lengths
            result = Integer.compare(this.getSize(), that.getSize());
        }
        return result;
    }


    @Override
    public boolean equals(Object object) {
        if (object == null || !(object instanceof BinaryString)) return false;
        BinaryString that = (BinaryString) object;
        if (this == that) return true;  // same object
        return Arrays.equals(this.bytes, that.bytes);
    }


    @Override
    public int hashCode() {
        return Arrays.hashCode(bytes);
    }


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


    @Override
    public String toString(String indentation) {
        return toString(64, indentation);
    }


    /**
     * This method converts the binary string into the specified base encoded string. If the
     * encoded string is longer than 80 characters, it is broken up into separate lines with
     * a maximum of 80 characters per line.
     *
     * @param base The base to be used for encoding the bytes (2, 16, 32, 64).
     * @return The base 64 encoded string for the binary string.
     */
    public String toString(int base) {
        return toString(base, "");
    }


    /**
     * This method converts the binary string into the specified base encoded string. If the
     * encoded string is longer than 80 characters, it is broken up into separate lines with
     * a maximum of 80 characters per line.  The specified whitespace indentation string is
     * used to prefix each line.
     *
     * @param base The base to be used for encoding the bytes (2, 16, 32, 64).
     * @param indentation The (whitespace only) string that should be used to prefix each line.
     * @return The base 64 encoded string for the binary string.
     */
    public String toString(int base, String indentation) {
        switch (base) {
            case 2:
                return Base02Utils.encode(bytes, indentation);
            case 16:
                return Base16Utils.encode(bytes, indentation);
            case 32:
                return Base32Utils.encode(bytes, indentation);
            case 64:
                return Base64Utils.encode(bytes, indentation);
            default:
                throw new NumberFormatException("Base " + base + " binary strings not supported.");
        }
    }


    /**
     * This method returns a byte array containing the binary string.
     *
     * @return A byte array containing the binary string.
     */
    public byte[] toBytes() {
        return Arrays.copyOf(bytes, bytes.length);
    }


    @Override
    public int getSize() {
        return bytes.length;
    }


    @Override
    public Byte[] toArray() {
        Byte[] array = new Byte[bytes.length];
        int index = 0;
        for (Byte b : this) {
            array[index++] = b;
        }
        return array;
    }


    private final class BinaryIterator extends Iterator {

        int index;

        private BinaryIterator() {
            this.index = 0;
        }

        @Override
        public void toStart() {
            this.index = 0;
        }

        @Override
        public void toIndex(int index) {
            if (index > 0) {
                this.index = index - 1;  // convert to ordinal indexing
            } else {
                this.index = bytes.length + index;  // index from end of bytes
            }
        }

        @Override
        public void toEnd() {
            this.index = bytes.length;
        }

        @Override
        public boolean hasPrevious() {
            return index > 0;
        }

        @Override
        public Byte getPrevious() {
            if (index == 0) throw new NoSuchElementException();
            Byte element = bytes[--index];
            return element;
        }

        @Override
        public boolean hasNext() {
            return index < bytes.length;
        }

        @Override
        public Byte getNext() {
            if (index == bytes.length) throw new NoSuchElementException();
            Byte element = bytes[index++];
            return element;
        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy