Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,21 @@

import org.apache.plc4x.java.spi.buffers.api.exceptions.BufferException;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

public abstract class AbstractBuffer implements Buffer {

protected final Stack<WithOption[]> context;
// Used as a stack (push/pop/peek). ArrayDeque instead of java.util.Stack: a buffer is a
// single-threaded, per-message scratch object (positionInBits and the backing array are
// themselves unsynchronized), so Stack's synchronization (it extends the synchronized Vector)
// guards nothing here while adding a monitor enter/exit to getContext() on every field.
protected final Deque<WithOption[]> context;

public AbstractBuffer(WithOption... options) {
context = new Stack<>();
context = new ArrayDeque<>();
context.push(options);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,31 @@ protected ByteOrder getByteOrder(WithOption... options) {
return byteOrder.get();
}
}
return new ByteOrderBigEndian();
return ByteOrderBigEndian.INSTANCE;
}

// ---- Byte-aligned integer fast-path helpers (shared by Read/Write byte buffers) ----

/**
* Structural fast-path eligibility for the byte-aligned integer fast path: no per-field
* options, the cursor on an absolute byte boundary (see {@link #isAligned()}), and a
* whole number of bytes requested. The caller combines this with EXACT-CLASS checks
* ({@code getClass() == ...}, not {@code instanceof}) on the ALREADY RESOLVED encoding/byte
* order (plain binary / two's-complement, big-endian) so resolution happens exactly once per
* field, and a registered subclass with overridden codec behaviour falls through to the
* virtual-dispatch slow path instead of being silently bypassed by the fast path.
*/
protected boolean isByteAlignedWholeBytes(int numBits, WithOption[] options) {
// isAligned() tests the ABSOLUTE bit index (startBit + positionInBits) — the same predicate the
// readBits/writeBits whole-byte fast paths use — so a non-byte-aligned sub-buffer correctly
// falls through to the generic path (the aligned fast paths index by (startBit+positionInBits)/8).
return options.length == 0 && isAligned() && (numBits & 7) == 0;
}

/** Big-endian two's-complement sign extension of the low {@code numBits} of {@code raw}. */
protected static long signExtend(long raw, int numBits) {
int shift = 64 - numBits;
return (raw << shift) >> shift;
}

protected Optional<Encoding> getUnsignedIntegerEncoding(WithOption... options) {
Expand Down Expand Up @@ -156,8 +180,16 @@ protected void ensureAvailable(int bitsNeeded) throws BufferException {
}
}

/**
* Whether the current cursor sits on a byte boundary of the BACKING ARRAY. This must be tested on
* the absolute bit index ({@code startBit + positionInBits}), not on {@code positionInBits} alone:
* a sub-buffer created at a non-byte-aligned offset (see {@code createSubBuffer}) has a non-zero
* {@code startBit} while its own {@code positionInBits} is 0. The whole-byte {@code arraycopy}
* fast paths in {@code readBits}/{@code writeBits} index the backing array by
* {@code (startBit + positionInBits) / 8}, so only absolute alignment makes that copy correct.
*/
protected boolean isAligned() {
return (positionInBits % 8) == 0;
return ((startBit + positionInBits) % 8) == 0;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@
import org.apache.plc4x.java.spi.buffers.api.ReadBuffer;
import org.apache.plc4x.java.spi.buffers.api.WithOption;
import org.apache.plc4x.java.spi.buffers.api.exceptions.BufferException;
import org.apache.plc4x.java.spi.buffers.bytebased.byteorder.ByteOrder;
import org.apache.plc4x.java.spi.buffers.bytebased.byteorder.ByteOrderBigEndian;
import org.apache.plc4x.java.spi.buffers.bytebased.byteorder.ByteOrderManager;
import org.apache.plc4x.java.spi.buffers.bytebased.encoding.Encoding;
import org.apache.plc4x.java.spi.buffers.bytebased.encoding.EncodingDefault;
import org.apache.plc4x.java.spi.buffers.bytebased.encoding.EncodingManager;
import org.apache.plc4x.java.spi.buffers.bytebased.encoding.EncodingRaw;
import org.apache.plc4x.java.spi.buffers.bytebased.encoding.EncodingTwosComplement;
import org.apache.plc4x.java.spi.buffers.bytebased.encoding.EncodingUnsignedBinary;

import java.math.BigDecimal;
import java.math.BigInteger;
Expand Down Expand Up @@ -91,6 +95,23 @@ public byte[] readBits(int numBits, WithOption... options) throws BufferExceptio
}
}

