1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.util;
18
19 import java.io.IOException;
20 import java.io.InvalidObjectException;
21 import java.util.Arrays;
22 import java.util.ConcurrentModificationException;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.Objects;
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 public class SortedArrayStringMap implements StringMap {
51
52
53
54
55 private static final int DEFAULT_INITIAL_CAPACITY = 4;
56 private static final long serialVersionUID = -5748905872274478116L;
57 private static final int HASHVAL = 31;
58
59 private static final TriConsumer<String, Object, StringMap> PUT_ALL = new TriConsumer<String, Object, StringMap>() {
60 @Override
61 public void accept(final String key, final Object value, final StringMap contextData) {
62 contextData.putValue(key, value);
63 }
64 };
65
66
67
68
69 private static final String[] EMPTY = {};
70 private static final String FROZEN = "Frozen collection cannot be modified";
71
72 private transient String[] keys = EMPTY;
73 private transient Object[] values = EMPTY;
74
75
76
77
78 private transient int size;
79
80
81
82
83
84
85
86 private int threshold;
87 private boolean immutable;
88 private transient boolean iterating;
89
90 public SortedArrayStringMap() {
91 this(DEFAULT_INITIAL_CAPACITY);
92 }
93
94 public SortedArrayStringMap(final int initialCapacity) {
95 if (initialCapacity < 1) {
96 throw new IllegalArgumentException("Initial capacity must be at least one but was " + initialCapacity);
97 }
98 threshold = ceilingNextPowerOfTwo(initialCapacity);
99 }
100
101 public SortedArrayStringMap(final ReadOnlyStringMap other) {
102 if (other instanceof SortedArrayStringMap) {
103 initFrom0((SortedArrayStringMap) other);
104 } else if (other != null) {
105 resize(ceilingNextPowerOfTwo(other.size()));
106 other.forEach(PUT_ALL, this);
107 }
108 }
109
110 private void assertNotFrozen() {
111 if (immutable) {
112 throw new UnsupportedOperationException(FROZEN);
113 }
114 }
115
116 private void assertNoConcurrentModification() {
117 if (iterating) {
118 throw new ConcurrentModificationException();
119 }
120 }
121
122 @Override
123 public void clear() {
124 if (keys == EMPTY) {
125 return;
126 }
127 assertNotFrozen();
128 assertNoConcurrentModification();
129
130 Arrays.fill(keys, 0, size, null);
131 Arrays.fill(values, 0, size, null);
132 size = 0;
133 }
134
135 @Override
136 public boolean containsKey(final String key) {
137 return indexOfKey(key) >= 0;
138 }
139
140 @Override
141 public Map<String, String> toMap() {
142 final Map<String, String> result = new HashMap<>(size());
143 for (int i = 0; i < size(); i++) {
144 final Object value = getValueAt(i);
145 result.put(getKeyAt(i), value == null ? null : String.valueOf(value));
146 }
147 return result;
148 }
149
150 @Override
151 public void freeze() {
152 immutable = true;
153 }
154
155 @Override
156 public boolean isFrozen() {
157 return immutable;
158 }
159
160 @SuppressWarnings("unchecked")
161 @Override
162 public <V> V getValue(final String key) {
163 final int index = indexOfKey(key);
164 if (index < 0) {
165 return null;
166 }
167 return (V) values[index];
168 }
169
170 @Override
171 public boolean isEmpty() {
172 return size == 0;
173 }
174
175 int indexOfKey(final String key) {
176 if (keys == EMPTY) {
177 return -1;
178 }
179 if (key == null) {
180 return nullKeyIndex();
181 }
182 final int start = size > 0 && keys[0] == null ? 1 : 0;
183 return Arrays.binarySearch(keys, start, size, key);
184 }
185
186 private int nullKeyIndex() {
187 return size > 0 && keys[0] == null ? 0 : ~0;
188 }
189
190 @Override
191 public void putValue(final String key, final Object value) {
192 assertNotFrozen();
193 assertNoConcurrentModification();
194
195 if (keys == EMPTY) {
196 inflateTable(threshold);
197 }
198 final int index = indexOfKey(key);
199 if (index >= 0) {
200 keys[index] = key;
201 values[index] = value;
202 } else {
203 insertAt(~index, key, value);
204 }
205 }
206
207 private void insertAt(final int index, final String key, final Object value) {
208 ensureCapacity();
209 System.arraycopy(keys, index, keys, index + 1, size - index);
210 System.arraycopy(values, index, values, index + 1, size - index);
211 keys[index] = key;
212 values[index] = value;
213 size++;
214 }
215
216 @Override
217 public void putAll(final ReadOnlyStringMap source) {
218 if (source == this || source.isEmpty()) {
219 return;
220 }
221 assertNotFrozen();
222 assertNoConcurrentModification();
223
224 if (source instanceof SortedArrayStringMap) {
225 if (this.size == 0) {
226 initFrom0((SortedArrayStringMap) source);
227 } else {
228 merge((SortedArrayStringMap) source);
229 }
230 } else if (source != null) {
231 source.forEach(PUT_ALL, this);
232 }
233 }
234
235 private void initFrom0(final SortedArrayStringMap other) {
236 if (keys.length < other.size) {
237 keys = new String[other.threshold];
238 values = new Object[other.threshold];
239 }
240 System.arraycopy(other.keys, 0, keys, 0, other.size);
241 System.arraycopy(other.values, 0, values, 0, other.size);
242
243 size = other.size;
244 threshold = other.threshold;
245 }
246
247 private void merge(final SortedArrayStringMap other) {
248 final String[] myKeys = keys;
249 final Object[] myVals = values;
250 final int newSize = other.size + this.size;
251 threshold = ceilingNextPowerOfTwo(newSize);
252 if (keys.length < threshold) {
253 keys = new String[threshold];
254 values = new Object[threshold];
255 }
256
257 boolean overwrite = true;
258 if (other.size() > size()) {
259
260 System.arraycopy(myKeys, 0, keys, other.size, this.size);
261 System.arraycopy(myVals, 0, values, other.size, this.size);
262
263
264 System.arraycopy(other.keys, 0, keys, 0, other.size);
265 System.arraycopy(other.values, 0, values, 0, other.size);
266 size = other.size;
267
268
269 overwrite = false;
270 } else {
271 System.arraycopy(myKeys, 0, keys, 0, this.size);
272 System.arraycopy(myVals, 0, values, 0, this.size);
273
274
275 System.arraycopy(other.keys, 0, keys, this.size, other.size);
276 System.arraycopy(other.values, 0, values, this.size, other.size);
277
278
279 }
280 for (int i = size; i < newSize; i++) {
281 final int index = indexOfKey(keys[i]);
282 if (index < 0) {
283 insertAt(~index, keys[i], values[i]);
284 } else if (overwrite) {
285 keys[index] = keys[i];
286 values[index] = values[i];
287 }
288 }
289
290 Arrays.fill(keys, size, newSize, null);
291 Arrays.fill(values, size, newSize, null);
292 }
293
294 private void ensureCapacity() {
295 if (size >= threshold) {
296 resize(threshold * 2);
297 }
298 }
299
300 private void resize(final int newCapacity) {
301 final String[] oldKeys = keys;
302 final Object[] oldValues = values;
303
304 keys = new String[newCapacity];
305 values = new Object[newCapacity];
306
307 System.arraycopy(oldKeys, 0, keys, 0, size);
308 System.arraycopy(oldValues, 0, values, 0, size);
309
310 threshold = newCapacity;
311 }
312
313
314
315
316 private void inflateTable(final int toSize) {
317 threshold = toSize;
318 keys = new String[toSize];
319 values = new Object[toSize];
320 }
321
322 @Override
323 public void remove(final String key) {
324 if (keys == EMPTY) {
325 return;
326 }
327
328 final int index = indexOfKey(key);
329 if (index >= 0) {
330 assertNotFrozen();
331 assertNoConcurrentModification();
332
333 System.arraycopy(keys, index + 1, keys, index, size - 1 - index);
334 System.arraycopy(values, index + 1, values, index, size - 1 - index);
335 keys[size - 1] = null;
336 values[size - 1] = null;
337 size--;
338 }
339 }
340
341 String getKeyAt(final int index) {
342 if (index < 0 || index >= size) {
343 return null;
344 }
345 return keys[index];
346 }
347
348 @SuppressWarnings("unchecked")
349 <V> V getValueAt(final int index) {
350 if (index < 0 || index >= size) {
351 return null;
352 }
353 return (V) values[index];
354 }
355
356 @Override
357 public int size() {
358 return size;
359 }
360
361 @SuppressWarnings("unchecked")
362 @Override
363 public <V> void forEach(final BiConsumer<String, ? super V> action) {
364 iterating = true;
365 try {
366 for (int i = 0; i < size; i++) {
367 action.accept(keys[i], (V) values[i]);
368 }
369 } finally {
370 iterating = false;
371 }
372 }
373
374 @SuppressWarnings("unchecked")
375 @Override
376 public <V, T> void forEach(final TriConsumer<String, ? super V, T> action, final T state) {
377 iterating = true;
378 try {
379 for (int i = 0; i < size; i++) {
380 action.accept(keys[i], (V) values[i], state);
381 }
382 } finally {
383 iterating = false;
384 }
385 }
386
387 @Override
388 public boolean equals(final Object obj) {
389 if (obj == this) {
390 return true;
391 }
392 if (!(obj instanceof SortedArrayStringMap)) {
393 return false;
394 }
395 final SortedArrayStringMap other = (SortedArrayStringMap) obj;
396 if (this.size() != other.size()) {
397 return false;
398 }
399 for (int i = 0; i < size(); i++) {
400 if (!Objects.equals(keys[i], other.keys[i])) {
401 return false;
402 }
403 if (!Objects.equals(values[i], other.values[i])) {
404 return false;
405 }
406 }
407 return true;
408 }
409
410 @Override
411 public int hashCode() {
412 int result = 37;
413 result = HASHVAL * result + size;
414 result = HASHVAL * result + hashCode(keys, size);
415 result = HASHVAL * result + hashCode(values, size);
416 return result;
417 }
418
419 private static int hashCode(final Object[] values, final int length) {
420 int result = 1;
421 for (int i = 0; i < length; i++) {
422 result = HASHVAL * result + (values[i] == null ? 0 : values[i].hashCode());
423 }
424 return result;
425 }
426
427 @Override
428 public String toString() {
429 final StringBuilder sb = new StringBuilder(256);
430 sb.append('{');
431 for (int i = 0; i < size; i++) {
432 if (i > 0) {
433 sb.append(", ");
434 }
435 sb.append(keys[i]).append('=');
436 sb.append(values[i] == this ? "(this map)" : values[i]);
437 }
438 sb.append('}');
439 return sb.toString();
440 }
441
442
443
444
445
446
447
448
449
450
451
452
453 private void writeObject(final java.io.ObjectOutputStream s) throws IOException {
454
455 s.defaultWriteObject();
456
457
458 if (keys == EMPTY) {
459 s.writeInt(ceilingNextPowerOfTwo(threshold));
460 } else {
461 s.writeInt(keys.length);
462 }
463
464
465 s.writeInt(size);
466
467
468 if (size > 0) {
469 for (int i = 0; i < size; i++) {
470 s.writeObject(keys[i]);
471 s.writeObject(values[i]);
472 }
473 }
474 }
475
476
477
478
479
480
481
482
483
484
485 private static int ceilingNextPowerOfTwo(final int x) {
486 final int BITS_PER_INT = 32;
487 return 1 << (BITS_PER_INT - Integer.numberOfLeadingZeros(x - 1));
488 }
489
490
491
492
493
494 private void readObject(final java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
495
496 s.defaultReadObject();
497
498
499 keys = EMPTY;
500 values = EMPTY;
501
502
503 final int capacity = s.readInt();
504 if (capacity < 0) {
505 throw new InvalidObjectException("Illegal capacity: " + capacity);
506 }
507
508
509 final int mappings = s.readInt();
510 if (mappings < 0) {
511 throw new InvalidObjectException("Illegal mappings count: " + mappings);
512 }
513
514
515 if (mappings > 0) {
516 inflateTable(capacity);
517 } else {
518 threshold = capacity;
519 }
520
521
522 for (int i = 0; i < mappings; i++) {
523 keys[i] = (String) s.readObject();
524 values[i] = s.readObject();
525 }
526 size = mappings;
527 }
528 }