1 package org.djutils.multikeymap;
2
3 import java.util.Arrays;
4 import java.util.LinkedHashMap;
5 import java.util.List;
6 import java.util.Map;
7 import java.util.Set;
8 import java.util.function.Supplier;
9
10 import org.djutils.exceptions.Throw;
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 public class MultiKeyMap<T>
29 {
30
31
32 private final Class<?>[] keyTypes;
33
34
35 private final Map<Object, Object> map = new LinkedHashMap<>();
36
37
38
39
40
41 public MultiKeyMap(final Class<?>... keyTypes)
42 {
43 Throw.when(keyTypes.length < 1, IllegalArgumentException.class, "keyTypes may not be empty");
44 this.keyTypes = keyTypes;
45 }
46
47
48
49
50
51
52
53
54
55 public T get(final Supplier<T> supplier, final Object... keys)
56 {
57 return getValue(supplier, Arrays.asList(keys));
58 }
59
60
61
62
63
64
65 public T get(final Object... keys)
66 {
67 return getValue(null, Arrays.asList(keys));
68 }
69
70
71
72
73
74
75 public MultiKeyMap<T> getSubMap(final Object... keys)
76 {
77 Throw.when(keys.length >= this.keyTypes.length, IllegalArgumentException.class, "Too many keys");
78 return getSubMap(false, Arrays.asList(keys));
79 }
80
81
82
83
84
85
86
87
88 public T put(final T newValue, final Object... keys)
89 {
90 List<Object> keyList = Arrays.asList(keys);
91 Map<Object, T> branch = getFinalMap(true, keyList);
92 Object key = getFinalKey(keyList);
93 return branch.put(key, newValue);
94 }
95
96
97
98
99
100
101 private Object getFinalKey(final List<Object> keys)
102 {
103 Object key = keys.get(keys.size() - 1);
104 Throw.whenNull(key, "key may not be null");
105 Throw.when(key != null && !this.keyTypes[keys.size() - 1].isAssignableFrom(key.getClass()),
106 IllegalArgumentException.class, "Key %s is not of %s.", key, this.keyTypes[keys.size() - 1]);
107 return key;
108 }
109
110
111
112
113
114
115
116
117 @SuppressWarnings("unchecked")
118 private Map<Object, T> getFinalMap(final boolean createBranches, final List<Object> keys)
119 {
120 Throw.when(keys.size() != this.keyTypes.length, IllegalArgumentException.class, "Incorrect number of keys.");
121 MultiKeyMap<T> finalMap = (MultiKeyMap<T>) getSubMap(createBranches, keys.subList(0, keys.size() - 1));
122 if (null == finalMap)
123 {
124 return null;
125 }
126 return (Map<Object, T>) finalMap.map;
127 }
128
129
130
131
132
133
134
135
136 private T getValue(final Supplier<T> supplier, final List<Object> keys)
137 {
138 Throw.when(keys.size() != this.keyTypes.length, IllegalArgumentException.class, "Wrong number of keys");
139 Map<Object, T> branch = getFinalMap(null != supplier, keys);
140 if (null == branch)
141 {
142 return null;
143 }
144 Object key = getFinalKey(keys);
145 T leaf = branch.get(key);
146 if (leaf == null)
147 {
148 if (null == supplier)
149 {
150 return null;
151 }
152
153 leaf = supplier.get();
154 branch.put(key, leaf);
155 }
156 return (T) leaf;
157 }
158
159
160
161
162
163
164
165 public Set<Object> getKeys(final Object... keys)
166 {
167 return getSubMap(false, Arrays.asList(keys)).map.keySet();
168 }
169
170
171
172
173
174
175
176
177 @SuppressWarnings("unchecked")
178 private MultiKeyMap<T> getSubMap(final boolean createMissingBranches, final List<Object> keys)
179 {
180 if (keys.size() == 0)
181 {
182 return this;
183 }
184 Throw.when(keys.size() > this.keyTypes.length, IllegalArgumentException.class, "Too many keys");
185 Throw.when(keys.get(0) != null && !this.keyTypes[0].isAssignableFrom(keys.get(0).getClass()),
186 IllegalArgumentException.class, "Key %s is not of %s.", keys.get(0), this.keyTypes[0]);
187 MultiKeyMap<T> subMap = (MultiKeyMap<T>) this.map.get(keys.get(0));
188 if (null == subMap && !createMissingBranches)
189 {
190 return null;
191 }
192 if (null == subMap)
193 {
194
195 Class<Object>[] subTypes = new Class[this.keyTypes.length - 1];
196 System.arraycopy(this.keyTypes, 1, subTypes, 0, this.keyTypes.length - 1);
197 subMap = new MultiKeyMap<T>(subTypes);
198 this.map.put(keys.get(0), subMap);
199 }
200 return (subMap.getSubMap(createMissingBranches, keys.subList(1, keys.size())));
201 }
202
203
204
205
206
207
208 public Object clear(final Object... keys)
209 {
210 return clear(Arrays.asList(keys));
211
212 }
213
214
215
216
217
218
219 @SuppressWarnings("unchecked")
220 private Object clear(final List<Object> keys)
221 {
222 if (keys.size() == 0)
223 {
224 this.map.clear();
225 return this;
226 }
227 MultiKeyMap<T> subMap = getSubMap(false, keys.subList(0, keys.size() - 1));
228 if (null == subMap)
229 {
230 return null;
231 }
232 if (keys.size() == this.keyTypes.length)
233 {
234 Map<Object, T> endMap = (Map<Object, T>) subMap.map;
235 return endMap.remove(keys.get(keys.size() - 1));
236 }
237 return subMap.map.remove(keys.get(keys.size() - 1));
238 }
239
240
241 @Override
242 public String toString()
243 {
244 return "MultiKeyMap [types=" + Arrays.toString(this.keyTypes) + ", map=" + this.map + "]";
245 }
246
247 }