├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── io │ └── dmitryivanov │ └── crdt │ ├── GSet.java │ ├── LWWSet.java │ ├── ORSet.java │ ├── OURSet.java │ ├── TwoPSet.java │ └── helpers │ ├── ComparisonChain.java │ ├── Operations.java │ └── Primitives.java └── test └── java └── io └── dmitryivanov └── crdt ├── GSetTests.java ├── LWWSetTests.java ├── ORSetTests.java ├── OURSetTests.java └── TwoPSetTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | target/ 15 | .DS_Store 16 | .idea/ 17 | .classpath 18 | .project 19 | .settings/ 20 | .externalToolBuilders/ 21 | maven-eclipse.xml 22 | release.properties 23 | *.iml 24 | classes/ 25 | .java-version 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Dmitry Ivanov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # java-crdt 2 | 3 | Collection of common CRDTs for Java. 4 | 5 | ### CRDT Sets 6 | 7 | - G-Set: Grow-Only Set that allows only addition operations. 8 | - 2P-Set: 2-Phase Set which allows removing element only once. 9 | - LWW-Set: Last-Write-Wins-Element Set. Uses 'timestamps' associated with addition and deletion operations for picking the winner. 10 | - OR-Set: Observed-Removed Set. Associates unique tag with each addition operation. Deletion is applied for particular tag. 11 | - OUR-Set: Observed-Updated-Removed Set. Uses unique identifiers (UUIDs) for distingishing different elements within a set. Conflict resolution is based on the 'lastModified' timestamp value associated with each element state in the set. 12 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | io.dmitryivanov 8 | java-crdt 9 | 1.0-SNAPSHOT 10 | 11 | 12 | UTF-8 13 | UTF-8 14 | 15 | 16 | 17 | 18 | junit 19 | junit 20 | 4.11 21 | test 22 | 23 | 24 | com.google.code.findbugs 25 | jsr305 26 | 3.0.0 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | org.apache.maven.plugins 35 | maven-compiler-plugin 36 | 3.1 37 | 38 | 1.7 39 | 1.7 40 | 1.7 41 | -Xlint:all 42 | true 43 | true 44 | 1024 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | org.apache.maven.plugins 53 | maven-compiler-plugin 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/main/java/io/dmitryivanov/crdt/GSet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Dmitry Ivanov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package io.dmitryivanov.crdt; 26 | 27 | import io.dmitryivanov.crdt.helpers.Operations; 28 | 29 | import java.util.Collections; 30 | import java.util.HashSet; 31 | import java.util.Set; 32 | 33 | public class GSet { 34 | 35 | private Set set; 36 | 37 | public GSet() { 38 | set = new HashSet<>(); 39 | } 40 | 41 | GSet(Set set) { 42 | this.set = new HashSet<>(set); 43 | } 44 | 45 | public void add(final E elem) { 46 | set.add(elem); 47 | } 48 | 49 | public Set lookup() { 50 | return Collections.unmodifiableSet(set); 51 | } 52 | 53 | public GSet merge(GSet anotherGSet) { 54 | final HashSet newSet = new HashSet<>(set); 55 | newSet.addAll(anotherGSet.getSet()); 56 | return new GSet<>(newSet); 57 | } 58 | 59 | public GSet diff(GSet anotherGSet) { 60 | return new GSet<>(Operations.diff(set, anotherGSet.lookup())); 61 | } 62 | 63 | // Visible for testing 64 | Set getSet() { 65 | return set; 66 | } 67 | 68 | @Override 69 | public boolean equals(Object o) { 70 | if (this == o) return true; 71 | if (o == null || getClass() != o.getClass()) return false; 72 | 73 | GSet gSet = (GSet) o; 74 | 75 | return set.equals(gSet.set); 76 | 77 | } 78 | 79 | @Override 80 | public int hashCode() { 81 | return set.hashCode(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/io/dmitryivanov/crdt/LWWSet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Dmitry Ivanov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package io.dmitryivanov.crdt; 26 | 27 | import io.dmitryivanov.crdt.helpers.Operations; 28 | 29 | import java.util.Set; 30 | 31 | public class LWWSet { 32 | 33 | private GSet> addSet; 34 | 35 | private GSet> removeSet; 36 | 37 | public static class ElementState { 38 | private long timestamp; 39 | private E element; 40 | 41 | public ElementState(long timestamp, E element) { 42 | this.timestamp = timestamp; 43 | this.element = element; 44 | } 45 | 46 | public long getTimestamp() { 47 | return timestamp; 48 | } 49 | 50 | public E getElement() { 51 | return element; 52 | } 53 | 54 | @Override 55 | public boolean equals(Object o) { 56 | if (this == o) return true; 57 | if (o == null || getClass() != o.getClass()) return false; 58 | 59 | ElementState that = (ElementState) o; 60 | 61 | return timestamp == that.timestamp && !(element != null ? !element.equals(that.element) : that.element != null); 62 | } 63 | 64 | @Override 65 | public int hashCode() { 66 | int result = (int) (timestamp ^ (timestamp >>> 32)); 67 | result = 31 * result + (element != null ? element.hashCode() : 0); 68 | return result; 69 | } 70 | } 71 | 72 | public LWWSet() { 73 | this.addSet = new GSet<>(); 74 | this.removeSet = new GSet<>(); 75 | } 76 | 77 | LWWSet(GSet> addSet, GSet> removeSet) { 78 | this.addSet = addSet; 79 | this.removeSet = removeSet; 80 | } 81 | 82 | public void add(ElementState elementState) { 83 | addSet.add(elementState); 84 | } 85 | 86 | public void remove(ElementState elementState) { 87 | removeSet.add(elementState); 88 | } 89 | 90 | public LWWSet merge(LWWSet anotherLwwSet) { 91 | return new LWWSet<>(addSet.merge(anotherLwwSet.addSet), removeSet.merge(anotherLwwSet.removeSet)); 92 | } 93 | 94 | public LWWSet diff(LWWSet anotherLwwSet) { 95 | final LWWSet mergeResult = merge(anotherLwwSet); 96 | return new LWWSet<>( 97 | mergeResult.getAddSet().diff(anotherLwwSet.getAddSet()), 98 | mergeResult.getRemoveSet().diff(anotherLwwSet.getRemoveSet())); 99 | } 100 | 101 | public Set lookup() { 102 | Set> lookup = addSet.lookup(); 103 | return Operations.filteredAndMapped(lookup, new Operations.Predicate>() { 104 | @Override 105 | public boolean call(ElementState element) { 106 | return nonRemoved(element); 107 | } 108 | }, new Operations.Mapper, E>() { 109 | @Override 110 | public E call(ElementState element) { 111 | return element.element; 112 | } 113 | }); 114 | } 115 | 116 | private boolean nonRemoved(final ElementState addState) { 117 | Set> removes = Operations.filtered(removeSet.lookup(), 118 | new Operations.Predicate>() { 119 | @Override 120 | public boolean call(ElementState element) { 121 | return element.getElement().equals(addState.getElement()) 122 | && element.getTimestamp() > addState.getTimestamp(); 123 | } 124 | }); 125 | return removes.isEmpty(); 126 | } 127 | 128 | // Visible for testing 129 | GSet> getAddSet() { 130 | return new GSet>().merge(addSet); 131 | } 132 | 133 | // Visible for testing 134 | GSet> getRemoveSet() { 135 | return new GSet>().merge(removeSet); 136 | } 137 | 138 | @Override 139 | public boolean equals(Object o) { 140 | if (this == o) return true; 141 | if (o == null || getClass() != o.getClass()) return false; 142 | 143 | LWWSet lwwSet = (LWWSet) o; 144 | 145 | return addSet.equals(lwwSet.addSet) && removeSet.equals(lwwSet.removeSet); 146 | } 147 | 148 | @Override 149 | public int hashCode() { 150 | int result = addSet.hashCode(); 151 | result = 31 * result + removeSet.hashCode(); 152 | return result; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/io/dmitryivanov/crdt/ORSet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Dmitry Ivanov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package io.dmitryivanov.crdt; 26 | 27 | import io.dmitryivanov.crdt.helpers.Operations; 28 | 29 | import java.util.Set; 30 | 31 | public class ORSet { 32 | 33 | private GSet> addSet; 34 | 35 | private GSet> removeSet; 36 | 37 | public static class ElementState { 38 | private String tag; 39 | private E element; 40 | 41 | public ElementState(String tag, E element) { 42 | this.tag = tag; 43 | this.element = element; 44 | } 45 | 46 | public String getTag() { 47 | return tag; 48 | } 49 | 50 | public E getElement() { 51 | return element; 52 | } 53 | 54 | @Override 55 | public boolean equals(Object o) { 56 | if (this == o) return true; 57 | if (o == null || getClass() != o.getClass()) return false; 58 | 59 | ElementState that = (ElementState) o; 60 | 61 | return !(tag != null ? !tag.equals(that.tag) : that.tag != null) && !(element != null ? !element.equals(that.element) : that.element != null); 62 | } 63 | 64 | @Override 65 | public int hashCode() { 66 | int result = tag != null ? tag.hashCode() : 0; 67 | result = 31 * result + (element != null ? element.hashCode() : 0); 68 | return result; 69 | } 70 | } 71 | 72 | public ORSet() { 73 | this.addSet = new GSet<>(); 74 | this.removeSet = new GSet<>(); 75 | } 76 | 77 | ORSet(GSet> addSet, GSet> removeSet) { 78 | this.addSet = addSet; 79 | this.removeSet = removeSet; 80 | } 81 | 82 | public void add(ElementState elementState) { 83 | addSet.add(elementState); 84 | } 85 | 86 | public void remove(ElementState elementState) { 87 | removeSet.add(elementState); 88 | } 89 | 90 | public ORSet merge(ORSet anotherORSet) { 91 | return new ORSet<>(addSet.merge(anotherORSet.addSet), removeSet.merge(anotherORSet.removeSet)); 92 | } 93 | 94 | public ORSet diff(ORSet anotherORSet) { 95 | return new ORSet<>(addSet.diff(anotherORSet.addSet), removeSet.diff(anotherORSet.removeSet)); 96 | } 97 | 98 | public Set lookup() { 99 | return Operations.filteredAndMapped(addSet.lookup(), new Operations.Predicate>() { 100 | @Override 101 | public boolean call(ElementState element) { 102 | return nonRemoved(element); 103 | } 104 | }, new Operations.Mapper, E>() { 105 | @Override 106 | public E call(ElementState element) { 107 | return element.element; 108 | } 109 | }); 110 | } 111 | 112 | private boolean nonRemoved(final ElementState addState) { 113 | Set> removes = Operations.filtered(removeSet.lookup(), 114 | new Operations.Predicate>() { 115 | @Override 116 | public boolean call(ElementState element) { 117 | return element.getElement().equals(addState.getElement()) 118 | && element.getTag().equals(addState.getTag()); 119 | } 120 | }); 121 | return removes.isEmpty(); 122 | } 123 | 124 | // Visible for testing 125 | GSet> getAddSet() { 126 | return new GSet>().merge(addSet); 127 | } 128 | 129 | // Visible for testing 130 | GSet> getRemoveSet() { 131 | return new GSet>().merge(removeSet); 132 | } 133 | 134 | @Override 135 | public boolean equals(Object o) { 136 | if (this == o) return true; 137 | if (o == null || getClass() != o.getClass()) return false; 138 | 139 | ORSet orSet = (ORSet) o; 140 | 141 | return addSet.equals(orSet.addSet) && removeSet.equals(orSet.removeSet); 142 | } 143 | 144 | @Override 145 | public int hashCode() { 146 | int result = addSet.hashCode(); 147 | result = 31 * result + removeSet.hashCode(); 148 | return result; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/io/dmitryivanov/crdt/OURSet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Dmitry Ivanov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package io.dmitryivanov.crdt; 26 | 27 | import io.dmitryivanov.crdt.helpers.ComparisonChain; 28 | import io.dmitryivanov.crdt.helpers.Operations; 29 | 30 | import java.util.*; 31 | 32 | public class OURSet> { 33 | 34 | private Set> elements = new HashSet<>(); 35 | 36 | public static class ElementState> implements Comparable> { 37 | private UUID id; 38 | private boolean removed; 39 | private long timestamp; 40 | private E element; 41 | 42 | public ElementState(UUID id, boolean removed, long timestamp, E element) { 43 | this.id = id; 44 | this.removed = removed; 45 | this.timestamp = timestamp; 46 | this.element = element; 47 | } 48 | 49 | public ElementState(UUID id, long timestamp, E element) { 50 | this(id, false, timestamp, element); 51 | } 52 | 53 | @Override 54 | public int compareTo(ElementState anotherElementState) { 55 | return ComparisonChain.start() 56 | .compare(getId(), anotherElementState.getId()) 57 | .compare(getTimestamp(), anotherElementState.getTimestamp()) 58 | .compareFalseFirst(isRemoved(), anotherElementState.isRemoved()) 59 | .compare(getElement(), anotherElementState.getElement()) 60 | .result(); 61 | } 62 | 63 | public UUID getId() { 64 | return id; 65 | } 66 | 67 | public boolean isRemoved() { 68 | return removed; 69 | } 70 | 71 | public long getTimestamp() { 72 | return timestamp; 73 | } 74 | 75 | public E getElement() { 76 | return element; 77 | } 78 | 79 | @Override 80 | public boolean equals(Object o) { 81 | if (this == o) return true; 82 | if (o == null || getClass() != o.getClass()) return false; 83 | ElementState that = (ElementState) o; 84 | return removed == that.removed && timestamp == that.timestamp && Objects.equals(id, that.id) && Objects.equals(element, that.element); 85 | } 86 | 87 | @Override 88 | public int hashCode() { 89 | return Objects.hash(id, removed, timestamp, element); 90 | } 91 | } 92 | 93 | public OURSet() { 94 | elements = new HashSet<>(); 95 | } 96 | 97 | OURSet(Collection> elementStates) { 98 | elements = new HashSet<>(elementStates); 99 | } 100 | 101 | public void add(ElementState elementState) { 102 | Set> conflicts = new HashSet<>(); 103 | conflicts.add(elementState); 104 | Set> rest = new HashSet<>(); 105 | 106 | for (ElementState e : getElements()) { 107 | if (e.getId().equals(elementState.getId())) { 108 | conflicts.add(e); 109 | } else { 110 | rest.add(e); 111 | } 112 | } 113 | 114 | ElementState winner = Operations.select(conflicts, 115 | new Operations.Predicate2, ElementState>() { 116 | @Override 117 | public boolean call(ElementState first, ElementState second) { 118 | return first.compareTo(second) < 0; 119 | } 120 | }); 121 | rest.add(winner); 122 | 123 | this.elements = rest; 124 | } 125 | 126 | public void remove(ElementState elementState) { 127 | add(new ElementState<>(elementState.id, true, elementState.timestamp, elementState.element)); 128 | } 129 | 130 | public OURSet merge(OURSet anotherOURSet) { 131 | final Set> union = Operations.union(elements, anotherOURSet.getElements()); 132 | 133 | // group by elements id 134 | final Map>> index = Operations.groupBy(union, new Operations.Mapper, UUID>() { 135 | @Override 136 | public UUID call(ElementState element) { 137 | return element.id; 138 | } 139 | }); 140 | 141 | //apply the merging logic 142 | final Map> mergeResult = Operations.mapValues(index, 143 | new Operations.Mapper>, ElementState>() { 144 | @Override 145 | public ElementState call(Collection> conflicts) { 146 | final ElementState mergedState; 147 | if (conflicts.size() > 1) { 148 | mergedState = Collections.max(conflicts); 149 | } else { 150 | mergedState = conflicts.iterator().next(); 151 | } 152 | return mergedState; 153 | } 154 | }); 155 | 156 | return new OURSet<>(new HashSet<>(mergeResult.values())); 157 | } 158 | 159 | public OURSet diff(OURSet anotherOURSet) { 160 | final OURSet mergeResult = merge(anotherOURSet); 161 | final Set> diff = Operations.diff(mergeResult.getElements(), anotherOURSet.getElements()); 162 | 163 | return new OURSet<>(diff); 164 | } 165 | 166 | public Set lookup() { 167 | return Operations.filteredAndMapped(elements, new Operations.Predicate>() { 168 | @Override 169 | public boolean call(ElementState element) { 170 | return !element.removed; 171 | } 172 | }, new Operations.Mapper, E>() { 173 | @Override 174 | public E call(ElementState element) { 175 | return element.element; 176 | } 177 | }); 178 | } 179 | 180 | public Set> getElements() { 181 | return Collections.unmodifiableSet(elements); 182 | } 183 | 184 | @Override 185 | public boolean equals(Object o) { 186 | if (this == o) return true; 187 | if (o == null || getClass() != o.getClass()) return false; 188 | 189 | OURSet ourSet = (OURSet) o; 190 | 191 | return elements.equals(ourSet.elements); 192 | } 193 | 194 | @Override 195 | public int hashCode() { 196 | return elements.hashCode(); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/main/java/io/dmitryivanov/crdt/TwoPSet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Dmitry Ivanov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package io.dmitryivanov.crdt; 26 | 27 | import io.dmitryivanov.crdt.helpers.Operations; 28 | 29 | import java.util.Collections; 30 | import java.util.HashSet; 31 | import java.util.Set; 32 | 33 | public class TwoPSet { 34 | 35 | private GSet addSet; 36 | 37 | private GSet removeSet; 38 | 39 | public TwoPSet() { 40 | addSet = new GSet<>(); 41 | removeSet = new GSet<>(); 42 | } 43 | 44 | private TwoPSet(GSet addSet, GSet removeSet) { 45 | this.addSet = new GSet<>(addSet.lookup()); 46 | this.removeSet = new GSet<>(removeSet.lookup()); 47 | } 48 | 49 | public void add(final E element) { 50 | addSet.add(element); 51 | } 52 | 53 | public void remove(final E element) { 54 | removeSet.add(element); 55 | } 56 | 57 | public Set lookup() { 58 | Set resultSet = Operations.diff(addSet.lookup(), removeSet.lookup()); 59 | return Collections.unmodifiableSet(resultSet); 60 | } 61 | 62 | public TwoPSet merge(TwoPSet another2PSet) { 63 | return new TwoPSet<>(addSet.merge(another2PSet.addSet), removeSet.merge(another2PSet.removeSet)); 64 | } 65 | 66 | public TwoPSet diff(TwoPSet anotherSet) { 67 | return new TwoPSet<>(addSet.diff(anotherSet.addSet), removeSet.diff(anotherSet.removeSet)); 68 | } 69 | 70 | private Set union(HashSet firstSet, HashSet secondSet) { 71 | final HashSet result = new HashSet<>(); 72 | result.addAll(firstSet); 73 | result.addAll(secondSet); 74 | return Collections.unmodifiableSet(result); 75 | } 76 | 77 | private Set diff(HashSet firstSet, HashSet secondSet) { 78 | return Operations.diff(firstSet, secondSet); 79 | } 80 | 81 | // Visible for testing 82 | GSet getAddSet() { 83 | return new GSet<>(addSet.lookup()); 84 | } 85 | 86 | // Visible for testing 87 | GSet getRemoveSet() { 88 | return new GSet<>(removeSet.lookup()); 89 | } 90 | 91 | @Override 92 | public boolean equals(Object o) { 93 | if (this == o) return true; 94 | if (o == null || getClass() != o.getClass()) return false; 95 | 96 | TwoPSet twoPSet = (TwoPSet) o; 97 | 98 | return addSet.equals(twoPSet.addSet) && removeSet.equals(twoPSet.removeSet); 99 | } 100 | 101 | @Override 102 | public int hashCode() { 103 | int result = addSet.hashCode(); 104 | result = 31 * result + removeSet.hashCode(); 105 | return result; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/io/dmitryivanov/crdt/helpers/ComparisonChain.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 The Guava Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.dmitryivanov.crdt.helpers; 18 | 19 | import javax.annotation.Nullable; 20 | 21 | import java.util.Comparator; 22 | 23 | /** 24 | * A utility for performing a chained comparison statement. For example: 25 | *
   {@code
 26 |  *
 27 |  *   public int compareTo(Foo that) {
 28 |  *     return ComparisonChain.start()
 29 |  *         .compare(this.aString, that.aString)
 30 |  *         .compare(this.anInt, that.anInt)
 31 |  *         .compare(this.anEnum, that.anEnum, Ordering.natural().nullsLast())
 32 |  *         .result();
 33 |  *   }}
