code: package zemberek.core.io;
  
  import com.google.common.base.Preconditions;
  import java.io.ByteArrayInputStream;
  import java.io.IOException;
  import java.io.OutputStream;
  
  /**
   * Byte related low level functions.
   */
  public class Bytes {
  
    public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
  
    /**
     * converts an unsigned integer array to a byte array. this is useful when defining byte arrays in
     * the code without using (byte) casts. Note that integers sent to this method should be unsigned.
     * integer are sugggested to be written in hex format.
     *
     * @param uints an integer array formed from unsigned ints.
     * @return a byte array.
     * @throws IllegalArgumentException if an array item is smaller than zero or larger than 255
     * (0xff)
     */
    public static byte[] toByteArray(int... uints) {
      byte[] bytez = new byte[uints.length];
      for (int i = 0; i < uints.length; i++) {
        if (uints[i] > 255 || uints[i] < 0) {
          throw new IllegalArgumentException(
              "Cannot convert to byte. Number should be between 0 and (255) 0xff. " +
                  "Number:" + uints[i]);
        }
        bytez[i] = (byte) (uints[i] & 0xff);
      }
      return bytez;
    }
  
    /**
     * converts a byte array to an integer. byte array may be with the lenght of 1,2,3 or 4.
     *
     * @param pb byte array
     * @param bigEndian endianness.
     * @return an integer represented byt the byte array
     * @throws IllegalArgumentException if byte array size is larger than 4
     */
    public static int toInt(byte[] pb, boolean bigEndian) {
  
      switch (pb.length) {
        case 1:
          return pb[0] & 0xff;
        case 2:
          if (bigEndian) {
            return (pb[0] << 8 & 0xff00) | (pb[1] & 0xff);
          } else {
            return (pb[1] << 8 & 0xff00) | (pb[0] & 0xff);
          }
        case 3:
          if (bigEndian) {
            return (pb[0] << 16 & 0xff0000) | (pb[1] << 8 & 0xff00) | (pb[2] & 0xff);
          } else {
            return (pb[2] << 16 & 0xff0000) | (pb[1] << 8 & 0xff00) | (pb[0] & 0xff);
          }
        case 4:
          if (bigEndian) {
            return (pb[0] << 24 & 0xff000000) |
                (pb[1] << 16 & 0xff0000) |
                (pb[2] << 8 & 0xff00) |
                (pb[3] & 0xff);
          } else {
            return (pb[3] << 24 & 0xff000000) |
                (pb[2] << 16 & 0xff0000) |
                (pb[1] << 8 & 0xff00) |
                (pb[0] & 0xff);
          }
        default:
          throw new IllegalArgumentException("1,2,3 or 4 byte arrays allowed. size:" + pb.length);
      }
    }
  
    public static int normalize(int i, int bitCount) {
      int max = 0xffffffff >>> (32 - bitCount);
      if (i > max) {
        throw new IllegalArgumentException("The integer cannot fit to bit boundaries.");
      }
      if (i > (max >>> 1)) {
        return i - (max + 1);
      } else {
        return i;
      }
    }
  
    public static void normalize(int iarr[], int bitCount) {
      for (int i = 0; i < iarr.length; i++) {
        iarr[i] = normalize(iarr[i], bitCount);
      }
    }
  
  
    public static byte[] toByteArray(int i, int size, boolean isBigEndian) {
      switch (size) {
        case 1:
          return new byte[]{(byte) i};
        case 2:
          if (isBigEndian) {
            return new byte[]{(byte) (i >>> 8 & 0xff), (byte) (i & 0xff)};
          } else {
            return new byte[]{(byte) (i & 0xff), (byte) (i >>> 8 & 0xff)};
          }
        case 3:
          if (isBigEndian) {
            return new byte[]{(byte) (i >>> 16 & 0xff), (byte) (i >>> 8 & 0xff), (byte) (i & 0xff)};
          } else {
            return new byte[]{(byte) (i & 0xff), (byte) (i >>> 8 & 0xff), (byte) (i >>> 16 & 0xff)};
          }
        case 4:
          return toByteArray(i, isBigEndian);
        default:
          throw new IllegalArgumentException("1,2,3 or 4 size values are allowed. size:" + size);
      }
    }
  
    /**
     * converts 4 bytes to an integer
     *
     * @param b0 first byte
     * @param b1 second byte
     * @param b2 third byte
     * @param b3 forth byte
     * @param bigEndian , if we want it in big endian format
     * @return integer formed from bytes.
     */
    public static int toInt(byte b0, byte b1, byte b2, byte b3, boolean bigEndian) {
      if (bigEndian) {
        return (b0 << 24 & 0xff000000) |
            (b1 << 16 & 0xff0000) |
            (b2 << 8 & 0xff00) |
            (b3 & 0xff);
      } else {
        return (b3 << 24 & 0xff000000) |
            (b2 << 16 & 0xff0000) |
            (b1 << 8 & 0xff00) |
            (b0 & 0xff);
      }
    }
  
    /**
     * converts an integer to 4 byte array.
     *
     * @param i the number.
     * @param bigEndian endianness.
     * @return byte array generated from the integer.
     */
    public static byte[] toByteArray(int i, boolean bigEndian) {
      byte[] ba = new byte[4];
      if (bigEndian) {
        ba[0] = (byte) (i >>> 24);
        ba[1] = (byte) (i >>> 16 & 0xff);
        ba[2] = (byte) (i >>> 8 & 0xff);
        ba[3] = (byte) (i & 0xff);
      } else {
        ba[0] = (byte) (i & 0xff);
        ba[1] = (byte) (i >>> 8 & 0xff);
        ba[2] = (byte) (i >>> 16 & 0xff);
        ba[3] = (byte) (i >>> 24);
      }
      return ba;
    }
  
    /**
     * converts a short to 2 byte array.
     *
     * @param i the number.
     * @param bigEndian endianness.
     * @return byte array generated from the short.
     */
    public static byte[] toByteArray(short i, boolean bigEndian) {
      byte[] ba = new byte[2];
      if (bigEndian) {
        ba[0] = (byte) (i >>> 8);
        ba[1] = (byte) (i & 0xff);
      } else {
        ba[0] = (byte) (i & 0xff);
        ba[1] = (byte) (i >>> 8 & 0xff);
      }
      return ba;
    }
  
    /**
     * Converts a byte array to an integer array. byte array length must be an order of 4.
     *
     * @param ba byte array
     * @param amount amount of bytes to convert to int.
     * @param bigEndian true if big endian.
     * @return an integer array formed form byte array.
     * @throws IllegalArgumentException if amount is smaller than 4, larger than byte array, or not an
     * order of 4.
     */
    public static int[] toIntArray(byte[] ba, int amount, boolean bigEndian) {
      final int size = determineSize(amount, ba.length, 4);
      int[] result = new int[size / 4];
      int i = 0;
      for (int j = 0; j < size; j += 4) {
        if (bigEndian) {
          result[i++] = toInt(ba[j], ba[j + 1], ba[j + 2], ba[j + 3], true);
        } else {
          result[i++] = toInt(ba[j + 3], ba[j + 2], ba[j + 1], ba[j], true);
        }
      }
      return result;
    }
  
  
    /**
     * Converts a byte array to an integer array. byte array length must be an order of 4.
     *
     * @param ba byte array
     * @param amount amount of bytes to convert to int.
     * @param bytePerInteger byte count per integer.
     * @param bigEndian true if big endian.
     * @return an integer array formed form byte array.
     * @throws IllegalArgumentException if amount is smaller than 4, larger than byte array, or not an
     * order of 4.
     */
    public static int[] toIntArray(byte[] ba, final int amount, final int bytePerInteger,
        boolean bigEndian) {
      final int size = determineSize(amount, ba.length, bytePerInteger);
      int[] result = new int[size / bytePerInteger];
      int i = 0;
      byte[] bytez = new byte[bytePerInteger];
      for (int j = 0; j < size; j += bytePerInteger) {
        System.arraycopy(ba, j, bytez, 0, bytePerInteger);
        if (bigEndian) {
          result[i++] = toInt(bytez, true);
        } else {
          result[i++] = toInt(bytez, false);
        }
      }
      return result;
    }
  
    /**
     * Converts a byte array to an integer array. byte array length must be an order of 4.
     *
     * @param ba byte array
     * @param amount amount of bytes to convert to int.
     * @param bytePerInteger byte count per integer.
     * @param bitAmount bit count where the value will be mapped.
     * @param bigEndian true if big endian.
     * @return an integer array formed form byte array.
     * @throws IllegalArgumentException if amount is smaller than 4, larger than byte array, or not an
     * order of 4.
     */
    public static int[] toReducedBitIntArray(
        byte[] ba,
        final int amount,
        int bytePerInteger,
        int bitAmount,
        boolean bigEndian) {
      final int size = determineSize(amount, ba.length, bytePerInteger);
      int[] result = new int[size / bytePerInteger];
      int i = 0;
      byte[] bytez = new byte[bytePerInteger];
      for (int j = 0; j < size; j += bytePerInteger) {
        System.arraycopy(ba, j, bytez, 0, bytePerInteger);
        if (bigEndian) {
          result[i++] = normalize(toInt(bytez, true), bitAmount);
        } else {
          result[i++] = normalize(toInt(bytez, false), bitAmount);
        }
      }
      return result;
    }
  
  
    private static int determineSize(int amount, int arrayLength, int order) {
      if (amount < order || amount > arrayLength) {
        throw new IllegalArgumentException(
            "amount of bytes to read cannot be smaller than " + order +
                " or larger than array length. Amount is:" + amount);
      }
  
      final int size = amount < arrayLength ? amount : arrayLength;
      if (size % order != 0) {
        throw new IllegalArgumentException(
            "array size must be an order of " + order + ". The size is:" + arrayLength);
      }
  
      return size;
    }
  
    /**
     * Converts a byte array to a short array. byte array length must be an order of 2.
     *
     * @param ba byte array
     * @param amount amount of bytes to convert to short.
     * @param bigEndian true if big endian.
     * @return a short array formed from byte array.
     * @throws IllegalArgumentException if amount is smaller than 2, larger than byte array, or not an
     * order of 2.
     */
    public static short[] toShortArray(byte[] ba, int amount, boolean bigEndian) {
      final int size = determineSize(amount, ba.length, 2);
      short[] result = new short[size / 2];
      int i = 0;
      for (int j = 0; j < size; j += 2) {
        if (bigEndian) {
          result[i++] = (short) (ba[j] << 8 & 0xff00 | ba[j + 1] & 0xff);
        } else {
          result[i++] = (short) (ba[j + 1] << 8 & 0xff00 | ba[j] & 0xff);
        }
      }
      return result;
    }
  
    /**
     * Converts a given array of shorts to a byte array.
     *
     * @param sa short array
     * @param amount amount of data to convert from input array
     * @param bigEndian if it is big endian
     * @return an array of bytes converted from the input array of shorts. 0xBABE becomes 0xBA, 0xBE
     * (Big Endian) or 0xBE, 0xBA (Little Endian)
     */
    public static byte[] toByteArray(short[] sa, int amount, boolean bigEndian) {
      final int size = amount < sa.length ? amount : sa.length;
      byte[] result = new byte[size * 2];
      for (int j = 0; j < size; j++) {
        final byte bh = (byte) (sa[j] >>> 8);
        final byte bl = (byte) (sa[j] & 0xff);
        if (bigEndian) {
          result[j * 2] = bh;
          result[j * 2 + 1] = bl;
        } else {
          result[j * 2] = bl;
          result[j * 2 + 1] = bh;
        }
      }
      return result;
    }
  
    /**
     * Converts a given array of ints to a byte array.
     *
     * @param ia <code>int</code> array
     * @param amount Amount of data to be converted from input array
     * @param bytePerInteger Byte count per integer.
     * @param bigEndian If it is big endian
     * @return an array of bytes converted from the input array of shorts. when bytePerInteger = 2,
     * ia = {0x0000CAFE, 0x0000BABE} returns {0xCA, 0xFE, 0xBA, 0xBE} (Big Endian) returns {0xFE,
     * 0xCA, 0xBE, 0xBA } (Little Endian) when bytePerInteger=4, ia = {0xCAFEBABE} return  {0xCA,
     * 0xFE, 0xBA, 0xBE} (Big Endian) returns { 0xBE, 0xBA, 0xFE, 0xCA} (Little Endian)
     */
    public static byte[] toByteArray(int[] ia, int amount, int bytePerInteger, boolean bigEndian) {
  
      if (bytePerInteger < 1 || bytePerInteger > 4) {
        throw new IllegalArgumentException(
            "bytePerInteger parameter can only be 1,2,3 or 4. But it is:" + bytePerInteger);
      }
      if (amount > ia.length || amount < 0) {
        throw new IllegalArgumentException(
            "Amount cannot be negative or more than input array length. Amount:" + amount);
      }
  
      final int size = amount < ia.length ? amount : ia.length;
      byte[] result = new byte[size * bytePerInteger];
      for (int j = 0; j < size; j++) {
        final byte[] piece = toByteArray(ia[j], bytePerInteger, bigEndian);
        System.arraycopy(piece, 0, result, j * bytePerInteger, bytePerInteger);
      }
      return result;
    }
  
    /**
     * converts a byte to a hexadecimal string with special xx formatting. it always return two
     * characters
     * <pre>
     * <p>for 0 , returns "00"
     * <p>for 1..15 returns "00".."0f"
     * </pre>
     *
     * @param b byte
     * @return hex string.
     */
    public static String toHexWithZeros(byte b) {
      if (b == 0) {
        return "00";
      }
      String s = toHex(b);
      if (s.length() == 1) {
        return "0" + s;
      } else {
        return s;
      }
    }
  
    /**
     * converts a byte to a hexadecimal string. it eliminates left zeros.
     * <pre>
     * <p>for 0 , returns "0"
     * <p>for 1 to 15 returns "0".."f"
     * </pre>
     *
     * @param b byte
     * @return hex string.
     */
    public static String toHex(byte b) {
      return String.format("%x", b);
    }
  
    /**
     * converts byte array to a hexadecimal string. it ignores the zeros on the left side.
     * <pre>
     * <p>{0x00, 0x0c, 0x11, 0x01, 0x00} -> "c110100"
     * </pre>
     *
     * @param bytes byte array, should be non-null, and not empty.
     * @return a String representation of the number represented by the byte array. empty string is
     * byte array is empty.
     * @throws NullPointerException if byte array is null
     */
    public static String toHex(byte[] bytes) {
      Preconditions.checkNotNull(bytes, "byte array cannot be null.");
      if (bytes.length == 0) {
        return Strings.EMPTY_STRING;
      }
      StringBuilder builder = new StringBuilder(bytes.length * 2);
      boolean nonZeroFound = false;
      for (byte b : bytes) {
        if (!nonZeroFound) {
          if (b != 0) {
            builder.append(toHex(b));
            nonZeroFound = true;
          }
          continue;
        }
        builder.append(toHexWithZeros(b));
      }
      //if all bytes are zero, loop above produces nothing. so we return "0"
      if (builder.length() == 0 && bytes.length > 0) {
        return "0";
      } else {
        return builder.toString();
      }
    }
  
    /**
     * converts a byte array to a hexadecimal string with special xx formatting. it does not ignore
     * the left zeros.
     * <pre>
     * <p>{0x00, 0x0c, 0x11, 0x00} -> "000c1100"
     * <pre>
     *
     * @param bytes byte array
     * @return hex string.  empty string is byte array is empty.
     * @throws NullPointerException if byte array is null
     */
    public static String toHexWithZeros(byte[] bytes) {
      Preconditions.checkNotNull(bytes, "byte array cannot be null.");
      if (bytes.length == 0) {
        return Strings.EMPTY_STRING;
      }
      StringBuilder builder = new StringBuilder(bytes.length * 2);
      for (byte b : bytes) {
        builder.append(toHexWithZeros(b));
      }
      return builder.toString();
    }
  
    /**
     * dumps the bytes to an Outputstream.
     *
     * @param os outputstream.
     * @param bytes bytes to dump
     * @param columns column number
     * @throws java.io.IOException if an error occurs while writing.
     */
    public static void hexDump(OutputStream os, byte[] bytes, int columns) throws IOException {
      Dumper.hexDump(new ByteArrayInputStream(bytes, 0, bytes.length), os, columns, bytes.length);
    }
  
    /**
     * dumps the bytes to Console.
     *
     * @param bytes bytes to dump
     * @param columns column number
     * @throws java.io.IOException if an error occurs while writing.
     */
    public static void hexDump(byte[] bytes, int columns) throws IOException {
      Dumper.hexDump(new ByteArrayInputStream(bytes, 0, bytes.length), System.out, columns,
          bytes.length);
    }
  
  }
  