/**
* Reads {@code numBits} (a whole number of bytes) big-endian from the current byte-aligned
* position into a long and advances the position. Callers gate this behind the fast-path
* eligibility checks; no intermediate byte[] or per-field ByteOrder object is allocated.
*/
private long readAlignedBytesBE(int numBits) throws BufferException {
ensureAvailable(numBits);
int byteIndex = (startBit + positionInBits) / 8;
int numBytes = numBits / 8;
long v = 0;
for (int i = 0; i < numBytes; i++) {
v = (v << 8) | (buffer[byteIndex + i] & 0xFF);
}
positionInBits += numBits;
return v;
}

@Override
public byte readUnsignedByte(int numBits, WithOption... options) throws BufferException {
if (numBits < 1 || numBits > 7) {
Expand Down Expand Up @@ -118,10 +139,17 @@ public short readUnsignedShort(int numBits, WithOption... options) throws Buffer

Optional<Encoding> encodingOptional = getUnsignedIntegerEncoding(options);
Encoding encoding = encodingOptional.orElseThrow(() -> new BufferException("No encoding defined for unsigned integer values"));
ByteOrder byteOrder = getByteOrder(options);
if (isByteAlignedWholeBytes(numBits, options)
&& encoding.getClass() == EncodingUnsignedBinary.class
&& byteOrder.getClass() == ByteOrderBigEndian.class) {
return (short) readAlignedBytesBE(numBits);
}

if(encoding instanceof EncodingDefault encodingDefault) {
ensureAvailable(numBits);
byte[] bytes = readBits(numBits);
bytes = getByteOrder(options).process(bytes);
bytes = byteOrder.process(bytes);
return encodingDefault.decodeShort(numBits, bytes);
} else if(encoding instanceof EncodingRaw encodingRaw) {
return encodingRaw.decodeShort(numBits, this);
Expand All @@ -137,10 +165,19 @@ public int readUnsignedInt(int numBits, WithOption... options) throws BufferExce

Optional<Encoding> encodingOptional = getUnsignedIntegerEncoding(options);
Encoding encoding = encodingOptional.orElseThrow(() -> new BufferException("No encoding defined for unsigned integer values"));
ByteOrder byteOrder = getByteOrder(options);
// Fast path: byte-aligned whole-byte plain-binary big-endian read straight from the backing
// array; encoding/byte order are resolved once above and reused by the slow path below.
if (isByteAlignedWholeBytes(numBits, options)
&& encoding.getClass() == EncodingUnsignedBinary.class
&& byteOrder.getClass() == ByteOrderBigEndian.class) {
return (int) readAlignedBytesBE(numBits);
}

if(encoding instanceof EncodingDefault encodingDefault) {
ensureAvailable(numBits);
byte[] bytes = readBits(numBits);
bytes = getByteOrder(options).process(bytes);
bytes = byteOrder.process(bytes);
return encodingDefault.decodeInt(numBits, bytes);
} else if(encoding instanceof EncodingRaw encodingRaw) {
return encodingRaw.decodeInt(numBits, this);
Expand All @@ -156,10 +193,17 @@ public long readUnsignedLong(int numBits, WithOption... options) throws BufferEx

Optional<Encoding> encodingOptional = getUnsignedIntegerEncoding(options);
Encoding encoding = encodingOptional.orElseThrow(() -> new BufferException("No encoding defined for unsigned integer values"));
ByteOrder byteOrder = getByteOrder(options);
if (isByteAlignedWholeBytes(numBits, options)
&& encoding.getClass() == EncodingUnsignedBinary.class
&& byteOrder.getClass() == ByteOrderBigEndian.class) {
return readAlignedBytesBE(numBits);
}

if(encoding instanceof EncodingDefault encodingDefault) {
ensureAvailable(numBits);
byte[] bytes = readBits(numBits);
bytes = getByteOrder(options).process(bytes);
bytes = byteOrder.process(bytes);
return encodingDefault.decodeLong(numBits, bytes);
} else if(encoding instanceof EncodingRaw encodingRaw) {
return encodingRaw.decodeLong(numBits, this);
Expand Down Expand Up @@ -213,10 +257,17 @@ public short readSignedShort(int numBits, WithOption... options) throws BufferEx

Optional<Encoding> encodingOptional = getSignedIntegerEncoding(options);
Encoding encoding = encodingOptional.orElseThrow(() -> new BufferException("No encoding defined for signed integer values"));
ByteOrder byteOrder = getByteOrder(options);
if (isByteAlignedWholeBytes(numBits, options)
&& encoding.getClass() == EncodingTwosComplement.class
&& byteOrder.getClass() == ByteOrderBigEndian.class) {
return (short) signExtend(readAlignedBytesBE(numBits), numBits);
}

if(encoding instanceof EncodingDefault encodingDefault) {
ensureAvailable(numBits);
byte[] bytes = readBits(numBits);
bytes = getByteOrder(options).process(bytes);
bytes = byteOrder.process(bytes);
return encodingDefault.decodeShort(numBits, bytes);
} else if(encoding instanceof EncodingRaw encodingRaw) {
return encodingRaw.decodeShort(numBits, this);
Expand All @@ -232,10 +283,17 @@ public int readSignedInt(int numBits, WithOption... options) throws BufferExcept

Optional<Encoding> encodingOptional = getSignedIntegerEncoding(options);
Encoding encoding = encodingOptional.orElseThrow(() -> new BufferException("No encoding defined for signed integer values"));
ByteOrder byteOrder = getByteOrder(options);
if (isByteAlignedWholeBytes(numBits, options)
&& encoding.getClass() == EncodingTwosComplement.class
&& byteOrder.getClass() == ByteOrderBigEndian.class) {
return (int) signExtend(readAlignedBytesBE(numBits), numBits);
}

if(encoding instanceof EncodingDefault encodingDefault) {
ensureAvailable(numBits);
byte[] bytes = readBits(numBits);
bytes = getByteOrder(options).process(bytes);
bytes = byteOrder.process(bytes);
return encodingDefault.decodeInt(numBits, bytes);
} else if(encoding instanceof EncodingRaw encodingRaw) {
return encodingRaw.decodeInt(numBits, this);
Expand All @@ -251,10 +309,17 @@ public long readSignedLong(int numBits, WithOption... options) throws BufferExce

Optional<Encoding> encodingOptional = getSignedIntegerEncoding(options);
Encoding encoding = encodingOptional.orElseThrow(() -> new BufferException("No encoding defined for signed integer values"));
ByteOrder byteOrder = getByteOrder(options);
if (isByteAlignedWholeBytes(numBits, options)
&& encoding.getClass() == EncodingTwosComplement.class
&& byteOrder.getClass() == ByteOrderBigEndian.class) {
return signExtend(readAlignedBytesBE(numBits), numBits);
}

if(encoding instanceof EncodingDefault encodingDefault) {
ensureAvailable(numBits);
byte[] bytes = readBits(numBits);
bytes = getByteOrder(options).process(bytes);
bytes = byteOrder.process(bytes);
return encodingDefault.decodeLong(numBits, bytes);
} else if(encoding instanceof EncodingRaw encodingRaw) {
return encodingRaw.decodeLong(numBits, this);
Expand Down
Loading
Loading