| /* |
| * 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.flink.api.java.typeutils.runtime; |
| |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Modifier; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.Map; |
| import java.util.Objects; |
| |
| import org.apache.flink.annotation.Internal; |
| import org.apache.flink.annotation.VisibleForTesting; |
| import org.apache.flink.api.common.ExecutionConfig; |
| import org.apache.flink.api.common.typeutils.CompatibilityResult; |
| import org.apache.flink.api.common.typeutils.CompatibilityUtil; |
| import org.apache.flink.api.common.typeutils.GenericTypeSerializerConfigSnapshot; |
| import org.apache.flink.api.common.typeutils.TypeSerializer; |
| import org.apache.flink.api.common.typeutils.TypeSerializerConfigSnapshot; |
| import org.apache.flink.api.common.typeutils.TypeSerializerSerializationUtil; |
| import org.apache.flink.api.common.typeutils.UnloadableDummyTypeSerializer; |
| import org.apache.flink.api.java.tuple.Tuple2; |
| import org.apache.flink.api.java.typeutils.TypeExtractor; |
| import org.apache.flink.core.memory.ByteArrayInputStreamWithPos; |
| import org.apache.flink.core.memory.ByteArrayOutputStreamWithPos; |
| import org.apache.flink.core.memory.DataInputView; |
| import org.apache.flink.core.memory.DataInputViewStreamWrapper; |
| import org.apache.flink.core.memory.DataOutputView; |
| import org.apache.flink.core.memory.DataOutputViewStreamWrapper; |
| import org.apache.flink.util.Preconditions; |
| |
| import static org.apache.flink.util.Preconditions.checkNotNull; |
| |
| @Internal |
| public final class PojoSerializer<T> extends TypeSerializer<T> { |
| |
| // Flags for the header |
| private static byte IS_NULL = 1; |
| private static byte NO_SUBCLASS = 2; |
| private static byte IS_SUBCLASS = 4; |
| private static byte IS_TAGGED_SUBCLASS = 8; |
| |
| private static final long serialVersionUID = 1L; |
| |
| // -------------------------------------------------------------------------------------------- |
| // PojoSerializer parameters |
| // -------------------------------------------------------------------------------------------- |
| |
| /** The POJO type class. */ |
| private final Class<T> clazz; |
| |
| /** |
| * Fields of the POJO and their serializers. |
| * |
| * <p>The fields are kept as a separate transient member, with their serialization |
| * handled with the {@link #readObject(ObjectInputStream)} and {@link #writeObject(ObjectOutputStream)} |
| * methods. |
| * |
| * <p>These may be reconfigured in {@link #ensureCompatibility(TypeSerializerConfigSnapshot)}. |
| */ |
| private transient Field[] fields; |
| private TypeSerializer<Object>[] fieldSerializers; |
| private final int numFields; |
| |
| /** |
| * Registered subclasses and their serializers. |
| * Each subclass to their registered class tag is maintained as a separate map ordered by the class tag. |
| * |
| * <p>These may be reconfigured in {@link #ensureCompatibility(TypeSerializerConfigSnapshot)}. |
| */ |
| private LinkedHashMap<Class<?>, Integer> registeredClasses; |
| private TypeSerializer<?>[] registeredSerializers; |
| |
| /** |
| * Cache of non-registered subclasses to their serializers, created on-the-fly. |
| * |
| * <p>This cache is persisted and will be repopulated with reconfigured serializers |
| * in {@link #ensureCompatibility(TypeSerializerConfigSnapshot)}. |
| */ |
| private transient HashMap<Class<?>, TypeSerializer<?>> subclassSerializerCache; |
| |
| // -------------------------------------------------------------------------------------------- |
| |
| /** |
| * Configuration of the current execution. |
| * |
| * <p>Nested serializers created using this will have the most up-to-date configuration, |
| * and can be resolved for backwards compatibility with previous configuration |
| * snapshots in {@link #ensureCompatibility(TypeSerializerConfigSnapshot)}. |
| */ |
| private final ExecutionConfig executionConfig; |
| |
| private transient ClassLoader cl; |
| |
| @SuppressWarnings("unchecked") |
| public PojoSerializer( |
| Class<T> clazz, |
| TypeSerializer<?>[] fieldSerializers, |
| Field[] fields, |
| ExecutionConfig executionConfig) { |
| |
| this.clazz = checkNotNull(clazz); |
| this.fieldSerializers = (TypeSerializer<Object>[]) checkNotNull(fieldSerializers); |
| this.fields = checkNotNull(fields); |
| this.numFields = fieldSerializers.length; |
| this.executionConfig = checkNotNull(executionConfig); |
| |
| for (int i = 0; i < numFields; i++) { |
| this.fields[i].setAccessible(true); |
| } |
| |
| cl = Thread.currentThread().getContextClassLoader(); |
| |
| // We only want those classes that are not our own class and are actually sub-classes. |
| LinkedHashSet<Class<?>> registeredSubclasses = |
| getRegisteredSubclassesFromExecutionConfig(clazz, executionConfig); |
| |
| this.registeredClasses = createRegisteredSubclassTags(registeredSubclasses); |
| this.registeredSerializers = createRegisteredSubclassSerializers(registeredSubclasses, executionConfig); |
| |
| this.subclassSerializerCache = new HashMap<>(); |
| } |
| |
| public PojoSerializer( |
| Class<T> clazz, |
| Field[] fields, |
| TypeSerializer<Object>[] fieldSerializers, |
| LinkedHashMap<Class<?>, Integer> registeredClasses, |
| TypeSerializer<?>[] registeredSerializers, |
| HashMap<Class<?>, TypeSerializer<?>> subclassSerializerCache) { |
| |
| this.clazz = checkNotNull(clazz); |
| this.fields = checkNotNull(fields); |
| this.numFields = fields.length; |
| this.fieldSerializers = checkNotNull(fieldSerializers); |
| this.registeredClasses = checkNotNull(registeredClasses); |
| this.registeredSerializers = checkNotNull(registeredSerializers); |
| this.subclassSerializerCache = checkNotNull(subclassSerializerCache); |
| |
| this.executionConfig = null; |
| } |
| |
| @Override |
| public boolean isImmutableType() { |
| return false; |
| } |
| |
| @Override |
| public PojoSerializer<T> duplicate() { |
| boolean stateful = false; |
| TypeSerializer<?>[] duplicateFieldSerializers = new TypeSerializer[fieldSerializers.length]; |
| |
| for (int i = 0; i < fieldSerializers.length; i++) { |
| duplicateFieldSerializers[i] = fieldSerializers[i].duplicate(); |
| if (duplicateFieldSerializers[i] != fieldSerializers[i]) { |
| // at least one of them is stateful |
| stateful = true; |
| } |
| } |
| |
| if (stateful) { |
| return new PojoSerializer<T>(clazz, duplicateFieldSerializers, fields, executionConfig); |
| } else { |
| return this; |
| } |
| } |
| |
| |
| @Override |
| public T createInstance() { |
| if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())) { |
| return null; |
| } |
| try { |
| T t = clazz.newInstance(); |
| initializeFields(t); |
| return t; |
| } |
| catch (Exception e) { |
| throw new RuntimeException("Cannot instantiate class.", e); |
| } |
| } |
| |
| protected void initializeFields(T t) { |
| for (int i = 0; i < numFields; i++) { |
| if (fields[i] != null) { |
| try { |
| fields[i].set(t, fieldSerializers[i].createInstance()); |
| } catch (IllegalAccessException e) { |
| throw new RuntimeException("Cannot initialize fields.", e); |
| } |
| } |
| } |
| } |
| |
| @Override |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| public T copy(T from) { |
| if (from == null) { |
| return null; |
| } |
| |
| Class<?> actualType = from.getClass(); |
| if (actualType == clazz) { |
| T target; |
| try { |
| target = (T) from.getClass().newInstance(); |
| } |
| catch (Throwable t) { |
| throw new RuntimeException("Cannot instantiate class.", t); |
| } |
| // no subclass |
| try { |
| for (int i = 0; i < numFields; i++) { |
| if (fields[i] != null) { |
| Object value = fields[i].get(from); |
| if (value != null) { |
| Object copy = fieldSerializers[i].copy(value); |
| fields[i].set(target, copy); |
| } else { |
| fields[i].set(target, null); |
| } |
| } |
| } |
| } catch (IllegalAccessException e) { |
| throw new RuntimeException("Error during POJO copy, this should not happen since we check the fields before."); |
| |
| } |
| return target; |
| } else { |
| // subclass |
| TypeSerializer subclassSerializer = getSubclassSerializer(actualType); |
| return (T) subclassSerializer.copy(from); |
| } |
| } |
| |
| @Override |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| public T copy(T from, T reuse) { |
| if (from == null) { |
| return null; |
| } |
| |
| Class<?> actualType = from.getClass(); |
| if (reuse == null || actualType != reuse.getClass()) { |
| // cannot reuse, do a non-reuse copy |
| return copy(from); |
| } |
| |
| if (actualType == clazz) { |
| try { |
| for (int i = 0; i < numFields; i++) { |
| if (fields[i] != null) { |
| Object value = fields[i].get(from); |
| if (value != null) { |
| Object reuseValue = fields[i].get(reuse); |
| Object copy; |
| if (reuseValue != null) { |
| copy = fieldSerializers[i].copy(value, reuseValue); |
| } else { |
| copy = fieldSerializers[i].copy(value); |
| } |
| fields[i].set(reuse, copy); |
| } else { |
| fields[i].set(reuse, null); |
| } |
| } |
| } |
| } catch (IllegalAccessException e) { |
| throw new RuntimeException("Error during POJO copy, this should not happen since we check the fields before.", e); |
| } |
| } else { |
| TypeSerializer subclassSerializer = getSubclassSerializer(actualType); |
| reuse = (T) subclassSerializer.copy(from, reuse); |
| } |
| |
| return reuse; |
| } |
| |
| @Override |
| public int getLength() { |
| return -1; |
| } |
| |
| |
| @Override |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| public void serialize(T value, DataOutputView target) throws IOException { |
| int flags = 0; |
| // handle null values |
| if (value == null) { |
| flags |= IS_NULL; |
| target.writeByte(flags); |
| return; |
| } |
| |
| Integer subclassTag = -1; |
| Class<?> actualClass = value.getClass(); |
| TypeSerializer subclassSerializer = null; |
| if (clazz != actualClass) { |
| subclassTag = registeredClasses.get(actualClass); |
| if (subclassTag != null) { |
| flags |= IS_TAGGED_SUBCLASS; |
| subclassSerializer = registeredSerializers[subclassTag]; |
| } else { |
| flags |= IS_SUBCLASS; |
| subclassSerializer = getSubclassSerializer(actualClass); |
| } |
| } else { |
| flags |= NO_SUBCLASS; |
| } |
| |
| target.writeByte(flags); |
| |
| // if its a registered subclass, write the class tag id, otherwise write the full classname |
| if ((flags & IS_SUBCLASS) != 0) { |
| target.writeUTF(actualClass.getName()); |
| } else if ((flags & IS_TAGGED_SUBCLASS) != 0) { |
| target.writeByte(subclassTag); |
| } |
| |
| // if its a subclass, use the corresponding subclass serializer, |
| // otherwise serialize each field with our field serializers |
| if ((flags & NO_SUBCLASS) != 0) { |
| try { |
| for (int i = 0; i < numFields; i++) { |
| Object o = (fields[i] != null) ? fields[i].get(value) : null; |
| if (o == null) { |
| target.writeBoolean(true); // null field handling |
| } else { |
| target.writeBoolean(false); |
| fieldSerializers[i].serialize(o, target); |
| } |
| } |
| } catch (IllegalAccessException e) { |
| throw new RuntimeException("Error during POJO copy, this should not happen since we check the fields before.", e); |
| } |
| } else { |
| // subclass |
| if (subclassSerializer != null) { |
| subclassSerializer.serialize(value, target); |
| } |
| } |
| } |
| |
| @Override |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| public T deserialize(DataInputView source) throws IOException { |
| int flags = source.readByte(); |
| if((flags & IS_NULL) != 0) { |
| return null; |
| } |
| |
| T target; |
| |
| Class<?> actualSubclass = null; |
| TypeSerializer subclassSerializer = null; |
| |
| if ((flags & IS_SUBCLASS) != 0) { |
| String subclassName = source.readUTF(); |
| try { |
| actualSubclass = Class.forName(subclassName, true, cl); |
| } catch (ClassNotFoundException e) { |
| throw new RuntimeException("Cannot instantiate class.", e); |
| } |
| subclassSerializer = getSubclassSerializer(actualSubclass); |
| target = (T) subclassSerializer.createInstance(); |
| // also initialize fields for which the subclass serializer is not responsible |
| initializeFields(target); |
| } else if ((flags & IS_TAGGED_SUBCLASS) != 0) { |
| |
| int subclassTag = source.readByte(); |
| subclassSerializer = registeredSerializers[subclassTag]; |
| target = (T) subclassSerializer.createInstance(); |
| // also initialize fields for which the subclass serializer is not responsible |
| initializeFields(target); |
| } else { |
| target = createInstance(); |
| } |
| |
| if ((flags & NO_SUBCLASS) != 0) { |
| try { |
| for (int i = 0; i < numFields; i++) { |
| boolean isNull = source.readBoolean(); |
| |
| if (fields[i] != null) { |
| if (isNull) { |
| fields[i].set(target, null); |
| } else { |
| Object field = fieldSerializers[i].deserialize(source); |
| fields[i].set(target, field); |
| } |
| } else if (!isNull) { |
| // read and dump a pre-existing field value |
| fieldSerializers[i].deserialize(source); |
| } |
| } |
| } catch (IllegalAccessException e) { |
| throw new RuntimeException("Error during POJO copy, this should not happen since we check the fields before.", e); |
| } |
| } else { |
| if (subclassSerializer != null) { |
| target = (T) subclassSerializer.deserialize(target, source); |
| } |
| } |
| return target; |
| } |
| |
| @Override |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| public T deserialize(T reuse, DataInputView source) throws IOException { |
| |
| // handle null values |
| int flags = source.readByte(); |
| if((flags & IS_NULL) != 0) { |
| return null; |
| } |
| |
| Class<?> subclass = null; |
| TypeSerializer subclassSerializer = null; |
| if ((flags & IS_SUBCLASS) != 0) { |
| String subclassName = source.readUTF(); |
| try { |
| subclass = Class.forName(subclassName, true, cl); |
| } catch (ClassNotFoundException e) { |
| throw new RuntimeException("Cannot instantiate class.", e); |
| } |
| subclassSerializer = getSubclassSerializer(subclass); |
| |
| if (reuse == null || subclass != reuse.getClass()) { |
| // cannot reuse |
| reuse = (T) subclassSerializer.createInstance(); |
| // also initialize fields for which the subclass serializer is not responsible |
| initializeFields(reuse); |
| } |
| } else if ((flags & IS_TAGGED_SUBCLASS) != 0) { |
| int subclassTag = source.readByte(); |
| subclassSerializer = registeredSerializers[subclassTag]; |
| |
| if (reuse == null || ((PojoSerializer)subclassSerializer).clazz != reuse.getClass()) { |
| // cannot reuse |
| reuse = (T) subclassSerializer.createInstance(); |
| // also initialize fields for which the subclass serializer is not responsible |
| initializeFields(reuse); |
| } |
| } else { |
| if (reuse == null || clazz != reuse.getClass()) { |
| reuse = createInstance(); |
| } |
| } |
| |
| if ((flags & NO_SUBCLASS) != 0) { |
| try { |
| for (int i = 0; i < numFields; i++) { |
| boolean isNull = source.readBoolean(); |
| |
| if (fields[i] != null) { |
| if (isNull) { |
| fields[i].set(reuse, null); |
| } else { |
| Object field; |
| |
| Object reuseField = fields[i].get(reuse); |
| if (reuseField != null) { |
| field = fieldSerializers[i].deserialize(reuseField, source); |
| } else { |
| field = fieldSerializers[i].deserialize(source); |
| } |
| |
| fields[i].set(reuse, field); |
| } |
| } else if (!isNull) { |
| // read and dump a pre-existing field value |
| fieldSerializers[i].deserialize(source); |
| } |
| } |
| } catch (IllegalAccessException e) { |
| throw new RuntimeException("Error during POJO copy, this should not happen since we check the fields before.", e); |
| } |
| } else { |
| if (subclassSerializer != null) { |
| reuse = (T) subclassSerializer.deserialize(reuse, source); |
| } |
| } |
| |
| return reuse; |
| } |
| |
| @Override |
| public void copy(DataInputView source, DataOutputView target) throws IOException { |
| // copy the flags |
| int flags = source.readByte(); |
| target.writeByte(flags); |
| |
| if ((flags & IS_NULL) != 0) { |
| // is a null value, nothing further to copy |
| return; |
| } |
| |
| TypeSerializer<?> subclassSerializer = null; |
| if ((flags & IS_SUBCLASS) != 0) { |
| String className = source.readUTF(); |
| target.writeUTF(className); |
| try { |
| Class<?> subclass = Class.forName(className, true, Thread.currentThread() |
| .getContextClassLoader()); |
| subclassSerializer = getSubclassSerializer(subclass); |
| } catch (ClassNotFoundException e) { |
| throw new RuntimeException("Cannot instantiate class.", e); |
| } |
| } else if ((flags & IS_TAGGED_SUBCLASS) != 0) { |
| int subclassTag = source.readByte(); |
| target.writeByte(subclassTag); |
| subclassSerializer = registeredSerializers[subclassTag]; |
| } |
| |
| if ((flags & NO_SUBCLASS) != 0) { |
| for (int i = 0; i < numFields; i++) { |
| boolean isNull = source.readBoolean(); |
| target.writeBoolean(isNull); |
| if (!isNull) { |
| fieldSerializers[i].copy(source, target); |
| } |
| } |
| } else { |
| if (subclassSerializer != null) { |
| subclassSerializer.copy(source, target); |
| } |
| } |
| } |
| |
| @Override |
| public int hashCode() { |
| return 31 * (31 * Arrays.hashCode(fieldSerializers) + Arrays.hashCode(registeredSerializers)) + |
| Objects.hash(clazz, numFields, registeredClasses); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj instanceof PojoSerializer) { |
| PojoSerializer<?> other = (PojoSerializer<?>) obj; |
| |
| return other.canEqual(this) && |
| clazz == other.clazz && |
| Arrays.equals(fieldSerializers, other.fieldSerializers) && |
| Arrays.equals(registeredSerializers, other.registeredSerializers) && |
| numFields == other.numFields && |
| registeredClasses.equals(other.registeredClasses); |
| } else { |
| return false; |
| } |
| } |
| |
| @Override |
| public boolean canEqual(Object obj) { |
| return obj instanceof PojoSerializer; |
| } |
| |
| // -------------------------------------------------------------------------------------------- |
| // Serializer configuration snapshotting & compatibility |
| // -------------------------------------------------------------------------------------------- |
| |
| @Override |
| public PojoSerializerConfigSnapshot<T> snapshotConfiguration() { |
| return buildConfigSnapshot( |
| clazz, |
| registeredClasses, |
| registeredSerializers, |
| fields, |
| fieldSerializers, |
| subclassSerializerCache); |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public CompatibilityResult<T> ensureCompatibility(TypeSerializerConfigSnapshot configSnapshot) { |
| if (configSnapshot instanceof PojoSerializerConfigSnapshot) { |
| final PojoSerializerConfigSnapshot<T> config = (PojoSerializerConfigSnapshot<T>) configSnapshot; |
| |
| boolean requiresMigration = false; |
| |
| if (clazz.equals(config.getTypeClass())) { |
| if (this.numFields == config.getFieldToSerializerConfigSnapshot().size()) { |
| |
| CompatibilityResult<?> compatResult; |
| |
| // ----------- check field order and compatibility of field serializers ----------- |
| |
| // reordered fields and their serializers; |
| // this won't be applied to this serializer until all compatibility checks have been completed |
| final Field[] reorderedFields = new Field[this.numFields]; |
| final TypeSerializer<Object>[] reorderedFieldSerializers = |
| (TypeSerializer<Object>[]) new TypeSerializer<?>[this.numFields]; |
| |
| int i = 0; |
| for (Map.Entry<String, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> fieldToConfigSnapshotEntry |
| : config.getFieldToSerializerConfigSnapshot().entrySet()) { |
| |
| int fieldIndex = findField(fieldToConfigSnapshotEntry.getKey()); |
| if (fieldIndex != -1) { |
| reorderedFields[i] = fields[fieldIndex]; |
| |
| compatResult = CompatibilityUtil.resolveCompatibilityResult( |
| fieldToConfigSnapshotEntry.getValue().f0, |
| UnloadableDummyTypeSerializer.class, |
| fieldToConfigSnapshotEntry.getValue().f1, |
| fieldSerializers[fieldIndex]); |
| |
| if (compatResult.isRequiresMigration()) { |
| requiresMigration = true; |
| |
| if (compatResult.getConvertDeserializer() != null) { |
| reorderedFieldSerializers[i] = (TypeSerializer<Object>) compatResult.getConvertDeserializer(); |
| } else { |
| return CompatibilityResult.requiresMigration(); |
| } |
| } else { |
| reorderedFieldSerializers[i] = fieldSerializers[fieldIndex]; |
| } |
| } else { |
| return CompatibilityResult.requiresMigration(); |
| } |
| |
| i++; |
| } |
| |
| // ---- check subclass registration order and compatibility of registered serializers ---- |
| |
| // reordered subclass registrations and their serializers; |
| // this won't be applied to this serializer until all compatibility checks have been completed |
| final LinkedHashMap<Class<?>, Integer> reorderedRegisteredSubclassesToClasstags; |
| final TypeSerializer<?>[] reorderedRegisteredSubclassSerializers; |
| |
| final LinkedHashMap<Class<?>, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> previousRegistrations = |
| config.getRegisteredSubclassesToSerializerConfigSnapshots(); |
| |
| // the reconfigured list of registered subclasses will be the previous registered |
| // subclasses in the original order with new subclasses appended at the end |
| LinkedHashSet<Class<?>> reorderedRegisteredSubclasses = new LinkedHashSet<>(); |
| reorderedRegisteredSubclasses.addAll(previousRegistrations.keySet()); |
| reorderedRegisteredSubclasses.addAll( |
| getRegisteredSubclassesFromExecutionConfig(clazz, executionConfig)); |
| |
| // re-establish the registered class tags and serializers |
| reorderedRegisteredSubclassesToClasstags = createRegisteredSubclassTags(reorderedRegisteredSubclasses); |
| reorderedRegisteredSubclassSerializers = createRegisteredSubclassSerializers( |
| reorderedRegisteredSubclasses, executionConfig); |
| |
| i = 0; |
| for (Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot> previousRegisteredSerializerConfig : previousRegistrations.values()) { |
| // check compatibility of subclass serializer |
| compatResult = CompatibilityUtil.resolveCompatibilityResult( |
| previousRegisteredSerializerConfig.f0, |
| UnloadableDummyTypeSerializer.class, |
| previousRegisteredSerializerConfig.f1, |
| reorderedRegisteredSubclassSerializers[i]); |
| |
| if (compatResult.isRequiresMigration()) { |
| requiresMigration = true; |
| |
| if (compatResult.getConvertDeserializer() == null) { |
| return CompatibilityResult.requiresMigration(); |
| } |
| } |
| |
| i++; |
| } |
| |
| // ------------------ ensure compatibility of non-registered subclass serializers ------------------ |
| |
| // the rebuilt cache for non-registered subclass serializers; |
| // this won't be applied to this serializer until all compatibility checks have been completed |
| HashMap<Class<?>, TypeSerializer<?>> rebuiltCache = new HashMap<>(); |
| |
| for (Map.Entry<Class<?>, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> previousCachedEntry |
| : config.getNonRegisteredSubclassesToSerializerConfigSnapshots().entrySet()) { |
| |
| TypeSerializer<?> cachedSerializer = createSubclassSerializer(previousCachedEntry.getKey()); |
| |
| // check compatibility of cached subclass serializer |
| compatResult = CompatibilityUtil.resolveCompatibilityResult( |
| previousCachedEntry.getValue().f0, |
| UnloadableDummyTypeSerializer.class, |
| previousCachedEntry.getValue().f1, |
| cachedSerializer); |
| |
| if (compatResult.isRequiresMigration()) { |
| requiresMigration = true; |
| |
| if (compatResult.getConvertDeserializer() != null) { |
| rebuiltCache.put(previousCachedEntry.getKey(), cachedSerializer); |
| } else { |
| return CompatibilityResult.requiresMigration(); |
| } |
| } else { |
| rebuiltCache.put(previousCachedEntry.getKey(), cachedSerializer); |
| } |
| } |
| |
| // completed compatibility checks; up to this point, we can just reconfigure |
| // the serializer so that it is compatible and migration is not required |
| |
| if (!requiresMigration) { |
| this.fields = reorderedFields; |
| this.fieldSerializers = reorderedFieldSerializers; |
| |
| this.registeredClasses = reorderedRegisteredSubclassesToClasstags; |
| this.registeredSerializers = reorderedRegisteredSubclassSerializers; |
| |
| this.subclassSerializerCache = rebuiltCache; |
| |
| return CompatibilityResult.compatible(); |
| } else { |
| return CompatibilityResult.requiresMigration( |
| new PojoSerializer<>( |
| clazz, |
| reorderedFields, |
| reorderedFieldSerializers, |
| reorderedRegisteredSubclassesToClasstags, |
| reorderedRegisteredSubclassSerializers, |
| rebuiltCache)); |
| } |
| } |
| } |
| } |
| |
| return CompatibilityResult.requiresMigration(); |
| } |
| |
| public static final class PojoSerializerConfigSnapshot<T> extends GenericTypeSerializerConfigSnapshot<T> { |
| |
| private static final int VERSION = 1; |
| |
| /** |
| * Ordered map of POJO fields to their corresponding serializers and its configuration snapshots. |
| * |
| * <p>Ordering of the fields is kept so that new Pojo serializers for previous data |
| * may reorder the fields in case they are different. The order of the fields need to |
| * stay the same for binary compatibility, as the field order is part of the serialization format. |
| */ |
| private LinkedHashMap<String, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> fieldToSerializerConfigSnapshot; |
| |
| /** |
| * Ordered map of registered subclasses to their corresponding serializers and its configuration snapshots. |
| * |
| * <p>Ordering of the registered subclasses is kept so that new Pojo serializers for previous data |
| * may retain the same class tag used for registered subclasses. Newly registered subclasses that |
| * weren't present before should be appended with the next available class tag. |
| */ |
| private LinkedHashMap<Class<?>, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> registeredSubclassesToSerializerConfigSnapshots; |
| |
| /** |
| * Previously cached non-registered subclass serializers and its configuration snapshots. |
| * |
| * <p>This is kept so that new Pojo serializers may eagerly repopulate their |
| * cache with reconfigured subclass serializers. |
| */ |
| private HashMap<Class<?>, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> nonRegisteredSubclassesToSerializerConfigSnapshots; |
| |
| private boolean ignoreTypeSerializerSerialization; |
| |
| /** This empty nullary constructor is required for deserializing the configuration. */ |
| public PojoSerializerConfigSnapshot() {} |
| |
| public PojoSerializerConfigSnapshot( |
| Class<T> pojoType, |
| LinkedHashMap<String, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> fieldToSerializerConfigSnapshot, |
| LinkedHashMap<Class<?>, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> registeredSubclassesToSerializerConfigSnapshots, |
| HashMap<Class<?>, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> nonRegisteredSubclassesToSerializerConfigSnapshots) { |
| |
| this( |
| pojoType, |
| fieldToSerializerConfigSnapshot, |
| registeredSubclassesToSerializerConfigSnapshots, |
| nonRegisteredSubclassesToSerializerConfigSnapshots, |
| false); |
| } |
| |
| public PojoSerializerConfigSnapshot( |
| Class<T> pojoType, |
| LinkedHashMap<String, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> fieldToSerializerConfigSnapshot, |
| LinkedHashMap<Class<?>, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> registeredSubclassesToSerializerConfigSnapshots, |
| HashMap<Class<?>, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> nonRegisteredSubclassesToSerializerConfigSnapshots, |
| boolean ignoreTypeSerializerSerialization) { |
| |
| super(pojoType); |
| |
| this.fieldToSerializerConfigSnapshot = |
| Preconditions.checkNotNull(fieldToSerializerConfigSnapshot); |
| this.registeredSubclassesToSerializerConfigSnapshots = |
| Preconditions.checkNotNull(registeredSubclassesToSerializerConfigSnapshots); |
| this.nonRegisteredSubclassesToSerializerConfigSnapshots = |
| Preconditions.checkNotNull(nonRegisteredSubclassesToSerializerConfigSnapshots); |
| |
| this.ignoreTypeSerializerSerialization = ignoreTypeSerializerSerialization; |
| } |
| |
| @Override |
| public void write(DataOutputView out) throws IOException { |
| super.write(out); |
| |
| try ( |
| ByteArrayOutputStreamWithPos outWithPos = new ByteArrayOutputStreamWithPos(); |
| DataOutputViewStreamWrapper outViewWrapper = new DataOutputViewStreamWrapper(outWithPos)) { |
| |
| // --- write fields and their serializers, in order |
| |
| out.writeInt(fieldToSerializerConfigSnapshot.size()); |
| for (Map.Entry<String, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> entry |
| : fieldToSerializerConfigSnapshot.entrySet()) { |
| |
| outViewWrapper.writeUTF(entry.getKey()); |
| |
| out.writeInt(outWithPos.getPosition()); |
| if (!ignoreTypeSerializerSerialization) { |
| TypeSerializerSerializationUtil.writeSerializer(outViewWrapper, entry.getValue().f0); |
| } |
| |
| out.writeInt(outWithPos.getPosition()); |
| TypeSerializerSerializationUtil.writeSerializerConfigSnapshot(outViewWrapper, entry.getValue().f1); |
| } |
| |
| // --- write registered subclasses and their serializers, in registration order |
| |
| out.writeInt(registeredSubclassesToSerializerConfigSnapshots.size()); |
| for (Map.Entry<Class<?>, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> entry |
| : registeredSubclassesToSerializerConfigSnapshots.entrySet()) { |
| |
| outViewWrapper.writeUTF(entry.getKey().getName()); |
| |
| out.writeInt(outWithPos.getPosition()); |
| if (!ignoreTypeSerializerSerialization) { |
| TypeSerializerSerializationUtil.writeSerializer(outViewWrapper, entry.getValue().f0); |
| } |
| |
| out.writeInt(outWithPos.getPosition()); |
| TypeSerializerSerializationUtil.writeSerializerConfigSnapshot(outViewWrapper, entry.getValue().f1); |
| } |
| |
| // --- write snapshot of non-registered subclass serializer cache |
| |
| out.writeInt(nonRegisteredSubclassesToSerializerConfigSnapshots.size()); |
| for (Map.Entry<Class<?>, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> entry |
| : nonRegisteredSubclassesToSerializerConfigSnapshots.entrySet()) { |
| |
| outViewWrapper.writeUTF(entry.getKey().getName()); |
| |
| out.writeInt(outWithPos.getPosition()); |
| if (!ignoreTypeSerializerSerialization) { |
| TypeSerializerSerializationUtil.writeSerializer(outViewWrapper, entry.getValue().f0); |
| } |
| |
| out.writeInt(outWithPos.getPosition()); |
| TypeSerializerSerializationUtil.writeSerializerConfigSnapshot(outViewWrapper, entry.getValue().f1); |
| } |
| |
| out.writeInt(outWithPos.getPosition()); |
| out.write(outWithPos.getBuf(), 0 , outWithPos.getPosition()); |
| } |
| } |
| |
| @Override |
| public void read(DataInputView in) throws IOException { |
| super.read(in); |
| |
| int numFields = in.readInt(); |
| int[] fieldSerializerOffsets = new int[numFields * 2]; |
| for (int i = 0; i < numFields; i++) { |
| fieldSerializerOffsets[i * 2] = in.readInt(); |
| fieldSerializerOffsets[i * 2 + 1] = in.readInt(); |
| } |
| |
| |
| int numRegisteredSubclasses = in.readInt(); |
| int[] registeredSerializerOffsets = new int[numRegisteredSubclasses * 2]; |
| for (int i = 0; i < numRegisteredSubclasses; i++) { |
| registeredSerializerOffsets[i * 2] = in.readInt(); |
| registeredSerializerOffsets[i * 2 + 1] = in.readInt(); |
| } |
| |
| int numCachedSubclassSerializers = in.readInt(); |
| int[] cachedSerializerOffsets = new int[numCachedSubclassSerializers * 2]; |
| for (int i = 0; i < numCachedSubclassSerializers; i++) { |
| cachedSerializerOffsets[i * 2] = in.readInt(); |
| cachedSerializerOffsets[i * 2 + 1] = in.readInt(); |
| } |
| |
| int totalBytes = in.readInt(); |
| byte[] buffer = new byte[totalBytes]; |
| in.readFully(buffer); |
| |
| try ( |
| ByteArrayInputStreamWithPos inWithPos = new ByteArrayInputStreamWithPos(buffer); |
| DataInputViewStreamWrapper inViewWrapper = new DataInputViewStreamWrapper(inWithPos)) { |
| |
| // --- read fields and their serializers, in order |
| |
| this.fieldToSerializerConfigSnapshot = new LinkedHashMap<>(numFields); |
| String fieldName; |
| TypeSerializer<?> fieldSerializer; |
| TypeSerializerConfigSnapshot fieldSerializerConfigSnapshot; |
| for (int i = 0; i < numFields; i++) { |
| fieldName = inViewWrapper.readUTF(); |
| |
| inWithPos.setPosition(fieldSerializerOffsets[i * 2]); |
| fieldSerializer = TypeSerializerSerializationUtil.tryReadSerializer(inViewWrapper, getUserCodeClassLoader(), true); |
| |
| inWithPos.setPosition(fieldSerializerOffsets[i * 2 + 1]); |
| fieldSerializerConfigSnapshot = TypeSerializerSerializationUtil.readSerializerConfigSnapshot(inViewWrapper, getUserCodeClassLoader()); |
| |
| fieldToSerializerConfigSnapshot.put( |
| fieldName, |
| new Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>(fieldSerializer, fieldSerializerConfigSnapshot)); |
| } |
| |
| // --- read registered subclasses and their serializers, in registration order |
| |
| this.registeredSubclassesToSerializerConfigSnapshots = new LinkedHashMap<>(numRegisteredSubclasses); |
| String registeredSubclassname; |
| Class<?> registeredSubclass; |
| TypeSerializer<?> registeredSubclassSerializer; |
| TypeSerializerConfigSnapshot registeredSubclassSerializerConfigSnapshot; |
| for (int i = 0; i < numRegisteredSubclasses; i++) { |
| registeredSubclassname = inViewWrapper.readUTF(); |
| try { |
| registeredSubclass = Class.forName(registeredSubclassname, true, getUserCodeClassLoader()); |
| } catch (ClassNotFoundException e) { |
| throw new IOException("Cannot find requested class " + registeredSubclassname + " in classpath.", e); |
| } |
| |
| inWithPos.setPosition(registeredSerializerOffsets[i * 2]); |
| registeredSubclassSerializer = TypeSerializerSerializationUtil.tryReadSerializer(inViewWrapper, getUserCodeClassLoader(), true); |
| |
| inWithPos.setPosition(registeredSerializerOffsets[i * 2 + 1]); |
| registeredSubclassSerializerConfigSnapshot = TypeSerializerSerializationUtil.readSerializerConfigSnapshot(inViewWrapper, getUserCodeClassLoader()); |
| |
| this.registeredSubclassesToSerializerConfigSnapshots.put( |
| registeredSubclass, |
| new Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>(registeredSubclassSerializer, registeredSubclassSerializerConfigSnapshot)); |
| } |
| |
| // --- read snapshot of non-registered subclass serializer cache |
| |
| this.nonRegisteredSubclassesToSerializerConfigSnapshots = new HashMap<>(numCachedSubclassSerializers); |
| String cachedSubclassname; |
| Class<?> cachedSubclass; |
| TypeSerializer<?> cachedSubclassSerializer; |
| TypeSerializerConfigSnapshot cachedSubclassSerializerConfigSnapshot; |
| for (int i = 0; i < numCachedSubclassSerializers; i++) { |
| cachedSubclassname = inViewWrapper.readUTF(); |
| try { |
| cachedSubclass = Class.forName(cachedSubclassname, true, getUserCodeClassLoader()); |
| } catch (ClassNotFoundException e) { |
| throw new IOException("Cannot find requested class " + cachedSubclassname + " in classpath.", e); |
| } |
| |
| inWithPos.setPosition(cachedSerializerOffsets[i * 2]); |
| cachedSubclassSerializer = TypeSerializerSerializationUtil.tryReadSerializer(inViewWrapper, getUserCodeClassLoader(), true); |
| |
| inWithPos.setPosition(cachedSerializerOffsets[i * 2 + 1]); |
| cachedSubclassSerializerConfigSnapshot = TypeSerializerSerializationUtil.readSerializerConfigSnapshot(inViewWrapper, getUserCodeClassLoader()); |
| |
| this.nonRegisteredSubclassesToSerializerConfigSnapshots.put( |
| cachedSubclass, |
| new Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>(cachedSubclassSerializer, cachedSubclassSerializerConfigSnapshot)); |
| } |
| } |
| } |
| |
| @Override |
| public int getVersion() { |
| return VERSION; |
| } |
| |
| public LinkedHashMap<String, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> getFieldToSerializerConfigSnapshot() { |
| return fieldToSerializerConfigSnapshot; |
| } |
| |
| public LinkedHashMap<Class<?>, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> getRegisteredSubclassesToSerializerConfigSnapshots() { |
| return registeredSubclassesToSerializerConfigSnapshots; |
| } |
| |
| public HashMap<Class<?>, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> getNonRegisteredSubclassesToSerializerConfigSnapshots() { |
| return nonRegisteredSubclassesToSerializerConfigSnapshots; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return super.equals(obj) |
| && (obj instanceof PojoSerializerConfigSnapshot) |
| && fieldToSerializerConfigSnapshot.equals(((PojoSerializerConfigSnapshot) obj).getFieldToSerializerConfigSnapshot()) |
| && registeredSubclassesToSerializerConfigSnapshots.equals(((PojoSerializerConfigSnapshot) obj).getRegisteredSubclassesToSerializerConfigSnapshots()) |
| && nonRegisteredSubclassesToSerializerConfigSnapshots.equals(((PojoSerializerConfigSnapshot) obj).nonRegisteredSubclassesToSerializerConfigSnapshots); |
| } |
| |
| @Override |
| public int hashCode() { |
| return super.hashCode() |
| + Objects.hash( |
| fieldToSerializerConfigSnapshot, |
| registeredSubclassesToSerializerConfigSnapshots, |
| nonRegisteredSubclassesToSerializerConfigSnapshots); |
| } |
| } |
| |
| // -------------------------------------------------------------------------------------------- |
| |
| private void writeObject(ObjectOutputStream out) throws IOException, ClassNotFoundException { |
| out.defaultWriteObject(); |
| out.writeInt(fields.length); |
| for (Field field: fields) { |
| FieldSerializer.serializeField(field, out); |
| } |
| } |
| |
| private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { |
| in.defaultReadObject(); |
| int numFields = in.readInt(); |
| fields = new Field[numFields]; |
| for (int i = 0; i < numFields; i++) { |
| // the deserialized Field may be null if the field no longer exists in the POJO; |
| // in this case, when de-/serializing and copying instances using this serializer |
| // instance, the missing fields will simply be skipped |
| fields[i] = FieldSerializer.deserializeField(in); |
| } |
| |
| cl = Thread.currentThread().getContextClassLoader(); |
| subclassSerializerCache = new HashMap<Class<?>, TypeSerializer<?>>(); |
| } |
| |
| // -------------------------------------------------------------------------------------------- |
| // Utilities |
| // -------------------------------------------------------------------------------------------- |
| |
| /** |
| * Extracts the subclasses of the base POJO class registered in the execution config. |
| */ |
| private static LinkedHashSet<Class<?>> getRegisteredSubclassesFromExecutionConfig( |
| Class<?> basePojoClass, |
| ExecutionConfig executionConfig) { |
| |
| LinkedHashSet<Class<?>> subclassesInRegistrationOrder = new LinkedHashSet<>(executionConfig.getRegisteredPojoTypes().size()); |
| for (Class<?> registeredClass : executionConfig.getRegisteredPojoTypes()) { |
| if (registeredClass.equals(basePojoClass)) { |
| continue; |
| } |
| if (!basePojoClass.isAssignableFrom(registeredClass)) { |
| continue; |
| } |
| subclassesInRegistrationOrder.add(registeredClass); |
| } |
| |
| return subclassesInRegistrationOrder; |
| } |
| |
| /** |
| * Builds map of registered subclasses to their class tags. |
| * Class tags will be integers starting from 0, assigned incrementally with the order of provided subclasses. |
| */ |
| private static LinkedHashMap<Class<?>, Integer> createRegisteredSubclassTags(LinkedHashSet<Class<?>> registeredSubclasses) { |
| final LinkedHashMap<Class<?>, Integer> classToTag = new LinkedHashMap<>(); |
| |
| int id = 0; |
| for (Class<?> registeredClass : registeredSubclasses) { |
| classToTag.put(registeredClass, id); |
| id ++; |
| } |
| |
| return classToTag; |
| } |
| |
| /** |
| * Creates an array of serializers for provided list of registered subclasses. |
| * Order of returned serializers will correspond to order of provided subclasses. |
| */ |
| private static TypeSerializer<?>[] createRegisteredSubclassSerializers( |
| LinkedHashSet<Class<?>> registeredSubclasses, |
| ExecutionConfig executionConfig) { |
| |
| final TypeSerializer<?>[] subclassSerializers = new TypeSerializer[registeredSubclasses.size()]; |
| |
| int i = 0; |
| for (Class<?> registeredClass : registeredSubclasses) { |
| subclassSerializers[i] = TypeExtractor.createTypeInfo(registeredClass).createSerializer(executionConfig); |
| i++; |
| } |
| |
| return subclassSerializers; |
| } |
| |
| /** |
| * Fetches cached serializer for a non-registered subclass; |
| * also creates the serializer if it doesn't exist yet. |
| * |
| * This method is also exposed to package-private access |
| * for testing purposes. |
| */ |
| TypeSerializer<?> getSubclassSerializer(Class<?> subclass) { |
| TypeSerializer<?> result = subclassSerializerCache.get(subclass); |
| if (result == null) { |
| result = createSubclassSerializer(subclass); |
| subclassSerializerCache.put(subclass, result); |
| } |
| return result; |
| } |
| |
| private TypeSerializer<?> createSubclassSerializer(Class<?> subclass) { |
| TypeSerializer<?> serializer = TypeExtractor.createTypeInfo(subclass).createSerializer(executionConfig); |
| |
| if (serializer instanceof PojoSerializer) { |
| PojoSerializer<?> subclassSerializer = (PojoSerializer<?>) serializer; |
| subclassSerializer.copyBaseFieldOrder(this); |
| } |
| |
| return serializer; |
| } |
| |
| /** |
| * Finds and returns the order (0-based) of a POJO field. |
| * Returns -1 if the field does not exist for this POJO. |
| */ |
| private int findField(String fieldName) { |
| int foundIndex = 0; |
| for (Field field : fields) { |
| if (field != null && fieldName.equals(field.getName())) { |
| return foundIndex; |
| } |
| |
| foundIndex++; |
| } |
| |
| return -1; |
| } |
| |
| private void copyBaseFieldOrder(PojoSerializer<?> baseSerializer) { |
| // do nothing for now, but in the future, adapt subclass serializer to have same |
| // ordering as base class serializer so that binary comparison on base class fields |
| // can work |
| } |
| |
| /** |
| * Build and return a snapshot of the serializer's parameters and currently cached serializers. |
| */ |
| private static <T> PojoSerializerConfigSnapshot<T> buildConfigSnapshot( |
| Class<T> pojoType, |
| LinkedHashMap<Class<?>, Integer> registeredSubclassesToTags, |
| TypeSerializer<?>[] registeredSubclassSerializers, |
| Field[] fields, |
| TypeSerializer<?>[] fieldSerializers, |
| HashMap<Class<?>, TypeSerializer<?>> nonRegisteredSubclassSerializerCache) { |
| |
| final LinkedHashMap<String, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> fieldToSerializerConfigSnapshots = |
| new LinkedHashMap<>(fields.length); |
| |
| for (int i = 0; i < fields.length; i++) { |
| fieldToSerializerConfigSnapshots.put( |
| fields[i].getName(), |
| new Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>(fieldSerializers[i], fieldSerializers[i].snapshotConfiguration())); |
| } |
| |
| final LinkedHashMap<Class<?>, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> registeredSubclassesToSerializerConfigSnapshots = |
| new LinkedHashMap<>(registeredSubclassesToTags.size()); |
| |
| for (Map.Entry<Class<?>, Integer> entry : registeredSubclassesToTags.entrySet()) { |
| registeredSubclassesToSerializerConfigSnapshots.put( |
| entry.getKey(), |
| new Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>( |
| registeredSubclassSerializers[entry.getValue()], |
| registeredSubclassSerializers[entry.getValue()].snapshotConfiguration())); |
| } |
| |
| final HashMap<Class<?>, Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>> nonRegisteredSubclassesToSerializerConfigSnapshots = |
| new LinkedHashMap<>(nonRegisteredSubclassSerializerCache.size()); |
| |
| for (Map.Entry<Class<?>, TypeSerializer<?>> entry : nonRegisteredSubclassSerializerCache.entrySet()) { |
| nonRegisteredSubclassesToSerializerConfigSnapshots.put( |
| entry.getKey(), |
| new Tuple2<TypeSerializer<?>, TypeSerializerConfigSnapshot>( |
| entry.getValue(), |
| entry.getValue().snapshotConfiguration())); |
| } |
| |
| return new PojoSerializerConfigSnapshot<>( |
| pojoType, |
| fieldToSerializerConfigSnapshots, |
| registeredSubclassesToSerializerConfigSnapshots, |
| nonRegisteredSubclassesToSerializerConfigSnapshots); |
| } |
| |
| // -------------------------------------------------------------------------------------------- |
| // Test utilities |
| // -------------------------------------------------------------------------------------------- |
| |
| @VisibleForTesting |
| Field[] getFields() { |
| return fields; |
| } |
| |
| @VisibleForTesting |
| TypeSerializer<?>[] getFieldSerializers() { |
| return fieldSerializers; |
| } |
| |
| @VisibleForTesting |
| LinkedHashMap<Class<?>, Integer> getRegisteredClasses() { |
| return registeredClasses; |
| } |
| |
| @VisibleForTesting |
| TypeSerializer<?>[] getRegisteredSerializers() { |
| return registeredSerializers; |
| } |
| |
| @VisibleForTesting |
| HashMap<Class<?>, TypeSerializer<?>> getSubclassSerializerCache() { |
| return subclassSerializerCache; |
| } |
| } |