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

org.chiknrice.djeng.ByteUtil Maven / Gradle / Ivy

/*
 * Copyright (c) 2016 Ian Bondoc
 *
 * This file is part of Djeng
 *
 * Djeng 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.
 *
 * Djeng 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
 * .
 *
 */
package org.chiknrice.djeng;

import java.nio.ByteBuffer;

import static java.lang.String.format;

/**
 * @author Ian Bondoc
 */
public final class ByteUtil {

    private static final String HEX = "0123456789ABCDEF";

    /**
     * Gets the hex value of a given character.  This operation is case insensitive.  This should be much more efficient
     * than Integer.parseInt(s, 16).
     *
     * @param c the hex character to be
     * @return TODO
     * @throws IllegalArgumentException if the character is not a valid hex value (0 to F)
     */
    public static int hexValue(char c) {
        int value = HEX.indexOf(Character.toUpperCase(c));
        if (value == -1) {
            throw new IllegalArgumentException(String.format("Invalid hex char %s", c));
        }
        return value;
    }

    /**
     * Transforms bytes to an array of hex characters representing the nibbles
     *
     * @param bytes TODO
     * @return TODO
     */
    static char[] bytesToHexChars(byte[] bytes) {
        char[] chars = new char[bytes.length * 2];
        int charPos = chars.length - 1;// LSB

        for (int bytePos = bytes.length - 1; bytePos >= 0; bytePos--) {
            chars[charPos--] = HEX.charAt(bytes[bytePos] & 0x0f);
            chars[charPos--] = HEX.charAt((bytes[bytePos] & 0xf0) >> 4);
        }
        return chars;
    }

    /**
     * Transforms an even number of hex characters to a byte[]
     *
     * @param chars TODO
     * @return TODO
     */
    static byte[] hexCharsToBytes(char[] chars) {
        if (chars.length % 2 > 0) {
            throw new IllegalArgumentException("Odd character hex chars");
        }
        byte[] bytes = new byte[chars.length / 2];

        int length = chars.length;

        for (int charPos = length - 1; charPos >= 0; charPos--) {
            int bytePos = bytes.length - ((length - charPos - 1) / 2) - 1;
            boolean hi = (length - charPos) % 2 == 0;

            char c = chars[charPos];
            int hex = hexValue(c);

            bytes[bytePos] |= (hex << (hi ? 4 : 0));
        }
        return bytes;
    }

    /**
     * Encodes a byte[] to a string of hex characters representing the nibbles
     *
     * @param bytes TODO
     * @return TODO
     */
    public static String encodeHex(byte[] bytes) {
        return new String(bytesToHexChars(bytes));
    }

    /**
     * Decodes a string of (even) hex characters to nibbles in a byte[]
     *
     * @param hex TODO
     * @return TODO
     */
    public static byte[] decodeHex(String hex) {
        return hexCharsToBytes(hex.toCharArray());
    }

    /**
     * Encodes a string of numeric characters to BCD.  If the string is odd characters it will be padded with zero '0'
     * at the first nibble.
     *
     * @param value the string to be encoded
     * @return the encoded value
     * @throws IllegalArgumentException if the string contains non numeric characters
     */
    public static byte[] encodeBcd(String value) {
        validateBcd(value);
        int length = value.length();
        int padLength = length % 2;
        if (padLength == 0) {
            return decodeHex(value);
        } else {
            char[] chars = new char[length + padLength];
            value.getChars(0, length, chars, 1);
            chars[0] = '0';
            return hexCharsToBytes(chars);
        }
    }

    /**
     * Decodes bytes to a string of numeric characters representing the nibbles.
     *
     * @param bytes the bytes to be decoded
     * @return the decoded numeric string
     * @throws IllegalArgumentException if the bytes contains nibbles with value above 9 (A-F)
     */
    public static String decodeBcd(byte[] bytes) {
        String decoded = encodeHex(bytes);
        validateBcd(decoded);
        return decoded;
    }

    /**
     * Encodes a string of numeric characters to BCD_F which is left justified and 'F' padded. This method expects the
     * first numeric character to be non '0'.  If the characters are odd it would be padded with 'F' at the last
     * nibble.
     *
     * @param value the string to be encoded
     * @return the encoded value
     * @throws IllegalArgumentException if the string contains non numeric characters
     */
    public static byte[] encodeBcdF(String value) {
        validateBcdF(value);
        int length = value.length();
        int padLength = length % 2;
        if (padLength == 0) {
            return decodeHex(value);
        } else {
            char[] chars = new char[length + padLength];
            value.getChars(0, length, chars, 0);
            chars[length] = 'F';
            return hexCharsToBytes(chars);
        }
    }

