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