final override def putByteArray()

in daffodil-io/src/main/scala/org/apache/daffodil/io/DataOutputStreamImplMixin.scala [528:691]


  final override def putByteArray(
    array: Array[Byte],
    bitLengthFrom1: Long,
    finfo: FormatInfo,
    ignoreByteOrder: Boolean = false
  ): Boolean = {
    // this is to be used for an array generated by getByteArray. Thus, this
    // array is expected by to BigEndian MSBF. It must be transformed into an
    // array that the other putBytes/Bits/etc functions can accept. The length
    // in bits must be positive and must be <= the max size of an array in
    // bits, which is IntMax * 8
    Assert.usage(bitLengthFrom1 >= 1 && bitLengthFrom1 <= Int.MaxValue.toLong * 8)
    Assert.usage(isWritable)

    val res =
      if (
        maybeRelBitLimit0b.isDefined && maybeRelBitLimit0b.get < (relBitPos0b + bitLengthFrom1)
      ) {
        false
      } else {
        // Cannot rely on array.length since it is possible the array is bigger
        // than the number of bits we should put. This value is guaranteed to
        // fit in an integer due to the assertion at the top, so toInt is safe
        val bytesToPutFromArray = ((bitLengthFrom1 + 7) / 8).toInt

        // First thing we need to do is reverse the array if it's littleEndian,
        // but only bother checking if the bilength is more than a byte--single
        // byte sizes are common and it avoids the byteOrder check and
        // potential array allocation
        val bytes =
          if (
            bitLengthFrom1 > 8 &&
            finfo.byteOrder =:= ByteOrder.LittleEndian &&
            !ignoreByteOrder
          ) {
            // We need to reverse this array. However, we cannot modify this
            // array since it might come straight from the infoset, which could
            // potentiall affect expressions that reference this data. So
            // allocate a new array and populate it with the reverse of the old
            // array.
            val reversedArray = new Array[Byte](bytesToPutFromArray)
            var index = 0
            while (index < bytesToPutFromArray) {
              reversedArray(index) = array(bytesToPutFromArray - 1 - index)
              index += 1
            }
            reversedArray
          } else {
            // do not need to reverse, and the below code does not modify the
            // array, so we can just use this for outputting data
            array
          }

        // Determine the length of the new fragment after everything is added
        // from the array taking into account the current fragment limit, and
        // define a place to store its eventual value
        val newFragmentByteLimit = ((fragmentLastByteLimit + bitLengthFrom1) % 8).toInt
        var newFragmentByte: Int = 0

        if (fragmentLastByteLimit == 0) {
          // There is no current fragment byte, so we can just write all the
          // full bytes. Due to the assertion at the top, we know
          // bitLengthFrom1 / 8 must be <= MaxInt, so toInt is safe
          val fullBytes = (bitLengthFrom1 / 8).toInt
          realStream.write(bytes, 0, fullBytes)

          // There still maybe be a fragment byte. If so it will become our new
          // fragment byte. Note that this new byte contains too much data that
          // needs to be masked out, but we'll do that at the end.
          if (newFragmentByteLimit > 0) {
            newFragmentByte = Bits.asUnsignedByte(bytes(fullBytes))
          }
        } else {
          // There is an existing fragment byte. That means we must combine
          // all the bytes with the existing fragment byte to create
          // new bytes and a new fragment byte, taking bitOrder into account

          val isMSBF = finfo.bitOrder == BitOrder.MostSignificantBitFirst

          // Determine masks and shifts needed to combine the fragment byte
          // with array bytes
          val fragByteMask =
            if (isMSBF)
              Bits.maskR(fragmentLastByteLimit)
            else
              Bits.maskL(fragmentLastByteLimit)
          val curByteMask = ~fragByteMask & 0xff
          val fragShift = 8 - fragmentLastByteLimit
          val curShift = fragmentLastByteLimit

          // When we loop through the bytes we combine bytes with the existing
          // fragment byte, updating the new fragment byte as we go along. This
          // byte array might also have some fragment bits if the bitLength is
          // not a multiple of 8. So when we combine the fragment byte with the
          // last fragment byte, it may or may not create a complete new byte.
          // The below pre-determines where the last index in the byte array is,
          // and if that byte combined with a fragment byte will create a whole
          // byte or have some leftover.
          val lastIndexCreatesFullByte = {
            val fragmentBitsInArray = bitLengthFrom1 % 8
            val bitsAvailableInLastByte =
              if (fragmentBitsInArray == 0) 8 else fragmentBitsInArray
            (fragmentLastByteLimit + bitsAvailableInLastByte) >= 8
          }
          val lastIndex = bytesToPutFromArray - 1

          // For each byte, combine it with the previous fragment byte
          // according to bitOrder to create a new byte and a new fragment
          // byte. Potentially write the new byte to the stream if we had
          // enough bits for a full byte.
          newFragmentByte = fragmentLastByte
          var index = 0
          while (index < bytesToPutFromArray) {
            val curByte = Bits.asUnsignedByte(bytes(index))
            val newByte =
              if (isMSBF)
                ((curByte & curByteMask) >> curShift) | newFragmentByte
              else
                ((curByte & curByteMask) << curShift) | newFragmentByte

            if (index != lastIndex || lastIndexCreatesFullByte) {
              // If this is *not* the last byte, then we definitely got 8 bits
              // from this index and can write a full byte and update the
              // fragment with the remaining byte. If this *is* the last byte,
              // but what is left in the last byte combined with the fragment
              // byte creates a full byte, then write the full byte and update
              // the fragment byte with what is left over.
              realStream.write(Bits.asSignedByte(newByte))
              if (isMSBF)
                newFragmentByte = (curByte & fragByteMask) << fragShift
              else
                newFragmentByte = (curByte & fragByteMask) >> fragShift
            } else {
              // We are at the last byte, and the remaining bits combined with
              // the frag byte will not create a whole byte. The new byte we
              // calculated is actually the new frag byte.
              newFragmentByte = newByte
            }
            index += 1
          }
        }

        if (newFragmentByteLimit > 0) {
          // We have written all the data that can be written and now have
          // calculated a new fragment. However, since this is a fragment byte,
          // some of the bits need to be masked out.
          val mask =
            if (finfo.bitOrder == BitOrder.MostSignificantBitFirst)
              Bits.maskL(newFragmentByteLimit)
            else
              Bits.maskR(newFragmentByteLimit)
          setFragmentLastByte(newFragmentByte & mask, newFragmentByteLimit)
        } else {
          // After writing all the bytes, we ended on a byte boundary, there is
          // no fragment byte. Erase the existing one if it existed.
          setFragmentLastByte(0, 0)
        }

        setRelBitPos0b(relBitPos0b + ULong(bitLengthFrom1))
        setNonZeroLength()
        true
      }
    res
  }