, String> methodInfo = methodMap.get(returnType);
+ MethodType factoryType;
+ if (methodInfo == null) {
+ methodInfo = Tuple2.of(Function.class, "apply");
+ factoryType = jdkFunctionMethodType;
+ } else {
+ factoryType = MethodType.methodType(returnType, Object.class);
+ }
+ try {
+ CallSite callSite =
+ LambdaMetafactory.metafactory(
+ lookup,
+ methodInfo.f1,
+ MethodType.methodType(methodInfo.f0),
+ factoryType,
+ handle,
+ handle.type());
+ // Can't use invokeExact, since we can't specify exact target type for return variable.
+ return callSite.getTarget().invoke();
+ } catch (ClassNotFoundException | NoClassDefFoundError e) {
+ // ToByteFunction/ToBoolFunction/.. are not defined in jdk, if the classloader of
+ // fory functions `ToByteFunction/..` isn't parent classloader of classloader for getter
+ // represented by handle, then exception will be thrown.
+ return makeGetterFunction(lookup, handle, Object.class);
+ } catch (Throwable e) {
+ throw ExceptionUtils.throwException(e);
+ }
+ }
+
+ private static volatile Method getModuleMethod;
+
+ public static Object getModule(Class> cls) {
+ Preconditions.checkArgument(JdkVersion.MAJOR_VERSION >= 9);
+ if (getModuleMethod == null) {
+ try {
+ getModuleMethod = Class.class.getDeclaredMethod("getModule");
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ try {
+ return getModuleMethod.invoke(cls);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw ExceptionUtils.throwException(e);
+ }
+ }
+
+ // caller sensitive, must use MethodHandle to walk around the check.
+ private static volatile MethodHandle addReadsHandle;
+
+ public static Object addReads(Object thisModule, Object otherModule) {
+ Preconditions.checkArgument(JdkVersion.MAJOR_VERSION >= 9);
+ try {
+ if (addReadsHandle == null) {
+ Class> cls = Class.forName("java.lang.Module");
+ MethodHandles.Lookup lookup = _JDKAccess._trustedLookup(cls);
+ addReadsHandle = lookup.findVirtual(cls, "addReads", MethodType.methodType(cls, cls));
+ }
+ return addReadsHandle.invoke(thisModule, otherModule);
+ } catch (Throwable e) {
+ throw ExceptionUtils.throwException(e);
+ }
+ }
+
+ public static Lookup privateLookupIn(Class> targetClass, Lookup caller) {
+ return _Lookup.privateLookupIn(targetClass, caller);
+ }
+}
diff --git a/java/fory-core/src/main/java/org/apache/fory/util/unsafe/_Lookup.java b/java/fory-core/src/main/java/org/apache/fory/platform/internal/_Lookup.java
similarity index 99%
rename from java/fory-core/src/main/java/org/apache/fory/util/unsafe/_Lookup.java
rename to java/fory-core/src/main/java/org/apache/fory/platform/internal/_Lookup.java
index b719ac0afb..2ece8bb9ce 100644
--- a/java/fory-core/src/main/java/org/apache/fory/util/unsafe/_Lookup.java
+++ b/java/fory-core/src/main/java/org/apache/fory/platform/internal/_Lookup.java
@@ -17,7 +17,7 @@
* under the License.
*/
-package org.apache.fory.util.unsafe;
+package org.apache.fory.platform.internal;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
diff --git a/java/fory-core/src/main/java/org/apache/fory/reflect/FieldAccessor.java b/java/fory-core/src/main/java/org/apache/fory/reflect/FieldAccessor.java
index 1690fc0918..5aa6ebee5a 100644
--- a/java/fory-core/src/main/java/org/apache/fory/reflect/FieldAccessor.java
+++ b/java/fory-core/src/main/java/org/apache/fory/reflect/FieldAccessor.java
@@ -24,6 +24,7 @@
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
@@ -36,7 +37,7 @@
import org.apache.fory.exception.ForyException;
import org.apache.fory.platform.AndroidSupport;
import org.apache.fory.platform.GraalvmSupport;
-import org.apache.fory.platform.UnsafeOps;
+import org.apache.fory.platform.internal._JDKAccess;
import org.apache.fory.type.TypeUtils;
import org.apache.fory.util.Preconditions;
import org.apache.fory.util.function.Functions;
@@ -45,33 +46,74 @@
import org.apache.fory.util.function.ToFloatFunction;
import org.apache.fory.util.function.ToShortFunction;
import org.apache.fory.util.record.RecordUtils;
-import org.apache.fory.util.unsafe._JDKAccess;
+import sun.misc.Unsafe;
-/**
- * Field accessor for primitive types and object types.
- *
- * Note for primitive types, there will be box/unbox overhead.
- */
+/** Field accessor for primitive types and object types. */
@SuppressWarnings({"unchecked", "rawtypes"})
public abstract class FieldAccessor {
+ private static final Unsafe UNSAFE = AndroidSupport.IS_ANDROID ? null : _JDKAccess.UNSAFE;
+ private static final int REFLECTIVE_ACCESS = 0;
+ private static final int BOOLEAN_ACCESS = 1;
+ private static final int BYTE_ACCESS = 2;
+ private static final int CHAR_ACCESS = 3;
+ private static final int SHORT_ACCESS = 4;
+ private static final int INT_ACCESS = 5;
+ private static final int LONG_ACCESS = 6;
+ private static final int FLOAT_ACCESS = 7;
+ private static final int DOUBLE_ACCESS = 8;
+ private static final int OBJECT_ACCESS = 9;
+
protected final Field field;
protected final long fieldOffset;
+ private final int accessKind;
public FieldAccessor(Field field) {
this.field = field;
Preconditions.checkNotNull(field);
- long fieldOffset;
- try {
- fieldOffset = ReflectionUtils.getFieldOffset(field);
- } catch (UnsupportedOperationException e) {
- fieldOffset = -1;
+ this.fieldOffset = fieldOffset(field);
+ this.accessKind = accessKind(field, fieldOffset);
+ }
+
+ private static long fieldOffset(Field field) {
+ if (AndroidSupport.IS_ANDROID) {
+ return -1;
}
- this.fieldOffset = fieldOffset;
+ if (GraalvmSupport.isGraalBuildTime()) {
+ // Field offsets are rewritten by GraalVM and are not stable during native-image build time.
+ return -1;
+ }
+ return UNSAFE.objectFieldOffset(field);
}
protected FieldAccessor(Field field, long fieldOffset) {
this.field = field;
this.fieldOffset = fieldOffset;
+ this.accessKind = accessKind(field, fieldOffset);
+ }
+
+ private static int accessKind(Field field, long fieldOffset) {
+ if (fieldOffset == -1) {
+ return REFLECTIVE_ACCESS;
+ }
+ Class> fieldType = field.getType();
+ if (fieldType == boolean.class) {
+ return BOOLEAN_ACCESS;
+ } else if (fieldType == byte.class) {
+ return BYTE_ACCESS;
+ } else if (fieldType == char.class) {
+ return CHAR_ACCESS;
+ } else if (fieldType == short.class) {
+ return SHORT_ACCESS;
+ } else if (fieldType == int.class) {
+ return INT_ACCESS;
+ } else if (fieldType == long.class) {
+ return LONG_ACCESS;
+ } else if (fieldType == float.class) {
+ return FLOAT_ACCESS;
+ } else if (fieldType == double.class) {
+ return DOUBLE_ACCESS;
+ }
+ return OBJECT_ACCESS;
}
public abstract Object get(Object obj);
@@ -80,35 +122,137 @@ public void set(Object obj, Object value) {
throw new UnsupportedOperationException("Unsupported for field " + field);
}
+ public final void copy(Object sourceObject, Object targetObject) {
+ switch (accessKind) {
+ case BOOLEAN_ACCESS:
+ UNSAFE.putBoolean(targetObject, fieldOffset, UNSAFE.getBoolean(sourceObject, fieldOffset));
+ return;
+ case BYTE_ACCESS:
+ UNSAFE.putByte(targetObject, fieldOffset, UNSAFE.getByte(sourceObject, fieldOffset));
+ return;
+ case CHAR_ACCESS:
+ UNSAFE.putChar(targetObject, fieldOffset, UNSAFE.getChar(sourceObject, fieldOffset));
+ return;
+ case SHORT_ACCESS:
+ UNSAFE.putShort(targetObject, fieldOffset, UNSAFE.getShort(sourceObject, fieldOffset));
+ return;
+ case INT_ACCESS:
+ UNSAFE.putInt(targetObject, fieldOffset, UNSAFE.getInt(sourceObject, fieldOffset));
+ return;
+ case LONG_ACCESS:
+ UNSAFE.putLong(targetObject, fieldOffset, UNSAFE.getLong(sourceObject, fieldOffset));
+ return;
+ case FLOAT_ACCESS:
+ UNSAFE.putFloat(targetObject, fieldOffset, UNSAFE.getFloat(sourceObject, fieldOffset));
+ return;
+ case DOUBLE_ACCESS:
+ UNSAFE.putDouble(targetObject, fieldOffset, UNSAFE.getDouble(sourceObject, fieldOffset));
+ return;
+ case OBJECT_ACCESS:
+ UNSAFE.putObject(targetObject, fieldOffset, UNSAFE.getObject(sourceObject, fieldOffset));
+ return;
+ default:
+ putObject(targetObject, getObject(sourceObject));
+ }
+ }
+
+ public final void copyObject(Object sourceObject, Object targetObject) {
+ if (accessKind == OBJECT_ACCESS) {
+ UNSAFE.putObject(targetObject, fieldOffset, UNSAFE.getObject(sourceObject, fieldOffset));
+ } else {
+ putObject(targetObject, getObject(sourceObject));
+ }
+ }
+
public Field getField() {
return field;
}
+ public boolean getBoolean(Object targetObject) {
+ return (Boolean) get(targetObject);
+ }
+
+ public void putBoolean(Object targetObject, boolean value) {
+ set(targetObject, value);
+ }
+
+ public byte getByte(Object targetObject) {
+ return (Byte) get(targetObject);
+ }
+
+ public void putByte(Object targetObject, byte value) {
+ set(targetObject, value);
+ }
+
+ public char getChar(Object targetObject) {
+ return (Character) get(targetObject);
+ }
+
+ public void putChar(Object targetObject, char value) {
+ set(targetObject, value);
+ }
+
+ public short getShort(Object targetObject) {
+ return (Short) get(targetObject);
+ }
+
+ public void putShort(Object targetObject, short value) {
+ set(targetObject, value);
+ }
+
+ public int getInt(Object targetObject) {
+ return (Integer) get(targetObject);
+ }
+
+ public void putInt(Object targetObject, int value) {
+ set(targetObject, value);
+ }
+
+ public long getLong(Object targetObject) {
+ return (Long) get(targetObject);
+ }
+
+ public void putLong(Object targetObject, long value) {
+ set(targetObject, value);
+ }
+
+ public float getFloat(Object targetObject) {
+ return (Float) get(targetObject);
+ }
+
+ public void putFloat(Object targetObject, float value) {
+ set(targetObject, value);
+ }
+
+ public double getDouble(Object targetObject) {
+ return (Double) get(targetObject);
+ }
+
+ public void putDouble(Object targetObject, double value) {
+ set(targetObject, value);
+ }
+
public final void putObject(Object targetObject, Object object) {
- // For primitive fields, we must use set() which calls the correct UnsafeOps.putXxx method.
- // UnsafeOps.putObject writes object references, not primitive values.
+ // For primitive fields, we must use set() which calls the correct UNSAFE.putXxx method.
+ // UNSAFE.putObject writes object references, not primitive values.
if (fieldOffset != -1 && !field.getType().isPrimitive()) {
- UnsafeOps.putObject(targetObject, fieldOffset, object);
+ UNSAFE.putObject(targetObject, fieldOffset, object);
} else {
set(targetObject, object);
}
}
public final Object getObject(Object targetObject) {
- // For primitive fields, we must use get() which calls the correct UnsafeOps.getXxx method
- // and returns the boxed value. UnsafeOps.getObject interprets primitive bytes as object
+ // For primitive fields, we must use get() which calls the correct UNSAFE.getXxx method
+ // and returns the boxed value. UNSAFE.getObject interprets primitive bytes as object
// refs.
if (fieldOffset != -1 && !field.getType().isPrimitive()) {
- return UnsafeOps.getObject(targetObject, fieldOffset);
+ return UNSAFE.getObject(targetObject, fieldOffset);
} else {
return get(targetObject);
}
}
- public long getFieldOffset() {
- return fieldOffset;
- }
-
void checkObj(Object obj) {
if (!this.field.getDeclaringClass().isAssignableFrom(obj.getClass())) {
throw new IllegalArgumentException("Illegal class " + obj.getClass());
@@ -135,6 +279,7 @@ public Object getGetter() {
}
public static FieldAccessor createAccessor(Field field) {
+ Preconditions.checkArgument(!Modifier.isStatic(field.getModifiers()), field);
if (RecordUtils.isRecord(field.getDeclaringClass())) {
if (AndroidSupport.IS_ANDROID || GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) {
return new ReflectiveRecordFieldAccessor(field);
@@ -195,6 +340,15 @@ public static FieldAccessor createAccessor(Field field) {
}
}
+ public static FieldAccessor createStaticAccessor(Field field) {
+ Preconditions.checkArgument(Modifier.isStatic(field.getModifiers()), field);
+ if (AndroidSupport.IS_ANDROID) {
+ field.setAccessible(true);
+ return new ReflectiveStaticFieldAccessor(field);
+ }
+ return new StaticObjectAccessor(field);
+ }
+
static final class ReflectiveRecordFieldAccessor extends FieldGetter {
private final Method accessor;
@@ -236,14 +390,24 @@ public BooleanAccessor(Field field) {
@Override
public Object get(Object obj) {
+ return getBoolean(obj);
+ }
+
+ @Override
+ public boolean getBoolean(Object obj) {
checkObj(obj);
- return UnsafeOps.getBoolean(obj, fieldOffset);
+ return UNSAFE.getBoolean(obj, fieldOffset);
}
@Override
public void set(Object obj, Object value) {
+ putBoolean(obj, (Boolean) value);
+ }
+
+ @Override
+ public void putBoolean(Object obj, boolean value) {
checkObj(obj);
- UnsafeOps.putBoolean(obj, fieldOffset, (Boolean) value);
+ UNSAFE.putBoolean(obj, fieldOffset, value);
}
}
@@ -258,6 +422,11 @@ public BooleanGetter(Field field, Predicate getter) {
@Override
public Boolean get(Object obj) {
+ return getBoolean(obj);
+ }
+
+ @Override
+ public boolean getBoolean(Object obj) {
checkObj(obj);
return getter.test(obj);
}
@@ -272,14 +441,24 @@ public ByteAccessor(Field field) {
@Override
public Byte get(Object obj) {
+ return getByte(obj);
+ }
+
+ @Override
+ public byte getByte(Object obj) {
checkObj(obj);
- return UnsafeOps.getByte(obj, fieldOffset);
+ return UNSAFE.getByte(obj, fieldOffset);
}
@Override
public void set(Object obj, Object value) {
+ putByte(obj, (Byte) value);
+ }
+
+ @Override
+ public void putByte(Object obj, byte value) {
checkObj(obj);
- UnsafeOps.putByte(obj, fieldOffset, (Byte) value);
+ UNSAFE.putByte(obj, fieldOffset, value);
}
}
@@ -295,6 +474,11 @@ public ByteGetter(Field field, ToByteFunction getter) {
@Override
public Byte get(Object obj) {
+ return getByte(obj);
+ }
+
+ @Override
+ public byte getByte(Object obj) {
return getter.applyAsByte(obj);
}
}
@@ -308,14 +492,24 @@ public CharAccessor(Field field) {
@Override
public Character get(Object obj) {
+ return getChar(obj);
+ }
+
+ @Override
+ public char getChar(Object obj) {
checkObj(obj);
- return UnsafeOps.getChar(obj, fieldOffset);
+ return UNSAFE.getChar(obj, fieldOffset);
}
@Override
public void set(Object obj, Object value) {
+ putChar(obj, (Character) value);
+ }
+
+ @Override
+ public void putChar(Object obj, char value) {
checkObj(obj);
- UnsafeOps.putChar(obj, fieldOffset, (Character) value);
+ UNSAFE.putChar(obj, fieldOffset, value);
}
}
@@ -330,6 +524,11 @@ public CharGetter(Field field, ToCharFunction getter) {
@Override
public Character get(Object obj) {
+ return getChar(obj);
+ }
+
+ @Override
+ public char getChar(Object obj) {
return getter.applyAsChar(obj);
}
}
@@ -343,14 +542,24 @@ public ShortAccessor(Field field) {
@Override
public Short get(Object obj) {
+ return getShort(obj);
+ }
+
+ @Override
+ public short getShort(Object obj) {
checkObj(obj);
- return UnsafeOps.getShort(obj, fieldOffset);
+ return UNSAFE.getShort(obj, fieldOffset);
}
@Override
public void set(Object obj, Object value) {
+ putShort(obj, (Short) value);
+ }
+
+ @Override
+ public void putShort(Object obj, short value) {
checkObj(obj);
- UnsafeOps.putShort(obj, fieldOffset, (Short) value);
+ UNSAFE.putShort(obj, fieldOffset, value);
}
}
@@ -365,6 +574,11 @@ public ShortGetter(Field field, ToShortFunction getter) {
@Override
public Short get(Object obj) {
+ return getShort(obj);
+ }
+
+ @Override
+ public short getShort(Object obj) {
return getter.applyAsShort(obj);
}
}
@@ -378,14 +592,24 @@ public IntAccessor(Field field) {
@Override
public Integer get(Object obj) {
+ return getInt(obj);
+ }
+
+ @Override
+ public int getInt(Object obj) {
checkObj(obj);
- return UnsafeOps.getInt(obj, fieldOffset);
+ return UNSAFE.getInt(obj, fieldOffset);
}
@Override
public void set(Object obj, Object value) {
+ putInt(obj, (Integer) value);
+ }
+
+ @Override
+ public void putInt(Object obj, int value) {
checkObj(obj);
- UnsafeOps.putInt(obj, fieldOffset, (Integer) value);
+ UNSAFE.putInt(obj, fieldOffset, value);
}
}
@@ -400,6 +624,11 @@ public IntGetter(Field field, ToIntFunction getter) {
@Override
public Integer get(Object obj) {
+ return getInt(obj);
+ }
+
+ @Override
+ public int getInt(Object obj) {
return getter.applyAsInt(obj);
}
}
@@ -413,14 +642,24 @@ public LongAccessor(Field field) {
@Override
public Long get(Object obj) {
+ return getLong(obj);
+ }
+
+ @Override
+ public long getLong(Object obj) {
checkObj(obj);
- return UnsafeOps.getLong(obj, fieldOffset);
+ return UNSAFE.getLong(obj, fieldOffset);
}
@Override
public void set(Object obj, Object value) {
+ putLong(obj, (Long) value);
+ }
+
+ @Override
+ public void putLong(Object obj, long value) {
checkObj(obj);
- UnsafeOps.putLong(obj, fieldOffset, (Long) value);
+ UNSAFE.putLong(obj, fieldOffset, value);
}
}
@@ -435,6 +674,11 @@ public LongGetter(Field field, ToLongFunction getter) {
@Override
public Long get(Object obj) {
+ return getLong(obj);
+ }
+
+ @Override
+ public long getLong(Object obj) {
return getter.applyAsLong(obj);
}
}
@@ -448,14 +692,24 @@ public FloatAccessor(Field field) {
@Override
public Object get(Object obj) {
+ return getFloat(obj);
+ }
+
+ @Override
+ public float getFloat(Object obj) {
checkObj(obj);
- return UnsafeOps.getFloat(obj, fieldOffset);
+ return UNSAFE.getFloat(obj, fieldOffset);
}
@Override
public void set(Object obj, Object value) {
+ putFloat(obj, (Float) value);
+ }
+
+ @Override
+ public void putFloat(Object obj, float value) {
checkObj(obj);
- UnsafeOps.putFloat(obj, fieldOffset, (Float) value);
+ UNSAFE.putFloat(obj, fieldOffset, value);
}
}
@@ -470,6 +724,11 @@ public FloatGetter(Field field, ToFloatFunction getter) {
@Override
public Float get(Object obj) {
+ return getFloat(obj);
+ }
+
+ @Override
+ public float getFloat(Object obj) {
return getter.applyAsFloat(obj);
}
}
@@ -483,14 +742,24 @@ public DoubleAccessor(Field field) {
@Override
public Object get(Object obj) {
+ return getDouble(obj);
+ }
+
+ @Override
+ public double getDouble(Object obj) {
checkObj(obj);
- return UnsafeOps.getDouble(obj, fieldOffset);
+ return UNSAFE.getDouble(obj, fieldOffset);
}
@Override
public void set(Object obj, Object value) {
+ putDouble(obj, (Double) value);
+ }
+
+ @Override
+ public void putDouble(Object obj, double value) {
checkObj(obj);
- UnsafeOps.putDouble(obj, fieldOffset, (Double) value);
+ UNSAFE.putDouble(obj, fieldOffset, value);
}
}
@@ -505,6 +774,11 @@ public DoubleGetter(Field field, ToDoubleFunction getter) {
@Override
public Double get(Object obj) {
+ return getDouble(obj);
+ }
+
+ @Override
+ public double getDouble(Object obj) {
return getter.applyAsDouble(obj);
}
}
@@ -519,13 +793,13 @@ public ObjectAccessor(Field field) {
@Override
public Object get(Object obj) {
checkObj(obj);
- return UnsafeOps.getObject(obj, fieldOffset);
+ return UNSAFE.getObject(obj, fieldOffset);
}
@Override
public void set(Object obj, Object value) {
checkObj(obj);
- UnsafeOps.putObject(obj, fieldOffset, value);
+ UNSAFE.putObject(obj, fieldOffset, value);
}
}
@@ -544,6 +818,52 @@ public Object get(Object obj) {
}
}
+ static final class ReflectiveStaticFieldAccessor extends FieldAccessor {
+ ReflectiveStaticFieldAccessor(Field field) {
+ super(field, -1);
+ }
+
+ @Override
+ public Object get(Object obj) {
+ try {
+ return field.get(null);
+ } catch (IllegalAccessException | IllegalArgumentException e) {
+ throw new ForyException("Failed to read static field reflectively: " + field, e);
+ }
+ }
+
+ @Override
+ public void set(Object obj, Object value) {
+ try {
+ field.set(null, value);
+ } catch (IllegalAccessException | IllegalArgumentException e) {
+ throw new ForyException("Failed to write static field reflectively: " + field, e);
+ }
+ }
+ }
+
+ static final class StaticObjectAccessor extends FieldAccessor {
+ private final Object base;
+ private final long offset;
+
+ StaticObjectAccessor(Field field) {
+ super(field, -1);
+ Preconditions.checkArgument(!TypeUtils.isPrimitive(field.getType()));
+ base = UNSAFE.staticFieldBase(field);
+ offset = UNSAFE.staticFieldOffset(field);
+ }
+
+ @Override
+ public Object get(Object obj) {
+ return UNSAFE.getObject(base, offset);
+ }
+
+ @Override
+ public void set(Object obj, Object value) {
+ UNSAFE.putObject(base, offset, value);
+ }
+ }
+
static final class GeneratedAccessor extends FieldAccessor {
private static final ClassValueCache>>
cache = ClassValueCache.newClassKeyCache(8);
@@ -594,5 +914,149 @@ public void set(Object obj, Object value) {
throw new RuntimeException(e);
}
}
+
+ @Override
+ public boolean getBoolean(Object obj) {
+ try {
+ return (boolean) getter.invoke(obj);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void putBoolean(Object obj, boolean value) {
+ try {
+ setter.invoke(obj, value);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public byte getByte(Object obj) {
+ try {
+ return (byte) getter.invoke(obj);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void putByte(Object obj, byte value) {
+ try {
+ setter.invoke(obj, value);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public char getChar(Object obj) {
+ try {
+ return (char) getter.invoke(obj);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void putChar(Object obj, char value) {
+ try {
+ setter.invoke(obj, value);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public short getShort(Object obj) {
+ try {
+ return (short) getter.invoke(obj);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void putShort(Object obj, short value) {
+ try {
+ setter.invoke(obj, value);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public int getInt(Object obj) {
+ try {
+ return (int) getter.invoke(obj);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void putInt(Object obj, int value) {
+ try {
+ setter.invoke(obj, value);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public long getLong(Object obj) {
+ try {
+ return (long) getter.invoke(obj);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void putLong(Object obj, long value) {
+ try {
+ setter.invoke(obj, value);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public float getFloat(Object obj) {
+ try {
+ return (float) getter.invoke(obj);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void putFloat(Object obj, float value) {
+ try {
+ setter.invoke(obj, value);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public double getDouble(Object obj) {
+ try {
+ return (double) getter.invoke(obj);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void putDouble(Object obj, double value) {
+ try {
+ setter.invoke(obj, value);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
}
}
diff --git a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreator.java b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreator.java
index cc6d0c7d70..1134c21896 100644
--- a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreator.java
+++ b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreator.java
@@ -31,12 +31,16 @@
*
* Thread Safety: All implementations of ObjectCreator are thread-safe and can
* be safely used across multiple threads concurrently. The underlying creation mechanisms
- * (MethodHandle, Constructor, UnsafeOps.newInstance) are all thread-safe.
+ * (MethodHandle, Constructor, and supported constructor-bypassing allocation) are all thread-safe.
*
* @param the type of objects this creator can instantiate
*/
@ThreadSafe
public abstract class ObjectCreator {
+ private static final String[] NO_FIELDS = new String[0];
+ private static final Class>[] NO_TYPES = new Class>[0];
+ private static final boolean[] NO_FINAL_FIELDS = new boolean[0];
+
protected final Class type;
protected ObjectCreator(Class type) {
@@ -52,6 +56,34 @@ protected ObjectCreator(Class type) {
*/
public abstract T newInstance();
+ public boolean hasConstructorFields() {
+ return false;
+ }
+
+ public String[] getConstructorFieldNames() {
+ return NO_FIELDS;
+ }
+
+ public Class>[] getConstructorFieldDeclaringClasses() {
+ return null;
+ }
+
+ public Class>[] getConstructorFieldTypes() {
+ return NO_TYPES;
+ }
+
+ public boolean[] getConstructorFieldFinal() {
+ return NO_FINAL_FIELDS;
+ }
+
+ public boolean isConstructorPublic() {
+ return false;
+ }
+
+ public boolean isOnlyPublicConstructor() {
+ return false;
+ }
+
/**
* Creates a new instance of type T using the provided arguments.
*
diff --git a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java
index 00fc668539..d365c4e578 100644
--- a/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java
+++ b/java/fory-core/src/main/java/org/apache/fory/reflect/ObjectCreators.java
@@ -19,19 +19,32 @@
package org.apache.fory.reflect;
+import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Parameter;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.fory.annotation.ForyField;
import org.apache.fory.collection.ClassValueCache;
import org.apache.fory.collection.Tuple2;
import org.apache.fory.exception.ForyException;
import org.apache.fory.platform.AndroidSupport;
import org.apache.fory.platform.GraalvmSupport;
import org.apache.fory.platform.JdkVersion;
-import org.apache.fory.platform.UnsafeOps;
+import org.apache.fory.platform.internal._JDKAccess;
+import org.apache.fory.type.Descriptor;
+import org.apache.fory.type.TypeUtils;
import org.apache.fory.util.record.RecordUtils;
-import org.apache.fory.util.unsafe._JDKAccess;
+import sun.misc.Unsafe;
/**
* Factory class for creating and caching {@link ObjectCreator} instances.
@@ -45,8 +58,8 @@
* parameterized constructor invocation
* Classes with no-arg constructors: Uses {@link
* DeclaredNoArgCtrObjectCreator} with MethodHandle for fast invocation
- * Classes without accessible constructors: Uses {@link UnsafeObjectCreator}
- * with platform-specific unsafe allocation
+ * Classes without accessible constructors: Uses a private
+ * constructor-bypassing creator on runtimes where that is still supported
* GraalVM native image compatibility: Uses {@link
* ParentNoArgCtrObjectCreator} for constructor generate-based creation when needed
* Android compatibility: Uses reflection for records and no-arg
@@ -61,6 +74,7 @@
*/
@SuppressWarnings("unchecked")
public class ObjectCreators {
+ private static final Unsafe UNSAFE = AndroidSupport.IS_ANDROID ? null : _JDKAccess.UNSAFE;
private static final ClassValueCache> cache =
ClassValueCache.newClassKeySoftCache(8);
@@ -81,6 +95,18 @@ public static ObjectCreator getObjectCreator(Class type) {
return (ObjectCreator) cache.get(type, () -> creategetObjectCreator(type));
}
+ private static T allocateInstance(Class type) {
+ if (UNSAFE == null || JdkVersion.MAJOR_VERSION >= 25) {
+ throw new ForyException(
+ "Constructor-bypassing allocation is unsupported in this runtime for " + type);
+ }
+ try {
+ return (T) UNSAFE.allocateInstance(type);
+ } catch (InstantiationException e) {
+ throw new ForyException("Failed to allocate instance for " + type, e);
+ }
+ }
+
private static ObjectCreator creategetObjectCreator(Class type) {
if (RecordUtils.isRecord(type)) {
return new RecordObjectCreator<>(type);
@@ -90,7 +116,11 @@ private static ObjectCreator creategetObjectCreator(Class type) {
if (noArgConstructor != null) {
return new ReflectiveNoArgCtrObjectCreator<>(type, noArgConstructor);
}
- return new UnsupportedObjectCreator<>(type);
+ return new UnsupportedObjectCreator<>(
+ type, "Android cannot create " + type + " without an accessible no-arg constructor");
+ }
+ if (JdkVersion.MAJOR_VERSION >= 25 && noArgConstructor == null) {
+ return new ConstructorObjectCreator<>(type);
}
if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) {
if (noArgConstructor != null) {
@@ -105,6 +135,278 @@ private static ObjectCreator creategetObjectCreator(Class type) {
return new DeclaredNoArgCtrObjectCreator<>(type);
}
+ public static boolean supportsJdk25Creation(Class> type) {
+ if (JdkVersion.MAJOR_VERSION < 25 || RecordUtils.isRecord(type)) {
+ return true;
+ }
+ try {
+ ObjectCreator> creator = creategetObjectCreator(type);
+ return !(creator instanceof UnsupportedObjectCreator);
+ } catch (RuntimeException e) {
+ return false;
+ }
+ }
+
+ private static final class ConstructorMatch {
+ private final Constructor constructor;
+ private final String[] fieldNames;
+ private final Class>[] declaringClasses;
+ private final Class>[] fieldTypes;
+ private final boolean[] finalFields;
+ private final int score;
+
+ private ConstructorMatch(
+ Constructor constructor,
+ String[] fieldNames,
+ Class>[] declaringClasses,
+ Class>[] fieldTypes,
+ boolean[] finalFields,
+ int score) {
+ this.constructor = constructor;
+ this.fieldNames = fieldNames;
+ this.declaringClasses = declaringClasses;
+ this.fieldTypes = fieldTypes;
+ this.finalFields = finalFields;
+ this.score = score;
+ }
+ }
+
+ private static ConstructorMatch findConstructor(Class type) {
+ List fields = new ArrayList<>();
+ fields.addAll(Descriptor.getFields(type));
+ Map fieldsByName = new LinkedHashMap<>();
+ Map> fieldsByNameList = new LinkedHashMap<>();
+ Map fieldsById = new LinkedHashMap<>();
+ Set duplicateNames = new LinkedHashSet<>();
+ Set duplicateIds = new LinkedHashSet<>();
+ for (Field field : fields) {
+ fieldsByNameList.computeIfAbsent(field.getName(), name -> new ArrayList<>()).add(field);
+ Field previous = fieldsByName.put(field.getName(), field);
+ if (previous != null) {
+ duplicateNames.add(field.getName());
+ }
+ ForyField foryField = field.getAnnotation(ForyField.class);
+ if (foryField != null && foryField.id() >= 0) {
+ previous = fieldsById.put(foryField.id(), field);
+ if (previous != null) {
+ duplicateIds.add(foryField.id());
+ }
+ }
+ }
+ ConstructorMatch best = null;
+ for (Constructor> constructor : type.getDeclaredConstructors()) {
+ if (constructor.isSynthetic()) {
+ continue;
+ }
+ ConstructorMatch match =
+ matchConstructor(
+ type,
+ (Constructor) constructor,
+ fieldsByName,
+ fieldsByNameList,
+ fieldsById,
+ duplicateNames,
+ duplicateIds);
+ if (match != null && (best == null || match.score > best.score)) {
+ best = match;
+ }
+ }
+ if (best == null) {
+ throw new ForyException(
+ "JDK25 zero-Unsafe mode requires "
+ + "a bindable constructor because no no-arg constructor is available"
+ + " for "
+ + type
+ + ". Annotate the constructor with java.beans.ConstructorProperties or compile "
+ + "the class with -parameters.");
+ }
+ return best;
+ }
+
+ private static ConstructorMatch matchConstructor(
+ Class type,
+ Constructor constructor,
+ Map fieldsByName,
+ Map> fieldsByNameList,
+ Map fieldsById,
+ Set duplicateNames,
+ Set duplicateIds) {
+ Field[] fields =
+ constructorFields(
+ constructor, fieldsByName, fieldsByNameList, fieldsById, duplicateNames, duplicateIds);
+ if (fields == null) {
+ return null;
+ }
+ return matchConstructorFields(constructor, fields);
+ }
+
+ private static ConstructorMatch matchConstructorFields(
+ Constructor constructor, Field[] fields) {
+ Class>[] parameterTypes = constructor.getParameterTypes();
+ String[] names = new String[fields.length];
+ Class>[] declaringClasses = new Class>[fields.length];
+ Class>[] fieldTypes = new Class>[fields.length];
+ boolean[] finalFieldFlags = new boolean[fields.length];
+ for (int i = 0; i < fields.length; i++) {
+ Field field = fields[i];
+ if (!constructorTypeMatches(parameterTypes[i], field)) {
+ return null;
+ }
+ names[i] = field.getName();
+ declaringClasses[i] = field.getDeclaringClass();
+ fieldTypes[i] = field.getType();
+ finalFieldFlags[i] = Modifier.isFinal(field.getModifiers());
+ }
+ return new ConstructorMatch<>(
+ constructor, names, declaringClasses, fieldTypes, finalFieldFlags, 300 - fields.length);
+ }
+
+ private static Field[] constructorFields(
+ Constructor> constructor,
+ Map fieldsByName,
+ Map> fieldsByNameList,
+ Map fieldsById,
+ Set duplicateNames,
+ Set duplicateIds) {
+ Field[] fields = fieldsByForyFieldId(constructor, fieldsById, duplicateIds);
+ if (fields != null) {
+ return fields;
+ }
+ String[] names = constructorFieldNames(constructor);
+ if (names != null) {
+ if (names.length != constructor.getParameterCount()) {
+ return null;
+ }
+ return fieldsByName(constructor, fieldsByName, fieldsByNameList, duplicateNames, names);
+ }
+ return null;
+ }
+
+ private static Field[] fieldsByForyFieldId(
+ Constructor> constructor, Map fieldsById, Set duplicateIds) {
+ Parameter[] parameters = constructor.getParameters();
+ Field[] fields = new Field[parameters.length];
+ boolean hasForyFieldId = false;
+ for (int i = 0; i < parameters.length; i++) {
+ ForyField foryField = parameters[i].getAnnotation(ForyField.class);
+ if (foryField == null || foryField.id() < 0) {
+ continue;
+ }
+ hasForyFieldId = true;
+ int id = foryField.id();
+ if (duplicateIds.contains(id)) {
+ throw new ForyException("Constructor parameter id " + id + " is ambiguous");
+ }
+ fields[i] = fieldsById.get(id);
+ if (fields[i] == null) {
+ return null;
+ }
+ }
+ if (!hasForyFieldId) {
+ return null;
+ }
+ for (Field field : fields) {
+ if (field == null) {
+ return null;
+ }
+ }
+ return fields;
+ }
+
+ private static Field[] fieldsByName(
+ Constructor> constructor,
+ Map fieldsByName,
+ Map> fieldsByNameList,
+ Set duplicateNames,
+ String[] names) {
+ Field[] fields = new Field[names.length];
+ for (int i = 0; i < names.length; i++) {
+ String name = names[i];
+ if (duplicateNames.contains(name)) {
+ Field field = syntheticField(constructor, fieldsByNameList.get(name));
+ if (field == null) {
+ throw new ForyException(
+ "Constructor parameter "
+ + name
+ + " is ambiguous because "
+ + constructor.getDeclaringClass()
+ + " has duplicate field names");
+ }
+ fields[i] = field;
+ continue;
+ }
+ Field field = fieldsByName.get(name);
+ if (field == null) {
+ return null;
+ }
+ fields[i] = field;
+ }
+ return fields;
+ }
+
+ private static Field syntheticField(Constructor> constructor, List fields) {
+ if (fields == null) {
+ return null;
+ }
+ Class> declaringClass = constructor.getDeclaringClass();
+ for (Field field : fields) {
+ if (field.isSynthetic() && field.getDeclaringClass() == declaringClass) {
+ return field;
+ }
+ }
+ return null;
+ }
+
+ private static boolean constructorTypeMatches(Class> parameterType, Field field) {
+ Class> boxedParameterType = TypeUtils.boxedType(parameterType);
+ Class> boxedFieldType = TypeUtils.boxedType(field.getType());
+ return boxedParameterType.isAssignableFrom(boxedFieldType);
+ }
+
+ private static String[] constructorFieldNames(Constructor> constructor) {
+ String[] names = constructorProperties(constructor);
+ if (names != null) {
+ return names;
+ }
+ Parameter[] parameters = constructor.getParameters();
+ for (Parameter parameter : parameters) {
+ if (!parameter.isNamePresent()) {
+ return null;
+ }
+ }
+ names = new String[parameters.length];
+ for (int i = 0; i < parameters.length; i++) {
+ names[i] = parameters[i].getName();
+ }
+ return names;
+ }
+
+ private static String[] constructorProperties(Constructor> constructor) {
+ for (Annotation annotation : constructor.getDeclaredAnnotations()) {
+ if ("java.beans.ConstructorProperties".equals(annotation.annotationType().getName())) {
+ try {
+ return (String[]) annotation.annotationType().getMethod("value").invoke(annotation);
+ } catch (ReflectiveOperationException e) {
+ throw new ForyException("Failed to read ConstructorProperties for " + constructor, e);
+ }
+ }
+ }
+ return null;
+ }
+
+ private static MethodHandle constructorHandle(Class> type, Constructor> constructor) {
+ Lookup lookup = _JDKAccess._trustedLookup(type);
+ if (lookup == null) {
+ return null;
+ }
+ try {
+ return lookup.findConstructor(
+ type, MethodType.methodType(void.class, constructor.getParameterTypes()));
+ } catch (NoSuchMethodException | IllegalAccessException e) {
+ return null;
+ }
+ }
+
private static final class ReflectiveNoArgCtrObjectCreator extends ObjectCreator {
private final Constructor constructor;
@@ -134,24 +436,113 @@ public T newInstanceWithArguments(Object... arguments) {
}
private static final class UnsupportedObjectCreator extends ObjectCreator {
- private UnsupportedObjectCreator(Class type) {
+ private final String message;
+
+ private UnsupportedObjectCreator(Class type, String message) {
super(type);
+ this.message = message;
}
@Override
public T newInstance() {
- throw new ForyException(
- "Android cannot create " + type + " without an accessible no-arg constructor");
+ throw new ForyException(message);
}
@Override
public T newInstanceWithArguments(Object... arguments) {
+ throw new ForyException(message);
+ }
+ }
+
+ public static final class ConstructorObjectCreator extends ObjectCreator {
+ private final Constructor constructor;
+ private final MethodHandle handle;
+ private final String[] fieldNames;
+ private final Class>[] declaringClasses;
+ private final Class>[] fieldTypes;
+ private final boolean[] finalFields;
+
+ private ConstructorObjectCreator(Class type) {
+ super(type);
+ ConstructorMatch match = findConstructor(type);
+ constructor = match.constructor;
+ handle = constructorHandle(type, constructor);
+ fieldNames = match.fieldNames;
+ declaringClasses = match.declaringClasses;
+ fieldTypes = match.fieldTypes;
+ finalFields = match.finalFields;
+ try {
+ constructor.setAccessible(true);
+ } catch (RuntimeException e) {
+ throw new ForyException("Failed to make constructor accessible for " + type, e);
+ }
+ }
+
+ @Override
+ public boolean hasConstructorFields() {
+ return true;
+ }
+
+ @Override
+ public String[] getConstructorFieldNames() {
+ return fieldNames;
+ }
+
+ @Override
+ public Class>[] getConstructorFieldDeclaringClasses() {
+ return declaringClasses;
+ }
+
+ @Override
+ public Class>[] getConstructorFieldTypes() {
+ return fieldTypes;
+ }
+
+ @Override
+ public boolean[] getConstructorFieldFinal() {
+ return finalFields;
+ }
+
+ @Override
+ public boolean isConstructorPublic() {
+ return Modifier.isPublic(type.getModifiers())
+ && Modifier.isPublic(constructor.getModifiers());
+ }
+
+ @Override
+ public boolean isOnlyPublicConstructor() {
+ if (!isConstructorPublic()) {
+ return false;
+ }
+ for (Constructor> declaredConstructor : type.getDeclaredConstructors()) {
+ if (Modifier.isPublic(declaredConstructor.getModifiers())
+ && declaredConstructor != constructor) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public T newInstance() {
throw new ForyException(
- "Android cannot create " + type + " without a supported constructor path");
+ "JDK25 zero-Unsafe mode requires constructor field values to create " + type);
+ }
+
+ @Override
+ public T newInstanceWithArguments(Object... arguments) {
+ try {
+ if (handle == null) {
+ return constructor.newInstance(arguments);
+ }
+ return (T) handle.invokeWithArguments(arguments);
+ } catch (Throwable e) {
+ throw new ForyException("Failed to create instance using constructor: " + type, e);
+ }
}
}
- public static final class UnsafeObjectCreator extends ObjectCreator {
+ private static final class UnsafeObjectCreator extends ObjectCreator {
public UnsafeObjectCreator(Class type) {
super(type);
@@ -159,7 +550,7 @@ public UnsafeObjectCreator(Class type) {
@Override
public T newInstance() {
- return UnsafeOps.newInstance(type);
+ return ObjectCreators.allocateInstance(type);
}
@Override
diff --git a/java/fory-core/src/main/java/org/apache/fory/reflect/ReflectionUtils.java b/java/fory-core/src/main/java/org/apache/fory/reflect/ReflectionUtils.java
index 2af7dd0d0b..82647de3ae 100644
--- a/java/fory-core/src/main/java/org/apache/fory/reflect/ReflectionUtils.java
+++ b/java/fory-core/src/main/java/org/apache/fory/reflect/ReflectionUtils.java
@@ -51,13 +51,11 @@
import org.apache.fory.collection.ClassValueCache;
import org.apache.fory.exception.ForyException;
import org.apache.fory.platform.AndroidSupport;
-import org.apache.fory.platform.GraalvmSupport;
-import org.apache.fory.platform.UnsafeOps;
+import org.apache.fory.platform.internal._JDKAccess;
import org.apache.fory.util.ExceptionUtils;
import org.apache.fory.util.Preconditions;
import org.apache.fory.util.StringUtils;
import org.apache.fory.util.function.Functions;
-import org.apache.fory.util.unsafe._JDKAccess;
/** Reflection util. */
@Internal
@@ -456,42 +454,13 @@ public static List getSortedFields(Class> cls, boolean searchParent) {
public static List