    /**
     * Decodes bytes to a string of numeric characters representing the nibbles.  The last nibble can be 'F' indicating
     * an odd number of numeric characters.  If the last nibble is 'F' it would be dropped.
     *
     * @param bytes the bytes to be decoded
     * @return the decoded numeric string
     * @throws IllegalArgumentException if the last nibble contains any value of A to E, or if the rest of the nibbles
     *                                  has a non numeric values
     */
    public static String decodeBcdF(byte[] bytes) {
        char[] chars = bytesToHexChars(bytes);
        int length = chars[chars.length - 1] == 'F' ? chars.length - 1 : chars.length;
        String decoded = new String(chars, 0, length);
        validateBcdF(decoded);
        return decoded;
    }

    /**
     * Encodes a string with even number of characters.  The first character can either be '0' or '-' while the rest is
     * expected to be numeric.  The '-' specifies a negative number.  If the first character is 0 it would be replaced
     * with 'C' and if it is '-' it would be replaced with 'D'.
     *
     * @param value the string to be encoded
     * @return the encoded value
     * @throws IllegalArgumentException if the value is odd number of character; or the first char of value is neither
     *                                  '0' nor '-'; or if the rest contains non numeric characters
     */
    public static byte[] encodeCBcd(String value) {
        validateCBcd(value);
        boolean credit = value.charAt(0) != '-';
        char[] chars = value.toCharArray();
        chars[0] = credit ? 'C' : 'D';
        return hexCharsToBytes(chars);
    }

    /**
     * Decodes bytes to a string which represents a positive or a negative numeric value.  The first nibble is expected
     * to be any of '0', 'C', or 'D'.  'D' represents a negative value while '0' or 'C' represents non negative value.
     *
     * @param bytes the bytes to be decoded
     * @return the decoded numeric string
     * @throws IllegalArgumentException if the first nibble is anything but '0', 'C', or 'D' or the rest contains non
     *                                  numeric characters
     */
    public static String decodeCBcd(byte[] bytes) {
        char[] chars = bytesToHexChars(bytes);
        chars[0] = chars[0] == 'D' ? '-' : chars[0] == 'C' ? chars[0] = '0' : chars[0];
        String decoded = new String(chars);
        validateCBcd(decoded);
        return decoded;
    }

    /**
     * Encodes a string with even number of characters.  The first two character can either be '00' or '-0' while the
     * rest is expected to be numeric.  '-0' indicates a negative number.  If the first two characters are '00' it would
     * be replaced with '43' (which is ASCII character 'C') and if it is '-0' it would be replaced with '44' (which is
     * ASCII character 'D').
     *
     * @param value the string to be encoded
     * @return the encoded value
     * @throws IllegalArgumentException if the value is odd number of character; or if the string doesn't start with
     *                                  either "00" or "-0"; or if the rest contains non numeric characters
     */
    public static byte[] encodeCcBcd(String value) {
        validateCcBcd(value);
        boolean credit = !value.startsWith("-0");
        char[] chars = value.toCharArray();
        chars[0] = '4';
        chars[1] = credit ? '3' : '4';
        return hexCharsToBytes(chars);
    }

    /**
     * Decodes bytes to a string which represents a positive or a negative numeric value.  The first byte is expected to
     * be any of '0x00', '0x43', or '0x44'.  '0x44' represents a negative value while '0x00' or '0x43' represents non
     * negative value.
     *
     * @param bytes the bytes to be decoded
     * @return the decoded numeric string
     * @throws IllegalArgumentException if the first byte is anything but '0x00', '0x43', or '0x44' or the rest contains
     *                                  non numeric characters
     */
    public static String decodeCcBcd(byte[] bytes) {
        String prefix = new String(bytes, 0, 1);
        prefix = prefix.equals("D") ? "-0" : prefix.equals("C") ? "00" : null;
        char[] chars = bytesToHexChars(bytes);
        if (prefix != null) {
            chars[0] = prefix.charAt(0);
            chars[1] = prefix.charAt(1);
        }
        String decoded = new String(chars);
        validateCcBcd(decoded);
        return decoded;
    }

