Skip to content

Commit 3220b29

Browse files
author
nicolaiparlog
committed
Extract builder and 'EqHash' from 'EqualityTransformingMap'
This step prepares the implementation of 'EqualityTransformingSet' which will also use those classes.
1 parent 437da5d commit 3220b29

File tree

4 files changed

+282
-251
lines changed

4 files changed

+282
-251
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package org.codefx.libfx.collection.transform;
2+
3+
import java.util.function.BiPredicate;
4+
import java.util.function.ToIntFunction;
5+
6+
/**
7+
* Wraps elements which are pout into an inner hashing data structure and delegates {@link #equals(Object)} and
8+
* {@link #hashCode()} to functions specified during construction.
9+
*
10+
* @param <E>
11+
* the type of the wrapped elements
12+
*/
13+
class EqHash<E> {
14+
15+
/**
16+
* The default hash code used for null keys.
17+
* <p>
18+
* This value is mentioned in the comments of {@link EqualityTransformingMap} and {@link EqualityTransformingSet}.
19+
* Update on change.
20+
*/
21+
public static final int NULL_KEY_HASH_CODE = 0;
22+
23+
private final E element;
24+
private final BiPredicate<? super E, ? super E> equals;
25+
private final ToIntFunction<? super E> hash;
26+
27+
public EqHash(E element, BiPredicate<? super E, ? super E> equals, ToIntFunction<? super E> hash) {
28+
// null is allowed as an element
29+
assert equals != null : "The argument 'equals' must not be null.";
30+
assert hash != null : "The argument 'hash' must not be null.";
31+
32+
this.element = element;
33+
this.equals = equals;
34+
this.hash = hash;
35+
}
36+
37+
/**
38+
* @return the wrapped element
39+
*/
40+
public E getElement() {
41+
return element;
42+
}
43+
44+
@Override
45+
public int hashCode() {
46+
return hash.applyAsInt(element);
47+
}
48+
49+
@Override
50+
public boolean equals(Object obj) {
51+
if (this == obj)
52+
return true;
53+
if (obj == null)
54+
return false;
55+
if (!(obj instanceof EqHash))
56+
return false;
57+
58+
@SuppressWarnings("unchecked")
59+
// This cast is ok because no instance of EqHash can ever leave the inner map (without being transformed
60+
// by the equality transforming map).
61+
// If it can not leave it can not end up in an equality test in another map.
62+
EqHash<E> other = (EqHash<E>) obj;
63+
return equals.test(this.element, other.element);
64+
}
65+
66+
}
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package org.codefx.libfx.collection.transform;
2+
3+
import java.util.HashMap;
4+
import java.util.HashSet;
5+
import java.util.Map;
6+
import java.util.Objects;
7+
import java.util.Set;
8+
import java.util.function.BiPredicate;
9+
import java.util.function.ToIntFunction;
10+
11+
/**
12+
* Builds {@link EqualityTransformingSet}s and {@link EqualityTransformingMap}s.
13+
* <p>
14+
* TODO for simplification doc only talks about set
15+
* <p>
16+
* The implementations of {@code equals} and {@code hashCode} are provided as functions to the builder. They must of
17+
* course fulfill the general contract between those two methods (see {@link Object#hashCode() here}). The functions can
18+
* be provided on two ways:
19+
* <ol>
20+
* <li>Via the {@code with[Equals|Hash]}-methods. In this case, the functions will never be called with null instances,
21+
* which are handled by this map as follows:
22+
* <ul>
23+
* <li>{@code hashCode(null) == 0}
24+
* <li>{@code equals(null, null) == true};
25+
* <li>{@code equals(not-null, null) == false}
26+
* <li>{@code equals(null, not-null) == false}
27+
* </ul>
28+
* <li>If those defaults are not sufficient, the functions can handle null themselves. Those variants can be provided
29+
* via the {@code with[Equals|Hash]HandlingNull}-methods.
30+
* </ol>
31+
* <p>
32+
* The same builder instance can be reused to create multiple instances. The builder is not thread-safe and should not
33+
* be used concurrently.
34+
*
35+
* @param <E>
36+
* the type of elements maintained by the created set or keys by the created map.
37+
*/
38+
public class EqualityTransformingBuilder<E> {
39+
40+
private final Class<? super E> outerKeyTypeToken;
41+
private BiPredicate<? super E, ? super E> equals;
42+
private ToIntFunction<? super E> hash;
43+
44+
private EqualityTransformingBuilder(Class<? super E> outerKeyTypeToken) {
45+
this.outerKeyTypeToken = outerKeyTypeToken;
46+
// note that the methods from 'Objects' already implement the contract for null-safety
47+
// imposed by the transforming set and map
48+
this.equals = Objects::equals;
49+
this.hash = Objects::hashCode;
50+
}
51+
52+
// #begin SET PROPERTIES
53+
54+
/**
55+
* Returns a new builder.
56+
* <p>
57+
* This method can be called if no type token for the elements can be provided (if it can be, call the preferable
58+
* {@link #forKeyType(Class)} instead). A call might look as follows (for a generic type {@code T}):
59+
*
60+
* <pre>
61+
* EqualityTransformingBuilder.&lt;T&gt; forUnspecifiedKeyType();
62+
* </pre>
63+
*
64+
* @param <E>
65+
* the type of elements contained in the set created by the builder
66+
* @return a new builder
67+
*/
68+
public static <E> EqualityTransformingBuilder<E> forUnspecifiedKeyType() {
69+
return new EqualityTransformingBuilder<>(Object.class);
70+
}
71+
72+
/**
73+
* Returns a new builder to create equality transforming sets for elements of the specified type.
74+
* <p>
75+
* If a type token for the keys can not be provided, call {@link #forUnspecifiedKeyType()} instead.
76+
*
77+
* @param <E>
78+
* the type of elements contained in the set created by the builder
79+
* @param keyTypeToken
80+
* a type token for the elements contained in the set created by the builder
81+
* @return a new builder
82+
*/
83+
public static <E> EqualityTransformingBuilder<E> forKeyType(Class<? super E> keyTypeToken) {
84+
Objects.requireNonNull(keyTypeToken, "The argument 'keyTypeToken' must not be null.");
85+
return new EqualityTransformingBuilder<>(keyTypeToken);
86+
}
87+
88+
/**
89+
* @param equals
90+
* a function determining equality of keys; might be called with null keys
91+
* @return this builder
92+
*/
93+
public EqualityTransformingBuilder<E> withEqualsHandlingNull(BiPredicate<? super E, ? super E> equals) {
94+
Objects.requireNonNull(equals, "The argument 'equals' must not be null.");
95+
this.equals = equals;
96+
return this;
97+
}
98+
99+
/**
100+
* @param equals
101+
* a function determining equality of keys; will not be called with null keys
102+
* @return this builder
103+
*/
104+
public EqualityTransformingBuilder<E> withEquals(BiPredicate<? super E, ? super E> equals) {
105+
Objects.requireNonNull(equals, "The argument 'equals' must not be null.");
106+
return withEqualsHandlingNull(makeNullSafe(equals));
107+
}
108+
109+
private static <E> BiPredicate<? super E, ? super E> makeNullSafe(BiPredicate<? super E, ? super E> equals) {
110+
return (outerKey1, outerKey2) -> {
111+
if (outerKey1 == null && outerKey2 == null)
112+
return true;
113+
if (outerKey1 == null || outerKey2 == null)
114+
return false;
115+
116+
return equals.test(outerKey1, outerKey2);
117+
};
118+
}
119+
120+
/**
121+
* @param hash
122+
* a function computing the hash code of a key; might be called with null keys
123+
* @return this builder
124+
*/
125+
public EqualityTransformingBuilder<E> withHashHandlingNull(ToIntFunction<? super E> hash) {
126+
Objects.requireNonNull(hash, "The argument 'hash' must not be null.");
127+
this.hash = hash;
128+
return this;
129+
}
130+
131+
/**
132+
* @param hash
133+
* a function computing the hash code of a key; will not be called with null keys
134+
* @return this builder
135+
*/
136+
public EqualityTransformingBuilder<E> withHash(ToIntFunction<? super E> hash) {
137+
Objects.requireNonNull(hash, "The argument 'hash' must not be null.");
138+
return withHashHandlingNull(makeNullSafe(hash));
139+
}
140+
141+
private static <E> ToIntFunction<? super E> makeNullSafe(ToIntFunction<? super E> hash) {
142+
return outerKey -> outerKey == null ? EqHash.NULL_KEY_HASH_CODE : hash.applyAsInt(outerKey);
143+
}
144+
145+
// #end SET PROPERTIES
146+
147+
// #begin BUILD
148+
149+
/**
150+
* Creates a new {@link EqualityTransformingSet} by decorating a {@link HashSet}.
151+
*
152+
* @return a new instance of {@link EqualityTransformingSet}
153+
*/
154+
public EqualityTransformingSet<E> buildSet() {
155+
return new EqualityTransformingSet<>(new HashSet<>(), outerKeyTypeToken, equals, hash);
156+
}
157+
158+
/**
159+
* Creates a new {@link EqualityTransformingSet} by decorating the specified set.
160+
*
161+
* @param emptySet
162+
* an empty set which is not otherwise referenced
163+
* @return a new instance of {@link EqualityTransformingSet}
164+
*/
165+
public EqualityTransformingSet<E> buildSetDecorating(Set<Object> emptySet) {
166+
return new EqualityTransformingSet<>(emptySet, outerKeyTypeToken, equals, hash);
167+
}
168+
169+
/**
170+
* Creates a new {@link EqualityTransformingMap} by decorating a {@link HashMap}.
171+
*
172+
* @param <V>
173+
* the type of values mapped by the new map
174+
* @return a new instance of {@link EqualityTransformingMap}
175+
*/
176+
public <V> EqualityTransformingMap<E, V> buildMap() {
177+
return new EqualityTransformingMap<>(new HashMap<>(), outerKeyTypeToken, equals, hash);
178+
}
179+
180+
/**
181+
* Creates a new {@link EqualityTransformingMap} by decorating the specified map.
182+
*
183+
* @param <V>
184+
* the type of values mapped by the new map
185+
* @param emptyMap
186+
* an empty map which is not otherwise referenced
187+
* @return a new instance of {@link EqualityTransformingMap}
188+
*/
189+
public <V> EqualityTransformingMap<E, V> buildMapDecorating(Map<Object, Object> emptyMap) {
190+
return new EqualityTransformingMap<>(emptyMap, outerKeyTypeToken, equals, hash);
191+
}
192+
193+
// #end BUILD
194+
195+
}

0 commit comments

Comments
 (0)