34 | * 35 | *

The value of this expression will have the same sign as the first 36 | * nonzero comparison result in the chain, or will be zero if every 37 | * comparison result was zero. 38 | * 39 | *

Note: {@code ComparisonChain} instances are immutable. For 40 | * this utility to work correctly, calls must be chained as illustrated above. 41 | * 42 | *

Performance note: Even though the {@code ComparisonChain} caller always 43 | * invokes its {@code compare} methods unconditionally, the {@code 44 | * ComparisonChain} implementation stops calling its inputs' {@link 45 | * Comparable#compareTo compareTo} and {@link Comparator#compare compare} 46 | * methods as soon as one of them returns a nonzero result. This optimization is 47 | * typically important only in the presence of expensive {@code compareTo} and 48 | * {@code compare} implementations. 49 | * 50 | *

See the Guava User Guide article on 52 | * {@code ComparisonChain}. 53 | * 54 | * @author Mark Davis 55 | * @author Kevin Bourrillion 56 | * @since 2.0 57 | */ 58 | public abstract class ComparisonChain { 59 | private ComparisonChain() {} 60 | 61 | /** 62 | * Begins a new chained comparison statement. See example in the class 63 | * documentation. 64 | */ 65 | public static ComparisonChain start() { 66 | return ACTIVE; 67 | } 68 | 69 | private static final ComparisonChain ACTIVE = 70 | new ComparisonChain() { 71 | @SuppressWarnings("unchecked") 72 | @Override 73 | public ComparisonChain compare(Comparable left, Comparable right) { 74 | return classify(left.compareTo(right)); 75 | } 76 | 77 | @Override 78 | public ComparisonChain compare( 79 | @Nullable T left, @Nullable T right, Comparator comparator) { 80 | return classify(comparator.compare(left, right)); 81 | } 82 | 83 | @Override 84 | public ComparisonChain compare(int left, int right) { 85 | return classify(Primitives.compare(left, right)); 86 | } 87 | 88 | @Override 89 | public ComparisonChain compare(long left, long right) { 90 | return classify(Primitives.compare(left, right)); 91 | } 92 | 93 | @Override 94 | public ComparisonChain compare(float left, float right) { 95 | return classify(Float.compare(left, right)); 96 | } 97 | 98 | @Override 99 | public ComparisonChain compare(double left, double right) { 100 | return classify(Double.compare(left, right)); 101 | } 102 | 103 | @Override 104 | public ComparisonChain compareTrueFirst(boolean left, boolean right) { 105 | return classify(Primitives.compare(right, left)); // reversed 106 | } 107 | 108 | @Override 109 | public ComparisonChain compareFalseFirst(boolean left, boolean right) { 110 | return classify(Primitives.compare(left, right)); 111 | } 112 | 113 | ComparisonChain classify(int result) { 114 | return (result < 0) ? LESS : (result > 0) ? GREATER : ACTIVE; 115 | } 116 | 117 | @Override 118 | public int result() { 119 | return 0; 120 | } 121 | }; 122 | 123 | private static final ComparisonChain LESS = new InactiveComparisonChain(-1); 124 | 125 | private static final ComparisonChain GREATER = new InactiveComparisonChain(1); 126 | 127 | private static final class InactiveComparisonChain extends ComparisonChain { 128 | final int result; 129 | 130 | InactiveComparisonChain(int result) { 131 | this.result = result; 132 | } 133 | 134 | @Override 135 | public ComparisonChain compare(@Nullable Comparable left, @Nullable Comparable right) { 136 | return this; 137 | } 138 | 139 | @Override 140 | public ComparisonChain compare( 141 | @Nullable T left, @Nullable T right, @Nullable Comparator comparator) { 142 | return this; 143 | } 144 | 145 | @Override 146 | public ComparisonChain compare(int left, int right) { 147 | return this; 148 | } 149 | 150 | @Override 151 | public ComparisonChain compare(long left, long right) { 152 | return this; 153 | } 154 | 155 | @Override 156 | public ComparisonChain compare(float left, float right) { 157 | return this; 158 | } 159 | 160 | @Override 161 | public ComparisonChain compare(double left, double right) { 162 | return this; 163 | } 164 | 165 | @Override 166 | public ComparisonChain compareTrueFirst(boolean left, boolean right) { 167 | return this; 168 | } 169 | 170 | @Override 171 | public ComparisonChain compareFalseFirst(boolean left, boolean right) { 172 | return this; 173 | } 174 | 175 | @Override 176 | public int result() { 177 | return result; 178 | } 179 | } 180 | 181 | /** 182 | * Compares two comparable objects as specified by {@link 183 | * Comparable#compareTo}, if the result of this comparison chain 184 | * has not already been determined. 185 | */ 186 | public abstract ComparisonChain compare(Comparable left, Comparable right); 187 | 188 | /** 189 | * Compares two objects using a comparator, if the result of this 190 | * comparison chain has not already been determined. 191 | */ 192 | public abstract ComparisonChain compare( 193 | @Nullable T left, @Nullable T right, Comparator comparator); 194 | 195 | /** 196 | * Compares two {@code int} values as specified by {@link Primitives#compare}, 197 | * if the result of this comparison chain has not already been 198 | * determined. 199 | */ 200 | public abstract ComparisonChain compare(int left, int right); 201 | 202 | /** 203 | * Compares two {@code long} values as specified by {@link Primitives#compare}, 204 | * if the result of this comparison chain has not already been 205 | * determined. 206 | */ 207 | public abstract ComparisonChain compare(long left, long right); 208 | 209 | /** 210 | * Compares two {@code float} values as specified by {@link 211 | * Float#compare}, if the result of this comparison chain has not 212 | * already been determined. 213 | */ 214 | public abstract ComparisonChain compare(float left, float right); 215 | 216 | /** 217 | * Compares two {@code double} values as specified by {@link 218 | * Double#compare}, if the result of this comparison chain has not 219 | * already been determined. 220 | */ 221 | public abstract ComparisonChain compare(double left, double right); 222 | 223 | /** 224 | * Discouraged synonym for {@link #compareFalseFirst}. 225 | * 226 | * @deprecated Use {@link #compareFalseFirst}; or, if the parameters passed 227 | * are being either negated or reversed, undo the negation or reversal and 228 | * use {@link #compareTrueFirst}. 229 | * @since 19.0 230 | */ 231 | @Deprecated 232 | public final ComparisonChain compare(Boolean left, Boolean right) { 233 | return compareFalseFirst(left, right); 234 | } 235 | 236 | /** 237 | * Compares two {@code boolean} values, considering {@code true} to be less 238 | * than {@code false}, if the result of this comparison chain has not 239 | * already been determined. 240 | * 241 | * @since 12.0 242 | */ 243 | public abstract ComparisonChain compareTrueFirst(boolean left, boolean right); 244 | 245 | /** 246 | * Compares two {@code boolean} values, considering {@code false} to be less 247 | * than {@code true}, if the result of this comparison chain has not 248 | * already been determined. 249 | * 250 | * @since 12.0 (present as {@code compare} since 2.0) 251 | */ 252 | public abstract ComparisonChain compareFalseFirst(boolean left, boolean right); 253 | 254 | /** 255 | * Ends this comparison chain and returns its result: a value having the 256 | * same sign as the first nonzero comparison result in the chain, or zero if 257 | * every result was zero. 258 | */ 259 | public abstract int result(); 260 | } -------------------------------------------------------------------------------- /src/main/java/io/dmitryivanov/crdt/helpers/Operations.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) pakoito 2016 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.dmitryivanov.crdt.helpers; 18 | 19 | import java.util.*; 20 | 21 | public final class Operations { 22 | private Operations() { 23 | // No instances 24 | } 25 | 26 | public static Set diff(Set firstSet, final Set secondSet) { 27 | return filtered(firstSet, new Predicate() { 28 | @Override 29 | public boolean call(E element) { 30 | return !secondSet.contains(element); 31 | } 32 | }); 33 | } 34 | 35 | public static Set union(Set firstSet, final Set secondSet) { 36 | final Set newSet = new HashSet<>(); 37 | newSet.addAll(firstSet); 38 | newSet.addAll(secondSet); 39 | return newSet; 40 | } 41 | 42 | public static Set filtered(Set set, Predicate predicate) { 43 | final Set newSet = new HashSet<>(); 44 | for (E element : set) { 45 | if (predicate.call(element)) { 46 | newSet.add(element); 47 | } 48 | } 49 | return newSet; 50 | } 51 | 52 | public static Set filteredAndMapped(Set set, Predicate predicate, 53 | Mapper mapper) { 54 | final Set newSet = new HashSet<>(); 55 | for (E element : set) { 56 | if (predicate.call(element)) { 57 | newSet.add(mapper.call(element)); 58 | } 59 | } 60 | return newSet; 61 | } 62 | 63 | public static E select(Set set, Predicate2 predicate) { 64 | if (set.isEmpty()) { 65 | throw new IllegalArgumentException("Empty set for select operation"); 66 | } 67 | E winner = set.iterator().next(); 68 | for (E element : set) { 69 | if (predicate.call(winner, element)) { 70 | winner = element; 71 | } 72 | } 73 | return winner; 74 | } 75 | 76 | public static Map> groupBy(Collection map, Mapper mapper) { 77 | final Map> newMap = new HashMap<>(map.size()); 78 | for (V element : map) { 79 | K key = mapper.call(element); 80 | Collection list = newMap.get(key); 81 | if (null == list) { 82 | list = new ArrayList<>(); 83 | } 84 | list.add(element); 85 | newMap.put(key, list); 86 | } 87 | return newMap; 88 | } 89 | 90 | public static Map mapValues(Map map, Mapper mapper) { 91 | final Map newMap = new HashMap<>(map.size()); 92 | for (Map.Entry entry : map.entrySet()) { 93 | newMap.put(entry.getKey(), mapper.call(entry.getValue())); 94 | } 95 | return newMap; 96 | } 97 | 98 | public interface Predicate { 99 | boolean call(E element); 100 | } 101 | 102 | public interface Predicate2 { 103 | boolean call(E first, F second); 104 | } 105 | 106 | public interface Mapper { 107 | R call(E element); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/io/dmitryivanov/crdt/helpers/Primitives.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 The Guava Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | 15 | package io.dmitryivanov.crdt.helpers; 16 | 17 | public final class Primitives { 18 | private Primitives() { 19 | // No instances 20 | } 21 | 22 | /** 23 | * Compares the two specified {@code boolean} values in the standard way ({@code false} is 24 | * considered less than {@code true}). The sign of the value returned is the same as that of 25 | * {@code ((Boolean) a).compareTo(b)}. 26 | *

27 | * Note for Java 7 and later: this method should be treated as deprecated; use the 28 | * equivalent {@link Boolean#compare} method instead. 29 | * 30 | * @param a the first {@code boolean} to compare 31 | * @param b the second {@code boolean} to compare 32 | * @return a positive number if only {@code a} is {@code true}, a negative number if only 33 | * {@code b} is true, or zero if {@code a == b} 34 | */ 35 | public static int compare(boolean a, boolean b) { 36 | return (a == b) ? 0 : (a ? 1 : -1); 37 | } 38 | 39 | /** 40 | * Compares the two specified {@code int} values. The sign of the value returned is the same as 41 | * that of {@code ((Integer) a).compareTo(b)}. 42 | * 43 | *

Note for Java 7 and later: this method should be treated as deprecated; use the 44 | * equivalent {@link Integer#compare} method instead. 45 | * 46 | * @param a the first {@code int} to compare 47 | * @param b the second {@code int} to compare 48 | * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is 49 | * greater than {@code b}; or zero if they are equal 50 | */ 51 | public static int compare(int a, int b) { 52 | return (a < b) ? -1 : ((a > b) ? 1 : 0); 53 | } 54 | 55 | /** 56 | * Compares the two specified {@code long} values. The sign of the value returned is the same as 57 | * that of {@code ((Long) a).compareTo(b)}. 58 | * 59 | *

Note for Java 7 and later: this method should be treated as deprecated; use the 60 | * equivalent {@link Long#compare} method instead. 61 | * 62 | * @param a the first {@code long} to compare 63 | * @param b the second {@code long} to compare 64 | * @return a negative value if {@code a} is less than {@code b}; a positive value if {@code a} is 65 | * greater than {@code b}; or zero if they are equal 66 | */ 67 | public static int compare(long a, long b) { 68 | return (a < b) ? -1 : ((a > b) ? 1 : 0); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/io/dmitryivanov/crdt/GSetTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Dmitry Ivanov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package io.dmitryivanov.crdt; 26 | 27 | import java.util.Set; 28 | import org.junit.Test; 29 | 30 | import static org.junit.Assert.assertEquals; 31 | import static org.junit.Assert.assertTrue; 32 | 33 | public class GSetTests { 34 | 35 | @Test 36 | public void testLookup() { 37 | GSet gSet = new GSet<>(); 38 | 39 | gSet.add("dog"); 40 | gSet.add("ape"); 41 | gSet.add("cat"); 42 | 43 | // Actual test 44 | Set result = gSet.lookup(); 45 | 46 | assertEquals(3, result.size()); 47 | assertTrue(result.contains("ape")); 48 | assertTrue(result.contains("dog")); 49 | assertTrue(result.contains("cat")); 50 | } 51 | 52 | @Test 53 | public void testMerge() { 54 | GSet firstGSet = new GSet<>(); 55 | firstGSet.add("dog"); 56 | firstGSet.add("ape"); 57 | 58 | GSet secondGSet = new GSet<>(); 59 | secondGSet.add("cat"); 60 | secondGSet.add("dog"); 61 | 62 | // Actual test 63 | GSet result = firstGSet.merge(secondGSet); 64 | 65 | assertEquals(3, result.lookup().size()); 66 | assertTrue(result.lookup().contains("dog")); 67 | assertTrue(result.lookup().contains("cat")); 68 | assertTrue(result.lookup().contains("dog")); 69 | 70 | GSet reverseResult = secondGSet.merge(firstGSet); 71 | 72 | assertEquals(result, reverseResult); 73 | } 74 | 75 | @Test 76 | public void testDiff() { 77 | GSet firstGSet = new GSet<>(); 78 | firstGSet.add("dog"); 79 | firstGSet.add("ape"); 80 | 81 | GSet secondGSet = new GSet<>(); 82 | secondGSet.add("cat"); 83 | secondGSet.add("dog"); 84 | 85 | GSet result = firstGSet.diff(secondGSet); 86 | 87 | assertEquals(1, result.lookup().size()); 88 | assertTrue(result.lookup().contains("ape")); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/test/java/io/dmitryivanov/crdt/LWWSetTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Dmitry Ivanov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package io.dmitryivanov.crdt; 26 | 27 | import org.junit.Test; 28 | 29 | import java.util.Set; 30 | 31 | import static org.junit.Assert.assertEquals; 32 | import static org.junit.Assert.assertTrue; 33 | 34 | public class LWWSetTests { 35 | 36 | @Test 37 | public void testLookup() { 38 | final LWWSet lwwSet = new LWWSet<>(); 39 | 40 | lwwSet.add(new LWWSet.ElementState<>(1, "dog")); 41 | lwwSet.add(new LWWSet.ElementState<>(1, "cat")); 42 | lwwSet.add(new LWWSet.ElementState<>(1, "ape")); 43 | lwwSet.add(new LWWSet.ElementState<>(1, "tiger")); 44 | 45 | lwwSet.remove(new LWWSet.ElementState<>(2, "cat")); 46 | lwwSet.remove(new LWWSet.ElementState<>(2, "dog")); 47 | 48 | // Actual test 49 | final Set lookup = lwwSet.lookup(); 50 | 51 | assertEquals(2, lookup.size()); 52 | assertTrue(lookup.contains("ape")); 53 | assertTrue(lookup.contains("tiger")); 54 | } 55 | 56 | @Test 57 | public void testMerge() { 58 | final LWWSet firstLwwSet = new LWWSet<>(); 59 | firstLwwSet.add(new LWWSet.ElementState<>(3, "ape")); 60 | firstLwwSet.add(new LWWSet.ElementState<>(1, "dog")); 61 | firstLwwSet.add(new LWWSet.ElementState<>(1, "cat")); 62 | firstLwwSet.remove(new LWWSet.ElementState<>(2, "cat")); 63 | 64 | final LWWSet secondLwwSet = new LWWSet<>(); 65 | secondLwwSet.add(new LWWSet.ElementState<>(1, "ape")); 66 | secondLwwSet.add(new LWWSet.ElementState<>(1, "tiger")); 67 | secondLwwSet.add(new LWWSet.ElementState<>(1, "cat")); 68 | secondLwwSet.remove(new LWWSet.ElementState<>(2, "ape")); 69 | 70 | // Actual test 71 | final LWWSet resultSet = firstLwwSet.merge(secondLwwSet); 72 | 73 | final GSet> resultAddSet = resultSet.getAddSet(); 74 | final Set> addLookup = resultAddSet.lookup(); 75 | assertEquals(5, addLookup.size()); 76 | assertTrue(addLookup.contains(new LWWSet.ElementState<>(1, "ape"))); 77 | assertTrue(addLookup.contains(new LWWSet.ElementState<>(3, "ape"))); 78 | assertTrue(addLookup.contains(new LWWSet.ElementState<>(1, "dog"))); 79 | assertTrue(addLookup.contains(new LWWSet.ElementState<>(1, "tiger"))); 80 | assertTrue(addLookup.contains(new LWWSet.ElementState<>(1, "cat"))); 81 | 82 | final GSet> resultRemoveSet = resultSet.getRemoveSet(); 83 | final Set> removeLookup = resultRemoveSet.lookup(); 84 | assertEquals(2, removeLookup.size()); 85 | assertTrue(removeLookup.contains(new LWWSet.ElementState<>(2, "cat"))); 86 | assertTrue(removeLookup.contains(new LWWSet.ElementState<>(2, "ape"))); 87 | } 88 | 89 | @Test 90 | public void testDiff() { 91 | final LWWSet firstLwwSet = new LWWSet<>(); 92 | firstLwwSet.add(new LWWSet.ElementState<>(3, "ape")); 93 | firstLwwSet.add(new LWWSet.ElementState<>(1, "dog")); 94 | firstLwwSet.add(new LWWSet.ElementState<>(2, "tiger")); 95 | firstLwwSet.add(new LWWSet.ElementState<>(1, "cat")); 96 | firstLwwSet.remove(new LWWSet.ElementState<>(2, "cat")); 97 | 98 | final LWWSet secondLwwSet = new LWWSet<>(); 99 | secondLwwSet.add(new LWWSet.ElementState<>(1, "ape")); 100 | secondLwwSet.add(new LWWSet.ElementState<>(3, "tiger")); 101 | secondLwwSet.add(new LWWSet.ElementState<>(1, "cat")); 102 | secondLwwSet.remove(new LWWSet.ElementState<>(2, "ape")); 103 | 104 | // Actual test 105 | final LWWSet resultSet = firstLwwSet.diff(secondLwwSet); 106 | 107 | final GSet> resultAddSet = resultSet.getAddSet(); 108 | assertEquals(3, resultAddSet.lookup().size()); 109 | assertTrue(resultAddSet.lookup().contains(new LWWSet.ElementState<>(3, "ape"))); 110 | assertTrue(resultAddSet.lookup().contains(new LWWSet.ElementState<>(1, "dog"))); 111 | assertTrue(resultAddSet.lookup().contains(new LWWSet.ElementState<>(2, "tiger"))); 112 | 113 | final GSet> resultRemoveSet = resultSet.getRemoveSet(); 114 | assertEquals(1, resultRemoveSet.lookup().size()); 115 | assertTrue(resultRemoveSet.lookup().contains(new LWWSet.ElementState<>(2, "cat"))); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/test/java/io/dmitryivanov/crdt/ORSetTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Dmitry Ivanov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package io.dmitryivanov.crdt; 26 | 27 | import org.junit.Test; 28 | 29 | import java.util.Set; 30 | 31 | import static org.junit.Assert.assertEquals; 32 | import static org.junit.Assert.assertTrue; 33 | 34 | public class ORSetTests { 35 | 36 | @Test 37 | public void testLookup() { 38 | final ORSet orSet = new ORSet<>(); 39 | 40 | orSet.add(new ORSet.ElementState<>("#a", "dog")); 41 | orSet.add(new ORSet.ElementState<>("#b", "cat")); 42 | orSet.add(new ORSet.ElementState<>("#c", "ape")); 43 | orSet.add(new ORSet.ElementState<>("#c", "tiger")); 44 | 45 | orSet.remove(new ORSet.ElementState<>("#a", "dog")); 46 | orSet.remove(new ORSet.ElementState<>("#b", "cat")); 47 | 48 | // Actual test 49 | final Set lookup = orSet.lookup(); 50 | 51 | assertEquals(2, lookup.size()); 52 | assertTrue(lookup.contains("ape")); 53 | assertTrue(lookup.contains("tiger")); 54 | } 55 | 56 | @Test 57 | public void testMerge() { 58 | final ORSet firstORSet = new ORSet<>(); 59 | firstORSet.add(new ORSet.ElementState<>("#b", "ape")); 60 | firstORSet.add(new ORSet.ElementState<>("#c", "dog")); 61 | firstORSet.add(new ORSet.ElementState<>("#d", "cat")); 62 | firstORSet.remove(new ORSet.ElementState<>("#d", "cat")); 63 | 64 | final ORSet secondORSet = new ORSet<>(); 65 | secondORSet.add(new ORSet.ElementState<>("#a", "ape")); 66 | secondORSet.add(new ORSet.ElementState<>("#h", "tiger")); 67 | secondORSet.add(new ORSet.ElementState<>("#d", "cat")); 68 | secondORSet.remove(new ORSet.ElementState<>("#a", "ape")); 69 | 70 | // Actual test 71 | final ORSet resultSet = firstORSet.merge(secondORSet); 72 | 73 | final GSet> resultAddSet = resultSet.getAddSet(); 74 | final Set> addLookup = resultAddSet.lookup(); 75 | assertEquals(5, addLookup.size()); 76 | assertTrue(addLookup.contains(new ORSet.ElementState<>("#a", "ape"))); 77 | assertTrue(addLookup.contains(new ORSet.ElementState<>("#b", "ape"))); 78 | assertTrue(addLookup.contains(new ORSet.ElementState<>("#c", "dog"))); 79 | assertTrue(addLookup.contains(new ORSet.ElementState<>("#d", "cat"))); 80 | assertTrue(addLookup.contains(new ORSet.ElementState<>("#h", "tiger"))); 81 | 82 | final GSet> resultRemoveSet = resultSet.getRemoveSet(); 83 | final Set> removeLookup = resultRemoveSet.lookup(); 84 | assertEquals(2, removeLookup.size()); 85 | assertTrue(removeLookup.contains(new ORSet.ElementState<>("#d", "cat"))); 86 | assertTrue(removeLookup.contains(new ORSet.ElementState<>("#a", "ape"))); 87 | 88 | ORSet reverseResultSet = secondORSet.merge(firstORSet); 89 | assertEquals(resultSet, reverseResultSet); 90 | } 91 | 92 | @Test 93 | public void testDiff() { 94 | final ORSet firstORSet = new ORSet<>(); 95 | firstORSet.add(new ORSet.ElementState<>("#b", "ape")); 96 | firstORSet.add(new ORSet.ElementState<>("#c", "dog")); 97 | firstORSet.add(new ORSet.ElementState<>("#d", "cat")); 98 | firstORSet.remove(new ORSet.ElementState<>("#d", "cat")); 99 | 100 | final ORSet secondORSet = new ORSet<>(); 101 | secondORSet.add(new ORSet.ElementState<>("#a", "ape")); 102 | secondORSet.add(new ORSet.ElementState<>("#h", "tiger")); 103 | secondORSet.add(new ORSet.ElementState<>("#d", "cat")); 104 | secondORSet.remove(new ORSet.ElementState<>("#a", "ape")); 105 | 106 | // Actual test 107 | final ORSet resultSet = firstORSet.diff(secondORSet); 108 | 109 | final GSet> resultAddSet = resultSet.getAddSet(); 110 | assertEquals(2, resultAddSet.lookup().size()); 111 | assertTrue(resultAddSet.lookup().contains(new ORSet.ElementState<>("#b", "ape"))); 112 | assertTrue(resultAddSet.lookup().contains(new ORSet.ElementState<>("#c", "dog"))); 113 | 114 | final GSet> resultRemoveSet = resultSet.getRemoveSet(); 115 | assertEquals(1, resultRemoveSet.lookup().size()); 116 | assertTrue(resultRemoveSet.lookup().contains(new ORSet.ElementState<>("#d", "cat"))); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/test/java/io/dmitryivanov/crdt/OURSetTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Dmitry Ivanov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package io.dmitryivanov.crdt; 26 | 27 | import org.junit.Test; 28 | 29 | import java.util.Set; 30 | import java.util.UUID; 31 | 32 | import static org.junit.Assert.assertEquals; 33 | import static org.junit.Assert.assertTrue; 34 | 35 | public class OURSetTests { 36 | 37 | @Test 38 | public void testLookup() { 39 | OURSet ourSet = new OURSet<>(); 40 | 41 | final OURSet.ElementState ape = new OURSet.ElementState<>(UUID.randomUUID(), System.currentTimeMillis(), "ape"); 42 | final OURSet.ElementState dog = new OURSet.ElementState<>(UUID.randomUUID(), System.currentTimeMillis(), "dog"); 43 | final OURSet.ElementState cat = new OURSet.ElementState<>(UUID.randomUUID(), System.currentTimeMillis(), "cat"); 44 | 45 | ourSet.add(ape); 46 | ourSet.add(dog); 47 | ourSet.add(cat); 48 | 49 | ourSet.remove(new OURSet.ElementState<>(UUID.randomUUID(), System.currentTimeMillis(), "tiger")); 50 | ourSet.remove(new OURSet.ElementState<>(cat.getId(), System.currentTimeMillis() + 1, cat.getElement())); 51 | 52 | // Actual test 53 | Set lookupResult = ourSet.lookup(); 54 | 55 | assertEquals(lookupResult.size(), 2); 56 | assertTrue(lookupResult.contains("dog")); 57 | assertTrue(lookupResult.contains("ape")); 58 | } 59 | 60 | @Test 61 | public void testMerge() { 62 | 63 | final OURSet.ElementState ape = new OURSet.ElementState<>(UUID.randomUUID(), System.currentTimeMillis(), "ape"); 64 | final OURSet.ElementState dog = new OURSet.ElementState<>(UUID.randomUUID(), System.currentTimeMillis(), "dog"); 65 | final OURSet.ElementState cat = new OURSet.ElementState<>(UUID.randomUUID(), System.currentTimeMillis(), "cat"); 66 | final OURSet.ElementState tiger = new OURSet.ElementState<>(UUID.randomUUID(), System.currentTimeMillis(), "tiger"); 67 | final OURSet.ElementState removedCat = new OURSet.ElementState<>(cat.getId(), true, cat.getTimestamp() + 1, "cat"); 68 | 69 | OURSet firstOURSet = new OURSet<>(); 70 | 71 | firstOURSet.add(ape); 72 | firstOURSet.add(dog); 73 | firstOURSet.remove(removedCat); 74 | 75 | OURSet secondOURSet = new OURSet<>(); 76 | secondOURSet.add(cat); 77 | secondOURSet.add(tiger); 78 | 79 | // Actual test 80 | OURSet mergeResult = firstOURSet.merge(secondOURSet); 81 | 82 | assertEquals(mergeResult.getElements().size(), 4); 83 | assertTrue(mergeResult.getElements().contains(ape)); 84 | assertTrue(mergeResult.getElements().contains(dog)); 85 | assertTrue(mergeResult.getElements().contains(tiger)); 86 | assertTrue(mergeResult.getElements().contains(removedCat)); 87 | 88 | OURSet reverseMergeResult = secondOURSet.merge(firstOURSet); 89 | assertEquals("'merge' should be symmetrical", mergeResult, reverseMergeResult); 90 | } 91 | 92 | @Test 93 | public void testDiff() { 94 | final OURSet.ElementState ape = new OURSet.ElementState<>(UUID.randomUUID(), System.currentTimeMillis(), "ape"); 95 | final OURSet.ElementState dog = new OURSet.ElementState<>(UUID.randomUUID(), System.currentTimeMillis(), "dog"); 96 | final OURSet.ElementState cat = new OURSet.ElementState<>(UUID.randomUUID(), System.currentTimeMillis(), "cat"); 97 | final OURSet.ElementState tiger = new OURSet.ElementState<>(UUID.randomUUID(), System.currentTimeMillis(), "tiger"); 98 | final OURSet.ElementState removedCat = new OURSet.ElementState<>(cat.getId(), true, cat.getTimestamp() + 1, "cat"); 99 | 100 | OURSet firstOURSet = new OURSet<>(); 101 | 102 | firstOURSet.add(ape); 103 | firstOURSet.add(dog); 104 | firstOURSet.remove(removedCat); 105 | 106 | OURSet secondOURSet = new OURSet<>(); 107 | secondOURSet.add(dog); 108 | secondOURSet.add(cat); 109 | secondOURSet.add(tiger); 110 | 111 | // Actual test 112 | OURSet diffResult = firstOURSet.diff(secondOURSet); 113 | 114 | assertEquals(diffResult.getElements().size(), 2); 115 | assertTrue(diffResult.getElements().contains(ape)); 116 | assertTrue(diffResult.getElements().contains(removedCat)); 117 | 118 | // Reverse diff 119 | OURSet reverseDiffResult = secondOURSet.diff(firstOURSet); 120 | 121 | assertEquals(reverseDiffResult.getElements().size(), 1); 122 | assertTrue(reverseDiffResult.getElements().contains(tiger)); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/test/java/io/dmitryivanov/crdt/TwoPSetTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 Dmitry Ivanov 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package io.dmitryivanov.crdt; 26 | 27 | import java.util.Set; 28 | 29 | import org.junit.Test; 30 | 31 | import static org.junit.Assert.assertEquals; 32 | import static org.junit.Assert.assertTrue; 33 | 34 | public class TwoPSetTests { 35 | 36 | @Test 37 | public void testLookup() { 38 | final TwoPSet twoPSet = new TwoPSet<>(); 39 | 40 | twoPSet.add("dog"); 41 | twoPSet.add("cat"); 42 | twoPSet.add("ape"); 43 | twoPSet.add("tiger"); 44 | 45 | twoPSet.remove("cat"); 46 | twoPSet.remove("dog"); 47 | 48 | // Actual test 49 | final Set lookup = twoPSet.lookup(); 50 | 51 | assertEquals(2, lookup.size()); 52 | assertTrue(lookup.contains("ape")); 53 | assertTrue(lookup.contains("tiger")); 54 | } 55 | 56 | @Test 57 | public void testMerge() { 58 | final TwoPSet firstTwoPSet = new TwoPSet<>(); 59 | firstTwoPSet.add("ape"); 60 | firstTwoPSet.add("dog"); 61 | firstTwoPSet.add("cat"); 62 | firstTwoPSet.remove("cat"); 63 | 64 | final TwoPSet secondTwoPSet = new TwoPSet<>(); 65 | secondTwoPSet.add("ape"); 66 | secondTwoPSet.add("tiger"); 67 | secondTwoPSet.add("cat"); 68 | secondTwoPSet.remove("ape"); 69 | 70 | // Actual test 71 | final TwoPSet resultSet = firstTwoPSet.merge(secondTwoPSet); 72 | 73 | final GSet resultAddSet = resultSet.getAddSet(); 74 | final Set resultAddSetLookup = resultAddSet.lookup(); 75 | assertEquals(4, resultAddSetLookup.size()); 76 | assertTrue(resultAddSetLookup.contains("ape")); 77 | assertTrue(resultAddSetLookup.contains("dog")); 78 | assertTrue(resultAddSetLookup.contains("cat")); 79 | assertTrue(resultAddSetLookup.contains("tiger")); 80 | 81 | final GSet resultRemoveSet = resultSet.getRemoveSet(); 82 | final Set resultRemoveSetLookup = resultRemoveSet.lookup(); 83 | assertEquals(2, resultRemoveSetLookup.size()); 84 | assertTrue(resultRemoveSetLookup.contains("cat")); 85 | assertTrue(resultRemoveSetLookup.contains("ape")); 86 | 87 | final TwoPSet reverseResult = secondTwoPSet.merge(firstTwoPSet); 88 | assertEquals(resultSet, reverseResult); 89 | 90 | final TwoPSet mergeItself = firstTwoPSet.merge(firstTwoPSet); 91 | assertEquals(firstTwoPSet, mergeItself); 92 | } 93 | 94 | @Test 95 | public void testDiff() { 96 | final TwoPSet firstTwoPSet = new TwoPSet<>(); 97 | firstTwoPSet.add("ape"); 98 | firstTwoPSet.add("dog"); 99 | firstTwoPSet.add("cat"); 100 | firstTwoPSet.remove("cat"); 101 | 102 | final TwoPSet secondTwoPSet = new TwoPSet<>(); 103 | secondTwoPSet.add("ape"); 104 | secondTwoPSet.add("tiger"); 105 | secondTwoPSet.add("cat"); 106 | secondTwoPSet.remove("ape"); 107 | 108 | // Actual test 109 | final TwoPSet resultSet = firstTwoPSet.diff(secondTwoPSet); 110 | 111 | final GSet resultAddSet = resultSet.getAddSet(); 112 | assertEquals(1, resultAddSet.lookup().size()); 113 | assertTrue(resultAddSet.lookup().contains("dog")); 114 | 115 | final GSet resultRemoveSet = resultSet.getRemoveSet(); 116 | assertEquals(1, resultRemoveSet.lookup().size()); 117 | assertTrue(resultRemoveSet.lookup().contains("cat")); 118 | 119 | // Reverse diff 120 | final TwoPSet resultSet2 = secondTwoPSet.diff(firstTwoPSet); 121 | 122 | final GSet resultAddSet2 = resultSet2.getAddSet(); 123 | assertEquals(1, resultAddSet2.lookup().size()); 124 | assertTrue(resultAddSet2.lookup().contains("tiger")); 125 | 126 | final GSet resultRemoveSet2 = resultSet2.getRemoveSet(); 127 | assertEquals(1, resultRemoveSet2.lookup().size()); 128 | assertTrue(resultRemoveSet2.lookup().contains("ape")); 129 | } 130 | } 131 | --------------------------------------------------------------------------------