blob: c70639bf1d9c450ccb99feb5511ae258f8e2bd65 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fury.serializer;
import java.io.Externalizable;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.NotActiveException;
import java.io.ObjectInputStream;
import java.io.ObjectInputValidation;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.ObjectStreamField;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.apache.fury.Fury;
import org.apache.fury.builder.CodecUtils;
import org.apache.fury.builder.Generated;
import org.apache.fury.collection.ObjectArray;
import org.apache.fury.collection.ObjectIntMap;
import org.apache.fury.logging.Logger;
import org.apache.fury.logging.LoggerFactory;
import org.apache.fury.memory.MemoryBuffer;
import org.apache.fury.resolver.ClassInfo;
import org.apache.fury.resolver.ClassResolver;
import org.apache.fury.resolver.FieldResolver;
import org.apache.fury.resolver.FieldResolver.ClassField;
import org.apache.fury.util.Platform;
import org.apache.fury.util.Preconditions;
import org.apache.fury.util.ReflectionUtils;
import org.apache.fury.util.Utils;
import org.apache.fury.util.unsafe._JDKAccess;
/**
* Implement jdk custom serialization only if following conditions are met:
*
* <ul>
* <li>`writeObject/readObject` occurs only at current class in class hierarchy.
* <li>`writeReplace/readResolve` don't occur in class hierarchy.
* <li>class hierarchy doesn't have duplicated fields. If any of those conditions are not met,
* fallback jdk custom serialization to {@link JavaSerializer}.
* </ul>
*
* <p>`ObjectInputStream#setObjectInputFilter` will be ignored by this serializer.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class ObjectStreamSerializer extends Serializer {
private static final Logger LOG = LoggerFactory.getLogger(ObjectStreamSerializer.class);
private final Constructor constructor;
private final ClassResolver classResolver;
private final SlotsInfo[] slotsInfos;
public ObjectStreamSerializer(Fury fury, Class<?> type) {
super(fury, type);
if (!Serializable.class.isAssignableFrom(type)) {
throw new IllegalArgumentException(
String.format("Class %s should implement %s.", type, Serializable.class));
}
LOG.warn(
"{} customized jdk serialization, which is inefficient. "
+ "Please replace it with a {} or implements {}",
type,
Serializer.class.getName(),
Externalizable.class.getName());
// stream serializer may be data serializer of ReplaceResolver serializer.
fury.getClassResolver().setSerializerIfAbsent(type, this);
Constructor constructor;
try {
constructor = type.getConstructor();
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
} catch (Exception e) {
constructor =
(Constructor) ReflectionUtils.getObjectFieldValue(ObjectStreamClass.lookup(type), "cons");
}
this.classResolver = fury.getClassResolver();
this.constructor = constructor;
List<SlotsInfo> slotsInfoList = new ArrayList<>();
Class<?> end = type;
// locate closest non-serializable superclass
while (end != null && Serializable.class.isAssignableFrom(end)) {
end = end.getSuperclass();
}
while (type != end) {
slotsInfoList.add(new SlotsInfo(fury, type));
type = type.getSuperclass();
}
Collections.reverse(slotsInfoList);
slotsInfos = slotsInfoList.toArray(new SlotsInfo[0]);
}
@Override
public void write(MemoryBuffer buffer, Object value) {
buffer.writeInt16((short) slotsInfos.length);
try {
for (SlotsInfo slotsInfo : slotsInfos) {
// create a classinfo to avoid null class bytes when class id is a
// replacement id.
classResolver.writeClass(buffer, slotsInfo.classInfo);
StreamClassInfo streamClassInfo = slotsInfo.streamClassInfo;
Method writeObjectMethod = streamClassInfo.writeObjectMethod;
if (writeObjectMethod == null) {
slotsInfo.slotsSerializer.write(buffer, value);
} else {
FuryObjectOutputStream objectOutputStream = slotsInfo.objectOutputStream;
Object oldObject = objectOutputStream.targetObject;
MemoryBuffer oldBuffer = objectOutputStream.buffer;
FuryObjectOutputStream.PutFieldImpl oldPutField = objectOutputStream.curPut;
boolean fieldsWritten = objectOutputStream.fieldsWritten;
try {
objectOutputStream.targetObject = value;
objectOutputStream.buffer = buffer;
objectOutputStream.curPut = null;
objectOutputStream.fieldsWritten = false;
if (streamClassInfo.writeObjectFunc != null) {
streamClassInfo.writeObjectFunc.accept(value, objectOutputStream);
} else {
writeObjectMethod.invoke(value, objectOutputStream);
}
} finally {
objectOutputStream.targetObject = oldObject;
objectOutputStream.buffer = oldBuffer;
objectOutputStream.curPut = oldPutField;
objectOutputStream.fieldsWritten = fieldsWritten;
}
}
}
} catch (Exception e) {
throwSerializationException(type, e);
}
}
@Override
public Object read(MemoryBuffer buffer) {
Object obj = null;
if (constructor != null) {
try {
obj = constructor.newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
Platform.throwException(e);
}
} else {
obj = Platform.newInstance(type);
}
fury.getRefResolver().reference(obj);
int numClasses = buffer.readInt16();
int slotIndex = 0;
try {
TreeMap<Integer, ObjectInputValidation> callbacks = new TreeMap<>(Collections.reverseOrder());
for (int i = 0; i < numClasses; i++) {
Class<?> currentClass = classResolver.readClassInternal(buffer);
SlotsInfo slotsInfo = slotsInfos[slotIndex++];
StreamClassInfo streamClassInfo = slotsInfo.streamClassInfo;
while (currentClass != slotsInfo.cls) {
// the receiver's version extends classes that are not extended by the sender's version.
Method readObjectNoData = streamClassInfo.readObjectNoData;
if (readObjectNoData != null) {
if (streamClassInfo.readObjectNoDataFunc != null) {
streamClassInfo.readObjectNoDataFunc.accept(obj);
} else {
readObjectNoData.invoke(obj);
}
}
slotsInfo = slotsInfos[slotIndex++];
}
Method readObjectMethod = streamClassInfo.readObjectMethod;
if (readObjectMethod == null) {
slotsInfo.slotsSerializer.readAndSetFields(buffer, obj);
} else {
FuryObjectInputStream objectInputStream = slotsInfo.objectInputStream;
MemoryBuffer oldBuffer = objectInputStream.buffer;
Object oldObject = objectInputStream.targetObject;
FuryObjectInputStream.GetFieldImpl oldGetField = objectInputStream.getField;
FuryObjectInputStream.GetFieldImpl getField =
(FuryObjectInputStream.GetFieldImpl) slotsInfo.getFieldPool.popOrNull();
if (getField == null) {
getField = new FuryObjectInputStream.GetFieldImpl(slotsInfo);
}
boolean fieldsRead = objectInputStream.fieldsRead;
try {
objectInputStream.fieldsRead = false;
objectInputStream.buffer = buffer;
objectInputStream.targetObject = obj;
objectInputStream.getField = getField;
objectInputStream.callbacks = callbacks;
if (streamClassInfo.readObjectFunc != null) {
streamClassInfo.readObjectFunc.accept(obj, objectInputStream);
} else {
readObjectMethod.invoke(obj, objectInputStream);
}
} finally {
objectInputStream.fieldsRead = fieldsRead;
objectInputStream.buffer = oldBuffer;
objectInputStream.targetObject = oldObject;
objectInputStream.getField = oldGetField;
slotsInfo.getFieldPool.add(getField);
objectInputStream.callbacks = null;
Arrays.fill(getField.vals, FuryObjectInputStream.NO_VALUE_STUB);
}
}
}
for (ObjectInputValidation validation : callbacks.values()) {
validation.validateObject();
}
} catch (InvocationTargetException | IllegalAccessException | InvalidObjectException e) {
throwSerializationException(type, e);
}
return obj;
}
private static void throwUnsupportedEncodingException(Class<?> cls)
throws UnsupportedEncodingException {
throw new UnsupportedEncodingException(
String.format(
"Use %s instead by `fury.registerSerializer(%s, new JavaSerializer(fury, %s))` or "
+ "implement a custom %s.",
JavaSerializer.class, cls, cls, Serializer.class));
}
private static void throwSerializationException(Class<?> type, Exception e) {
throw new RuntimeException(
String.format(
"Serialize object of type %s failed, "
+ "Try to use %s instead by `fury.registerSerializer(%s, new JavaSerializer(fury, %s))` or "
+ "implement a custom %s.",
type, JavaSerializer.class, type, type, Serializer.class),
e);
}
private static class StreamClassInfo {
private final Method writeObjectMethod;
private final Method readObjectMethod;
private final Method readObjectNoData;
private final BiConsumer writeObjectFunc;
private final BiConsumer readObjectFunc;
private final Consumer readObjectNoDataFunc;
private StreamClassInfo(Class<?> type) {
// ObjectStreamClass.lookup has cache inside, invocation cost won't be big.
ObjectStreamClass objectStreamClass = ObjectStreamClass.lookup(type);
// In JDK17, set private jdk method accessible will fail by default, use ObjectStreamClass
// instead, since it set accessible.
writeObjectMethod =
(Method) ReflectionUtils.getObjectFieldValue(objectStreamClass, "writeObjectMethod");
readObjectMethod =
(Method) ReflectionUtils.getObjectFieldValue(objectStreamClass, "readObjectMethod");
readObjectNoData =
(Method) ReflectionUtils.getObjectFieldValue(objectStreamClass, "readObjectNoData");
MethodHandles.Lookup lookup = _JDKAccess._trustedLookup(type);
BiConsumer writeObjectFunc = null, readObjectFunc = null;
Consumer readObjectNoDataFunc = null;
try {
if (writeObjectMethod != null) {
writeObjectFunc =
_JDKAccess.makeJDKBiConsumer(lookup, lookup.unreflect(writeObjectMethod));
}
if (readObjectMethod != null) {
readObjectFunc = _JDKAccess.makeJDKBiConsumer(lookup, lookup.unreflect(readObjectMethod));
}
if (readObjectNoData != null) {
readObjectNoDataFunc =
_JDKAccess.makeJDKConsumer(lookup, lookup.unreflect(readObjectNoData));
}
} catch (Exception e) {
Utils.ignore(e);
}
this.writeObjectFunc = writeObjectFunc;
this.readObjectFunc = readObjectFunc;
this.readObjectNoDataFunc = readObjectNoDataFunc;
}
}
private static final ClassValue<StreamClassInfo> STREAM_CLASS_INFO_CACHE =
new ClassValue<StreamClassInfo>() {
@Override
protected StreamClassInfo computeValue(Class<?> type) {
return new StreamClassInfo(type);
}
};
private static class SlotsInfo {
private final Class<?> cls;
private final ClassInfo classInfo;
private final StreamClassInfo streamClassInfo;
// mark non-final for async-jit to update it to jit-serializer.
private CompatibleSerializerBase slotsSerializer;
private final ObjectIntMap<String> fieldIndexMap;
private final FieldResolver putFieldsResolver;
private final CompatibleSerializer compatibleStreamSerializer;
private final FuryObjectOutputStream objectOutputStream;
private final FuryObjectInputStream objectInputStream;
private final ObjectArray getFieldPool;
public SlotsInfo(Fury fury, Class<?> type) {
this.cls = type;
classInfo = fury.getClassResolver().newClassInfo(type, null, ClassResolver.NO_CLASS_ID);
ObjectStreamClass objectStreamClass = ObjectStreamClass.lookup(type);
streamClassInfo = STREAM_CLASS_INFO_CACHE.get(type);
// `putFields/writeFields` will convert to fields value to be written by
// `CompatibleSerializer`,
// since `put` values may not exist in current class, which means container generic type are
// unavailable.
// (`serialPersistentFields` field can't provide generics.)
// So we need to mark all container fields type to `Object` to avoid reader deserialize data
// using field generic types.
Class<? extends Serializer> sc = CompatibleSerializer.class;
FieldResolver fieldResolver = FieldResolver.of(fury, type, false, true);
if (fury.getConfig().isCodeGenEnabled()
&& CodegenSerializer.supportCodegenForJavaSerialization(cls)) {
sc =
fury.getJITContext()
.registerSerializerJITCallback(
() -> CompatibleSerializer.class,
() ->
CodecUtils.loadOrGenCompatibleCodecClass(
cls,
fury,
fieldResolver,
Generated.GeneratedCompatibleSerializer.class),
c ->
this.slotsSerializer =
(CompatibleSerializerBase) Serializers.newSerializer(fury, type, c));
}
if (sc == CompatibleSerializer.class) {
this.slotsSerializer = new CompatibleSerializer(fury, type, fieldResolver);
} else {
this.slotsSerializer = (CompatibleSerializerBase) Serializers.newSerializer(fury, type, sc);
}
fieldIndexMap = new ObjectIntMap<>(4, 0.4f);
List<ClassField> allFields = new ArrayList<>();
for (ObjectStreamField serialField : objectStreamClass.getFields()) {
allFields.add(new ClassField(serialField.getName(), serialField.getType(), cls));
}
if (streamClassInfo.writeObjectMethod != null || streamClassInfo.readObjectMethod != null) {
putFieldsResolver = new FieldResolver(fury, cls, true, allFields, new HashSet<>());
AtomicInteger idx = new AtomicInteger(0);
for (FieldResolver.FieldInfo fieldInfo : putFieldsResolver.getAllFieldsList()) {
fieldIndexMap.put(fieldInfo.getName(), idx.getAndIncrement());
}
compatibleStreamSerializer = new CompatibleSerializer(fury, cls, putFieldsResolver);
} else {
putFieldsResolver = null;
compatibleStreamSerializer = null;
}
if (streamClassInfo.writeObjectMethod != null) {
try {
objectOutputStream = new FuryObjectOutputStream(this);
} catch (IOException e) {
Platform.throwException(e);
throw new IllegalStateException("unreachable");
}
} else {
objectOutputStream = null;
}
if (streamClassInfo.readObjectMethod != null) {
try {
objectInputStream = new FuryObjectInputStream(this);
} catch (IOException e) {
Platform.throwException(e);
throw new IllegalStateException("unreachable");
}
} else {
objectInputStream = null;
}
getFieldPool = new ObjectArray();
}
@Override
public String toString() {
return "SlotsInfo{" + "cls=" + cls + '}';
}
}
/**
* Implement serialization for object output with `writeObject/readObject` defined by java
* serialization output spec.
*
* @see <a href="https://docs.oracle.com/en/java/javase/18/docs/specs/serialization/output.html">
* Java Object Serialization Output Specification</a>
*/
private static class FuryObjectOutputStream extends ObjectOutputStream {
private final Fury fury;
private final SlotsInfo slotsInfo;
private MemoryBuffer buffer;
private Object targetObject;
private boolean fieldsWritten;
protected FuryObjectOutputStream(SlotsInfo slotsInfo) throws IOException {
super();
this.slotsInfo = slotsInfo;
this.fury = slotsInfo.slotsSerializer.fury;
}
@Override
protected final void writeObjectOverride(Object obj) throws IOException {
fury.writeRef(buffer, obj);
}
@Override
public void writeUnshared(Object obj) throws IOException {
fury.writeNonRef(buffer, obj);
}
/**
* PutField is used to write fields which may not exists in current class for compatibility.
* Note that the protocol should be compatible with `defaultReadObject`.
*
* @see <a
* href="https://docs.oracle.com/en/java/javase/18/docs/specs/serialization/input.html#the-objectinputstream.getfield-class">ObjectInputStream.GetField</a>
* @see ConcurrentHashMap
*/
// See `defaultReadObject` in ConcurrentHashMap#readObject skip fields written by
// `writeFields()`.
private class PutFieldImpl extends PutField {
private final Object[] vals;
PutFieldImpl() {
vals = new Object[slotsInfo.putFieldsResolver.getNumFields()];
}
private void putValue(String name, Object val) {
int index = slotsInfo.fieldIndexMap.get(name, -1);
if (index == -1) {
throw new IllegalArgumentException(
String.format(
"Field name %s not exist in class %s", name, slotsInfo.slotsSerializer.type));
}
vals[index] = val;
}
@Override
public void put(String name, boolean val) {
putValue(name, val);
}
@Override
public void put(String name, byte val) {
putValue(name, val);
}
@Override
public void put(String name, char val) {
putValue(name, val);
}
@Override
public void put(String name, short val) {
putValue(name, val);
}
@Override
public void put(String name, int val) {
putValue(name, val);
}
@Override
public void put(String name, long val) {
putValue(name, val);
}
@Override
public void put(String name, float val) {
putValue(name, val);
}
@Override
public void put(String name, double val) {
putValue(name, val);
}
@Override
public void put(String name, Object val) {
putValue(name, val);
}
@Deprecated
@Override
public void write(ObjectOutput out) throws IOException {
Class cls = slotsInfo.slotsSerializer.type;
throwUnsupportedEncodingException(cls);
}
}
private final ObjectArray putFieldsCache = new ObjectArray();
private PutFieldImpl curPut;
@Override
public PutField putFields() throws IOException {
if (curPut == null) {
Object o = putFieldsCache.popOrNull();
if (o == null) {
o = new PutFieldImpl();
}
curPut = (PutFieldImpl) o;
}
return curPut;
}
@Override
public void writeFields() throws IOException {
if (fieldsWritten) {
throw new NotActiveException("not in writeObject invocation or fields already written");
}
PutFieldImpl curPut = this.curPut;
if (curPut == null) {
throw new NotActiveException("no current PutField object");
}
slotsInfo.compatibleStreamSerializer.writeFieldsValues(buffer, curPut.vals);
Arrays.fill(curPut.vals, null);
putFieldsCache.add(curPut);
this.curPut = null;
fieldsWritten = true;
}
@Override
public void defaultWriteObject() throws IOException, NotActiveException {
if (fieldsWritten) {
throw new NotActiveException("not in writeObject invocation or fields already written");
}
slotsInfo.slotsSerializer.write(buffer, targetObject);
fieldsWritten = true;
}
@Override
public void reset() throws IOException {
Class cls = slotsInfo.slotsSerializer.getType();
// Fury won't invoke this method, throw exception if the user invokes it.
throwUnsupportedEncodingException(cls);
}
@Override
protected void annotateClass(Class<?> cl) throws IOException {
throw new IllegalStateException();
}
@Override
protected void annotateProxyClass(Class<?> cl) throws IOException {
throw new IllegalStateException();
}
@Override
protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException {
throw new UnsupportedEncodingException();
}
@Override
protected Object replaceObject(Object obj) throws IOException {
throw new UnsupportedEncodingException();
}
@Override
protected boolean enableReplaceObject(boolean enable) throws SecurityException {
throw new IllegalStateException();
}
@Override
protected void writeStreamHeader() throws IOException {
throw new IllegalStateException();
}
@Override
public void write(int b) throws IOException {
buffer.writeByte((byte) b);
}
@Override
public void write(byte[] b) throws IOException {
buffer.writeBytes(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
buffer.writeBytes(b, off, len);
}
@Override
public void writeBoolean(boolean v) throws IOException {
buffer.writeBoolean(v);
}
@Override
public void writeByte(int v) throws IOException {
buffer.writeByte((byte) v);
}
@Override
public void writeShort(int v) throws IOException {
buffer.writeInt16((short) v);
}
@Override
public void writeChar(int v) throws IOException {
buffer.writeChar((char) v);
}
@Override
public void writeInt(int v) throws IOException {
buffer.writeInt32(v);
}
@Override
public void writeLong(long v) throws IOException {
buffer.writeInt64(v);
}
@Override
public void writeFloat(float v) throws IOException {
buffer.writeFloat32(v);
}
@Override
public void writeDouble(double v) throws IOException {
buffer.writeFloat64(v);
}
@Override
public void writeBytes(String s) throws IOException {
Preconditions.checkNotNull(s);
int len = s.length();
for (int i = 0; i < len; i++) {
buffer.writeByte((byte) s.charAt(i));
}
}
@Override
public void writeChars(String s) throws IOException {
Preconditions.checkNotNull(s);
fury.writeJavaString(buffer, s);
}
@Override
public void writeUTF(String s) throws IOException {
Preconditions.checkNotNull(s);
fury.writeJavaString(buffer, s);
}
@Override
public void useProtocolVersion(int version) throws IOException {
Class cls = slotsInfo.cls;
throwUnsupportedEncodingException(cls);
}
@Override
public void flush() throws IOException {}
@Override
protected void drain() throws IOException {}
@Override
public void close() throws IOException {}
}
/**
* Implement serialization for object output with `writeObject/readObject` defined by java
* serialization input spec.
*
* @see <a href="https://docs.oracle.com/en/java/javase/18/docs/specs/serialization/input.html">
* Java Object Serialization Input Specification</a>
*/
private static class FuryObjectInputStream extends ObjectInputStream {
private final Fury fury;
private final SlotsInfo slotsInfo;
private MemoryBuffer buffer;
private Object targetObject;
private GetFieldImpl getField;
private boolean fieldsRead;
private TreeMap<Integer, ObjectInputValidation> callbacks;
protected FuryObjectInputStream(SlotsInfo slotsInfo) throws IOException {
this.fury = slotsInfo.slotsSerializer.fury;
this.slotsInfo = slotsInfo;
}
@Override
protected Object readObjectOverride() {
return fury.readRef(buffer);
}
@Override
public Object readUnshared() {
return fury.readNonRef(buffer);
}
private static final Object NO_VALUE_STUB = new Object();
private static class GetFieldImpl extends GetField {
private final SlotsInfo slotsInfo;
private final Object[] vals;
GetFieldImpl(SlotsInfo slotsInfo) {
this.slotsInfo = slotsInfo;
vals = new Object[slotsInfo.putFieldsResolver.getNumFields()];
Arrays.fill(vals, NO_VALUE_STUB);
}
@Override
public ObjectStreamClass getObjectStreamClass() {
return ObjectStreamClass.lookup(slotsInfo.cls);
}
@Override
public boolean defaulted(String name) throws IOException {
int index = slotsInfo.fieldIndexMap.get(name, -1);
checkFieldExists(name, index);
return vals[index] == NO_VALUE_STUB;
}
@Override
public boolean get(String name, boolean val) throws IOException {
Object fieldValue = getFieldValue(name);
if (fieldValue == NO_VALUE_STUB) {
return val;
}
return (boolean) fieldValue;
}
@Override
public byte get(String name, byte val) throws IOException {
Object fieldValue = getFieldValue(name);
if (fieldValue == NO_VALUE_STUB) {
return val;
}
return (byte) fieldValue;
}
@Override
public char get(String name, char val) throws IOException {
Object fieldValue = getFieldValue(name);
if (fieldValue == NO_VALUE_STUB) {
return val;
}
return (char) fieldValue;
}
@Override
public short get(String name, short val) throws IOException {
Object fieldValue = getFieldValue(name);
if (fieldValue == NO_VALUE_STUB) {
return val;
}
return (short) fieldValue;
}
@Override
public int get(String name, int val) throws IOException {
Object fieldValue = getFieldValue(name);
if (fieldValue == NO_VALUE_STUB) {
return val;
}
return (int) fieldValue;
}
@Override
public long get(String name, long val) throws IOException {
Object fieldValue = getFieldValue(name);
if (fieldValue == NO_VALUE_STUB) {
return val;
}
return (long) fieldValue;
}
@Override
public float get(String name, float val) throws IOException {
Object fieldValue = getFieldValue(name);
if (fieldValue == NO_VALUE_STUB) {
return val;
}
return (float) fieldValue;
}
@Override
public double get(String name, double val) throws IOException {
Object fieldValue = getFieldValue(name);
if (fieldValue == NO_VALUE_STUB) {
return val;
}
return (double) fieldValue;
}
@Override
public Object get(String name, Object val) throws IOException {
Object fieldValue = getFieldValue(name);
if (fieldValue == NO_VALUE_STUB) {
return val;
}
return fieldValue;
}
private Object getFieldValue(String name) {
int index = slotsInfo.fieldIndexMap.get(name, -1);
checkFieldExists(name, index);
return vals[index];
}
private void checkFieldExists(String name, int index) {
if (index == -1) {
throw new IllegalArgumentException(
String.format(
"Field name %s not exist in class %s", name, slotsInfo.slotsSerializer.type));
}
}
}
// `readFields`/`writeFields` can handle fields which doesn't exist in current class.
// `defaultReadObject` will skip those fields.
@Override
public GetField readFields() throws IOException {
if (fieldsRead) {
throw new NotActiveException("not in readObject invocation or fields already read");
}
slotsInfo.compatibleStreamSerializer.readFields(buffer, getField.vals);
fieldsRead = true;
return getField;
}
@Override
public void defaultReadObject() throws IOException, ClassNotFoundException {
if (fieldsRead) {
throw new NotActiveException("not in readObject invocation or fields already read");
}
slotsInfo.slotsSerializer.readAndSetFields(buffer, targetObject);
fieldsRead = true;
}
// At `registerValidation` point int `readObject` root is only partially correct. To fully
// restore it user may need access to other state which is created by the subclass and
// at this point will be null. Users thus use `registerValidation`.
// see `javax.swing.text.AbstractDocument.readObject` as an example.
@Override
public void registerValidation(ObjectInputValidation obj, int prio)
throws NotActiveException, InvalidObjectException {
// Since this method is only visible to fury, it won't be invoked by users outside
// `readObject`,
// we can skip check whether the caller is in `readObject`.
if (obj == null) {
throw new InvalidObjectException("null callback");
}
callbacks.put(prio, obj);
}
@Override
public int read() throws IOException {
return buffer.readByte() & 0xFF;
}
@Override
public int read(byte[] buf, int offset, int length) throws IOException {
if (buf == null) {
throw new NullPointerException();
}
int endOffset = offset + length;
if (offset < 0 || length < 0 || endOffset > buf.length || endOffset < 0) {
throw new IndexOutOfBoundsException();
}
int remaining = buffer.remaining();
if (remaining < length) {
buffer.readBytes(buf, offset, remaining);
return remaining;
} else {
buffer.readBytes(buf, offset, length);
return length;
}
}
@Override
public int available() throws IOException {
return buffer.remaining();
}
@Override
public boolean readBoolean() throws IOException {
return buffer.readBoolean();
}
@Override
public byte readByte() throws IOException {
return buffer.readByte();
}
@Override
public int readUnsignedByte() throws IOException {
int b = buffer.readByte();
return b & 0xff;
}
@Override
public short readShort() throws IOException {
return buffer.readInt16();
}
public int readUnsignedShort() throws IOException {
int b = buffer.readInt16();
return b & 0xffff;
}
@Override
public char readChar() throws IOException {
return buffer.readChar();
}
@Override
public int readInt() throws IOException {
return buffer.readInt32();
}
@Override
public long readLong() throws IOException {
return buffer.readInt64();
}
@Override
public float readFloat() throws IOException {
return buffer.readFloat32();
}
@Override
public double readDouble() throws IOException {
return buffer.readFloat64();
}
@Override
public void readFully(byte[] data) throws IOException {
buffer.readBytes(data);
}
@Override
public void readFully(byte[] data, int offset, int size) throws IOException {
buffer.readBytes(data, offset, size);
}
@Override
public int skipBytes(int len) throws IOException {
buffer.increaseReaderIndex(len);
return len;
}
@Override
public String readLine() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public String readUTF() throws IOException {
return fury.readJavaString(buffer);
}
@Override
public void close() throws IOException {}
}
}