GOSSIP-59 OrSet implementation
diff --git a/src/main/java/org/apache/gossip/crdt/CrdtModule.java b/src/main/java/org/apache/gossip/crdt/CrdtModule.java
new file mode 100644
index 0000000..0c8a787
--- /dev/null
+++ b/src/main/java/org/apache/gossip/crdt/CrdtModule.java
@@ -0,0 +1,62 @@
+/*
+ * 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.gossip.crdt;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.Version;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+abstract class OrSetMixin<E> {
+  @JsonCreator
+  OrSetMixin(@JsonProperty("elements") Map<E, Set<UUID>> w, @JsonProperty("tombstones") Map<E, Set<UUID>> h) { }
+  @JsonProperty("elements") abstract Map<E, Set<UUID>> getElements();
+  @JsonProperty("tombstones") abstract Map<E, Set<UUID>> getTombstones();
+  @JsonIgnore abstract boolean isEmpty();
+}
+
+abstract class GrowOnlySetMixin<E>{
+  @JsonCreator
+  GrowOnlySetMixin(@JsonProperty("elements") Set<E> elements){ }
+  @JsonProperty("elements") abstract Set<E> getElements();
+  @JsonIgnore abstract boolean isEmpty();
+}
+
+//If anyone wants to take a stab at this. please have at it
+//https://github.com/FasterXML/jackson-datatype-guava/blob/master/src/main/java/com/fasterxml/jackson/datatype/guava/ser/MultimapSerializer.java
+public class CrdtModule extends SimpleModule {
+
+  private static final long serialVersionUID = 6134836523275023418L;
+
+  public CrdtModule() {
+    super("CrdtModule", new Version(0, 0, 0, "0.0.0", "org.apache.gossip", "gossip"));
+  }
+
+  @Override
+  public void setupModule(SetupContext context) {
+    context.setMixInAnnotations(OrSet.class, OrSetMixin.class);
+    context.setMixInAnnotations(GrowOnlySet.class, GrowOnlySetMixin.class);
+  }
+
+}
+
diff --git a/src/main/java/org/apache/gossip/crdt/CrdtSet.java b/src/main/java/org/apache/gossip/crdt/CrdtSet.java
index 3a5fbca..21b41da 100644
--- a/src/main/java/org/apache/gossip/crdt/CrdtSet.java
+++ b/src/main/java/org/apache/gossip/crdt/CrdtSet.java
@@ -20,7 +20,7 @@
 import java.util.Set;
 
 public interface CrdtSet<ElementType, SetType extends Set<ElementType>, R extends CrdtSet<ElementType, SetType, R>>
-extends Crdt<SetType, R>, Set<ElementType> {
+extends Crdt<SetType, R> {
 
 }
 
diff --git a/src/main/java/org/apache/gossip/crdt/GrowOnlySet.java b/src/main/java/org/apache/gossip/crdt/GrowOnlySet.java
index 0b1771b..9e2dd49 100644
--- a/src/main/java/org/apache/gossip/crdt/GrowOnlySet.java
+++ b/src/main/java/org/apache/gossip/crdt/GrowOnlySet.java
@@ -66,72 +66,58 @@
     return new GrowOnlySet<>(hidden);
   }
 
-  @Override
   public int size() {
     return hidden.size();
   }
 
-  @Override
   public boolean isEmpty() {
     return hidden.isEmpty();
   }
 
-  @Override
   public boolean contains(Object o) {
     return hidden.contains(o);
   }
 
-  @Override
   public Iterator<ElementType> iterator() {
     Set<ElementType> copy = new HashSet<>();
     copy.addAll(hidden);
     return copy.iterator();
   }
 
-  @Override
   public Object[] toArray() {
     return hidden.toArray();
   }
 
-  @Override
   public <T> T[] toArray(T[] a) {
     return hidden.toArray(a);
   }
 
-  @Override
   public boolean add(ElementType e) {
     throw new UnsupportedOperationException();
   }
 
-  @Override
   public boolean remove(Object o) {
     throw new UnsupportedOperationException();
   }
 
-  @Override
   public boolean containsAll(Collection<?> c) {
     return hidden.containsAll(c);
   }
 
-  @Override
   public boolean addAll(Collection<? extends ElementType> c) {
     throw new UnsupportedOperationException();
   }
 
-  @Override
   public boolean retainAll(Collection<?> c) {
     throw new UnsupportedOperationException();
   }
 
-  @Override
   public boolean removeAll(Collection<?> c) {
     throw new UnsupportedOperationException();
   }
 
-  @Override
   public void clear() {
     throw new UnsupportedOperationException();
-    
   }
 
   @Override
@@ -165,4 +151,7 @@
     return true;
   }
 
+  Set<ElementType> getElements(){
+    return hidden;
+  }
 }
diff --git a/src/main/java/org/apache/gossip/crdt/OrSet.java b/src/main/java/org/apache/gossip/crdt/OrSet.java
new file mode 100644
index 0000000..972377f
--- /dev/null
+++ b/src/main/java/org/apache/gossip/crdt/OrSet.java
@@ -0,0 +1,311 @@
+/*
+ * 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.gossip.crdt;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.UUID;
+
+import org.apache.gossip.crdt.OrSet.Builder.Operation;
+
+/*
+ * A immutable set 
+ */
+public class OrSet<E>  implements Crdt<Set<E>, OrSet<E>> {
+  
+  private final Map<E, Set<UUID>> elements = new HashMap<>();
+  private final Map<E, Set<UUID>> tombstones = new HashMap<>();
+  private final transient Set<E> val;
+  
+  public OrSet(){
+    val = computeValue();
+  }
+  
+  OrSet(Map<E, Set<UUID>> elements, Map<E, Set<UUID>> tombstones){
+    this.elements.putAll(elements);
+    this.tombstones.putAll(tombstones);
+    val = computeValue();
+  }
+  
+  @SafeVarargs
+  public OrSet(E ... elements){
+    for (E e: elements){
+      internalAdd(e);
+    }
+    val = computeValue();
+  }
+  
+  public OrSet(Builder<E>builder){
+    for (Builder<E>.OrSetElement<E> e: builder.elements){
+      if (e.operation == Operation.ADD){
+        internalAdd(e.element);
+      } else {
+        internalRemove(e.element);
+      }
+    }
+    val = computeValue();
+  }
+  
+  /**
+   * This constructor is the way to remove elements from an existing set
+   * @param set
+   * @param builder 
+   */
+  public OrSet(OrSet<E> set, Builder<E> builder){
+    elements.putAll(set.elements);
+    tombstones.putAll(set.tombstones);
+    for (Builder<E>.OrSetElement<E> e: builder.elements){
+      if (e.operation == Operation.ADD){
+        internalAdd(e.element);
+      } else {
+        internalRemove(e.element);
+      }
+    }
+    val = computeValue();
+  }
+
+  public OrSet(OrSet<E> left, OrSet<E> right){
+    elements.putAll(left.elements);
+    elements.putAll(right.elements);
+    tombstones.putAll(left.tombstones);
+    tombstones.putAll(right.tombstones);
+    val = computeValue();
+  }
+  
+  public OrSet.Builder<E> builder(){
+    return new OrSet.Builder<>();
+  }
+  
+  @Override
+  public OrSet<E> merge(OrSet<E> other) {
+    return new OrSet<E>(this, other);
+  }
+  
+  private void internalAdd(E element){
+    Set<UUID> l = elements.get(element);
+    if (l == null){
+      Set<UUID> d = new HashSet<UUID>();
+      d.add(UUID.randomUUID());
+      elements.put(element, d);
+    } else {
+      l.add(UUID.randomUUID());
+    }
+  }
+  
+  private void internalRemove(E element){
+    Set<UUID> elementIds = elements.get(element);
+    if (elementIds == null){
+      //deleting elements not in the list
+      return;
+    }
+    Set<UUID> current = tombstones.get(element);
+    if (current != null){
+      current.addAll(elementIds);
+    } else {
+      tombstones.put(element, elementIds);
+    }
+  }
+
+  /*
+   * Computes the live values by analyzing the elements and tombstones
+   */
+  private Set<E> computeValue(){
+    Set<E> values = new HashSet<>();
+    for (Entry<E, Set<UUID>> entry: elements.entrySet()){
+      if (entry.getValue() == null || entry.getValue().size() == 0){
+        continue;
+      }
+      Set<UUID> deleteIds = tombstones.get(entry.getKey());
+      if (deleteIds == null){
+        values.add(entry.getKey());
+      } else {
+        if (!deleteIds.containsAll(entry.getValue())){
+          values.add(entry.getKey());
+        } else {
+          //if all the entry uuid is deleted the entry is deleted
+        }
+      }
+    }
+    return values;
+  }
+  
+  @Override
+  public Set<E> value() {
+    return val;
+  }
+
+  @Override
+  public OrSet<E> optimize() {
+    return this;
+  }
+  
+  public static class Builder<E> {
+    public static enum Operation {
+      ADD, REMOVE
+    };
+
+    private class OrSetElement<EL> {
+      EL element;
+      Operation operation;
+
+      private OrSetElement(EL element, Operation operation) {
+        this.element = element;
+        this.operation = operation;
+      }
+    }
+
+    private List<OrSetElement<E>> elements = new ArrayList<>();
+
+    public Builder<E> add(E element) {
+      elements.add(new OrSetElement<E>(element, Operation.ADD));
+      return this;
+    }
+
+    public Builder<E> remove(E element) {
+      elements.add(new OrSetElement<E>(element, Operation.REMOVE));
+      return this;
+    }
+
+    public Builder<E> mutate(E element, Operation operation) {
+      elements.add(new OrSetElement<E>(element, operation));
+      return this;
+    }
+  }
+
+  
+  public int size() {
+    return value().size();
+  }
+
+  
+  public boolean isEmpty() {
+    return value().size() == 0;
+  }
+
+  
+  public boolean contains(Object o) {
+    return value().contains(o);
+  }
+
+  
+  public Iterator<E> iterator() {
+    Iterator<E> managed = value().iterator();
+    return new Iterator<E>() {
+
+      @Override
+      public void remove() {
+        throw new IllegalArgumentException();
+      }
+
+      @Override
+      public boolean hasNext() {
+        return managed.hasNext();
+      }
+
+      @Override
+      public E next() {
+        return managed.next();
+      }
+      
+    };
+  }
+
+  public Object[] toArray() {
+    return value().toArray();
+  }
+
+  public <T> T[] toArray(T[] a) {
+    return value().toArray(a);
+  }
+
+  public boolean add(E e) {
+    throw new IllegalArgumentException("Can not add");
+  }
+
+
+  public boolean remove(Object o) {
+    throw new IllegalArgumentException();
+  }
+
+  public boolean containsAll(Collection<?> c) {
+    return this.value().containsAll(c);
+  }
+
+  public boolean addAll(Collection<? extends E> c) {
+    throw new IllegalArgumentException();
+  }
+
+  public boolean retainAll(Collection<?> c) {
+    throw new IllegalArgumentException();
+  }
+
+  public boolean removeAll(Collection<?> c) {
+    throw new IllegalArgumentException();
+  }
+
+  public void clear() {
+    throw new IllegalArgumentException();
+  }
+
+  @Override
+  public String toString() {
+    return "OrSet [elements=" + elements + ", tombstones=" + tombstones + "]" ;
+  }
+
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((value() == null) ? 0 : value().hashCode());
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj)
+      return true;
+    if (obj == null)
+      return false;
+    if (getClass() != obj.getClass())
+      return false;
+    @SuppressWarnings("rawtypes")
+    OrSet other = (OrSet) obj;
+    if (elements == null) {
+      if (other.elements != null)
+        return false;
+    } else if (!value().equals(other.value()))
+      return false;
+    return true;
+  }
+
+  Map<E, Set<UUID>> getElements() {
+    return elements;
+  }
+
+  Map<E, Set<UUID>> getTombstones() {
+    return tombstones;
+  }
+
+}
diff --git a/src/main/java/org/apache/gossip/manager/GossipCore.java b/src/main/java/org/apache/gossip/manager/GossipCore.java
index de54597..dff6413 100644
--- a/src/main/java/org/apache/gossip/manager/GossipCore.java
+++ b/src/main/java/org/apache/gossip/manager/GossipCore.java
@@ -119,14 +119,15 @@
       sharedData.putIfAbsent(message.getKey(), message);
     } else {
       if (message.getPayload() instanceof Crdt){
-        SharedGossipDataMessage m = sharedData.get(message.getKey());
+        SharedGossipDataMessage curretnt = sharedData.get(message.getKey());
         SharedGossipDataMessage merged = new SharedGossipDataMessage();
         merged.setExpireAt(message.getExpireAt());
-        merged.setKey(m.getKey());
+        merged.setKey(curretnt.getKey());
         merged.setNodeId(message.getNodeId());
         merged.setTimestamp(message.getTimestamp());
-        merged.setPayload( ((Crdt) message.getPayload()).merge((Crdt)m.getPayload()));
-        sharedData.put(m.getKey(), merged);
+        Crdt mergedCrdt = ((Crdt) message.getPayload()).merge((Crdt)curretnt.getPayload());
+        merged.setPayload( mergedCrdt );
+        sharedData.put(curretnt.getKey(), merged);
       } else {
         if (previous.getTimestamp() < message.getTimestamp()) {
           sharedData.replace(message.getKey(), previous, message);
@@ -370,9 +371,10 @@
       copy.setExpireAt(message.getExpireAt());
       copy.setKey(message.getKey());
       copy.setNodeId(message.getNodeId());
+      copy.setTimestamp(message.getTimestamp());
       @SuppressWarnings("unchecked")
       Crdt merged = ((Crdt) ret.getPayload()).merge((Crdt) message.getPayload());
-      message.setPayload(merged);
+      copy.setPayload(merged);
       boolean replaced = sharedData.replace(message.getKey(), ret, copy);
       if (replaced){
         return merged;
diff --git a/src/main/java/org/apache/gossip/manager/random/RandomGossipManager.java b/src/main/java/org/apache/gossip/manager/random/RandomGossipManager.java
index bf8a8c3..3ac237a 100644
--- a/src/main/java/org/apache/gossip/manager/random/RandomGossipManager.java
+++ b/src/main/java/org/apache/gossip/manager/random/RandomGossipManager.java
@@ -18,9 +18,11 @@
 package org.apache.gossip.manager.random;
 
 import com.codahale.metrics.MetricRegistry;
+import com.fasterxml.jackson.core.JsonGenerator.Feature;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.gossip.GossipMember;
 import org.apache.gossip.GossipSettings;
+import org.apache.gossip.crdt.CrdtModule;
 import org.apache.gossip.event.GossipListener;
 import org.apache.gossip.manager.GossipManager;
 import org.apache.gossip.manager.handlers.DefaultMessageInvoker;
@@ -126,6 +128,8 @@
       if (objectMapper == null) {
         objectMapper = new ObjectMapper();
         objectMapper.enableDefaultTyping();
+        objectMapper.registerModule(new CrdtModule());
+        objectMapper.configure(Feature.WRITE_NUMBERS_AS_STRINGS, false);
       }
       if (messageInvoker == null) {
         messageInvoker = new DefaultMessageInvoker();
diff --git a/src/main/java/org/apache/gossip/udp/UdpSharedGossipDataMessage.java b/src/main/java/org/apache/gossip/udp/UdpSharedGossipDataMessage.java
index ba8e735..6020328 100644
--- a/src/main/java/org/apache/gossip/udp/UdpSharedGossipDataMessage.java
+++ b/src/main/java/org/apache/gossip/udp/UdpSharedGossipDataMessage.java
@@ -42,7 +42,9 @@
 
   @Override
   public String toString() {
-    return "UdpSharedGossipDataMessage [uriFrom=" + uriFrom + ", uuid=" + uuid + "]";
+    return "UdpSharedGossipDataMessage [uriFrom=" + uriFrom + ", uuid=" + uuid + ", getNodeId()="
+            + getNodeId() + ", getKey()=" + getKey() + ", getPayload()=" + getPayload()
+            + ", getTimestamp()=" + getTimestamp() + ", getExpireAt()=" + getExpireAt() + "]";
   }
 
 }
diff --git a/src/test/java/org/apache/gossip/DataTest.java b/src/test/java/org/apache/gossip/DataTest.java
index f05636b..3892e9b 100644
--- a/src/test/java/org/apache/gossip/DataTest.java
+++ b/src/test/java/org/apache/gossip/DataTest.java
@@ -29,6 +29,7 @@
 import java.util.concurrent.TimeUnit;
 
 import org.apache.gossip.crdt.GrowOnlySet;
+import org.apache.gossip.crdt.OrSet;
 import org.apache.gossip.model.GossipDataMessage;
 import org.apache.gossip.model.SharedGossipDataMessage;
 import org.junit.Test;
@@ -37,6 +38,8 @@
 
 public class DataTest {
   
+  private String orSetKey = "cror";
+  
   @Test
   public void dataTest() throws InterruptedException, UnknownHostException, URISyntaxException{
     GossipSettings settings = new GossipSettings();
@@ -88,11 +91,62 @@
     givenDifferentDatumsInSet(clients);
     assertThatListIsMerged(clients);
     
+    givenOrs(clients);
+    assertThatOrSetIsMerged(clients);
+    dropIt(clients);
+    assertThatOrSetDelIsMerged(clients);
+    
     for (int i = 0; i < clusterMembers; ++i) {
       clients.get(i).shutdown();
     }
   }
   
+  private void givenOrs(List<GossipService> clients) {
+    {
+      SharedGossipDataMessage d = new SharedGossipDataMessage();
+      d.setKey(orSetKey);
+      d.setPayload(new OrSet<String>("1", "2"));
+      d.setExpireAt(Long.MAX_VALUE);
+      d.setTimestamp(System.currentTimeMillis());
+      clients.get(0).getGossipManager().merge(d);
+    }
+    {
+      SharedGossipDataMessage d = new SharedGossipDataMessage();
+      d.setKey(orSetKey);
+      d.setPayload(new OrSet<String>("3", "4"));
+      d.setExpireAt(Long.MAX_VALUE);
+      d.setTimestamp(System.currentTimeMillis());
+      clients.get(1).getGossipManager().merge(d);
+    }
+  }
+  
+  private void dropIt(List<GossipService> clients) {
+    @SuppressWarnings("unchecked")
+    OrSet<String> o = (OrSet<String>) clients.get(0).getGossipManager().findCrdt(orSetKey);
+    OrSet<String> o2 = new OrSet<String>(o, new OrSet.Builder<String>().remove("3"));
+    SharedGossipDataMessage d = new SharedGossipDataMessage();
+    d.setKey(orSetKey);
+    d.setPayload(o2);
+    d.setExpireAt(Long.MAX_VALUE);
+    d.setTimestamp(System.currentTimeMillis());
+    clients.get(0).getGossipManager().merge(d);
+  }
+  
+  private void assertThatOrSetIsMerged(final List<GossipService> clients){
+    TUnit.assertThat(() ->  {
+      return clients.get(0).getGossipManager().findCrdt(orSetKey).value();
+    }).afterWaitingAtMost(10, TimeUnit.SECONDS).isEqualTo(new OrSet<String>("1", "2", "3", "4").value());
+    TUnit.assertThat(() ->  {
+      return clients.get(1).getGossipManager().findCrdt(orSetKey).value();
+    }).afterWaitingAtMost(10, TimeUnit.SECONDS).isEqualTo(new OrSet<String>("1", "2", "3", "4").value());
+  }
+  
+  private void assertThatOrSetDelIsMerged(final List<GossipService> clients){
+    TUnit.assertThat(() ->  {
+      return clients.get(0).getGossipManager().findCrdt(orSetKey);
+    }).afterWaitingAtMost(10, TimeUnit.SECONDS).equals(new OrSet<String>("1", "2", "4"));
+  }
+
   private void givenDifferentDatumsInSet(final List<GossipService> clients){
     clients.get(0).getGossipManager().merge(CrdtMessage("1"));
     clients.get(1).getGossipManager().merge(CrdtMessage("2"));
@@ -101,7 +155,7 @@
   private void assertThatListIsMerged(final List<GossipService> clients){
     TUnit.assertThat(() ->  {
       return clients.get(0).getGossipManager().findCrdt("cr");
-    }).afterWaitingAtMost(10, TimeUnit.SECONDS).equals(new GrowOnlySet<String>(Arrays.asList("1","2")));
+    }).afterWaitingAtMost(10, TimeUnit.SECONDS).isEqualTo(new GrowOnlySet<String>(Arrays.asList("1","2")));
   }
   
   private SharedGossipDataMessage CrdtMessage(String item){
diff --git a/src/test/java/org/apache/gossip/crdt/OrSetTest.java b/src/test/java/org/apache/gossip/crdt/OrSetTest.java
new file mode 100644
index 0000000..8b8766a
--- /dev/null
+++ b/src/test/java/org/apache/gossip/crdt/OrSetTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.gossip.crdt;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.apache.gossip.GossipService;
+import org.apache.gossip.GossipSettings;
+import org.apache.gossip.RemoteGossipMember;
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.codahale.metrics.MetricRegistry;
+
+public class OrSetTest {
+
+  @Test
+  public void atest() {
+    OrSet<Integer> i = new OrSet<>(new OrSet.Builder<Integer>().add(4).add(5).add(6).remove(5));
+    Assert.assertArrayEquals(Arrays.asList(4, 6).toArray(), i.value().toArray());
+  }
+  
+  @Test
+  public void mergeTest(){
+    OrSet<Integer> i = new OrSet<>(new OrSet.Builder<Integer>().add(4).add(5).add(6).remove(5));
+    Assert.assertArrayEquals(Arrays.asList(4, 6).toArray(), i.value().toArray());
+    OrSet<Integer> j = new OrSet<>(new OrSet.Builder<Integer>().add(9).add(4).add(5).remove(6));
+    OrSet<Integer> h = i.merge(j);
+    Assert.assertEquals(new OrSet<Integer>(4,6,9,5), h);
+  }
+  
+  @Test
+  public void mergeTest2(){
+    OrSet<Integer> i = new OrSet<>(new OrSet.Builder<Integer>().add(5).add(4).remove(4).add(6));
+    Assert.assertEquals(new OrSet<Integer>(5,6), i);
+    SortedSet<Integer> tree = new TreeSet<>();
+    for (Integer in: i.value()){
+      tree.add(in);
+    }
+    TreeSet<Integer> compare = new TreeSet<>();
+    compare.add(5);
+    compare.add(6);
+    Assert.assertEquals(tree, compare);
+  }
+  
+  @Test
+  public void mergeTest4() {
+    Assert.assertArrayEquals(new Integer[] {},
+            new OrSet<Integer>(new OrSet.Builder<Integer>().add(1).remove(1)).toArray());
+  }
+  
+  @Test
+  public void mergeTest3(){
+    OrSet<Integer> i = new OrSet<>(1);
+    OrSet<Integer> j = new OrSet<>(2);
+    OrSet<Integer> k = new OrSet<>(i.merge(j),  new OrSet.Builder<Integer>().remove(1));
+    Assert.assertArrayEquals(new Integer[] { 2 }, i.merge(j).merge(k).toArray());
+    Assert.assertArrayEquals(new Integer[] { 2 }, j.merge(i).merge(k).toArray());
+    Assert.assertArrayEquals(new Integer[] { 2 }, k.merge(i).merge(j).toArray());
+    Assert.assertArrayEquals(new Integer[] { 2 }, k.merge(j).merge(i).toArray());
+    Assert.assertEquals(j , i.merge(j.merge(k)));
+  }
+  
+  @Test
+  public void mergeTest9(){
+    OrSet<Integer> i = new OrSet<>(19);
+    OrSet<Integer> j = i.merge(i);
+    Assert.assertEquals(i.value(), j.value());
+  }
+  
+  @Test
+  public void serialTest() throws InterruptedException, URISyntaxException, IOException {
+    GossipService gossipService2 = new GossipService("a", new URI("udp://" + "127.0.0.1" + ":" + (29000 + 1)), "1", new HashMap<>(),
+            Arrays.asList(new RemoteGossipMember("a",
+                    new URI("udp://" + "127.0.0.1" + ":" + (29000 + 0)), "0")),
+            new GossipSettings(), (a, b) -> { }, new MetricRegistry());
+    OrSet<Integer> i = new OrSet<Integer>(new OrSet.Builder<Integer>().add(1).remove(1));
+    String s = gossipService2.getGossipManager().getObjectMapper().writeValueAsString(i);
+    @SuppressWarnings("unchecked")
+    OrSet<Integer> back = gossipService2.getGossipManager().getObjectMapper().readValue(s, OrSet.class);
+    Assert.assertEquals(back, i);
+  }
+  
+}