SignificantBytesBE.java

/*******************************************************************************
 * Copyright (c) 2025 Eclipse RDF4J contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 ******************************************************************************/

package org.eclipse.rdf4j.sail.lmdb.util;

import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * Fast reader for 1..8 "significant" big-endian bytes from a ByteBuffer. Chooses optimal path at runtime: - heap-backed
 * buffers: raw array indexing (no per-byte virtual calls), - direct/read-only buffers: absolute wide reads +
 * conditional byte-swap.
 *
 * Returns an unsigned value in the low bits of the long (0 .. 2^(8*n)-1).
 */
public final class SignificantBytesBE {
	private SignificantBytesBE() {
	}

	/**
	 * Read n (1..8) big-endian significant bytes from the buffer and advance position by n.
	 *
	 * @throws IllegalArgumentException if n is not in [1,8]
	 * @throws BufferUnderflowException if fewer than n bytes remain
	 */
	public static long read(ByteBuffer bb, int n) {
		return readDirect(bb, n);
	}

	// -------- Direct/read-only fast path (absolute wide reads + conditional byte swap) --------

	private static long u32(int x) {
		return x & 0xFFFF_FFFFL;
	}

	private static int u16(short x) {
		return x & 0xFFFF;
	}

	private static short getShortBE(ByteBuffer bb, boolean littleEndian) {
		short s = bb.getShort();
		return (littleEndian) ? Short.reverseBytes(s) : s;
	}

	private static int getIntBE(ByteBuffer bb, boolean littleEndian) {
		int i = bb.getInt();
		return (littleEndian) ? Integer.reverseBytes(i) : i;
	}

	private static long getLongBE(ByteBuffer bb, boolean littleEndian) {
		long l = bb.getLong();
		return (littleEndian) ? Long.reverseBytes(l) : l;
	}

	public static long readDirect(ByteBuffer bb, int n) {
		if (n < 3 || n > 8) {
			throw new IllegalArgumentException("n must be in [3,8]");
		}

		boolean littleEndian = bb.order() == ByteOrder.LITTLE_ENDIAN;

		switch (n) {
		case 8:
			return getLongBE(bb, littleEndian);
		case 7:
			return ((bb.get() & 0xFFL) << 48)
					| ((u32(getIntBE(bb, littleEndian)) << 16))
					| (u16(getShortBE(bb, littleEndian)));
		case 6:
			return (((long) u16(getShortBE(bb, littleEndian)) << 32))
					| (u32(getIntBE(bb, littleEndian)));
		case 5:
			return ((bb.get() & 0xFFL) << 32)
					| (u32(getIntBE(bb, littleEndian)));
		case 4:
			return u32(getIntBE(bb, littleEndian));
		case 3:
			return (((long) u16(getShortBE(bb, littleEndian)) << 8))
					| (bb.get() & 0xFFL);
		// TODO: add 1 and 2 byte cases here!!!
		default:
			throw new AssertionError("unreachable");
		}
	}

}