content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *    http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.arrow.vector.util;
  
  import java.math.BigDecimal;
  import java.math.BigInteger;
  import java.nio.ByteBuffer;
  import java.nio.ByteOrder;
  import org.apache.arrow.memory.ArrowBuf;
  import org.apache.arrow.memory.util.MemoryUtil;
  
  /** Utility methods for configurable precision Decimal values (e.g. {@link BigDecimal}). */
  public class DecimalUtility {
    private DecimalUtility() {}
  
    public static final byte[] zeroes =
        new byte[] {
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
        };
    public static final byte[] minus_one =
        new byte[] {
          -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
          -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
        };
    private static final boolean LITTLE_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN;
  
    /**
     * Read an ArrowType.Decimal at the given value index in the ArrowBuf and convert to a BigDecimal
     * with the given scale.
     */
    public static BigDecimal getBigDecimalFromArrowBuf(
        ArrowBuf bytebuf, int index, int scale, int byteWidth) {
      byte[] value = new byte[byteWidth];
      byte temp;
      final long startIndex = (long) index * byteWidth;
  
      bytebuf.getBytes(startIndex, value, 0, byteWidth);
      if (LITTLE_ENDIAN) {
        // Decimal stored as native endian, need to swap bytes to make BigDecimal if native endian is
        // LE
        int stop = byteWidth / 2;
        for (int i = 0, j; i < stop; i++) {
          temp = value[i];
          j = (byteWidth - 1) - i;
          value[i] = value[j];
          value[j] = temp;
        }
      }
      BigInteger unscaledValue = new BigInteger(value);
      return new BigDecimal(unscaledValue, scale);
    }
  
    /**
     * Read an ArrowType.Decimal from the ByteBuffer and convert to a BigDecimal with the given scale.
     */
    public static BigDecimal getBigDecimalFromByteBuffer(
        ByteBuffer bytebuf, int scale, int byteWidth) {
      byte[] value = new byte[byteWidth];
      bytebuf.get(value);
      BigInteger unscaledValue = new BigInteger(value);
      return new BigDecimal(unscaledValue, scale);
    }
  
    /**
     * Read an ArrowType.Decimal from the ArrowBuf at the given value index and return it as a byte
     * array.
     */
    public static byte[] getByteArrayFromArrowBuf(ArrowBuf bytebuf, int index, int byteWidth) {
      final byte[] value = new byte[byteWidth];
      final long startIndex = (long) index * byteWidth;
      bytebuf.getBytes(startIndex, value, 0, byteWidth);
      return value;
    }
  
    /**
     * Check that the BigDecimal scale equals the vectorScale and that the BigDecimal precision is
     * less than or equal to the vectorPrecision. If not, then an UnsupportedOperationException is
     * thrown, otherwise returns true.
     */
    public static boolean checkPrecisionAndScale(
        BigDecimal value, int vectorPrecision, int vectorScale) {
      if (value.scale() != vectorScale) {
        throw new UnsupportedOperationException(
            "BigDecimal scale must equal that in the Arrow vector: "
                + value.scale()
                + " != "
                + vectorScale);
      }
      if (value.precision() > vectorPrecision) {
        throw new UnsupportedOperationException(
            "BigDecimal precision cannot be greater than that in the Arrow "
                + "vector: "
                + value.precision()
                + " > "
                + vectorPrecision);
      }
      return true;
    }
  
    /**
     * Check that the BigDecimal scale equals the vectorScale and that the BigDecimal precision is
     * less than or equal to the vectorPrecision. Return true if so, otherwise return false.
     */
    public static boolean checkPrecisionAndScaleNoThrow(
        BigDecimal value, int vectorPrecision, int vectorScale) {
      return value.scale() == vectorScale && value.precision() < vectorPrecision;
    }
  
    /**
     * Check that the decimal scale equals the vectorScale and that the decimal precision is less than
     * or equal to the vectorPrecision. If not, then an UnsupportedOperationException is thrown,
     * otherwise returns true.
     */
    public static boolean checkPrecisionAndScale(
        int decimalPrecision, int decimalScale, int vectorPrecision, int vectorScale) {
      if (decimalScale != vectorScale) {
        throw new UnsupportedOperationException(
            "BigDecimal scale must equal that in the Arrow vector: "
                + decimalScale
                + " != "
                + vectorScale);
      }
      if (decimalPrecision > vectorPrecision) {
        throw new UnsupportedOperationException(
            "BigDecimal precision cannot be greater than that in the Arrow "
                + "vector: "
                + decimalPrecision
                + " > "
                + vectorPrecision);
      }
      return true;
    }
  
    /**
     * Write the given BigDecimal to the ArrowBuf at the given value index. Will throw an
     * UnsupportedOperationException if the decimal size is greater than the Decimal vector byte
     * width.
     */
    public static void writeBigDecimalToArrowBuf(
        BigDecimal value, ArrowBuf bytebuf, int index, int byteWidth) {
      final byte[] bytes = value.unscaledValue().toByteArray();
      writeByteArrayToArrowBufHelper(bytes, bytebuf, index, byteWidth);
    }
  
    /**
     * Write the given long to the ArrowBuf at the given value index. This routine extends the
     * original sign bit to a new upper area in 128-bit or 256-bit.
     */
    public static void writeLongToArrowBuf(long value, ArrowBuf bytebuf, int index, int byteWidth) {
      if (byteWidth != 16 && byteWidth != 32) {
        throw new UnsupportedOperationException(
            "DecimalUtility.writeLongToArrowBuf() currently supports "
                + "128-bit or 256-bit width data");
      }
      final long addressOfValue = bytebuf.memoryAddress() + (long) index * byteWidth;
      final long padValue = Long.signum(value) == -1 ? -1L : 0L;
      if (LITTLE_ENDIAN) {
        MemoryUtil.putLong(addressOfValue, value);
        for (int i = 1; i <= (byteWidth - 8) / 8; i++) {
          MemoryUtil.putLong(addressOfValue + Long.BYTES * i, padValue);
        }
      } else {
        for (int i = 0; i < (byteWidth - 8) / 8; i++) {
          MemoryUtil.putLong(addressOfValue + Long.BYTES * i, padValue);
        }
        MemoryUtil.putLong(addressOfValue + Long.BYTES * (byteWidth - 8) / 8, value);
      }
    }
  
    /**
     * Write the given byte array to the ArrowBuf at the given value index. Will throw an
     * UnsupportedOperationException if the decimal size is greater than the Decimal vector byte
     * width.
     */
    public static void writeByteArrayToArrowBuf(
        byte[] bytes, ArrowBuf bytebuf, int index, int byteWidth) {
      writeByteArrayToArrowBufHelper(bytes, bytebuf, index, byteWidth);
    }
  
    private static void writeByteArrayToArrowBufHelper(
        byte[] bytes, ArrowBuf bytebuf, int index, int byteWidth) {
      final long startIndex = (long) index * byteWidth;
      if (bytes.length > byteWidth) {
        throw new UnsupportedOperationException(
            "Decimal size greater than " + byteWidth + " bytes: " + bytes.length);
      }
  
      byte[] padBytes = bytes[0] < 0 ? minus_one : zeroes;
      if (LITTLE_ENDIAN) {
        // Decimal stored as native-endian, need to swap data bytes before writing to ArrowBuf if LE
        byte[] bytesLE = new byte[bytes.length];
        for (int i = 0; i < bytes.length; i++) {
          bytesLE[i] = bytes[bytes.length - 1 - i];
        }
  
        // Write LE data
        bytebuf.setBytes(startIndex, bytesLE, 0, bytes.length);
        bytebuf.setBytes(startIndex + bytes.length, padBytes, 0, byteWidth - bytes.length);
      } else {
        // Write BE data
        bytebuf.setBytes(startIndex + byteWidth - bytes.length, bytes, 0, bytes.length);
        bytebuf.setBytes(startIndex, padBytes, 0, byteWidth - bytes.length);
      }
    }
  }
  