/*
* 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.cep;
import org.apache.flink.annotation.Internal;
import org.apache.flink.api.common.typeutils.CompatibilityResult;
import org.apache.flink.api.common.typeutils.TypeSerializer;
import org.apache.flink.api.common.typeutils.TypeSerializerConfigSnapshot;
import org.apache.flink.core.memory.DataInputView;
import org.apache.flink.core.memory.DataOutputView;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.IdentityHashMap;
/**
* Type serializer which keeps track of the serialized objects so that each object is only
* serialized once. If the same object shall be serialized again, then a reference handle is
* written instead.
*
* Avoiding duplication is achieved by keeping an internal identity hash map. This map contains
* all serialized objects. To make the serializer work it is important that the same serializer
* is used for a coherent serialization run. After the serialization has stopped, the identity
* hash map should be cleared.
*
* @param <T> Type of the element to be serialized
*/
@Internal
public final class NonDuplicatingTypeSerializer<T> extends TypeSerializer<T> {
private static final long serialVersionUID = -7633631762221447524L;
// underlying type serializer
private final TypeSerializer<T> typeSerializer;
// here we store the already serialized objects
private transient IdentityHashMap<T, Integer> identityMap;
// here we store the already deserialized objects
private transient ArrayList<T> elementList;
public NonDuplicatingTypeSerializer(final TypeSerializer<T> typeSerializer) {
this.typeSerializer = typeSerializer;
this.identityMap = new IdentityHashMap<>();
this.elementList = new ArrayList<>();
}
public TypeSerializer<T> getTypeSerializer() {
return typeSerializer;
}
/**
* Clears the data structures containing the already serialized/deserialized objects. This
* effectively resets the type serializer.
*/
public void clearReferences() {
identityMap.clear();
elementList.clear();
}
@Override
public boolean isImmutableType() {
return typeSerializer.isImmutableType();
}
@Override
public TypeSerializer<T> duplicate() {
return new NonDuplicatingTypeSerializer<>(typeSerializer);
}
@Override
public T createInstance() {
return typeSerializer.createInstance();
}
@Override
public T copy(T from) {
return typeSerializer.copy(from);
}
@Override
public T copy(T from, T reuse) {
return typeSerializer.copy(from, reuse);
}
@Override
public int getLength() {
return typeSerializer.getLength();
}
/**
* Serializes the given record.
* <p>
* First a boolean indicating whether a reference handle (true) or the object (false) is
* written. Then, either the reference handle or the object is written.
*
* @param record The record to serialize.
* @param target The output view to write the serialized data to.
*
* @throws IOException
*/
public void serialize(T record, DataOutputView target) throws IOException {
if (identityMap.containsKey(record)) {
target.writeBoolean(true);
target.writeInt(identityMap.get(record));
} else {
target.writeBoolean(false);
typeSerializer.serialize(record, target);
}
}
/**
* Deserializes an object from the input view.
* <p>
* First it reads a boolean indicating whether a reference handle or a serialized object
* follows.
*
* @param source The input view from which to read the data.
* @return The deserialized object
* @throws IOException
*/
public T deserialize(DataInputView source) throws IOException {
boolean alreadyRead = source.readBoolean();
if (alreadyRead) {
int index = source.readInt();
return elementList.get(index);
} else {
T element = typeSerializer.deserialize(source);
elementList.add(element);
return element;
}
}
@Override
public T deserialize(T reuse, DataInputView source) throws IOException {
return deserialize(source);
}
@Override
public void copy(DataInputView source, DataOutputView target) throws IOException {
boolean alreadyRead = source.readBoolean();
if (alreadyRead) {
int index = source.readInt();
typeSerializer.serialize(elementList.get(index), target);
} else {
T element = typeSerializer.deserialize(source);
typeSerializer.serialize(element, target);
}
}
@Override
public boolean equals(Object obj) {
if (obj instanceof NonDuplicatingTypeSerializer) {
@SuppressWarnings("unchecked")
NonDuplicatingTypeSerializer<T> other = (NonDuplicatingTypeSerializer<T>)obj;
return (other.canEqual(this) && typeSerializer.equals(other.typeSerializer));
} else {
return false;
}
}
@Override
public boolean canEqual(Object obj) {
return obj instanceof NonDuplicatingTypeSerializer;
}
@Override
public int hashCode() {
return typeSerializer.hashCode();
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
this.identityMap = new IdentityHashMap<>();
this.elementList = new ArrayList<>();
}
@Override
public TypeSerializerConfigSnapshot snapshotConfiguration() {
throw new UnsupportedOperationException("This serializer is not registered for managed state.");
}
@Override
public CompatibilityResult<T> ensureCompatibility(TypeSerializerConfigSnapshot configSnapshot) {
throw new UnsupportedOperationException("This serializer is not registered for managed state.");
}
}