    private static void validateBcd(String string) {
        for (int i = 0; i < string.length(); i++) {
            if (!Character.isDigit(string.charAt(i))) {
                throw new IllegalArgumentException("Invalid BCD: " + string);
            }
        }
    }

    private static void validateBcdF(String string) {
        for (int i = 0; i < string.length(); i++) {
            char c = string.charAt(i);
            if (i == 0 ? c == '0' : !Character.isDigit(c)) {
                throw new IllegalArgumentException("Invalid BCD_F: " + string);
            }
        }
    }

    private static void validateCBcd(String string) {
        for (int i = 0; i < string.length(); i++) {
            char c = string.charAt(i);
            if (i == 0 ? c != '0' && c != '-' : !Character.isDigit(c)) {
                throw new IllegalArgumentException("Invalid C_BCD: " + string);
            }
        }
    }

    private static void validateCcBcd(String string) {
        for (int i = 0; i < string.length(); i++) {
            char c = string.charAt(i);
            if (i == 0 ? c != '0' && c != '-' : i == 1 ? c != '0' : !Character.isDigit(c)) {
                throw new IllegalArgumentException("Invalid CC_BCD: " + string);
            }
        }
    }

    /**
     * Creates a new ByteBuffer with capacity = length from the current position.  Creation of the buffer consumes
     * length bytes.
     *
     * @param origBuffer TODO
     * @param length TODO
     * @return TODO
     */
    public static ByteBuffer consumeToBuffer(ByteBuffer origBuffer, int length) {
        int origBufferLimit = origBuffer.limit();
        // will be new position of origBuffer
        int newBufferLimit = origBuffer.position() + length;
        // set new buffer limit
        origBuffer.limit(newBufferLimit);
        // perform slice
        ByteBuffer newBuffer = origBuffer.slice();
        // consume length
        origBuffer.position(newBufferLimit);
        // restore orig limit
        origBuffer.limit(origBufferLimit);
        return newBuffer;
    }

    /**
     * Creates a new ByteBuffer from the current position back up to length bytes
     *
     * @param origBuffer TODO
     * @param length TODO
     * @return TODO
     */
    static ByteBuffer recallToBuffer(ByteBuffer origBuffer, int length) {
        origBuffer.position(origBuffer.position() - length);
        return consumeToBuffer(origBuffer, length);
    }

    public static byte[] encodeBinary(Long value) {
        return encodeBinary(value, 8);
    }

    public static byte[] encodeBinary(Integer value) {
        return encodeBinary(value, 4);
    }

    public static byte[] encodeBinary(Number value, int maxBytes) {
        int size;
        if (value instanceof Long) {
            size = 8;
        } else {
            size = 4;
        }
        ByteBuffer buf = ByteBuffer.allocate(size);
        if (value instanceof Long) {
            buf.putLong((Long) value);
        } else {
            buf.putInt((Integer) value);
        }

        buf.flip();
        byte[] bytes = buf.array();
        if (maxBytes < bytes.length) {
            byte[] trimmed = new byte[maxBytes];
            System.arraycopy(bytes, bytes.length - maxBytes, trimmed, 0, trimmed.length);

            // verify no data trimmed
            buf = ByteBuffer.allocate(size);
            buf.put(bytes, 0, bytes.length - maxBytes);
            buf.clear();
            if (((size == 8) ? buf.getLong() : buf.getInt()) > 0) {
                throw new RuntimeException(format("%s trimmed on encoding to %d bytes", value.toString(), maxBytes));
            }

            // use trimmed
            bytes = trimmed;
        }
        return bytes;
    }

    public static Long decodeBinaryLong(byte[] bytes) {
        return wrap(bytes, 8).getLong();
    }

    public static Integer decodeBinaryInt(byte[] bytes) {
        return wrap(bytes, 4).getInt();
    }

    private static ByteBuffer wrap(byte[] bytes, int maxBytes) {
        ByteBuffer buf = ByteBuffer.allocate(maxBytes);
        if (bytes.length < maxBytes) {
            buf.position(maxBytes - bytes.length);
        }
        buf.put(bytes);
        buf.clear();
        return buf;
    }

    public static void main(String[] args) {
        int i = Integer.parseInt("F1", 16);
        System.out.println(i);
        System.out.println((byte) i);
        System.out.println(((byte) i) & 0xFF);
        ByteBuffer b = ByteBuffer.allocate(10);
        b.put((byte) i);
        b.rewind();
        System.out.println(b.get());
        b.rewind();
        System.out.println(b.get() & 0xFF);
        b.put(b);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy