diff --git a/buffer/src/main/kotlin/org/openrs2/buffer/BitBuf.kt b/buffer/src/main/kotlin/org/openrs2/buffer/BitBuf.kt new file mode 100644 index 00000000..e5c7861b --- /dev/null +++ b/buffer/src/main/kotlin/org/openrs2/buffer/BitBuf.kt @@ -0,0 +1,265 @@ +package org.openrs2.buffer + +import com.google.common.base.Preconditions +import io.netty.buffer.ByteBuf +import kotlin.math.min + +public class BitBuf( + private val buf: ByteBuf +) : AutoCloseable { + private var readerIndex: Long = buf.readerIndex().toLong() shl 3 + private set(value) { + field = value + buf.readerIndex((readerIndex shr 3).toInt()) + } + + private var writerIndex: Long = buf.writerIndex().toLong() shl 3 + private set(value) { + field = value + buf.writerIndex((writerIndex shr 3).toInt()) + } + + public fun getBoolean(index: Long): Boolean { + return getBits(index, 1) != 0 + } + + public fun getBit(index: Long): Int { + return getBits(index, 1) + } + + public fun getBits(index: Long, len: Int): Int { + Preconditions.checkArgument(len in 1..32) + + if (index < 0 || (index + len) > capacity()) { + throw IndexOutOfBoundsException() + } + + var value = 0 + + var remaining = len + var byteIndex = (index shr 3).toInt() + var bitIndex = (index and 7).toInt() + + while (remaining > 0) { + val n = min(8 - bitIndex, remaining) + val shift = (8 - (bitIndex + n)) and 7 + val mask = (1 shl n) - 1 + + val v = buf.getUnsignedByte(byteIndex).toInt() + value = value shl n + value = value or ((v shr shift) and mask) + + remaining -= n + byteIndex++ + bitIndex = 0 + } + + return value + } + + public fun readBoolean(): Boolean { + return readBits(1) != 0 + } + + public fun readBit(): Int { + return readBits(1) + } + + public fun readBits(len: Int): Int { + checkReadableBits(len) + + val value = getBits(readerIndex, len) + readerIndex += len + return value + } + + public fun skipBits(len: Int): BitBuf { + checkReadableBits(len) + readerIndex += len + + return this + } + + public fun setBoolean(index: Long, value: Boolean): BitBuf { + if (value) { + setBits(index, 1, 1) + } else { + setBits(index, 1, 0) + } + + return this + } + + public fun setBit(index: Long, value: Int): BitBuf { + setBits(index, 1, value) + + return this + } + + public fun setBits(index: Long, len: Int, value: Int): BitBuf { + Preconditions.checkArgument(len in 1..32) + + if (index < 0 || (index + len) > capacity()) { + throw IndexOutOfBoundsException() + } + + var remaining = len + var byteIndex = (index shr 3).toInt() + var bitIndex = (index and 7).toInt() + + while (remaining > 0) { + val n = min(8 - bitIndex, remaining) + val shift = (8 - (bitIndex + n)) and 7 + val mask = (1 shl n) - 1 + + var v = buf.getUnsignedByte(byteIndex).toInt() + v = v and (mask shl shift).inv() + v = v or (((value shr (remaining - n)) and mask) shl shift) + buf.setByte(byteIndex, v) + + remaining -= n + byteIndex++ + bitIndex = 0 + } + + return this + } + + public fun writeBoolean(value: Boolean): BitBuf { + if (value) { + writeBits(1, 1) + } else { + writeBits(1, 0) + } + + return this + } + + public fun writeBit(value: Int): BitBuf { + writeBits(1, value) + + return this + } + + public fun writeBits(len: Int, value: Int): BitBuf { + ensureWritable(len.toLong()) + + setBits(writerIndex, len, value) + writerIndex += len + + return this + } + + public fun writeZero(len: Int): BitBuf { + writeBits(len, 0) + + return this + } + + private fun checkReadableBits(len: Int) { + Preconditions.checkArgument(len >= 0) + + if ((readerIndex + len) > writerIndex) { + throw IndexOutOfBoundsException() + } + } + + public fun ensureWritable(len: Long): BitBuf { + Preconditions.checkArgument(len >= 0) + + if ((writerIndex + len) > maxCapacity()) { + throw IndexOutOfBoundsException() + } + + val currentByteIndex = writerIndex shr 3 + val nextByteIndex = (writerIndex + len + 7) shr 3 + + buf.ensureWritable((nextByteIndex - currentByteIndex).toInt()) + + return this + } + + public fun readableBits(): Long { + return writerIndex - readerIndex + } + + public fun writableBits(): Long { + return capacity() - writerIndex + } + + public fun maxWritableBits(): Long { + return maxCapacity() - writerIndex + } + + public fun capacity(): Long { + return buf.capacity().toLong() shl 3 + } + + public fun capacity(len: Long): BitBuf { + buf.capacity((len shr 3).toInt()) + return this + } + + public fun maxCapacity(): Long { + return buf.maxCapacity().toLong() shl 3 + } + + public fun isReadable(): Boolean { + return readerIndex < writerIndex + } + + public fun isReadable(len: Long): Boolean { + Preconditions.checkArgument(len >= 0) + return (readerIndex + len) <= writerIndex + } + + public fun isWritable(): Boolean { + return writerIndex < capacity() + } + + public fun isWritable(len: Long): Boolean { + Preconditions.checkArgument(len >= 0) + return (writerIndex + len) <= capacity() + } + + public fun readerIndex(): Long { + return readerIndex + } + + public fun readerIndex(index: Long): BitBuf { + if (index < 0 || index > writerIndex) { + throw IndexOutOfBoundsException() + } + + readerIndex = index + return this + } + + public fun writerIndex(): Long { + return writerIndex + } + + public fun writerIndex(index: Long): BitBuf { + if (index < readerIndex || index > capacity()) { + throw IndexOutOfBoundsException() + } + + writerIndex = index + return this + } + + public fun clear(): BitBuf { + readerIndex = 0 + writerIndex = 0 + return this + } + + override fun close() { + val bits = (((writerIndex + 7) and 7.toLong().inv()) - writerIndex).toInt() + if (bits != 0) { + writeZero(bits) + } + + readerIndex = (readerIndex + 7) and 7.toLong().inv() + } +} diff --git a/buffer/src/test/kotlin/org/openrs2/buffer/BitBufTest.kt b/buffer/src/test/kotlin/org/openrs2/buffer/BitBufTest.kt new file mode 100644 index 00000000..dd8a3d28 --- /dev/null +++ b/buffer/src/test/kotlin/org/openrs2/buffer/BitBufTest.kt @@ -0,0 +1,624 @@ +package org.openrs2.buffer + +import io.netty.buffer.ByteBufAllocator +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class BitBufTest { + @Test + fun testClear() { + ByteBufAllocator.DEFAULT.buffer().use { buf -> + buf.writeInt(1234567890) + buf.skipBytes(1) + + BitBuf(buf).use { bitBuf -> + assertEquals(8, bitBuf.readerIndex()) + assertEquals(32, bitBuf.writerIndex()) + + bitBuf.clear() + + assertEquals(0, bitBuf.readerIndex()) + assertEquals(0, bitBuf.writerIndex()) + } + + assertEquals(0, buf.readerIndex()) + assertEquals(0, buf.writerIndex()) + } + } + + @Test + fun testReadAlignment() { + ByteBufAllocator.DEFAULT.buffer().use { buf -> + buf.writeByte(0xFF) + + BitBuf(buf).use { bitBuf -> + assertEquals(0, bitBuf.readerIndex()) + assertEquals(0x7F, bitBuf.readBits(7)) + assertEquals(7, bitBuf.readerIndex()) + assertEquals(0, buf.readerIndex()) + } + + assertEquals(1, buf.readerIndex()) + } + } + + @Test + fun testWriteAlignment() { + ByteBufAllocator.DEFAULT.buffer().use { buf -> + buf.setByte(0, 0xFF) + + BitBuf(buf).use { bitBuf -> + assertEquals(0, bitBuf.writerIndex()) + bitBuf.writeBits(7, 0x7F) + assertEquals(7, bitBuf.writerIndex()) + assertEquals(0, buf.writerIndex()) + } + + assertEquals(1, buf.writerIndex()) + assertEquals(0xFE, buf.getUnsignedByte(0)) + } + } + + @Test + fun testSkipBits() { + ByteBufAllocator.DEFAULT.buffer().use { buf -> + buf.writeInt(1234567890) + + BitBuf(buf).use { bitBuf -> + assertFailsWith { + bitBuf.skipBits(-1) + } + + assertEquals(0, bitBuf.readerIndex()) + assertEquals(0, buf.readerIndex()) + + bitBuf.skipBits(0) + + assertEquals(0, bitBuf.readerIndex()) + assertEquals(0, buf.readerIndex()) + + bitBuf.skipBits(7) + + assertEquals(7, bitBuf.readerIndex()) + assertEquals(0, buf.readerIndex()) + + bitBuf.skipBits(2) + + assertEquals(9, bitBuf.readerIndex()) + assertEquals(1, buf.readerIndex()) + + bitBuf.skipBits(23) + + assertEquals(32, bitBuf.readerIndex()) + assertEquals(4, buf.readerIndex()) + + bitBuf.skipBits(0) + + assertEquals(32, bitBuf.readerIndex()) + assertEquals(4, buf.readerIndex()) + + assertFailsWith { + bitBuf.skipBits(1) + } + } + + assertEquals(4, buf.readerIndex()) + } + } + + @Test + fun testGetBits() { + ByteBufAllocator.DEFAULT.buffer(4, 4).use { buf -> + buf.writeInt(1234567890) + + BitBuf(buf).use { bitBuf -> + assertEquals(1234567890, bitBuf.getBits(0, 32)) + + assertEquals(0b0100100110010110, bitBuf.getBits(0, 16)) + assertEquals(0b0000001011010010, bitBuf.getBits(16, 16)) + + assertEquals(0b01001001, bitBuf.getBits(0, 8)) + assertEquals(0b10010110, bitBuf.getBits(8, 8)) + assertEquals(0b00000010, bitBuf.getBits(16, 8)) + assertEquals(0b11010010, bitBuf.getBits(24, 8)) + + assertEquals(0b10011001, bitBuf.getBits(4, 8)) + + assertEquals(0b100110010110, bitBuf.getBits(4, 12)) + + assertEquals(0, bitBuf.getBits(0, 1)) + assertEquals(0, bitBuf.getBit(0)) + assertEquals(false, bitBuf.getBoolean(0)) + + assertEquals(1, bitBuf.getBits(1, 1)) + assertEquals(1, bitBuf.getBit(1)) + assertEquals(true, bitBuf.getBoolean(1)) + + assertFailsWith { + bitBuf.getBits(-1, 1) + } + + assertFailsWith { + bitBuf.getBits(32, 1) + } + + assertFailsWith { + bitBuf.getBits(0, 0) + } + + assertFailsWith { + bitBuf.getBits(0, 33) + } + } + } + } + + @Test + fun testSetBits() { + ByteBufAllocator.DEFAULT.buffer(4, 4).use { buf -> + BitBuf(buf).use { bitBuf -> + bitBuf.setBits(0, 32, 1234567890) + } + + assertEquals(1234567890, buf.getInt(0)) + } + + ByteBufAllocator.DEFAULT.buffer(4, 4).use { buf -> + BitBuf(buf).use { bitBuf -> + bitBuf.setBits(0, 16, 0b0100100110010110) + bitBuf.setBits(16, 16, 0b0000001011010010) + } + + assertEquals(1234567890, buf.getInt(0)) + } + + ByteBufAllocator.DEFAULT.buffer(4, 4).use { buf -> + BitBuf(buf).use { bitBuf -> + bitBuf.setBits(0, 8, 0b01001001) + bitBuf.setBits(8, 8, 0b10010110) + bitBuf.setBits(16, 8, 0b00000010) + bitBuf.setBits(24, 8, 0b11010010) + } + + assertEquals(1234567890, buf.getInt(0)) + } + + ByteBufAllocator.DEFAULT.buffer(4, 4).use { buf -> + BitBuf(buf).use { bitBuf -> + bitBuf.setBits(0, 4, 0b0100) + bitBuf.setBits(4, 8, 0b10011001) + bitBuf.setBits(12, 4, 0b0110) + + bitBuf.setBits(16, 16, 0b0000001011000101) + + bitBuf.setBoolean(27, true) + bitBuf.setBits(29, 1, 0) + bitBuf.setBit(30, 1) + bitBuf.setBoolean(31, false) + } + + assertEquals(1234567890, buf.getInt(0)) + } + + ByteBufAllocator.DEFAULT.buffer(4, 4).use { buf -> + BitBuf(buf).use { bitBuf -> + assertFailsWith { + bitBuf.setBits(-1, 1, 0) + } + + assertFailsWith { + bitBuf.setBits(32, 1, 0) + } + + assertFailsWith { + bitBuf.setBits(0, 0, 0) + } + + assertFailsWith { + bitBuf.setBits(0, 33, 0) + } + } + } + } + + @Test + fun testRead() { + ByteBufAllocator.DEFAULT.buffer(4, 4).use { buf -> + buf.writeInt(1234567890) + + BitBuf(buf).use { bitBuf -> + assertEquals(0, bitBuf.readerIndex()) + assertEquals(32, bitBuf.readableBits()) + + assertTrue(bitBuf.isReadable()) + assertTrue(bitBuf.isReadable(0)) + assertTrue(bitBuf.isReadable(1)) + assertTrue(bitBuf.isReadable(31)) + assertTrue(bitBuf.isReadable(32)) + assertFalse(bitBuf.isReadable(33)) + + assertEquals(1234567890, bitBuf.readBits(32)) + assertEquals(32, bitBuf.readerIndex()) + assertEquals(0, bitBuf.readableBits()) + + assertFalse(bitBuf.isReadable()) + assertTrue(bitBuf.isReadable(0)) + assertFalse(bitBuf.isReadable(1)) + } + + assertEquals(4, buf.readerIndex()) + } + + ByteBufAllocator.DEFAULT.buffer(4, 4).use { buf -> + buf.writeInt(1234567890) + + BitBuf(buf).use { bitBuf -> + assertEquals(0, bitBuf.readerIndex()) + assertEquals(32, bitBuf.readableBits()) + + assertEquals(0b0100100110010110, bitBuf.readBits(16)) + + assertEquals(16, bitBuf.readerIndex()) + assertEquals(16, bitBuf.readableBits()) + + assertEquals(0b0000001011010010, bitBuf.readBits(16)) + + assertEquals(32, bitBuf.readerIndex()) + assertEquals(0, bitBuf.readableBits()) + } + + assertEquals(4, buf.readerIndex()) + } + + ByteBufAllocator.DEFAULT.buffer(4, 4).use { buf -> + buf.writeInt(1234567890) + + BitBuf(buf).use { bitBuf -> + assertEquals(0, bitBuf.readerIndex()) + assertEquals(32, bitBuf.readableBits()) + + assertEquals(0b01001001, bitBuf.readBits(8)) + + assertEquals(8, bitBuf.readerIndex()) + assertEquals(24, bitBuf.readableBits()) + + assertEquals(0b10010110, bitBuf.readBits(8)) + + assertEquals(16, bitBuf.readerIndex()) + assertEquals(16, bitBuf.readableBits()) + + assertEquals(0b00000010, bitBuf.readBits(8)) + + assertEquals(24, bitBuf.readerIndex()) + assertEquals(8, bitBuf.readableBits()) + + assertEquals(0b11010010, bitBuf.readBits(8)) + + assertEquals(32, bitBuf.readerIndex()) + assertEquals(0, bitBuf.readableBits()) + } + + assertEquals(4, buf.readerIndex()) + } + + ByteBufAllocator.DEFAULT.buffer(1, 1).use { buf -> + buf.writeByte(0b01010100) + + BitBuf(buf).use { bitBuf -> + assertEquals(0, bitBuf.readerIndex()) + assertEquals(8, bitBuf.readableBits()) + + assertEquals(0, bitBuf.readBits(1)) + + assertEquals(1, bitBuf.readerIndex()) + assertEquals(7, bitBuf.readableBits()) + + assertEquals(1, bitBuf.readBits(1)) + + assertEquals(2, bitBuf.readerIndex()) + assertEquals(6, bitBuf.readableBits()) + + assertEquals(0, bitBuf.readBit()) + + assertEquals(3, bitBuf.readerIndex()) + assertEquals(5, bitBuf.readableBits()) + + assertEquals(1, bitBuf.readBit()) + + assertEquals(4, bitBuf.readerIndex()) + assertEquals(4, bitBuf.readableBits()) + + assertFalse(bitBuf.readBoolean()) + + assertEquals(5, bitBuf.readerIndex()) + assertEquals(3, bitBuf.readableBits()) + + assertTrue(bitBuf.readBoolean()) + + assertEquals(6, bitBuf.readerIndex()) + assertEquals(2, bitBuf.readableBits()) + + bitBuf.skipBits(2) + + assertEquals(8, bitBuf.readerIndex()) + assertEquals(0, bitBuf.readableBits()) + } + + assertEquals(1, buf.readerIndex()) + } + + ByteBufAllocator.DEFAULT.buffer(5, 5).use { buf -> + buf.writerIndex(5) + + BitBuf(buf).use { bitBuf -> + assertFailsWith { + bitBuf.readBits(0) + } + + assertFailsWith { + bitBuf.readBits(33) + } + } + } + } + + @Test + fun testWrite() { + ByteBufAllocator.DEFAULT.buffer(4, 4).use { buf -> + BitBuf(buf).use { bitBuf -> + assertEquals(0, bitBuf.writerIndex()) + assertEquals(32, bitBuf.writableBits()) + + assertTrue(bitBuf.isWritable()) + assertTrue(bitBuf.isWritable(0)) + assertTrue(bitBuf.isWritable(1)) + assertTrue(bitBuf.isWritable(31)) + assertTrue(bitBuf.isWritable(32)) + assertFalse(bitBuf.isWritable(33)) + + bitBuf.writeBits(32, 1234567890) + + assertEquals(32, bitBuf.writerIndex()) + assertEquals(0, bitBuf.writableBits()) + + assertFalse(bitBuf.isWritable()) + assertTrue(bitBuf.isWritable(0)) + assertFalse(bitBuf.isWritable(1)) + } + + assertEquals(1234567890, buf.getInt(0)) + assertEquals(4, buf.writerIndex()) + } + + ByteBufAllocator.DEFAULT.buffer(4, 4).use { buf -> + BitBuf(buf).use { bitBuf -> + assertEquals(0, bitBuf.writerIndex()) + assertEquals(32, bitBuf.writableBits()) + + bitBuf.writeBits(16, 0b0100100110010110) + + assertEquals(16, bitBuf.writerIndex()) + assertEquals(16, bitBuf.writableBits()) + + bitBuf.writeBits(16, 0b0000001011010010) + + assertEquals(32, bitBuf.writerIndex()) + assertEquals(0, bitBuf.writableBits()) + } + + assertEquals(1234567890, buf.getInt(0)) + assertEquals(4, buf.writerIndex()) + } + + ByteBufAllocator.DEFAULT.buffer(4, 4).use { buf -> + BitBuf(buf).use { bitBuf -> + assertEquals(0, bitBuf.writerIndex()) + assertEquals(32, bitBuf.writableBits()) + + bitBuf.writeBits(8, 0b01001001) + + assertEquals(8, bitBuf.writerIndex()) + assertEquals(24, bitBuf.writableBits()) + + bitBuf.writeBits(8, 0b10010110) + + assertEquals(16, bitBuf.writerIndex()) + assertEquals(16, bitBuf.writableBits()) + + bitBuf.writeBits(8, 0b00000010) + + assertEquals(24, bitBuf.writerIndex()) + assertEquals(8, bitBuf.writableBits()) + + bitBuf.writeBits(8, 0b11010010) + + assertEquals(32, bitBuf.writerIndex()) + assertEquals(0, bitBuf.writableBits()) + } + + assertEquals(1234567890, buf.getInt(0)) + assertEquals(4, buf.writerIndex()) + } + + ByteBufAllocator.DEFAULT.buffer(1, 1).use { buf -> + BitBuf(buf).use { bitBuf -> + assertEquals(0, bitBuf.writerIndex()) + assertEquals(8, bitBuf.writableBits()) + + bitBuf.writeBits(1, 0) + + assertEquals(1, bitBuf.writerIndex()) + assertEquals(7, bitBuf.writableBits()) + + bitBuf.writeBits(1, 1) + + assertEquals(2, bitBuf.writerIndex()) + assertEquals(6, bitBuf.writableBits()) + + bitBuf.writeBit(0) + + assertEquals(3, bitBuf.writerIndex()) + assertEquals(5, bitBuf.writableBits()) + + bitBuf.writeBit(1) + + assertEquals(4, bitBuf.writerIndex()) + assertEquals(4, bitBuf.writableBits()) + + bitBuf.writeBoolean(false) + + assertEquals(5, bitBuf.writerIndex()) + assertEquals(3, bitBuf.writableBits()) + + bitBuf.writeBoolean(true) + + assertEquals(6, bitBuf.writerIndex()) + assertEquals(2, bitBuf.writableBits()) + + bitBuf.writeZero(2) + + assertEquals(8, bitBuf.writerIndex()) + assertEquals(0, bitBuf.writableBits()) + } + + assertEquals(0b01010100, buf.getUnsignedByte(0)) + assertEquals(1, buf.writerIndex()) + } + + ByteBufAllocator.DEFAULT.buffer(5, 5).use { buf -> + BitBuf(buf).use { bitBuf -> + assertFailsWith { + bitBuf.writeBits(0, 0) + } + + assertFailsWith { + bitBuf.writeBits(33, 0) + } + + assertFailsWith { + bitBuf.isWritable(-1) + } + } + } + } + + @Test + fun testExpand() { + ByteBufAllocator.DEFAULT.buffer(1, 2).use { buf -> + BitBuf(buf).use { bitBuf -> + assertEquals(8, bitBuf.capacity()) + assertEquals(8, bitBuf.writableBits()) + + assertEquals(16, bitBuf.maxCapacity()) + assertEquals(16, bitBuf.maxWritableBits()) + + bitBuf.writeBits(8, 0) + + assertEquals(0, bitBuf.writableBits()) + assertEquals(8, bitBuf.maxWritableBits()) + + bitBuf.writeBit(0) + + assertEquals(7, bitBuf.writableBits()) + assertEquals(7, bitBuf.maxWritableBits()) + + bitBuf.writeZero(7) + + assertEquals(0, bitBuf.writableBits()) + assertEquals(0, bitBuf.maxWritableBits()) + + assertFailsWith { + bitBuf.writeBit(0) + } + + assertFailsWith { + bitBuf.ensureWritable(-1) + } + } + } + } + + @Test + fun testCapacity() { + ByteBufAllocator.DEFAULT.buffer(4, 16).use { buf -> + BitBuf(buf).use { bitBuf -> + assertEquals(32, bitBuf.capacity()) + assertEquals(4, buf.capacity()) + + assertEquals(128, bitBuf.maxCapacity()) + assertEquals(16, buf.maxCapacity()) + + bitBuf.capacity(64) + + assertEquals(64, bitBuf.capacity()) + assertEquals(8, buf.capacity()) + } + } + } + + @Test + fun testReaderIndex() { + ByteBufAllocator.DEFAULT.buffer().use { buf -> + BitBuf(buf).use { bitBuf -> + bitBuf.writerIndex(10) + + assertEquals(0, bitBuf.readerIndex()) + + assertFailsWith { + bitBuf.readerIndex(-1) + } + + bitBuf.readerIndex(1) + assertEquals(1, bitBuf.readerIndex()) + + bitBuf.readerIndex(8) + assertEquals(8, bitBuf.readerIndex()) + + bitBuf.readerIndex(9) + assertEquals(9, bitBuf.readerIndex()) + + bitBuf.readerIndex(10) + assertEquals(10, bitBuf.readerIndex()) + + assertFailsWith { + bitBuf.readerIndex(11) + } + } + } + } + + @Test + fun testWriterIndex() { + ByteBufAllocator.DEFAULT.buffer(4, 4).use { buf -> + BitBuf(buf).use { bitBuf -> + bitBuf.writerIndex(20) + bitBuf.readerIndex(10) + + assertEquals(20, bitBuf.writerIndex()) + + assertFailsWith { + bitBuf.writerIndex(9) + } + + bitBuf.writerIndex(10) + assertEquals(10, bitBuf.writerIndex()) + + bitBuf.writerIndex(11) + assertEquals(11, bitBuf.writerIndex()) + + bitBuf.writerIndex(31) + assertEquals(31, bitBuf.writerIndex()) + + bitBuf.writerIndex(32) + assertEquals(32, bitBuf.writerIndex()) + + assertFailsWith { + bitBuf.writerIndex(33) + } + } + } + } +}