diff --git a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java index f5d61e628..e91739bf7 100644 --- a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java +++ b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java @@ -68,6 +68,21 @@ public class CBORGenerator extends GeneratorBase */ private final static int INDEFINITE_LENGTH = -2; // just to allow -1 as marker for "one too many" + // True when VarHandles are available on this runtime. Checked once at class + // load so that if CBORVarHandleUtil itself fails to load (e.g. Android + // without java.lang.invoke), we fall back to manual byte shifting instead + // of propagating NoClassDefFoundError. + private static final boolean _VARHANDLE_AVAILABLE; + static { + boolean available = false; + try { + available = CBORVarHandleUtil.INT_BE != null; + } catch (Throwable t) { + // CBORVarHandleUtil class not loadable — fall back to manual byte shifting + } + _VARHANDLE_AVAILABLE = available; + } + /* /********************************************************************** /* Configuration @@ -603,10 +618,15 @@ private final void _writeIntFull(int markerBase, int i) throws JacksonException _ensureRoomForOutput(5); _outputBuffer[_outputTail++] = (byte) (markerBase + SUFFIX_UINT32_ELEMENTS); - _outputBuffer[_outputTail++] = (byte) (i >> 24); - _outputBuffer[_outputTail++] = (byte) (i >> 16); - _outputBuffer[_outputTail++] = (byte) (i >> 8); - _outputBuffer[_outputTail++] = (byte) i; + if (_VARHANDLE_AVAILABLE) { + CBORVarHandleUtil.setInt(_outputBuffer, _outputTail, i); + _outputTail += 4; + } else { + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + } } // Helper method that works like `writeNumber(long)` but DOES NOT @@ -634,16 +654,21 @@ private final void _writeLongNoCheck(long l) throws JacksonException } else { _outputBuffer[_outputTail++] = (PREFIX_TYPE_INT_POS + SUFFIX_UINT64_ELEMENTS); } - int i = (int) (l >> 32); - _outputBuffer[_outputTail++] = (byte) (i >> 24); - _outputBuffer[_outputTail++] = (byte) (i >> 16); - _outputBuffer[_outputTail++] = (byte) (i >> 8); - _outputBuffer[_outputTail++] = (byte) i; - i = (int) l; - _outputBuffer[_outputTail++] = (byte) (i >> 24); - _outputBuffer[_outputTail++] = (byte) (i >> 16); - _outputBuffer[_outputTail++] = (byte) (i >> 8); - _outputBuffer[_outputTail++] = (byte) i; + if (_VARHANDLE_AVAILABLE) { + CBORVarHandleUtil.setLong(_outputBuffer, _outputTail, l); + _outputTail += 8; + } else { + int i = (int) (l >> 32); + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + i = (int) l; + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + } } private final void _writeFloatNoCheck(float f) throws JacksonException { @@ -654,33 +679,38 @@ private final void _writeFloatNoCheck(float f) throws JacksonException { * if there are cases where collapsing of NaN was needed (for non-Java * clients), this can be changed */ - int i = Float.floatToRawIntBits(f); _outputBuffer[_outputTail++] = BYTE_FLOAT32; - _outputBuffer[_outputTail++] = (byte) (i >> 24); - _outputBuffer[_outputTail++] = (byte) (i >> 16); - _outputBuffer[_outputTail++] = (byte) (i >> 8); - _outputBuffer[_outputTail++] = (byte) i; + if (_VARHANDLE_AVAILABLE) { + CBORVarHandleUtil.setFloat(_outputBuffer, _outputTail, f); + _outputTail += 4; + } else { + int i = Float.floatToRawIntBits(f); + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + } } private final void _writeDoubleNoCheck(double d) throws JacksonException { _ensureRoomForOutput(9); - // 17-Apr-2010, tatu: could also use 'doubleToIntBits', but it seems - // more accurate to use exact representation; and possibly faster. - // However, if there are cases where collapsing of NaN was needed (for - // non-Java clients), this can be changed - long l = Double.doubleToRawLongBits(d); _outputBuffer[_outputTail++] = BYTE_FLOAT64; - - int i = (int) (l >> 32); - _outputBuffer[_outputTail++] = (byte) (i >> 24); - _outputBuffer[_outputTail++] = (byte) (i >> 16); - _outputBuffer[_outputTail++] = (byte) (i >> 8); - _outputBuffer[_outputTail++] = (byte) i; - i = (int) l; - _outputBuffer[_outputTail++] = (byte) (i >> 24); - _outputBuffer[_outputTail++] = (byte) (i >> 16); - _outputBuffer[_outputTail++] = (byte) (i >> 8); - _outputBuffer[_outputTail++] = (byte) i; + if (_VARHANDLE_AVAILABLE) { + CBORVarHandleUtil.setDouble(_outputBuffer, _outputTail, d); + _outputTail += 8; + } else { + long l = Double.doubleToRawLongBits(d); + int i = (int) (l >> 32); + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + i = (int) l; + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + } } private final void _writeDoubleMinimal(double d) throws JacksonException { @@ -1024,14 +1054,19 @@ public void writeNumberUnsigned(long l) throws IOException { _verifyValueWrite("write number unsigned"); _ensureRoomForOutput(9); _outputBuffer[_outputTail++] = (byte) (PREFIX_TYPE_INT_POS + SUFFIX_UINT64_ELEMENTS); - _outputBuffer[_outputTail++] = (byte) (l >> 56); - _outputBuffer[_outputTail++] = (byte) (l >> 48); - _outputBuffer[_outputTail++] = (byte) (l >> 40); - _outputBuffer[_outputTail++] = (byte) (l >> 32); - _outputBuffer[_outputTail++] = (byte) (l >> 24); - _outputBuffer[_outputTail++] = (byte) (l >> 16); - _outputBuffer[_outputTail++] = (byte) (l >> 8); - _outputBuffer[_outputTail++] = (byte) l; + if (_VARHANDLE_AVAILABLE) { + CBORVarHandleUtil.setLong(_outputBuffer, _outputTail, l); + _outputTail += 8; + } else { + _outputBuffer[_outputTail++] = (byte) (l >> 56); + _outputBuffer[_outputTail++] = (byte) (l >> 48); + _outputBuffer[_outputTail++] = (byte) (l >> 40); + _outputBuffer[_outputTail++] = (byte) (l >> 32); + _outputBuffer[_outputTail++] = (byte) (l >> 24); + _outputBuffer[_outputTail++] = (byte) (l >> 16); + _outputBuffer[_outputTail++] = (byte) (l >> 8); + _outputBuffer[_outputTail++] = (byte) l; + } } @Override @@ -1058,16 +1093,21 @@ public JsonGenerator writeNumber(long l) throws JacksonException { } else { _outputBuffer[_outputTail++] = (PREFIX_TYPE_INT_POS + SUFFIX_UINT64_ELEMENTS); } - int i = (int) (l >> 32); - _outputBuffer[_outputTail++] = (byte) (i >> 24); - _outputBuffer[_outputTail++] = (byte) (i >> 16); - _outputBuffer[_outputTail++] = (byte) (i >> 8); - _outputBuffer[_outputTail++] = (byte) i; - i = (int) l; - _outputBuffer[_outputTail++] = (byte) (i >> 24); - _outputBuffer[_outputTail++] = (byte) (i >> 16); - _outputBuffer[_outputTail++] = (byte) (i >> 8); - _outputBuffer[_outputTail++] = (byte) i; + if (_VARHANDLE_AVAILABLE) { + CBORVarHandleUtil.setLong(_outputBuffer, _outputTail, l); + _outputTail += 8; + } else { + int i = (int) (l >> 32); + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + i = (int) l; + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + } return this; } diff --git a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java new file mode 100644 index 000000000..24e79dfe6 --- /dev/null +++ b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java @@ -0,0 +1,79 @@ +package tools.jackson.dataformat.cbor; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.ByteOrder; + +/** + * Utility class that provides {@link VarHandle} instances for efficient + * multi-byte primitive writes to byte arrays. Falls back gracefully + * if VarHandles are not available (e.g., on some Android runtimes). + * @since 3.3 + */ +final class CBORVarHandleUtil +{ + /** + * VarHandle for writing a {@code float} as 4 big-endian bytes. + * {@code null} if VarHandles are unavailable. + */ + static final VarHandle FLOAT_BE; + + /** + * VarHandle for writing a {@code double} as 8 big-endian bytes. + * {@code null} if VarHandles are unavailable. + */ + static final VarHandle DOUBLE_BE; + + /** + * VarHandle for writing an {@code int} as 4 big-endian bytes. + * {@code null} if VarHandles are unavailable. + */ + static final VarHandle INT_BE; + + /** + * VarHandle for writing a {@code long} as 8 big-endian bytes. + * {@code null} if VarHandles are unavailable. + */ + static final VarHandle LONG_BE; + + static { + VarHandle floatBe = null; + VarHandle doubleBe = null; + VarHandle intBe = null; + VarHandle longBe = null; + try { + floatBe = MethodHandles.byteArrayViewVarHandle(float[].class, ByteOrder.BIG_ENDIAN); + doubleBe = MethodHandles.byteArrayViewVarHandle(double[].class, ByteOrder.BIG_ENDIAN); + intBe = MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN); + longBe = MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); + } catch (Throwable t) { + // VarHandles not available (e.g., Android) — fall back to manual byte shifting + } + FLOAT_BE = floatBe; + DOUBLE_BE = doubleBe; + INT_BE = intBe; + LONG_BE = longBe; + } + + private CBORVarHandleUtil() { } + + // Helper methods that write primitives via the class's own VarHandle fields. + // Only called when the corresponding field is non-null, which implies + // VarHandle is available on this runtime. + + static void setInt(byte[] array, int offset, int value) { + INT_BE.set(array, offset, value); + } + + static void setLong(byte[] array, int offset, long value) { + LONG_BE.set(array, offset, value); + } + + static void setFloat(byte[] array, int offset, float value) { + FLOAT_BE.set(array, offset, value); + } + + static void setDouble(byte[] array, int offset, double value) { + DOUBLE_BE.set(array, offset, value); + } +}