diff --git a/avro/src/main/java/tools/jackson/dataformat/avro/deser/AvroVarHandleUtil.java b/avro/src/main/java/tools/jackson/dataformat/avro/deser/AvroVarHandleUtil.java
new file mode 100644
index 000000000..94b9f02bc
--- /dev/null
+++ b/avro/src/main/java/tools/jackson/dataformat/avro/deser/AvroVarHandleUtil.java
@@ -0,0 +1,86 @@
+package tools.jackson.dataformat.avro.deser;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.nio.ByteOrder;
+
+/**
+ * Utility class that provides {@link VarHandle}-based access for reading
+ * multi-byte primitives out of byte arrays.
+ *
+ * NOTE: handles here are LITTLE-endian, unlike the big-endian ones other
+ * Jackson binary backends need: Avro encodes {@code float} and {@code double}
+ * as little-endian IEEE-754.
+ *
+ * IMPORTANT: this class references {@link VarHandle} in field and method
+ * signatures, so on a runtime that does not provide {@code java.lang.invoke.VarHandle}
+ * at all it will fail to link, before any code here gets a chance to run.
+ * Callers MUST therefore both:
+ *
+ * - load this class from within a {@code try}/{@code catch (Throwable)} block,
+ * so that {@link LinkageError} is caught, and
+ * - keep the byte-shifting fallback in a class that does not reference
+ * {@link VarHandle}, so the fallback path never resolves this class.
+ *
+ * {@code JacksonAvroParserImpl} does both; see it for the pattern.
+ *
+ * @since 3.3
+ */
+final class AvroVarHandleUtil
+{
+ /**
+ * VarHandle for reading 4 little-endian bytes as an {@code int}.
+ * {@code null} if byte-array views are unsupported.
+ */
+ private static final VarHandle INT_LE;
+
+ /**
+ * VarHandle for reading 8 little-endian bytes as a {@code long}.
+ * {@code null} if byte-array views are unsupported.
+ */
+ private static final VarHandle LONG_LE;
+
+ static {
+ VarHandle intLe = null;
+ VarHandle longLe = null;
+ try {
+ intLe = MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN);
+ longLe = MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN);
+ } catch (Throwable t) {
+ // Byte-array views not supported: caller falls back to byte shifting
+ }
+ INT_LE = intLe;
+ // assigned last: non-null implies the handle above resolved too
+ LONG_LE = longLe;
+ }
+
+ private AvroVarHandleUtil() { }
+
+ /**
+ * @return {@code true} if the {@code getXxx()} methods may be called; if
+ * {@code false}, caller MUST use its own byte-shifting fallback
+ */
+ static boolean isAvailable() {
+ return LONG_LE != null;
+ }
+
+ // Helper methods that read primitives via the class's own VarHandle fields.
+ // Only called when {@link #isAvailable()} returned true; the handles are
+ // dereferenced unconditionally and the fallback lives in the caller.
+
+ /**
+ * Reads 4 bytes at given offset as a little-endian {@code int}; caller MUST
+ * have verified that {@code offset+4} is within bounds of given array.
+ */
+ static int getIntLE(byte[] array, int offset) {
+ return (int) INT_LE.get(array, offset);
+ }
+
+ /**
+ * Reads 8 bytes at given offset as a little-endian {@code long}; caller MUST
+ * have verified that {@code offset+8} is within bounds of given array.
+ */
+ static long getLongLE(byte[] array, int offset) {
+ return (long) LONG_LE.get(array, offset);
+ }
+}
diff --git a/avro/src/main/java/tools/jackson/dataformat/avro/deser/JacksonAvroParserImpl.java b/avro/src/main/java/tools/jackson/dataformat/avro/deser/JacksonAvroParserImpl.java
index 287df3b02..5f52d5dac 100644
--- a/avro/src/main/java/tools/jackson/dataformat/avro/deser/JacksonAvroParserImpl.java
+++ b/avro/src/main/java/tools/jackson/dataformat/avro/deser/JacksonAvroParserImpl.java
@@ -40,7 +40,31 @@ public class JacksonAvroParserImpl extends AvroParserImpl
}
sUtf8UnitLengths = table;
}
-
+
+ /**
+ * Whether VarHandles are usable on this runtime; probed once at class load.
+ * Being {@code static final} lets the branches in {@link #decodeFloat()} and
+ * {@link #decodeDouble()} fold away at JIT time.
+ *
+ * @since 3.3
+ */
+ private final static boolean _VARHANDLE_AVAILABLE = _checkVarHandleAvailable();
+
+ private static boolean _checkVarHandleAvailable() {
+ // NOTE: this call is what first loads `AvroVarHandleUtil`, and that class
+ // names `VarHandle` in its field/method signatures. On a runtime lacking
+ // `java.lang.invoke.VarHandle` (some Android builds) loading it raises
+ // `NoClassDefFoundError` -- an Error, not an Exception -- so `Throwable`
+ // is what has to be caught here. Without this guard the failure would
+ // propagate out of this class's initializer and make the parser unusable.
+ try {
+ return AvroVarHandleUtil.isAvailable();
+ } catch (Throwable t) {
+ return false;
+ }
+ }
+
+
/*
/**********************************************************************
/* Input source config
@@ -523,8 +547,14 @@ public JsonToken decodeFloat() throws IOException {
}
final byte[] buf = _inputBuffer;
_inputPtr = ptr+4;
- int i = (buf[ptr] & 0xff) | ((buf[ptr+1] & 0xff) << 8)
- | ((buf[ptr+2] & 0xff) << 16) | (buf[ptr+3] << 24);
+ // Avro encodes float as little-endian IEEE-754
+ final int i;
+ if (_VARHANDLE_AVAILABLE) {
+ i = AvroVarHandleUtil.getIntLE(buf, ptr);
+ } else {
+ i = (buf[ptr] & 0xff) | ((buf[ptr+1] & 0xff) << 8)
+ | ((buf[ptr+2] & 0xff) << 16) | (buf[ptr+3] << 24);
+ }
_numberFloat = Float.intBitsToFloat(i);
_numTypesValid = NR_FLOAT;
return JsonToken.VALUE_NUMBER_FLOAT;
@@ -543,15 +573,22 @@ public JsonToken decodeDouble() throws IOException {
ptr = _inputPtr;
}
final byte[] buf = _inputBuffer;
- int i = (buf[ptr] & 0xff) | ((buf[ptr+1] & 0xff) << 8)
- | ((buf[ptr+2] & 0xff) << 16) | (buf[ptr+3] << 24);
- ptr += 4;
- int i2 = (buf[ptr] & 0xff) | ((buf[ptr+1] & 0xff) << 8)
- | ((buf[ptr+2] & 0xff) << 16) | (buf[ptr+3] << 24);
-
+ // Avro encodes double as little-endian IEEE-754; the two 32-bit halves
+ // below combine to exactly a little-endian 8-byte read
+ final long l;
+ if (_VARHANDLE_AVAILABLE) {
+ l = AvroVarHandleUtil.getLongLE(buf, ptr);
+ ptr += 4;
+ } else {
+ int i = (buf[ptr] & 0xff) | ((buf[ptr+1] & 0xff) << 8)
+ | ((buf[ptr+2] & 0xff) << 16) | (buf[ptr+3] << 24);
+ ptr += 4;
+ int i2 = (buf[ptr] & 0xff) | ((buf[ptr+1] & 0xff) << 8)
+ | ((buf[ptr+2] & 0xff) << 16) | (buf[ptr+3] << 24);
+ l = (((long) i) & 0xffffffffL) | (((long) i2) << 32);
+ }
_inputPtr = ptr+4;
- _numberDouble = Double.longBitsToDouble((((long) i) & 0xffffffffL)
- | (((long) i2) << 32));
+ _numberDouble = Double.longBitsToDouble(l);
_numTypesValid = NR_DOUBLE;
return JsonToken.VALUE_NUMBER_FLOAT;
}