{"action":"created","comment":{"url":"https://api.github.com/repos/elastic/elasticsearch/pulls/comments/46122379","id":46122379,"diff_hunk":"@@ -19,263 +19,624 @@\n \n package org.elasticsearch.cluster;\n \n+import com.carrotsearch.hppc.cursors.IntCursor;\n+import com.carrotsearch.hppc.cursors.IntObjectCursor;\n import com.carrotsearch.hppc.cursors.ObjectCursor;\n import com.carrotsearch.hppc.cursors.ObjectObjectCursor;\n \n+import org.elasticsearch.common.collect.ImmutableOpenIntMap;\n import org.elasticsearch.common.collect.ImmutableOpenMap;\n import org.elasticsearch.common.io.stream.StreamInput;\n import org.elasticsearch.common.io.stream.StreamOutput;\n \n import java.io.IOException;\n import java.util.ArrayList;\n+import java.util.Arrays;\n+import java.util.Collections;\n import java.util.HashMap;\n+import java.util.HashSet;\n import java.util.List;\n import java.util.Map;\n+import java.util.Set;\n \n public final class DiffableUtils {\n     private DiffableUtils() {\n     }\n \n     /**\n+     * Returns a map key serializer for String keys\n+     */\n+    public static KeySerializer<String> getStringKeySerializer() {\n+        return StringKeySerializer.INSTANCE;\n+    }\n+\n+    /**\n+     * Returns a map key serializer for Integer keys. Encodes as Int.\n+     */\n+    public static KeySerializer<Integer> getIntKeySerializer() {\n+        return IntKeySerializer.INSTANCE;\n+    }\n+\n+    /**\n+     * Returns a map key serializer for Integer keys. Encodes as VInt.\n+     */\n+    public static KeySerializer<Integer> getVIntKeySerializer() {\n+        return VIntKeySerializer.INSTANCE;\n+    }\n+\n+    /**\n      * Calculates diff between two ImmutableOpenMaps of Diffable objects\n      */\n-    public static <T extends Diffable<T>> Diff<ImmutableOpenMap<String, T>> diff(ImmutableOpenMap<String, T> before, ImmutableOpenMap<String, T> after) {\n+    public static <K, T extends Diffable<T>> Diff<ImmutableOpenMap<K, T>> diff(ImmutableOpenMap<K, T> before, ImmutableOpenMap<K, T> after, KeySerializer<K> keySerializer) {\n         assert after != null && before != null;\n-        return new ImmutableOpenMapDiff<>(before, after);\n+        return new ImmutableOpenMapDiff<>(before, after, keySerializer, DiffableValueSerializer.getWriteOnlyInstance());\n+    }\n+\n+    /**\n+     * Calculates diff between two ImmutableOpenMaps of non-diffable objects\n+     */\n+    public static <K, T> Diff<ImmutableOpenMap<K, T>> diff(ImmutableOpenMap<K, T> before, ImmutableOpenMap<K, T> after, KeySerializer<K> keySerializer, NonDiffableValueSerializer<K, T> valueSerializer) {\n+        assert after != null && before != null;\n+        return new ImmutableOpenMapDiff<>(before, after, keySerializer, valueSerializer);\n+    }\n+\n+    /**\n+     * Calculates diff between two ImmutableOpenIntMaps of Diffable objects\n+     */\n+    public static <T extends Diffable<T>> Diff<ImmutableOpenIntMap<T>> diff(ImmutableOpenIntMap<T> before, ImmutableOpenIntMap<T> after, KeySerializer<Integer> keySerializer) {\n+        assert after != null && before != null;\n+        return new ImmutableOpenIntMapDiff<>(before, after, keySerializer, DiffableValueSerializer.getWriteOnlyInstance());\n+    }\n+\n+    /**\n+     * Calculates diff between two ImmutableOpenIntMaps of non-diffable objects\n+     */\n+    public static <T> Diff<ImmutableOpenIntMap<T>> diff(ImmutableOpenIntMap<T> before, ImmutableOpenIntMap<T> after, KeySerializer<Integer> keySerializer, NonDiffableValueSerializer<Integer, T> valueSerializer) {\n+        assert after != null && before != null;\n+        return new ImmutableOpenIntMapDiff<>(before, after, keySerializer, valueSerializer);\n     }\n \n     /**\n      * Calculates diff between two Maps of Diffable objects.\n      */\n-    public static <T extends Diffable<T>> Diff<Map<String, T>> diff(Map<String, T> before, Map<String, T> after) {\n+    public static <K, T extends Diffable<T>> Diff<Map<K, T>> diff(Map<K, T> before, Map<K, T> after, KeySerializer<K> keySerializer) {\n         assert after != null && before != null;\n-        return new JdkMapDiff<>(before, after);\n+        return new JdkMapDiff<>(before, after, keySerializer, DiffableValueSerializer.getWriteOnlyInstance());\n     }\n \n     /**\n-     * Loads an object that represents difference between two ImmutableOpenMaps\n+     * Calculates diff between two Maps of non-diffable objects\n      */\n-    public static <T extends Diffable<T>> Diff<ImmutableOpenMap<String, T>> readImmutableOpenMapDiff(StreamInput in, KeyedReader<T> keyedReader) throws IOException {\n-        return new ImmutableOpenMapDiff<>(in, keyedReader);\n+    public static <K, T> Diff<Map<K, T>> diff(Map<K, T> before, Map<K, T> after, KeySerializer<K> keySerializer, NonDiffableValueSerializer<K, T> valueSerializer) {\n+        assert after != null && before != null;\n+        return new JdkMapDiff<>(before, after, keySerializer, valueSerializer);\n     }\n \n     /**\n-     * Loads an object that represents difference between two Maps.\n+     * Loads an object that represents difference between two ImmutableOpenMaps\n      */\n-    public static <T extends Diffable<T>> Diff<Map<String, T>> readJdkMapDiff(StreamInput in, KeyedReader<T> keyedReader) throws IOException {\n-        return new JdkMapDiff<>(in, keyedReader);\n+    public static <K, T> Diff<ImmutableOpenMap<K, T>> readImmutableOpenMapDiff(StreamInput in, KeySerializer<K> keySerializer, ValueSerializer<K, T> valueSerializer) throws IOException {\n+        return new ImmutableOpenMapDiff<>(in, keySerializer, valueSerializer);\n     }\n \n     /**\n      * Loads an object that represents difference between two ImmutableOpenMaps\n      */\n-    public static <T extends Diffable<T>> Diff<ImmutableOpenMap<String, T>> readImmutableOpenMapDiff(StreamInput in, T proto) throws IOException {\n-        return new ImmutableOpenMapDiff<>(in, new PrototypeReader<>(proto));\n+    public static <T> Diff<ImmutableOpenIntMap<T>> readImmutableOpenIntMapDiff(StreamInput in, KeySerializer<Integer> keySerializer, ValueSerializer<Integer, T> valueSerializer) throws IOException {\n+        return new ImmutableOpenIntMapDiff<>(in, keySerializer, valueSerializer);\n     }\n \n     /**\n-     * Loads an object that represents difference between two Maps.\n+     * Loads an object that represents difference between two Maps of Diffable objects\n      */\n-    public static <T extends Diffable<T>> Diff<Map<String, T>> readJdkMapDiff(StreamInput in, T proto) throws IOException {\n-        return new JdkMapDiff<>(in, new PrototypeReader<>(proto));\n+    public static <K, T> Diff<Map<K, T>> readJdkMapDiff(StreamInput in, KeySerializer<K> keySerializer, ValueSerializer<K, T> valueSerializer) throws IOException {\n+        return new JdkMapDiff<>(in, keySerializer, valueSerializer);\n     }\n \n     /**\n-     * A reader that can deserialize an object. The reader can select the deserialization type based on the key. It's\n-     * used in custom metadata deserialization.\n+     * Loads an object that represents difference between two ImmutableOpenMaps of Diffable objects using Diffable proto object\n      */\n-    public interface KeyedReader<T> {\n+    public static <K, T extends Diffable<T>> Diff<ImmutableOpenMap<K, T>> readImmutableOpenMapDiff(StreamInput in, KeySerializer<K> keySerializer, T proto) throws IOException {\n+        return new ImmutableOpenMapDiff<>(in, keySerializer, new DiffablePrototypeValueReader<>(proto));\n+    }\n \n-        /**\n-         * reads an object of the type T from the stream input\n-         */\n-        T readFrom(StreamInput in, String key) throws IOException;\n+    /**\n+     * Loads an object that represents difference between two ImmutableOpenIntMaps of Diffable objects using Diffable proto object\n+     */\n+    public static <T extends Diffable<T>> Diff<ImmutableOpenIntMap<T>> readImmutableOpenIntMapDiff(StreamInput in, KeySerializer<Integer> keySerializer, T proto) throws IOException {\n+        return new ImmutableOpenIntMapDiff<>(in, keySerializer, new DiffablePrototypeValueReader<>(proto));\n+    }\n \n-        /**\n-         * reads an object that respresents differences between two objects with the type T from the stream input\n-         */\n-        Diff<T> readDiffFrom(StreamInput in, String key) throws IOException;\n+    /**\n+     * Loads an object that represents difference between two Maps of Diffable objects using Diffable proto object\n+     */\n+    public static <K, T extends Diffable<T>> Diff<Map<K, T>> readJdkMapDiff(StreamInput in, KeySerializer<K> keySerializer, T proto) throws IOException {\n+        return new JdkMapDiff<>(in, keySerializer, new DiffablePrototypeValueReader<>(proto));\n     }\n \n     /**\n-     * Implementation of the KeyedReader that is using a prototype object for reading operations\n+     * Represents differences between two Maps of (possibly diffable) objects.\n      *\n-     * Note: this implementation is ignoring the key.\n+     * @param <T> the diffable object\n      */\n-    public static class PrototypeReader<T extends Diffable<T>> implements KeyedReader<T> {\n-        private T proto;\n+    private static class JdkMapDiff<K, T> extends MapDiff<K, T, Map<K, T>> {\n \n-        public PrototypeReader(T proto) {\n-            this.proto = proto;\n+        protected JdkMapDiff(StreamInput in, KeySerializer<K> keySerializer, ValueSerializer<K, T> valueSerializer) throws IOException {\n+            super(in, keySerializer, valueSerializer);\n         }\n \n-        @Override\n-        public T readFrom(StreamInput in, String key) throws IOException {\n-            return proto.readFrom(in);\n+        public JdkMapDiff(Map<K, T> before, Map<K, T> after,\n+                          KeySerializer<K> keySerializer, ValueSerializer<K, T> valueSerializer) {\n+            super(keySerializer, valueSerializer);\n+            assert after != null && before != null;\n+            for (K key : before.keySet()) {\n+                if (!after.containsKey(key)) {\n+                    deletes.add(key);\n+                }\n+            }\n+            for (Map.Entry<K, T> partIter : after.entrySet()) {\n+                T beforePart = before.get(partIter.getKey());\n+                if (beforePart == null || (valueSerializer.supportsDiffableValues() == false)) {\n+                    upserts.put(partIter.getKey(), partIter.getValue());\n+                } else if (partIter.getValue().equals(beforePart) == false) {\n+                    diffs.put(partIter.getKey(), valueSerializer.computeDiff(partIter.getValue(), beforePart));\n+                }\n+            }\n         }\n \n         @Override\n-        public Diff<T> readDiffFrom(StreamInput in, String key) throws IOException {\n-            return proto.readDiffFrom(in);\n+        public Map<K, T> apply(Map<K, T> map) {\n+            Map<K, T> builder = new HashMap<>();\n+            builder.putAll(map);\n+\n+            for (K part : deletes) {\n+                builder.remove(part);\n+            }\n+\n+            for (Map.Entry<K, Diff<T>> diff : diffs.entrySet()) {\n+                builder.put(diff.getKey(), diff.getValue().apply(builder.get(diff.getKey())));\n+            }\n+\n+            for (Map.Entry<K, T> upsert : upserts.entrySet()) {\n+                builder.put(upsert.getKey(), upsert.getValue());\n+            }\n+            return builder;\n         }\n     }\n \n     /**\n-     * Represents differences between two Maps of Diffable objects.\n+     * Represents differences between two ImmutableOpenMap of (possibly diffable) objects\n      *\n-     * @param <T> the diffable object\n+     * @param <T> the object type\n      */\n-    private static class JdkMapDiff<T extends Diffable<T>> extends MapDiff<T, Map<String, T>> {\n+    private static class ImmutableOpenMapDiff<K, T> extends MapDiff<K, T, ImmutableOpenMap<K, T>> {\n \n-        protected JdkMapDiff(StreamInput in, KeyedReader<T> reader) throws IOException {\n-            super(in, reader);\n+        protected ImmutableOpenMapDiff(StreamInput in, KeySerializer<K> keySerializer, ValueSerializer<K, T> valueSerializer) throws IOException {\n+            super(in, keySerializer, valueSerializer);\n         }\n \n-        public JdkMapDiff(Map<String, T> before, Map<String, T> after) {\n+        public ImmutableOpenMapDiff(ImmutableOpenMap<K, T> before, ImmutableOpenMap<K, T> after,\n+                                    KeySerializer<K> keySerializer, ValueSerializer<K, T> valueSerializer) {\n+            super(keySerializer, valueSerializer);\n             assert after != null && before != null;\n-            for (String key : before.keySet()) {\n-                if (!after.containsKey(key)) {\n-                    deletes.add(key);\n+            for (ObjectCursor<K> key : before.keys()) {\n+                if (!after.containsKey(key.value)) {\n+                    deletes.add(key.value);\n                 }\n             }\n-            for (Map.Entry<String, T> partIter : after.entrySet()) {\n-                T beforePart = before.get(partIter.getKey());\n-                if (beforePart == null) {\n-                    adds.put(partIter.getKey(), partIter.getValue());\n-                } else if (partIter.getValue().equals(beforePart) == false) {\n-                    diffs.put(partIter.getKey(), partIter.getValue().diff(beforePart));\n+            for (ObjectObjectCursor<K, T> partIter : after) {\n+                T beforePart = before.get(partIter.key);\n+                if (beforePart == null || (valueSerializer.supportsDiffableValues() == false)) {\n+                    upserts.put(partIter.key, partIter.value);\n+                } else if (partIter.value.equals(beforePart) == false) {\n+                    diffs.put(partIter.key, valueSerializer.computeDiff(partIter.value, beforePart));\n                 }\n             }\n         }\n \n         @Override\n-        public Map<String, T> apply(Map<String, T> map) {\n-            Map<String, T> builder = new HashMap<>();\n+        public ImmutableOpenMap<K, T> apply(ImmutableOpenMap<K, T> map) {\n+            ImmutableOpenMap.Builder<K, T> builder = ImmutableOpenMap.builder();\n             builder.putAll(map);\n \n-            for (String part : deletes) {\n+            for (K part : deletes) {\n                 builder.remove(part);\n             }\n \n-            for (Map.Entry<String, Diff<T>> diff : diffs.entrySet()) {\n+            for (Map.Entry<K, Diff<T>> diff : diffs.entrySet()) {\n                 builder.put(diff.getKey(), diff.getValue().apply(builder.get(diff.getKey())));\n             }\n \n-            for (Map.Entry<String, T> additon : adds.entrySet()) {\n-                builder.put(additon.getKey(), additon.getValue());\n+            for (Map.Entry<K, T> upsert : upserts.entrySet()) {\n+                builder.put(upsert.getKey(), upsert.getValue());\n             }\n-            return builder;\n+            return builder.build();\n         }\n     }\n \n     /**\n-     * Represents differences between two ImmutableOpenMap of diffable objects\n+     * Represents differences between two ImmutableOpenIntMap of (possibly diffable) objects\n      *\n-     * @param <T> the diffable object\n+     * @param <T> the object type\n      */\n-    private static class ImmutableOpenMapDiff<T extends Diffable<T>> extends MapDiff<T, ImmutableOpenMap<String, T>> {\n+    private static class ImmutableOpenIntMapDiff<T> extends MapDiff<Integer, T, ImmutableOpenIntMap<T>> {\n \n-        protected ImmutableOpenMapDiff(StreamInput in, KeyedReader<T> reader) throws IOException {\n-            super(in, reader);\n+        protected ImmutableOpenIntMapDiff(StreamInput in, KeySerializer<Integer> keySerializer, ValueSerializer<Integer, T> valueSerializer) throws IOException {\n+            super(in, keySerializer, valueSerializer);\n         }\n \n-        public ImmutableOpenMapDiff(ImmutableOpenMap<String, T> before, ImmutableOpenMap<String, T> after) {\n+        public ImmutableOpenIntMapDiff(ImmutableOpenIntMap<T> before, ImmutableOpenIntMap<T> after,\n+                                       KeySerializer<Integer> keySerializer, ValueSerializer<Integer, T> valueSerializer) {\n+            super(keySerializer, valueSerializer);\n             assert after != null && before != null;\n-            for (ObjectCursor<String> key : before.keys()) {\n+            for (IntCursor key : before.keys()) {\n                 if (!after.containsKey(key.value)) {\n                     deletes.add(key.value);\n                 }\n             }\n-            for (ObjectObjectCursor<String, T> partIter : after) {\n+            for (IntObjectCursor<T> partIter : after) {\n                 T beforePart = before.get(partIter.key);\n-                if (beforePart == null) {\n-                    adds.put(partIter.key, partIter.value);\n+                if (beforePart == null || (valueSerializer.supportsDiffableValues() == false)) {\n+                    upserts.put(partIter.key, partIter.value);\n                 } else if (partIter.value.equals(beforePart) == false) {\n-                    diffs.put(partIter.key, partIter.value.diff(beforePart));\n+                    diffs.put(partIter.key, valueSerializer.computeDiff(partIter.value, beforePart));\n                 }\n             }\n         }\n \n         @Override\n-        public ImmutableOpenMap<String, T> apply(ImmutableOpenMap<String, T> map) {\n-            ImmutableOpenMap.Builder<String, T> builder = ImmutableOpenMap.builder();\n+        public ImmutableOpenIntMap<T> apply(ImmutableOpenIntMap<T> map) {\n+            ImmutableOpenIntMap.Builder<T> builder = ImmutableOpenIntMap.builder();\n             builder.putAll(map);\n \n-            for (String part : deletes) {\n+            for (Integer part : deletes) {\n                 builder.remove(part);\n             }\n \n-            for (Map.Entry<String, Diff<T>> diff : diffs.entrySet()) {\n+            for (Map.Entry<Integer, Diff<T>> diff : diffs.entrySet()) {\n                 builder.put(diff.getKey(), diff.getValue().apply(builder.get(diff.getKey())));\n             }\n \n-            for (Map.Entry<String, T> additon : adds.entrySet()) {\n-                builder.put(additon.getKey(), additon.getValue());\n+            for (Map.Entry<Integer, T> upsert : upserts.entrySet()) {\n+                builder.put(upsert.getKey(), upsert.getValue());\n             }\n             return builder.build();\n         }\n     }\n \n     /**\n-     * Represents differences between two maps of diffable objects\n+     * Represents differences between two maps of objects and is used as base class for different map implementations.\n      *\n-     * This class is used as base class for different map implementations\n+     * Implements serialization. How differences are applied is left to subclasses.\n      *\n-     * @param <T> the diffable object\n+     * @param <K> the type of map keys\n+     * @param <T> the type of map values\n+     * @param <M> the map implementation type\n      */\n-    private static abstract class MapDiff<T extends Diffable<T>, M> implements Diff<M> {\n+    private static abstract class MapDiff<K, T, M> implements Diff<M> {\n \n-        protected final List<String> deletes;\n-        protected final Map<String, Diff<T>> diffs;\n-        protected final Map<String, T> adds;\n+        protected final List<K> deletes;\n+        protected final Map<K, Diff<T>> diffs; // incremental updates\n+        protected final Map<K, T> upserts; // additions or full updates\n+        protected final KeySerializer<K> keySerializer;\n+        protected final ValueSerializer<K, T> valueSerializer;\n \n-        protected MapDiff() {\n+        protected MapDiff(KeySerializer<K> keySerializer, ValueSerializer<K, T> valueSerializer) {\n+            this.keySerializer = keySerializer;\n+            this.valueSerializer = valueSerializer;\n             deletes = new ArrayList<>();\n             diffs = new HashMap<>();\n-            adds = new HashMap<>();\n+            upserts = new HashMap<>();\n         }\n \n-        protected MapDiff(StreamInput in, KeyedReader<T> reader) throws IOException {\n+        protected MapDiff(StreamInput in, KeySerializer<K> keySerializer, ValueSerializer<K, T> valueSerializer) throws IOException {\n+            this.keySerializer = keySerializer;\n+            this.valueSerializer = valueSerializer;\n             deletes = new ArrayList<>();\n             diffs = new HashMap<>();\n-            adds = new HashMap<>();\n+            upserts = new HashMap<>();\n             int deletesCount = in.readVInt();\n             for (int i = 0; i < deletesCount; i++) {\n-                deletes.add(in.readString());\n+                deletes.add(keySerializer.readKey(in));\n             }\n-\n             int diffsCount = in.readVInt();\n             for (int i = 0; i < diffsCount; i++) {\n-                String key = in.readString();\n-                Diff<T> diff = reader.readDiffFrom(in, key);\n+                K key = keySerializer.readKey(in);\n+                Diff<T> diff = valueSerializer.readDiffFrom(in, key);\n                 diffs.put(key, diff);\n             }\n-\n-            int addsCount = in.readVInt();\n-            for (int i = 0; i < addsCount; i++) {\n-                String key = in.readString();\n-                T part = reader.readFrom(in, key);\n-                adds.put(key, part);\n+            int upsertsCount = in.readVInt();\n+            for (int i = 0; i < upsertsCount; i++) {\n+                K key = keySerializer.readKey(in);\n+                T newValue = valueSerializer.readFrom(in, key);\n+                upserts.put(key, newValue);\n             }\n         }\n+\n         @Override\n         public void writeTo(StreamOutput out) throws IOException {\n             out.writeVInt(deletes.size());\n-            for (String delete : deletes) {\n-                out.writeString(delete);\n+            for (K delete : deletes) {\n+                keySerializer.writeKey(delete, out);\n             }\n-\n             out.writeVInt(diffs.size());\n-            for (Map.Entry<String, Diff<T>> entry : diffs.entrySet()) {\n-                out.writeString(entry.getKey());\n-                entry.getValue().writeTo(out);\n+            for (Map.Entry<K, Diff<T>> entry : diffs.entrySet()) {\n+                keySerializer.writeKey(entry.getKey(), out);\n+                valueSerializer.writeDiffTo(entry.getValue(), out);\n+            }\n+            out.writeVInt(upserts.size());\n+            for (Map.Entry<K, T> entry : upserts.entrySet()) {\n+                keySerializer.writeKey(entry.getKey(), out);\n+                valueSerializer.writeTo(entry.getValue(), out);\n+            }\n+        }\n+    }\n+\n+    /**\n+     * Provides read and write operations to serialize keys of map\n+     * @param <K> type of key\n+     */\n+    public interface KeySerializer<K> {\n+        void writeKey(K key, StreamOutput out) throws IOException;\n+        K readKey(StreamInput in) throws IOException;\n+    }\n+\n+    /**\n+     * Serializes String keys of a map\n+     */\n+    private static final class StringKeySerializer implements KeySerializer<String> {\n+        private static final StringKeySerializer INSTANCE = new StringKeySerializer();\n+\n+        @Override\n+        public void writeKey(String key, StreamOutput out) throws IOException {\n+            out.writeString(key);\n+        }\n+\n+        @Override\n+        public String readKey(StreamInput in) throws IOException {\n+            return in.readString();\n+        }\n+    }\n+\n+    /**\n+     * Serializes Integer keys of a map as an Int\n+     */\n+    private static final class IntKeySerializer implements KeySerializer<Integer> {\n+        public static final IntKeySerializer INSTANCE = new IntKeySerializer();\n+\n+        @Override\n+        public void writeKey(Integer key, StreamOutput out) throws IOException {\n+            out.writeInt(key);\n+        }\n+\n+        @Override\n+        public Integer readKey(StreamInput in) throws IOException {\n+            return in.readInt();\n+        }\n+    }\n+\n+    /**\n+     * Serializes Integer keys of a map as a VInt\n+     */\n+    private static final class VIntKeySerializer implements KeySerializer<Integer> {\n+        public static final IntKeySerializer INSTANCE = new IntKeySerializer();\n+\n+        @Override\n+        public void writeKey(Integer key, StreamOutput out) throws IOException {\n+            out.writeVInt(key);\n+        }\n+\n+        @Override\n+        public Integer readKey(StreamInput in) throws IOException {\n+            return in.readVInt();\n+        }\n+    }\n+\n+    /**\n+     * Provides read and write operations to serialize map values.\n+     * Reading of values can be made dependent on map key.\n+     *\n+     * Also provides operations to distinguish whether map values are diffable.\n+     *\n+     * Should not be directly implemented, instead implement either\n+     * {@link DiffableValueSerializer} or {@link NonDiffableValueSerializer}.\n+     *\n+     * @param <K> key type of map\n+     * @param <V> value type of map\n+     */\n+    public interface ValueSerializer<K, V> {\n+\n+        /**\n+         * Writes value to stream\n+         */\n+        void writeTo(V value, StreamOutput out) throws IOException;\n+\n+        /**\n+         * Reads value from stream\n+         */\n+        V readFrom(StreamInput in, K key) throws IOException;\n+\n+        /**\n+         * Whether this serializer supports diffable values\n+         */\n+        boolean supportsDiffableValues();\n+\n+        /**\n+         * Computes diff if this serializer supports diffable values\n+         */\n+        Diff<V> computeDiff(V value, V beforePart);\n+\n+        /**\n+         * Writes value as diff to stream if this serializer supports diffable values\n+         */\n+        void writeDiffTo(Diff<V> value, StreamOutput out) throws IOException;\n+\n+        /**\n+         * Reads value as diff from stream if this serializer supports diffable values\n+         */\n+        Diff<V> readDiffFrom(StreamInput in, K key) throws IOException;\n+    }\n+\n+    /**\n+     * Serializer for Diffable map values. Needs to implement readFrom and readDiffFrom methods.\n+     *\n+     * @param <K> type of map keys\n+     * @param <V> type of map values\n+     */\n+    public static abstract class DiffableValueSerializer<K, V extends Diffable<V>> implements ValueSerializer<K, V> {\n+        private static final DiffableValueSerializer WRITE_ONLY_INSTANCE = new DiffableValueSerializer() {\n+            @Override\n+            public Object readFrom(StreamInput in, Object key) throws IOException {\n+                throw new UnsupportedOperationException();\n             }\n \n-            out.writeVInt(adds.size());\n-            for (Map.Entry<String, T> entry : adds.entrySet()) {\n-                out.writeString(entry.getKey());\n-                entry.getValue().writeTo(out);\n+            @Override\n+            public Diff<Object> readDiffFrom(StreamInput in, Object key) throws IOException {\n+                throw new UnsupportedOperationException();\n             }\n+        };\n+\n+        private static <K, V extends Diffable<V>> DiffableValueSerializer<K, V> getWriteOnlyInstance() {\n+            return WRITE_ONLY_INSTANCE;\n+        }\n+\n+        @Override\n+        public boolean supportsDiffableValues() {\n+            return true;\n+        }\n+\n+        @Override\n+        public Diff<V> computeDiff(V value, V beforePart) {\n+            return value.diff(beforePart);\n+        }\n+\n+        @Override\n+        public void writeTo(V value, StreamOutput out) throws IOException {\n+            value.writeTo(out);\n+        }\n+\n+        public void writeDiffTo(Diff<V> value, StreamOutput out) throws IOException {\n+            value.writeTo(out);\n+        }\n+    }\n+\n+    /**\n+     * Serializer for non-diffable map values\n+     *\n+     * @param <K> type of map keys\n+     * @param <V> type of map values\n+     */\n+    public static abstract class NonDiffableValueSerializer<K, V> implements ValueSerializer<K, V> {\n+        @Override\n+        public boolean supportsDiffableValues() {\n+            return false;\n+        }\n+\n+        @Override\n+        public Diff<V> computeDiff(V value, V beforePart) {\n+            throw new UnsupportedOperationException();\n+        }\n+\n+        @Override\n+        public void writeDiffTo(Diff<V> value, StreamOutput out) throws IOException {\n+            throw new UnsupportedOperationException();\n+        }\n+\n+        @Override\n+        public Diff<V> readDiffFrom(StreamInput in, K key) throws IOException {\n+            throw new UnsupportedOperationException();\n+        }\n+    }\n+\n+    /**\n+     * Implementation of ValueSerializer that delegates to another ValueSerializer.\n+     * Useful for overriding single methods.\n+     *\n+     * @param <K> type of map keys\n+     * @param <V> type of map values\n+     */\n+    public static class DelegateValueSerializer<K, V> implements ValueSerializer<K, V> {","path":"core/src/main/java/org/elasticsearch/cluster/DiffableUtils.java","position":647,"original_position":647,"commit_id":"951b856355ebf5d03cf57611b2d24716a2a61e77","original_commit_id":"951b856355ebf5d03cf57611b2d24716a2a61e77","user":{"login":"bleskes","id":1006375,"avatar_url":"https://avatars.githubusercontent.com/u/1006375?v=3","gravatar_id":"","url":"https://api.github.com/users/bleskes","html_url":"https://github.com/bleskes","followers_url":"https://api.github.com/users/bleskes/followers","following_url":"https://api.github.com/users/bleskes/following{/other_user}","gists_url":"https://api.github.com/users/bleskes/gists{/gist_id}","starred_url":"https://api.github.com/users/bleskes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bleskes/subscriptions","organizations_url":"https://api.github.com/users/bleskes/orgs","repos_url":"https://api.github.com/users/bleskes/repos","events_url":"https://api.github.com/users/bleskes/events{/privacy}","received_events_url":"https://api.github.com/users/bleskes/received_events","type":"User","site_admin":false},"body":"it seems we only use this for DiffablePrototypeValueReader, where we can inherit from DiffableValueSerializer  . Makes me wonder if we need this class...","created_at":"2015-11-30T09:38:15Z","updated_at":"2015-11-30T09:38:15Z","html_url":"https://github.com/elastic/elasticsearch/pull/14964#discussion_r46122379","pull_request_url":"https://api.github.com/repos/elastic/elasticsearch/pulls/14964","_links":{"self":{"href":"https://api.github.com/repos/elastic/elasticsearch/pulls/comments/46122379"},"html":{"href":"https://github.com/elastic/elasticsearch/pull/14964#discussion_r46122379"},"pull_request":{"href":"https://api.github.com/repos/elastic/elasticsearch/pulls/14964"}}},"pull_request":{"url":"https://api.github.com/repos/elastic/elasticsearch/pulls/14964","id":51654459,"html_url":"https://github.com/elastic/elasticsearch/pull/14964","diff_url":"https://github.com/elastic/elasticsearch/pull/14964.diff","patch_url":"https://github.com/elastic/elasticsearch/pull/14964.patch","issue_url":"https://api.github.com/repos/elastic/elasticsearch/issues/14964","number":14964,"state":"open","locked":false,"title":"Persist currently started allocation IDs to index metadata","user":{"login":"ywelsch","id":3718355,"avatar_url":"https://avatars.githubusercontent.com/u/3718355?v=3","gravatar_id":"","url":"https://api.github.com/users/ywelsch","html_url":"https://github.com/ywelsch","followers_url":"https://api.github.com/users/ywelsch/followers","following_url":"https://api.github.com/users/ywelsch/following{/other_user}","gists_url":"https://api.github.com/users/ywelsch/gists{/gist_id}","starred_url":"https://api.github.com/users/ywelsch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ywelsch/subscriptions","organizations_url":"https://api.github.com/users/ywelsch/orgs","repos_url":"https://api.github.com/users/ywelsch/repos","events_url":"https://api.github.com/users/ywelsch/events{/privacy}","received_events_url":"https://api.github.com/users/ywelsch/received_events","type":"User","site_admin":false},"body":"These allocation IDs serve as candidates to decide future primary shards\r\n\r\nSubtask of #14739","created_at":"2015-11-24T12:40:59Z","updated_at":"2015-11-30T09:38:15Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7157622fe5bd639117eafa44d4bc779b85e5ce4a","assignee":{"login":"bleskes","id":1006375,"avatar_url":"https://avatars.githubusercontent.com/u/1006375?v=3","gravatar_id":"","url":"https://api.github.com/users/bleskes","html_url":"https://github.com/bleskes","followers_url":"https://api.github.com/users/bleskes/followers","following_url":"https://api.github.com/users/bleskes/following{/other_user}","gists_url":"https://api.github.com/users/bleskes/gists{/gist_id}","starred_url":"https://api.github.com/users/bleskes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bleskes/subscriptions","organizations_url":"https://api.github.com/users/bleskes/orgs","repos_url":"https://api.github.com/users/bleskes/repos","events_url":"https://api.github.com/users/bleskes/events{/privacy}","received_events_url":"https://api.github.com/users/bleskes/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/elastic/elasticsearch/pulls/14964/commits","review_comments_url":"https://api.github.com/repos/elastic/elasticsearch/pulls/14964/comments","review_comment_url":"https://api.github.com/repos/elastic/elasticsearch/pulls/comments{/number}","comments_url":"https://api.github.com/repos/elastic/elasticsearch/issues/14964/comments","statuses_url":"https://api.github.com/repos/elastic/elasticsearch/statuses/951b856355ebf5d03cf57611b2d24716a2a61e77","head":{"label":"ywelsch:feature/persist-allocid-indexmetadata","ref":"feature/persist-allocid-indexmetadata","sha":"951b856355ebf5d03cf57611b2d24716a2a61e77","user":{"login":"ywelsch","id":3718355,"avatar_url":"https://avatars.githubusercontent.com/u/3718355?v=3","gravatar_id":"","url":"https://api.github.com/users/ywelsch","html_url":"https://github.com/ywelsch","followers_url":"https://api.github.com/users/ywelsch/followers","following_url":"https://api.github.com/users/ywelsch/following{/other_user}","gists_url":"https://api.github.com/users/ywelsch/gists{/gist_id}","starred_url":"https://api.github.com/users/ywelsch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ywelsch/subscriptions","organizations_url":"https://api.github.com/users/ywelsch/orgs","repos_url":"https://api.github.com/users/ywelsch/repos","events_url":"https://api.github.com/users/ywelsch/events{/privacy}","received_events_url":"https://api.github.com/users/ywelsch/received_events","type":"User","site_admin":false},"repo":{"id":43299643,"name":"elasticsearch","full_name":"ywelsch/elasticsearch","owner":{"login":"ywelsch","id":3718355,"avatar_url":"https://avatars.githubusercontent.com/u/3718355?v=3","gravatar_id":"","url":"https://api.github.com/users/ywelsch","html_url":"https://github.com/ywelsch","followers_url":"https://api.github.com/users/ywelsch/followers","following_url":"https://api.github.com/users/ywelsch/following{/other_user}","gists_url":"https://api.github.com/users/ywelsch/gists{/gist_id}","starred_url":"https://api.github.com/users/ywelsch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ywelsch/subscriptions","organizations_url":"https://api.github.com/users/ywelsch/orgs","repos_url":"https://api.github.com/users/ywelsch/repos","events_url":"https://api.github.com/users/ywelsch/events{/privacy}","received_events_url":"https://api.github.com/users/ywelsch/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/ywelsch/elasticsearch","description":"Open Source, Distributed, RESTful Search Engine","fork":true,"url":"https://api.github.com/repos/ywelsch/elasticsearch","forks_url":"https://api.github.com/repos/ywelsch/elasticsearch/forks","keys_url":"https://api.github.com/repos/ywelsch/elasticsearch/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ywelsch/elasticsearch/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ywelsch/elasticsearch/teams","hooks_url":"https://api.github.com/repos/ywelsch/elasticsearch/hooks","issue_events_url":"https://api.github.com/repos/ywelsch/elasticsearch/issues/events{/number}","events_url":"https://api.github.com/repos/ywelsch/elasticsearch/events","assignees_url":"https://api.github.com/repos/ywelsch/elasticsearch/assignees{/user}","branches_url":"https://api.github.com/repos/ywelsch/elasticsearch/branches{/branch}","tags_url":"https://api.github.com/repos/ywelsch/elasticsearch/tags","blobs_url":"https://api.github.com/repos/ywelsch/elasticsearch/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ywelsch/elasticsearch/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ywelsch/elasticsearch/git/refs{/sha}","trees_url":"https://api.github.com/repos/ywelsch/elasticsearch/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ywelsch/elasticsearch/statuses/{sha}","languages_url":"https://api.github.com/repos/ywelsch/elasticsearch/languages","stargazers_url":"https://api.github.com/repos/ywelsch/elasticsearch/stargazers","contributors_url":"https://api.github.com/repos/ywelsch/elasticsearch/contributors","subscribers_url":"https://api.github.com/repos/ywelsch/elasticsearch/subscribers","subscription_url":"https://api.github.com/repos/ywelsch/elasticsearch/subscription","commits_url":"https://api.github.com/repos/ywelsch/elasticsearch/commits{/sha}","git_commits_url":"https://api.github.com/repos/ywelsch/elasticsearch/git/commits{/sha}","comments_url":"https://api.github.com/repos/ywelsch/elasticsearch/comments{/number}","issue_comment_url":"https://api.github.com/repos/ywelsch/elasticsearch/issues/comments{/number}","contents_url":"https://api.github.com/repos/ywelsch/elasticsearch/contents/{+path}","compare_url":"https://api.github.com/repos/ywelsch/elasticsearch/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ywelsch/elasticsearch/merges","archive_url":"https://api.github.com/repos/ywelsch/elasticsearch/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ywelsch/elasticsearch/downloads","issues_url":"https://api.github.com/repos/ywelsch/elasticsearch/issues{/number}","pulls_url":"https://api.github.com/repos/ywelsch/elasticsearch/pulls{/number}","milestones_url":"https://api.github.com/repos/ywelsch/elasticsearch/milestones{/number}","notifications_url":"https://api.github.com/repos/ywelsch/elasticsearch/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ywelsch/elasticsearch/labels{/name}","releases_url":"https://api.github.com/repos/ywelsch/elasticsearch/releases{/id}","created_at":"2015-09-28T12:30:36Z","updated_at":"2015-09-28T12:31:12Z","pushed_at":"2015-11-27T15:39:52Z","git_url":"git://github.com/ywelsch/elasticsearch.git","ssh_url":"git@github.com:ywelsch/elasticsearch.git","clone_url":"https://github.com/ywelsch/elasticsearch.git","svn_url":"https://github.com/ywelsch/elasticsearch","homepage":"https://www.elastic.co/products/elasticsearch","size":177702,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"elastic:master","ref":"master","sha":"2f87e0a4d9b414efe3c7ab5aa35a1112a0b963a5","user":{"login":"elastic","id":6764390,"avatar_url":"https://avatars.githubusercontent.com/u/6764390?v=3","gravatar_id":"","url":"https://api.github.com/users/elastic","html_url":"https://github.com/elastic","followers_url":"https://api.github.com/users/elastic/followers","following_url":"https://api.github.com/users/elastic/following{/other_user}","gists_url":"https://api.github.com/users/elastic/gists{/gist_id}","starred_url":"https://api.github.com/users/elastic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/elastic/subscriptions","organizations_url":"https://api.github.com/users/elastic/orgs","repos_url":"https://api.github.com/users/elastic/repos","events_url":"https://api.github.com/users/elastic/events{/privacy}","received_events_url":"https://api.github.com/users/elastic/received_events","type":"Organization","site_admin":false},"repo":{"id":507775,"name":"elasticsearch","full_name":"elastic/elasticsearch","owner":{"login":"elastic","id":6764390,"avatar_url":"https://avatars.githubusercontent.com/u/6764390?v=3","gravatar_id":"","url":"https://api.github.com/users/elastic","html_url":"https://github.com/elastic","followers_url":"https://api.github.com/users/elastic/followers","following_url":"https://api.github.com/users/elastic/following{/other_user}","gists_url":"https://api.github.com/users/elastic/gists{/gist_id}","starred_url":"https://api.github.com/users/elastic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/elastic/subscriptions","organizations_url":"https://api.github.com/users/elastic/orgs","repos_url":"https://api.github.com/users/elastic/repos","events_url":"https://api.github.com/users/elastic/events{/privacy}","received_events_url":"https://api.github.com/users/elastic/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/elastic/elasticsearch","description":"Open Source, Distributed, RESTful Search Engine","fork":false,"url":"https://api.github.com/repos/elastic/elasticsearch","forks_url":"https://api.github.com/repos/elastic/elasticsearch/forks","keys_url":"https://api.github.com/repos/elastic/elasticsearch/keys{/key_id}","collaborators_url":"https://api.github.com/repos/elastic/elasticsearch/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/elastic/elasticsearch/teams","hooks_url":"https://api.github.com/repos/elastic/elasticsearch/hooks","issue_events_url":"https://api.github.com/repos/elastic/elasticsearch/issues/events{/number}","events_url":"https://api.github.com/repos/elastic/elasticsearch/events","assignees_url":"https://api.github.com/repos/elastic/elasticsearch/assignees{/user}","branches_url":"https://api.github.com/repos/elastic/elasticsearch/branches{/branch}","tags_url":"https://api.github.com/repos/elastic/elasticsearch/tags","blobs_url":"https://api.github.com/repos/elastic/elasticsearch/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/elastic/elasticsearch/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/elastic/elasticsearch/git/refs{/sha}","trees_url":"https://api.github.com/repos/elastic/elasticsearch/git/trees{/sha}","statuses_url":"https://api.github.com/repos/elastic/elasticsearch/statuses/{sha}","languages_url":"https://api.github.com/repos/elastic/elasticsearch/languages","stargazers_url":"https://api.github.com/repos/elastic/elasticsearch/stargazers","contributors_url":"https://api.github.com/repos/elastic/elasticsearch/contributors","subscribers_url":"https://api.github.com/repos/elastic/elasticsearch/subscribers","subscription_url":"https://api.github.com/repos/elastic/elasticsearch/subscription","commits_url":"https://api.github.com/repos/elastic/elasticsearch/commits{/sha}","git_commits_url":"https://api.github.com/repos/elastic/elasticsearch/git/commits{/sha}","comments_url":"https://api.github.com/repos/elastic/elasticsearch/comments{/number}","issue_comment_url":"https://api.github.com/repos/elastic/elasticsearch/issues/comments{/number}","contents_url":"https://api.github.com/repos/elastic/elasticsearch/contents/{+path}","compare_url":"https://api.github.com/repos/elastic/elasticsearch/compare/{base}...{head}","merges_url":"https://api.github.com/repos/elastic/elasticsearch/merges","archive_url":"https://api.github.com/repos/elastic/elasticsearch/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/elastic/elasticsearch/downloads","issues_url":"https://api.github.com/repos/elastic/elasticsearch/issues{/number}","pulls_url":"https://api.github.com/repos/elastic/elasticsearch/pulls{/number}","milestones_url":"https://api.github.com/repos/elastic/elasticsearch/milestones{/number}","notifications_url":"https://api.github.com/repos/elastic/elasticsearch/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/elastic/elasticsearch/labels{/name}","releases_url":"https://api.github.com/repos/elastic/elasticsearch/releases{/id}","created_at":"2010-02-08T13:20:56Z","updated_at":"2015-11-30T09:13:05Z","pushed_at":"2015-11-30T09:34:13Z","git_url":"git://github.com/elastic/elasticsearch.git","ssh_url":"git@github.com:elastic/elasticsearch.git","clone_url":"https://github.com/elastic/elasticsearch.git","svn_url":"https://github.com/elastic/elasticsearch","homepage":"https://www.elastic.co/products/elasticsearch","size":224097,"stargazers_count":13749,"watchers_count":13749,"language":"Java","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":4562,"mirror_url":null,"open_issues_count":1229,"forks":4562,"open_issues":1229,"watchers":13749,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/elastic/elasticsearch/pulls/14964"},"html":{"href":"https://github.com/elastic/elasticsearch/pull/14964"},"issue":{"href":"https://api.github.com/repos/elastic/elasticsearch/issues/14964"},"comments":{"href":"https://api.github.com/repos/elastic/elasticsearch/issues/14964/comments"},"review_comments":{"href":"https://api.github.com/repos/elastic/elasticsearch/pulls/14964/comments"},"review_comment":{"href":"https://api.github.com/repos/elastic/elasticsearch/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/elastic/elasticsearch/pulls/14964/commits"},"statuses":{"href":"https://api.github.com/repos/elastic/elasticsearch/statuses/951b856355ebf5d03cf57611b2d24716a2a61e77"}}}}