Serializes an {@code Object} to the specified stream.
+ * + *The stream will be closed once the object is written. + * This avoids the need for a finally clause, and maybe also exception + * handling, in the application code.
+ * + *The stream passed in is not buffered internally within this method. + * This is the responsibility of your application if desired.
+ * + * @param obj the object to serialize to bytes, may be null + * @param outputStream the stream to write to, must not be null + * @throws IllegalArgumentException if {@code outputStream} is {@code null} + * @throws HoodieSerializationException (runtime) if the serialization fails + */ + public static void serialize(final Serializable obj, final OutputStream outputStream) { + if (outputStream == null) { + throw new IllegalArgumentException("The OutputStream must not be null"); + } + ObjectOutputStream out = null; + try { + // stream closed in the finally + out = new ObjectOutputStream(outputStream); + out.writeObject(obj); + + } catch (final IOException ex) { + throw new HoodieSerializationException("unable to serialize object", ex); + } finally { + try { + if (out != null) { + out.close(); + } + } catch (final IOException ex) { // NOPMD + // ignore close exception + } + } + } + + /** + *Serializes an {@code Object} to a byte array for + * storage/serialization.
+ * + * @param obj the object to serialize to bytes + * @return a byte[] with the converted Serializable + * @throws HoodieSerializationException (runtime) if the serialization fails + */ + public static byte[] serialize(final Serializable obj) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(512); + serialize(obj, baos); + return baos.toByteArray(); + } + + // Deserialize + //----------------------------------------------------------------------- + + /** + *+ * Deserializes an {@code Object} from the specified stream. + *
+ * + *+ * The stream will be closed once the object is written. This avoids the need for a finally clause, and maybe also + * exception handling, in the application code. + *
+ * + *+ * The stream passed in is not buffered internally within this method. This is the responsibility of your + * application if desired. + *
+ * + *+ * If the call site incorrectly types the return value, a {@link ClassCastException} is thrown from the call site. + * Without Generics in this declaration, the call site must type cast and can cause the same ClassCastException. + * Note that in both cases, the ClassCastException is in the call site, not in this method. + *
+ * + * @param+ * Deserializes a single {@code Object} from an array of bytes. + *
+ * + *+ * If the call site incorrectly types the return value, a {@link ClassCastException} is thrown from the call site. + * Without Generics in this declaration, the call site must type cast and can cause the same ClassCastException. + * Note that in both cases, the ClassCastException is in the call site, not in this method. + *
+ * + * @paramJoins the elements of the provided array into a single String + * containing the provided list of elements.
+ * + *No separator is added to the joined String. + * Null objects or empty strings within the array are represented by + * empty strings.
+ * + *+ * StringUtils.join(null) = null + * StringUtils.join([]) = "" + * StringUtils.join([null]) = "" + * StringUtils.join(["a", "b", "c"]) = "abc" + * StringUtils.join([null, "", "a"]) = "a" + *+ */ + public static
An immutable pair consisting of two {@code Object} elements.
+ * + *Although the implementation is immutable, there is no restriction on the objects + * that may be stored. If mutable objects are stored in the pair, then the pair + * itself effectively becomes mutable. The class is also {@code final}, so a subclass + * can not add undesirable behaviour.
+ * + *#ThreadSafe# if both paired objects are thread-safe
+ * + * @paramObtains an immutable pair of from two objects inferring the generic types.
+ * + *This factory allows the pair to be created using inference to + * obtain the generic types.
+ * + * @paramThrows {@code UnsupportedOperationException}.
+ * + *This pair is immutable, so this operation is not supported.
+ * + * @param value the value to set + * @return never + * @throws UnsupportedOperationException as this operation is not supported + */ + @Override + public R setValue(final R value) { + throw new UnsupportedOperationException(); + } + +} diff --git a/hoodie-common/src/main/java/com/uber/hoodie/common/util/collection/ImmutableTriple.java b/hoodie-common/src/main/java/com/uber/hoodie/common/util/collection/ImmutableTriple.java new file mode 100644 index 000000000..f986cc9f6 --- /dev/null +++ b/hoodie-common/src/main/java/com/uber/hoodie/common/util/collection/ImmutableTriple.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2018 Uber Technologies, Inc. (hoodie-dev-group@uber.com) + * + * Licensed 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 com.uber.hoodie.common.util.collection; + +/** + * (NOTE: Adapted from Apache commons-lang3) + *An immutable triple consisting of three {@code Object} elements.
+ * + *Although the implementation is immutable, there is no restriction on the objects + * that may be stored. If mutable objects are stored in the triple, then the triple + * itself effectively becomes mutable. The class is also {@code final}, so a subclass + * can not add undesirable behaviour.
+ * + *#ThreadSafe# if all three objects are thread-safe
+ * + * @paramObtains an immutable triple of from three objects inferring the generic types.
+ * + *This factory allows the triple to be created using inference to + * obtain the generic types.
+ * + * @paramA pair consisting of two elements.
+ * + *This class is an abstract implementation defining the basic API. + * It refers to the elements as 'left' and 'right'. It also implements the + * {@code Map.Entry} interface where the key is 'left' and the value is 'right'.
+ * + *Subclass implementations may be mutable or immutable. + * However, there is no restriction on the type of the stored objects that may be stored. + * If mutable objects are stored in the pair, then the pair itself effectively becomes mutable.
+ * + * @paramObtains an immutable pair of from two objects inferring the generic types.
+ * + *This factory allows the pair to be created using inference to + * obtain the generic types.
+ * + * @paramGets the left element from this pair.
+ * + *When treated as a key-value pair, this is the key.
+ * + * @return the left element, may be null + */ + public abstract L getLeft(); + + /** + *Gets the right element from this pair.
+ * + *When treated as a key-value pair, this is the value.
+ * + * @return the right element, may be null + */ + public abstract R getRight(); + + /** + *Gets the key from this pair.
+ * + *This method implements the {@code Map.Entry} interface returning the + * left element as the key.
+ * + * @return the left element as the key, may be null + */ + @Override + public final L getKey() { + return getLeft(); + } + + /** + *Gets the value from this pair.
+ * + *This method implements the {@code Map.Entry} interface returning the + * right element as the value.
+ * + * @return the right element as the value, may be null + */ + @Override + public R getValue() { + return getRight(); + } + + //----------------------------------------------------------------------- + + /** + *Compares the pair based on the left element followed by the right element. + * The types must be {@code Comparable}.
+ * + * @param other the other pair, not null + * @return negative if this is less, zero if equal, positive if greater + */ + @Override + public int compareTo(final PairCompares this pair to another based on the two elements.
+ * + * @param obj the object to compare to, null returns false + * @return true if the elements of the pair are equal + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Map.Entry, ?>) { + final Map.Entry, ?> other = (Map.Entry, ?>) obj; + return getKey().equals(other.getKey()) + && getValue().equals(other.getValue()); + } + return false; + } + + /** + *Returns a suitable hash code. + * The hash code follows the definition in {@code Map.Entry}.
+ * + * @return the hash code + */ + @Override + public int hashCode() { + // see Map.Entry API specification + return (getKey() == null ? 0 : getKey().hashCode()) + ^ (getValue() == null ? 0 : getValue().hashCode()); + } + + /** + *Returns a String representation of this pair using the format {@code ($left,$right)}.
+ * + * @return a string describing this object, not null + */ + @Override + public String toString() { + return new StringBuilder().append('(').append(getLeft()).append(',').append(getRight()).append(')').toString(); + } + + /** + *Formats the receiver using the given format.
+ * + *This uses {@link java.util.Formattable} to perform the formatting. Two variables may + * be used to embed the left and right elements. Use {@code %1$s} for the left + * element (key) and {@code %2$s} for the right element (value). + * The default format used by {@code toString()} is {@code (%1$s,%2$s)}.
+ * + * @param format the format string, optionally containing {@code %1$s} and {@code %2$s}, not null + * @return the formatted string, not null + */ + public String toString(final String format) { + return String.format(format, getLeft(), getRight()); + } + +} diff --git a/hoodie-common/src/main/java/com/uber/hoodie/common/util/collection/Triple.java b/hoodie-common/src/main/java/com/uber/hoodie/common/util/collection/Triple.java new file mode 100644 index 000000000..eeec5fd49 --- /dev/null +++ b/hoodie-common/src/main/java/com/uber/hoodie/common/util/collection/Triple.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2018 Uber Technologies, Inc. (hoodie-dev-group@uber.com) + * + * Licensed 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 com.uber.hoodie.common.util.collection; + +import java.io.Serializable; +import org.apache.commons.lang.builder.CompareToBuilder; + +/** + * (NOTE: Adapted from Apache commons-lang3) + *A triple consisting of three elements.
+ * + *This class is an abstract implementation defining the basic API. + * It refers to the elements as 'left', 'middle' and 'right'.
+ * + *Subclass implementations may be mutable or immutable. + * However, there is no restriction on the type of the stored objects that may be stored. + * If mutable objects are stored in the triple, then the triple itself effectively becomes mutable.
+ * + * @paramObtains an immutable triple of from three objects inferring the generic types.
+ * + *This factory allows the triple to be created using inference to + * obtain the generic types.
+ * + * @paramGets the left element from this triple.
+ * + * @return the left element, may be null + */ + public abstract L getLeft(); + + /** + *Gets the middle element from this triple.
+ * + * @return the middle element, may be null + */ + public abstract M getMiddle(); + + /** + *Gets the right element from this triple.
+ * + * @return the right element, may be null + */ + public abstract R getRight(); + + //----------------------------------------------------------------------- + + /** + *Compares the triple based on the left element, followed by the middle element, + * finally the right element. + * The types must be {@code Comparable}.
+ * + * @param other the other triple, not null + * @return negative if this is less, zero if equal, positive if greater + */ + @Override + public int compareTo(final TripleCompares this triple to another based on the three elements.
+ * + * @param obj the object to compare to, null returns false + * @return true if the elements of the triple are equal + */ + @SuppressWarnings("deprecation") // ObjectUtils.equals(Object, Object) has been deprecated in 3.2 + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Triple, ?, ?>) { + final Triple, ?, ?> other = (Triple, ?, ?>) obj; + return getLeft().equals(other.getLeft()) + && getMiddle().equals(other.getMiddle()) + && getRight().equals(other.getRight()); + } + return false; + } + + /** + *Returns a suitable hash code.
+ * + * @return the hash code + */ + @Override + public int hashCode() { + return (getLeft() == null ? 0 : getLeft().hashCode()) + ^ (getMiddle() == null ? 0 : getMiddle().hashCode()) + ^ (getRight() == null ? 0 : getRight().hashCode()); + } + + /** + *Returns a String representation of this triple using the format {@code ($left,$middle,$right)}.
+ * + * @return a string describing this object, not null + */ + @Override + public String toString() { + return new StringBuilder().append('(').append(getLeft()).append(',').append(getMiddle()).append(',') + .append(getRight()).append(')').toString(); + } + + /** + *Formats the receiver using the given format.
+ * + *This uses {@link java.util.Formattable} to perform the formatting. Three variables may + * be used to embed the left and right elements. Use {@code %1$s} for the left + * element, {@code %2$s} for the middle and {@code %3$s} for the right element. + * The default format used by {@code toString()} is {@code (%1$s,%2$s,%3$s)}.
+ * + * @param format the format string, optionally containing {@code %1$s}, {@code %2$s} and {@code %3$s}, not null + * @return the formatted string, not null + */ + public String toString(final String format) { + return String.format(format, getLeft(), getMiddle(), getRight()); + } + +} + diff --git a/hoodie-common/src/main/java/com/uber/hoodie/common/util/collection/converter/HoodieRecordConverter.java b/hoodie-common/src/main/java/com/uber/hoodie/common/util/collection/converter/HoodieRecordConverter.java index 6aac7b365..1f5ad6b1f 100644 --- a/hoodie-common/src/main/java/com/uber/hoodie/common/util/collection/converter/HoodieRecordConverter.java +++ b/hoodie-common/src/main/java/com/uber/hoodie/common/util/collection/converter/HoodieRecordConverter.java @@ -22,14 +22,14 @@ import com.uber.hoodie.common.model.HoodieRecordLocation; import com.uber.hoodie.common.model.HoodieRecordPayload; import com.uber.hoodie.common.util.HoodieAvroUtils; import com.uber.hoodie.common.util.ReflectionUtils; -import com.uber.hoodie.exception.HoodieNotSerializableException; +import com.uber.hoodie.common.util.SerializationUtils; +import com.uber.hoodie.common.util.collection.Pair; +import com.uber.hoodie.common.util.collection.Triple; +import com.uber.hoodie.exception.HoodieSerializationException; import java.io.IOException; import java.util.Optional; import org.apache.avro.Schema; import org.apache.avro.generic.GenericRecord; -import org.apache.commons.lang3.SerializationUtils; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.commons.lang3.tuple.Triple; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; @@ -71,7 +71,7 @@ public class HoodieRecordConverter