Skip to content

Commit 0cdfa09

Browse files
author
nicolaiparlog
committed
Create a 'TransformingMapBuilder'
1 parent b1ac5b5 commit 0cdfa09

File tree

3 files changed

+230
-41
lines changed

3 files changed

+230
-41
lines changed

src/main/java/org/codefx/libfx/collection/transform/TransformingMap.java

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
* If the {@link java.util.stream.Stream streams} returned by this map's views are told to
2222
* {@link java.util.stream.Stream#sorted() sort} themself, they will do so on the base of the comparator returned by the
2323
* inner map view's spliterator (e.g. based on the natural order of {@code IK} or {@code IV} if it has one).
24+
* <p>
25+
* {@code TransformingMap}s are created with a {@link TransformingMapBuilder}.
2426
*
2527
* @param <IK>
2628
* the inner key type, i.e. the type of the keys contained in the wrapped/inner map
@@ -38,20 +40,14 @@ public final class TransformingMap<IK, OK, IV, OV> extends AbstractTransformingM
3840
private final Map<IK, IV> innerMap;
3941

4042
private final Class<? super OK> outerKeyTypeToken;
41-
4243
private final Class<? super IK> innerKeyTypeToken;
43-
44-
private final Function<IK, OK> transformToOuterKey;
45-
46-
private final Function<OK, IK> transformToInnerKey;
44+
private final Function<? super IK, ? extends OK> transformToOuterKey;
45+
private final Function<? super OK, ? extends IK> transformToInnerKey;
4746

4847
private final Class<? super OV> outerValueTypeToken;
49-
5048
private final Class<? super IV> innerValueTypeToken;
51-
52-
private final Function<IV, OV> transformToOuterValue;
53-
54-
private final Function<OV, IV> transformToInnerValue;
49+
private final Function<? super IV, ? extends OV> transformToOuterValue;
50+
private final Function<? super OV, ? extends IV> transformToInnerValue;
5551

5652
// #end FIELDS
5753

@@ -64,40 +60,42 @@ public final class TransformingMap<IK, OK, IV, OV> extends AbstractTransformingM
6460
* the wrapped map
6561
* @param innerKeyTypeToken
6662
* the token for the inner key type
63+
* @param outerKeyTypeToken
64+
* the token for the outer key type
6765
* @param transformToOuterKey
6866
* transforms a key from an inner to an outer key type; will never be called with null argument and must
6967
* not produce null
70-
* @param outerKeyTypeToken
71-
* the token for the outer key type
7268
* @param transformToInnerKey
7369
* transforms a key from an outer to an inner key type; will never be called with null argument and must
7470
* not produce null
7571
* @param innerValueTypeToken
7672
* the token for the inner value type
73+
* @param outerValueTypeToken
74+
* the token for the outer value type
7775
* @param transformToOuterValue
7876
* transforms a value from an inner to an outer value type; will never be called with null argument and
7977
* must not produce null
80-
* @param outerValueTypeToken
81-
* the token for the outer value type
8278
* @param transformToInnerValue
8379
* transforms a value from an outer to an inner value type; will never be called with null argument and
8480
* must not produce null
8581
*/
86-
public TransformingMap(
82+
TransformingMap(
8783
Map<IK, IV> innerMap,
88-
Class<IK> innerKeyTypeToken, Function<IK, OK> transformToOuterKey,
89-
Class<OK> outerKeyTypeToken, Function<OK, IK> transformToInnerKey,
90-
Class<IV> innerValueTypeToken, Function<IV, OV> transformToOuterValue,
91-
Class<OV> outerValueTypeToken, Function<OV, IV> transformToInnerValue) {
84+
Class<? super IK> innerKeyTypeToken, Class<? super OK> outerKeyTypeToken,
85+
Function<? super IK, ? extends OK> transformToOuterKey,
86+
Function<? super OK, ? extends IK> transformToInnerKey,
87+
Class<? super IV> innerValueTypeToken, Class<? super OV> outerValueTypeToken,
88+
Function<? super IV, ? extends OV> transformToOuterValue,
89+
Function<? super OV, ? extends IV> transformToInnerValue) {
9290

9391
Objects.requireNonNull(innerMap, "The argument 'innerMap' must not be null.");
9492
Objects.requireNonNull(innerKeyTypeToken, "The argument 'innerKeyTypeToken' must not be null.");
95-
Objects.requireNonNull(transformToOuterKey, "The argument 'transformToOuterKey' must not be null.");
9693
Objects.requireNonNull(outerKeyTypeToken, "The argument 'outerKeyTypeToken' must not be null.");
94+
Objects.requireNonNull(transformToOuterKey, "The argument 'transformToOuterKey' must not be null.");
9795
Objects.requireNonNull(transformToInnerKey, "The argument 'transformToInnerKey' must not be null.");
9896
Objects.requireNonNull(innerValueTypeToken, "The argument 'innerValueTypeToken' must not be null.");
99-
Objects.requireNonNull(transformToOuterValue, "The argument 'transformToOuterValue' must not be null.");
10097
Objects.requireNonNull(outerValueTypeToken, "The argument 'outerValueTypeToken' must not be null.");
98+
Objects.requireNonNull(transformToOuterValue, "The argument 'transformToOuterValue' must not be null.");
10199
Objects.requireNonNull(transformToInnerValue, "The argument 'transformToInnerValue' must not be null.");
102100

103101
this.innerMap = innerMap;
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package org.codefx.libfx.collection.transform;
2+
3+
import java.util.Map;
4+
import java.util.Objects;
5+
import java.util.function.Function;
6+
7+
/**
8+
* Builder for {@link TransformingMap}s.
9+
* <p>
10+
* A builder can be obtained by calling {@link #forTypes(Class, Class, Class, Class) forTypes} or
11+
* {@link #forTypesUnknown()}. The building method TODO can only be called after transformations from inner to outer
12+
* keys and values and vice versa have been set.
13+
*
14+
* @param <IK>
15+
* the inner key type of the created transforming map, i.e. the type of the keys contained in the
16+
* wrapped/inner map
17+
* @param <OK>
18+
* the outer key type of the created transforming map, i.e. the type of keys appearing to be in this map
19+
* @param <IV>
20+
* the inner value type of the created transforming map, i.e. the type of the values contained in the
21+
* wrapped/inner map
22+
* @param <OV>
23+
* the outer value type of the created transforming map, i.e. the type of values appearing to be in this map
24+
*/
25+
public class TransformingMapBuilder<IK, OK, IV, OV> {
26+
27+
// #begin FIELDS
28+
29+
private final Class<? super OK> outerKeyTypeToken;
30+
private final Class<? super IK> innerKeyTypeToken;
31+
private Function<? super IK, ? extends OK> transformToOuterKey;
32+
private Function<? super OK, ? extends IK> transformToInnerKey;
33+
34+
private final Class<? super OV> outerValueTypeToken;
35+
private final Class<? super IV> innerValueTypeToken;
36+
private Function<? super IV, ? extends OV> transformToOuterValue;
37+
private Function<? super OV, ? extends IV> transformToInnerValue;
38+
39+
// #end FIELDS
40+
41+
// #begin CONSTRUCTION
42+
43+
private TransformingMapBuilder(
44+
Class<? super IK> innerKeyTypeToken, Class<? super OK> outerKeyTypeToken,
45+
Class<? super IV> innerValueTypeToken, Class<? super OV> outerValueTypeToken) {
46+
47+
Objects.requireNonNull(innerKeyTypeToken, "The argument 'innerKeyTypeToken' must not be null.");
48+
Objects.requireNonNull(outerKeyTypeToken, "The argument 'outerKeyTypeToken' must not be null.");
49+
Objects.requireNonNull(innerValueTypeToken, "The argument 'innerValueTypeToken' must not be null.");
50+
Objects.requireNonNull(outerValueTypeToken, "The argument 'outerValueTypeToken' must not be null.");
51+
52+
this.innerKeyTypeToken = innerKeyTypeToken;
53+
this.outerKeyTypeToken = outerKeyTypeToken;
54+
this.innerValueTypeToken = innerValueTypeToken;
55+
this.outerValueTypeToken = outerValueTypeToken;
56+
}
57+
58+
/**
59+
* Creates a new builder for the specified inner and outer key and value types.
60+
*
61+
* @param <IK>
62+
* the inner key type of the created transforming map, i.e. the type of the keys contained in the
63+
* wrapped/inner map
64+
* @param <OK>
65+
* the outer key type of the created transforming map, i.e. the type of keys appearing to be in this map
66+
* @param <IV>
67+
* the inner value type of the created transforming map, i.e. the type of the values contained in the
68+
* wrapped/inner map
69+
* @param <OV>
70+
* the outer value type of the created transforming map, i.e. the type of values appearing to be in this
71+
* map
72+
* @param innerKeyTypeToken
73+
* the token for the inner key type
74+
* @param outerKeyTypeToken
75+
* the token for the outer key type
76+
* @param innerValueTypeToken
77+
* the token for the inner value type
78+
* @param outerValueTypeToken
79+
* the token for the outer value type
80+
* @return a new builder
81+
*/
82+
public static <IK, OK, IV, OV> TransformingMapBuilder<IK, OK, IV, OV> forTypes(
83+
Class<? super IK> innerKeyTypeToken, Class<? super OK> outerKeyTypeToken,
84+
Class<? super IV> innerValueTypeToken, Class<? super OV> outerValueTypeToken) {
85+
86+
return new TransformingMapBuilder<>(
87+
innerKeyTypeToken, outerKeyTypeToken, innerValueTypeToken, outerValueTypeToken);
88+
}
89+
90+
/**
91+
* Creates a new builder for unknown inner and outer key and value types.
92+
* <p>
93+
* This is equivalent to calling {@link #forTypes(Class, Class, Class, Class) forTypes(Object.class, Object.class,
94+
* Object.class, Object.class)}. To obtain a builder for {@code <IK, OK, IV, OV>} you will have to call
95+
* {@code TransformingMapBuilder.<IK, OK, IV, OV> forTypesUnknown()}.
96+
*
97+
* @param <IK>
98+
* the inner key type of the created transforming map, i.e. the type of the keys contained in the
99+
* wrapped/inner map
100+
* @param <OK>
101+
* the outer key type of the created transforming map, i.e. the type of keys appearing to be in this map
102+
* @param <IV>
103+
* the inner value type of the created transforming map, i.e. the type of the values contained in the
104+
* wrapped/inner map
105+
* @param <OV>
106+
* the outer value type of the created transforming map, i.e. the type of values appearing to be in this
107+
* map
108+
* @return a new builder
109+
*/
110+
public static <IK, OK, IV, OV> TransformingMapBuilder<IK, OK, IV, OV> forTypesUnknown() {
111+
return forTypes(Object.class, Object.class, Object.class, Object.class);
112+
}
113+
114+
// #end CONSTRUCTION
115+
116+
// #begin SET FIELDS
117+
118+
/**
119+
* Sets the transformation from inner to outer keys which will be used by the created map.
120+
*
121+
* @param transformToOuterKey
122+
* transforms inner to outer keys
123+
* @return this builder
124+
*/
125+
public TransformingMapBuilder<IK, OK, IV, OV> toOuterKey(Function<? super IK, ? extends OK> transformToOuterKey) {
126+
Objects.requireNonNull(transformToOuterKey, "The argument 'transformToOuterKey' must not be null.");
127+
128+
this.transformToOuterKey = transformToOuterKey;
129+
return this;
130+
}
131+
132+
/**
133+
* Sets the transformation from outer to inner keys which will be used by the created map.
134+
*
135+
* @param transformToInnerKey
136+
* transforms outer to inner keys
137+
* @return this builder
138+
*/
139+
public TransformingMapBuilder<IK, OK, IV, OV> toInnerKey(Function<? super OK, ? extends IK> transformToInnerKey) {
140+
Objects.requireNonNull(transformToInnerKey, "The argument 'transformToInnerKey' must not be null.");
141+
142+
this.transformToInnerKey = transformToInnerKey;
143+
return this;
144+
}
145+
146+
/**
147+
* Sets the transformation from inner to outer values which will be used by the created map.
148+
*
149+
* @param transformToOuterValue
150+
* transforms inner to outer values
151+
* @return this builder
152+
*/
153+
public TransformingMapBuilder<IK, OK, IV, OV> toOuterValue(Function<? super IV, ? extends OV> transformToOuterValue) {
154+
Objects.requireNonNull(transformToOuterValue, "The argument 'transformToOuterValue' must not be null.");
155+
156+
this.transformToOuterValue = transformToOuterValue;
157+
return this;
158+
}
159+
160+
/**
161+
* Sets the transformation from outer to inner values which will be used by the created map.
162+
*
163+
* @param transformToInnerValue
164+
* transforms outer to inner values
165+
* @return this builder
166+
*/
167+
public TransformingMapBuilder<IK, OK, IV, OV> toInnerValue(Function<? super OV, ? extends IV> transformToInnerValue) {
168+
Objects.requireNonNull(transformToInnerValue, "The argument 'transformToInnerValue' must not be null.");
169+
170+
this.transformToInnerValue = transformToInnerValue;
171+
return this;
172+
}
173+
174+
// #end SET FIELDS
175+
176+
// #begin BUILD
177+
178+
/**
179+
* Creates a {@link TransformingMap} which transforms/decorates the specified map.
180+
*
181+
* @param map
182+
* the map to transform; will be the inner map of the returned transformation
183+
* @return a new {@link TransformingMap}
184+
*/
185+
public TransformingMap<IK, OK, IV, OV> transformMap(Map<IK, IV> map) {
186+
return new TransformingMap<>(map,
187+
innerKeyTypeToken, outerKeyTypeToken, transformToOuterKey, transformToInnerKey,
188+
innerValueTypeToken, outerValueTypeToken, transformToOuterValue, transformToInnerValue);
189+
}
190+
191+
// #end BUILD
192+
193+
}

src/test/java/org/codefx/libfx/collection/transform/TransformingMapTest.java

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -134,16 +134,15 @@ private static Map<Feline, Feline> createBackedByMammalMap(Object[] entries) {
134134
String valueName = ((Feline) ((Entry<?, ?>) entry).getValue()).getName();
135135
mammals.put(new Mammal(keyName), new Mammal(valueName));
136136
}
137-
return new TransformingMap<Mammal, Feline, Mammal, Feline>(
138-
mammals,
139-
/*
140-
* Because 'Feline' does not uphold the Liskov Substitution Principle (by having its own 'toString'
141-
* method) felines can not masquerade as mammals. Hence create a new mammal for each feline.
142-
*/
143-
Mammal.class, mammal -> new Feline(mammal.getName()),
144-
Feline.class, feline -> new Mammal(feline.getName()),
145-
Mammal.class, mammal -> new Feline(mammal.getName()),
146-
Feline.class, feline -> new Mammal(feline.getName()));
137+
// Because 'Feline' does not uphold the Liskov Substitution Principle (by having its own 'toString'
138+
// method) felines can not masquerade as mammals. Hence create a new mammal for each feline.
139+
return TransformingMapBuilder
140+
.forTypes(Mammal.class, Feline.class, Mammal.class, Feline.class)
141+
.toOuterKey(mammal -> new Feline(mammal.getName()))
142+
.toInnerKey(feline -> new Cat(feline.getName()))
143+
.toOuterValue(mammal -> new Feline(mammal.getName()))
144+
.toInnerValue(feline -> new Cat(feline.getName()))
145+
.transformMap(mammals);
147146
}
148147

149148
private static Map<Feline, Feline> createBackedByCatMap(Object[] entries) {
@@ -153,16 +152,15 @@ private static Map<Feline, Feline> createBackedByCatMap(Object[] entries) {
153152
String valueName = ((Feline) ((Entry<?, ?>) entry).getValue()).getName();
154153
cats.put(new Cat(keyName), new Cat(valueName));
155154
}
156-
return new TransformingMap<Cat, Feline, Cat, Feline>(
157-
cats,
158-
/*
159-
* Because 'Cat' does not uphold the Liskov Substitution Principle (by having its own 'toString'
160-
* method) cats can not masquerade as felines. Hence create a new feline for each cat.
161-
*/
162-
Cat.class, cat -> new Feline(cat.getName()),
163-
Feline.class, feline -> new Cat(feline.getName()),
164-
Cat.class, cat -> new Feline(cat.getName()),
165-
Feline.class, feline -> new Cat(feline.getName()));
155+
// Because 'Cat' does not uphold the Liskov Substitution Principle (by having its own 'toString'
156+
// method) cats can not masquerade as felines. Hence create a new feline for each cat.
157+
return TransformingMapBuilder
158+
.forTypes(Cat.class, Feline.class, Cat.class, Feline.class)
159+
.toOuterKey(cat -> new Feline(cat.getName()))
160+
.toInnerKey(feline -> new Cat(feline.getName()))
161+
.toOuterValue(cat -> new Feline(cat.getName()))
162+
.toInnerValue(feline -> new Cat(feline.getName()))
163+
.transformMap(cats);
166164
}
167165
}
168166

0 commit comments

Comments
 (0)