Skip to content

Commit dfc1164

Browse files
authored
JAVA-3070: Make CqlVector and CqlDuration serializable (#1676)
1 parent 63ed4ce commit dfc1164

File tree

4 files changed

+119
-2
lines changed

4 files changed

+119
-2
lines changed

core/src/main/java/com/datastax/oss/driver/api/core/data/CqlDuration.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.datastax.oss.driver.shaded.guava.common.base.Preconditions;
2121
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList;
2222
import edu.umd.cs.findbugs.annotations.NonNull;
23+
import java.io.Serializable;
2324
import java.time.Duration;
2425
import java.time.Period;
2526
import java.time.temporal.ChronoUnit;
@@ -42,7 +43,9 @@
4243
* in time, regardless of the calendar).
4344
*/
4445
@Immutable
45-
public final class CqlDuration implements TemporalAmount {
46+
public final class CqlDuration implements TemporalAmount, Serializable {
47+
48+
private static final long serialVersionUID = 1L;
4649

4750
@VisibleForTesting static final long NANOS_PER_MICRO = 1000L;
4851
@VisibleForTesting static final long NANOS_PER_MILLI = 1000 * NANOS_PER_MICRO;
@@ -75,8 +78,11 @@ public final class CqlDuration implements TemporalAmount {
7578
private static final ImmutableList<TemporalUnit> TEMPORAL_UNITS =
7679
ImmutableList.of(ChronoUnit.MONTHS, ChronoUnit.DAYS, ChronoUnit.NANOS);
7780

81+
/** @serial */
7882
private final int months;
83+
/** @serial */
7984
private final int days;
85+
/** @serial */
8086
private final long nanoseconds;
8187

8288
private CqlDuration(int months, int days, long nanoseconds) {

core/src/main/java/com/datastax/oss/driver/api/core/data/CqlVector.java

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@
2222
import com.datastax.oss.driver.shaded.guava.common.collect.Iterables;
2323
import com.datastax.oss.driver.shaded.guava.common.collect.Streams;
2424
import edu.umd.cs.findbugs.annotations.NonNull;
25+
import java.io.IOException;
26+
import java.io.InvalidObjectException;
27+
import java.io.ObjectInputStream;
28+
import java.io.ObjectOutputStream;
29+
import java.io.ObjectStreamException;
30+
import java.io.Serializable;
2531
import java.util.ArrayList;
2632
import java.util.Arrays;
2733
import java.util.Iterator;
@@ -44,7 +50,7 @@
4450
* where possible we've tried to make the API of this class similar to the equivalent methods on
4551
* {@link List}.
4652
*/
47-
public class CqlVector<T extends Number> implements Iterable<T> {
53+
public class CqlVector<T extends Number> implements Iterable<T>, Serializable {
4854

4955
/**
5056
* Create a new CqlVector containing the specified values.
@@ -190,4 +196,58 @@ public int hashCode() {
190196
public String toString() {
191197
return Iterables.toString(this.list);
192198
}
199+
200+
/**
201+
* Serialization proxy for CqlVector. Allows serialization regardless of implementation of list
202+
* field.
203+
*
204+
* @param <T> inner type of CqlVector, assume Number is always Serializable.
205+
*/
206+
private static class SerializationProxy<T extends Number> implements Serializable {
207+
208+
private static final long serialVersionUID = 1;
209+
210+
private transient List<T> list;
211+
212+
SerializationProxy(CqlVector<T> vector) {
213+
this.list = vector.list;
214+
}
215+
216+
// Reconstruct CqlVector's list of elements.
217+
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
218+
stream.defaultReadObject();
219+
220+
int size = stream.readInt();
221+
list = new ArrayList<>(size);
222+
for (int i = 0; i < size; i++) {
223+
list.add((T) stream.readObject());
224+
}
225+
}
226+
227+
// Return deserialized proxy object as CqlVector.
228+
private Object readResolve() throws ObjectStreamException {
229+
return new CqlVector(list);
230+
}
231+
232+
// Write size of CqlVector followed by items in order.
233+
private void writeObject(ObjectOutputStream stream) throws IOException {
234+
stream.defaultWriteObject();
235+
236+
stream.writeInt(list.size());
237+
for (T item : list) {
238+
stream.writeObject(item);
239+
}
240+
}
241+
}
242+
243+
/** @serialData The number of elements in the vector, followed by each element in-order. */
244+
private Object writeReplace() {
245+
return new SerializationProxy(this);
246+
}
247+
248+
private void readObject(@SuppressWarnings("unused") ObjectInputStream stream)
249+
throws InvalidObjectException {
250+
// Should never be called since we serialized a proxy
251+
throw new InvalidObjectException("Proxy required");
252+
}
193253
}

core/src/test/java/com/datastax/oss/driver/api/core/data/CqlDurationTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static org.assertj.core.api.Assertions.fail;
2121

2222
import com.datastax.oss.driver.TestDataProviders;
23+
import com.datastax.oss.driver.internal.SerializationHelper;
2324
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
2425
import com.tngtech.java.junit.dataprovider.UseDataProvider;
2526
import java.time.ZonedDateTime;
@@ -190,4 +191,18 @@ public void should_subtract_from_temporal() {
190191
assertThat(dateTime.minus(CqlDuration.from("1h15s15ns")))
191192
.isEqualTo("2018-10-03T22:59:44.999999985-07:00[America/Los_Angeles]");
192193
}
194+
195+
@Test
196+
public void should_serialize_and_deserialize() throws Exception {
197+
CqlDuration initial = CqlDuration.from("3mo2d15s");
198+
CqlDuration deserialized = SerializationHelper.serializeAndDeserialize(initial);
199+
assertThat(deserialized).isEqualTo(initial);
200+
}
201+
202+
@Test
203+
public void should_serialize_and_deserialize_negative() throws Exception {
204+
CqlDuration initial = CqlDuration.from("-2d15m");
205+
CqlDuration deserialized = SerializationHelper.serializeAndDeserialize(initial);
206+
assertThat(deserialized).isEqualTo(initial);
207+
}
193208
}

core/src/test/java/com/datastax/oss/driver/api/core/data/CqlVectorTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2020

2121
import com.datastax.oss.driver.api.core.type.codec.TypeCodecs;
22+
import com.datastax.oss.driver.internal.SerializationHelper;
2223
import com.datastax.oss.driver.shaded.guava.common.collect.Iterators;
24+
import java.util.AbstractList;
2325
import java.util.ArrayList;
2426
import java.util.Arrays;
27+
import java.util.Collections;
2528
import java.util.List;
2629
import java.util.stream.Collectors;
2730
import org.assertj.core.util.Lists;
@@ -195,4 +198,37 @@ public void should_correctly_compare_vectors() {
195198
assertThat(vector1).isNotSameAs(vector5);
196199
assertThat(vector1).isNotEqualTo(vector5);
197200
}
201+
202+
@Test
203+
public void should_serialize_and_deserialize() throws Exception {
204+
CqlVector<Float> initial = CqlVector.newInstance(VECTOR_ARGS);
205+
CqlVector<Float> deserialized = SerializationHelper.serializeAndDeserialize(initial);
206+
assertThat(deserialized).isEqualTo(initial);
207+
}
208+
209+
@Test
210+
public void should_serialize_and_deserialize_empty_vector() throws Exception {
211+
CqlVector<Float> initial = CqlVector.newInstance(Collections.emptyList());
212+
CqlVector<Float> deserialized = SerializationHelper.serializeAndDeserialize(initial);
213+
assertThat(deserialized).isEqualTo(initial);
214+
}
215+
216+
@Test
217+
public void should_serialize_and_deserialize_unserializable_list() throws Exception {
218+
CqlVector<Float> initial =
219+
CqlVector.newInstance(
220+
new AbstractList<Float>() {
221+
@Override
222+
public Float get(int index) {
223+
return VECTOR_ARGS[index];
224+
}
225+
226+
@Override
227+
public int size() {
228+
return VECTOR_ARGS.length;
229+
}
230+
});
231+
CqlVector<Float> deserialized = SerializationHelper.serializeAndDeserialize(initial);
232+
assertThat(deserialized).isEqualTo(initial);
233+
}
198234
}

0 commit comments

Comments
 (0)