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
}