package ch.unibe.scg.cells; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; /** * An ObjectOutputStream that allows making shallow copies of objects. * * <p> * Ordinarily, using serialization to copy objects naturally leads to deep copies. * To circumvent this, and allow shallow copies via serializations, we have to `smuggle` the * live objects around the serialization format. * * <p> * Now, obviously, this breaks the entire purpose of serialization: to have everything encoded in * binary. However, it can come in handy if serialization is merely used to make copies of objects, * as is the case in the InMemoryPipeline, which uses serialization to copy mappers. * * <p> * Please <em>never</em> set this class to public. Don't talk about it, either. */ class ShallowSerializingCopy { /** * Use {@link ShallowSerializingCopy} to smuggle ourselves around serialization. * InMemoryShufflers should NOT get copied while the mappers that hold them get copied. */ static class SerializableLiveObject implements Serializable { final private static long serialVersionUID = 1L; final private static String error = "This class can only be serialized in a " + ShallowSerializingCopy.class.getName() + "If you're trying to serialize a table, consider using one from " + "the ch.unibe.chscg.cells.hadoop package."; private transient Object liveObject; SerializableLiveObject(Object liveObject) { this.liveObject = liveObject; } private void writeObject(ObjectOutputStream out) throws IOException { if (!(out instanceof ShallowSerializingCopy.LiveObjectOutputStream)) { throw new UnsupportedOperationException(error + liveObject.toString()); } LiveObjectOutputStream shallowOut = (ShallowSerializingCopy.LiveObjectOutputStream) out; shallowOut.writeLiveObject(liveObject); } private void readObject(java.io.ObjectInputStream in) throws IOException { if (!(in instanceof LiveObjectInputStream)) { throw new UnsupportedOperationException(error + liveObject.toString()); } LiveObjectInputStream shallowIn = (LiveObjectInputStream) in; liveObject = shallowIn.readLiveObject(); } private Object readResolve() { assert liveObject != null; return liveObject; } } private static class LiveObjectOutputStream extends ObjectOutputStream { final private static AtomicLong nextId = new AtomicLong(); final private Map<Long, Object> liveObjects; LiveObjectOutputStream(OutputStream out, Map<Long, Object> liveObjects) throws IOException { super(out); this.liveObjects = liveObjects; } void writeLiveObject(Object o) throws IOException { long key = nextId.incrementAndGet(); writeLong(key); liveObjects.put(key, o); } } private static class LiveObjectInputStream extends ObjectInputStream { final private Map<Long, Object> liveObjects; LiveObjectInputStream(InputStream in, Map<Long, Object> liveObjects) throws IOException { super(in); this.liveObjects = liveObjects; } Object readLiveObject() throws IOException { long key = readLong(); return liveObjects.get(key); } } /** Use serialization to clone a mapper. */ static <T> T clone(T in) throws IOException { ByteArrayOutputStream bOut = new ByteArrayOutputStream(); Map<Long, Object> smugglerCache = new HashMap<>(); try (ObjectOutputStream out = new LiveObjectOutputStream(bOut, smugglerCache)) { out.writeObject(in); } try { return (T) new LiveObjectInputStream( new ByteArrayInputStream(bOut.toByteArray()), smugglerCache) .readObject(); } catch (ClassNotFoundException e) { throw new AssertionError("We just serialized this object a minute ago. The class cannot be missing", e); } } }