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.ByteArrayInputStream;
20 import java.io.ByteArrayOutputStream;
21 import java.io.IOException;
22 import java.io.InvalidObjectException;
23 import java.io.ObjectInputStream;
24 import java.io.ObjectOutputStream;
25 import java.io.StreamCorruptedException;
26 import java.lang.reflect.InvocationTargetException;
27 import java.lang.reflect.Method;
28 import java.lang.reflect.Modifier;
29 import java.util.Arrays;
30 import java.util.Collection;
31 import java.util.ConcurrentModificationException;
32 import java.util.HashMap;
33 import java.util.Map;
34 import java.util.Objects;
35
36 import org.apache.logging.log4j.status.StatusLogger;
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 public class SortedArrayStringMap implements IndexedStringMap {
62
63
64
65
66 private static final int DEFAULT_INITIAL_CAPACITY = 4;
67 private static final long serialVersionUID = -5748905872274478116L;
68 private static final int HASHVAL = 31;
69
70 private static final TriConsumer<String, Object, StringMap> PUT_ALL = (key, value, contextData) -> contextData.putValue(key, value);
71
72
73
74
75 private static final String[] EMPTY = {};
76 private static final String FROZEN = "Frozen collection cannot be modified";
77
78 private transient String[] keys = EMPTY;
79 private transient Object[] values = EMPTY;
80
81
82
83
84 private transient int size;
85
86 private static final Method setObjectInputFilter;
87 private static final Method getObjectInputFilter;
88 private static final Method newObjectInputFilter;
89
90 static {
91 Method[] methods = ObjectInputStream.class.getMethods();
92 Method setMethod = null;
93 Method getMethod = null;
94 for (final Method method : methods) {
95 if (method.getName().equals("setObjectInputFilter")) {
96 setMethod = method;
97 } else if (method.getName().equals("getObjectInputFilter")) {
98 getMethod = method;
99 }
100 }
101 Method newMethod = null;
102 try {
103 if (setMethod != null) {
104 final Class<?> clazz = Class.forName("org.apache.logging.log4j.util.internal.DefaultObjectInputFilter");
105 methods = clazz.getMethods();
106 for (final Method method : methods) {
107 if (method.getName().equals("newInstance") && Modifier.isStatic(method.getModifiers())) {
108 newMethod = method;
109 break;
110 }
111 }
112 }
113 } catch (final ClassNotFoundException ex) {
114
115 }
116 newObjectInputFilter = newMethod;
117 setObjectInputFilter = setMethod;
118 getObjectInputFilter = getMethod;
119 }
120
121
122
123
124
125
126
127 private int threshold;
128 private boolean immutable;
129 private transient boolean iterating;
130
131 public SortedArrayStringMap() {
132 this(DEFAULT_INITIAL_CAPACITY);
133 }
134
135 public SortedArrayStringMap(final int initialCapacity) {
136 if (initialCapacity < 0) {
137 throw new IllegalArgumentException("Initial capacity must be at least zero but was " + initialCapacity);
138 }
139 threshold = ceilingNextPowerOfTwo(initialCapacity == 0 ? 1 : initialCapacity);
140 }
141
142 public SortedArrayStringMap(final ReadOnlyStringMap other) {
143 if (other instanceof SortedArrayStringMap) {
144 initFrom0((SortedArrayStringMap) other);
145 } else if (other != null) {
146 resize(ceilingNextPowerOfTwo(other.size()));
147 other.forEach(PUT_ALL, this);
148 }
149 }
150
151 public SortedArrayStringMap(final Map<String, ?> map) {
152 resize(ceilingNextPowerOfTwo(map.size()));
153 for (final Map.Entry<String, ?> entry : map.entrySet()) {
154 putValue(entry.getKey(), entry.getValue());
155 }
156 }
157
158 private void assertNotFrozen() {
159 if (immutable) {
160 throw new UnsupportedOperationException(FROZEN);
161 }
162 }
163
164 private void assertNoConcurrentModification() {
165 if (iterating) {
166 throw new ConcurrentModificationException();
167 }
168 }
169
170 @Override
171 public void clear() {
172 if (keys == EMPTY) {
173 return;
174 }
175 assertNotFrozen();
176 assertNoConcurrentModification();
177
178 Arrays.fill(keys, 0, size, null);
179 Arrays.fill(values, 0, size, null);
180 size = 0;
181 }
182
183 @Override
184 public boolean containsKey(final String key) {
185 return indexOfKey(key) >= 0;
186 }
187
188 @Override
189 public Map<String, String> toMap() {
190 final Map<String, String> result = new HashMap<>(size());
191 for (int i = 0; i < size(); i++) {
192 final Object value = getValueAt(i);
193 result.put(getKeyAt(i), value == null ? null : String.valueOf(value));
194 }
195 return result;
196 }
197
198 @Override
199 public void freeze() {
200 immutable = true;
201 }
202
203 @Override
204 public boolean isFrozen() {
205 return immutable;
206 }
207
208 @SuppressWarnings("unchecked")
209 @Override
210 public <V> V getValue(final String key) {
211 final int index = indexOfKey(key);
212 if (index < 0) {
213 return null;
214 }
215 return (V) values[index];
216 }
217
218 @Override
219 public boolean isEmpty() {
220 return size == 0;
221 }
222
223 @Override
224 public int indexOfKey(final String key) {
225 if (keys == EMPTY) {
226 return -1;
227 }
228 if (key == null) {
229 return nullKeyIndex();
230 }
231 final int start = size > 0 && keys[0] == null ? 1 : 0;
232 return Arrays.binarySearch(keys, start, size, key);
233 }
234
235 private int nullKeyIndex() {
236 return size > 0 && keys[0] == null ? 0 : ~0;
237 }
238
239 @Override
240 public void putValue(final String key, final Object value) {
241 assertNotFrozen();
242 assertNoConcurrentModification();
243
244 if (keys == EMPTY) {
245 inflateTable(threshold);
246 }
247 final int index = indexOfKey(key);
248 if (index >= 0) {
249 keys[index] = key;
250 values[index] = value;
251 } else {
252 insertAt(~index, key, value);
253 }
254 }
255
256 private void insertAt(final int index, final String key, final Object value) {
257 ensureCapacity();
258 System.arraycopy(keys, index, keys, index + 1, size - index);
259 System.arraycopy(values, index, values, index + 1, size - index);
260 keys[index] = key;
261 values[index] = value;
262 size++;
263 }
264
265 @Override
266 public void putAll(final ReadOnlyStringMap source) {
267 if (source == this || source == null || source.isEmpty()) {
268
269
270
271 return;
272 }
273 assertNotFrozen();
274 assertNoConcurrentModification();
275
276 if (source instanceof SortedArrayStringMap) {
277 if (this.size == 0) {
278 initFrom0((SortedArrayStringMap) source);
279 } else {
280 merge((SortedArrayStringMap) source);
281 }
282 } else if (source != null) {
283 source.forEach(PUT_ALL, this);
284 }
285 }
286
287 private void initFrom0(final SortedArrayStringMap other) {
288 if (keys.length < other.size) {
289 keys = new String[other.threshold];
290 values = new Object[other.threshold];
291 }
292 System.arraycopy(other.keys, 0, keys, 0, other.size);
293 System.arraycopy(other.values, 0, values, 0, other.size);
294
295 size = other.size;
296 threshold = other.threshold;
297 }
298
299 private void merge(final SortedArrayStringMap other) {
300 final String[] myKeys = keys;
301 final Object[] myVals = values;
302 final int newSize = other.size + this.size;
303 threshold = ceilingNextPowerOfTwo(newSize);
304 if (keys.length < threshold) {
305 keys = new String[threshold];
306 values = new Object[threshold];
307 }
308
309 boolean overwrite = true;
310 if (other.size() > size()) {
311
312 System.arraycopy(myKeys, 0, keys, other.size, this.size);
313 System.arraycopy(myVals, 0, values, other.size, this.size);
314
315
316 System.arraycopy(other.keys, 0, keys, 0, other.size);
317 System.arraycopy(other.values, 0, values, 0, other.size);
318 size = other.size;
319
320
321 overwrite = false;
322 } else {
323 System.arraycopy(myKeys, 0, keys, 0, this.size);
324 System.arraycopy(myVals, 0, values, 0, this.size);
325
326
327 System.arraycopy(other.keys, 0, keys, this.size, other.size);
328 System.arraycopy(other.values, 0, values, this.size, other.size);
329
330
331 }
332 for (int i = size; i < newSize; i++) {
333 final int index = indexOfKey(keys[i]);
334 if (index < 0) {
335 insertAt(~index, keys[i], values[i]);
336 } else if (overwrite) {
337 keys[index] = keys[i];
338 values[index] = values[i];
339 }
340 }
341
342 Arrays.fill(keys, size, newSize, null);
343 Arrays.fill(values, size, newSize, null);
344 }
345
346 private void ensureCapacity() {
347 if (size >= threshold) {
348 resize(threshold * 2);
349 }
350 }
351
352 private void resize(final int newCapacity) {
353 final String[] oldKeys = keys;
354 final Object[] oldValues = values;
355
356 keys = new String[newCapacity];
357 values = new Object[newCapacity];
358
359 System.arraycopy(oldKeys, 0, keys, 0, size);
360 System.arraycopy(oldValues, 0, values, 0, size);
361
362 threshold = newCapacity;
363 }
364
365
366
367
368 private void inflateTable(final int toSize) {
369 threshold = toSize;
370 keys = new String[toSize];
371 values = new Object[toSize];
372 }
373
374 @Override
375 public void remove(final String key) {
376 if (keys == EMPTY) {
377 return;
378 }
379
380 final int index = indexOfKey(key);
381 if (index >= 0) {
382 assertNotFrozen();
383 assertNoConcurrentModification();
384
385 System.arraycopy(keys, index + 1, keys, index, size - 1 - index);
386 System.arraycopy(values, index + 1, values, index, size - 1 - index);
387 keys[size - 1] = null;
388 values[size - 1] = null;
389 size--;
390 }
391 }
392
393 @Override
394 public String getKeyAt(final int index) {
395 if (index < 0 || index >= size) {
396 return null;
397 }
398 return keys[index];
399 }
400
401 @SuppressWarnings("unchecked")
402 @Override
403 public <V> V getValueAt(final int index) {
404 if (index < 0 || index >= size) {
405 return null;
406 }
407 return (V) values[index];
408 }
409
410 @Override
411 public int size() {
412 return size;
413 }
414
415 @SuppressWarnings("unchecked")
416 @Override
417 public <V> void forEach(final BiConsumer<String, ? super V> action) {
418 iterating = true;
419 try {
420 for (int i = 0; i < size; i++) {
421 action.accept(keys[i], (V) values[i]);
422 }
423 } finally {
424 iterating = false;
425 }
426 }
427
428 @SuppressWarnings("unchecked")
429 @Override
430 public <V, T> void forEach(final TriConsumer<String, ? super V, T> action, final T state) {
431 iterating = true;
432 try {
433 for (int i = 0; i < size; i++) {
434 action.accept(keys[i], (V) values[i], state);
435 }
436 } finally {
437 iterating = false;
438 }
439 }
440
441 @Override
442 public boolean equals(final Object obj) {
443 if (obj == this) {
444 return true;
445 }
446 if (!(obj instanceof SortedArrayStringMap)) {
447 return false;
448 }
449 final SortedArrayStringMap other = (SortedArrayStringMap) obj;
450 if (this.size() != other.size()) {
451 return false;
452 }
453 for (int i = 0; i < size(); i++) {
454 if (!Objects.equals(keys[i], other.keys[i])) {
455 return false;
456 }
457 if (!Objects.equals(values[i], other.values[i])) {
458 return false;
459 }
460 }
461 return true;
462 }
463
464 @Override
465 public int hashCode() {
466 int result = 37;
467 result = HASHVAL * result + size;
468 result = HASHVAL * result + hashCode(keys, size);
469 result = HASHVAL * result + hashCode(values, size);
470 return result;
471 }
472
473 private static int hashCode(final Object[] values, final int length) {
474 int result = 1;
475 for (int i = 0; i < length; i++) {
476 result = HASHVAL * result + (values[i] == null ? 0 : values[i].hashCode());
477 }
478 return result;
479 }
480
481 @Override
482 public String toString() {
483 final StringBuilder sb = new StringBuilder(256);
484 sb.append('{');
485 for (int i = 0; i < size; i++) {
486 if (i > 0) {
487 sb.append(", ");
488 }
489 sb.append(keys[i]).append('=');
490 sb.append(values[i] == this ? "(this map)" : values[i]);
491 }
492 sb.append('}');
493 return sb.toString();
494 }
495
496
497
498
499
500
501
502
503
504
505
506
507 private void writeObject(final java.io.ObjectOutputStream s) throws IOException {
508
509 s.defaultWriteObject();
510
511
512 if (keys == EMPTY) {
513 s.writeInt(ceilingNextPowerOfTwo(threshold));
514 } else {
515 s.writeInt(keys.length);
516 }
517
518
519 s.writeInt(size);
520
521
522 if (size > 0) {
523 for (int i = 0; i < size; i++) {
524 s.writeObject(keys[i]);
525 try {
526 s.writeObject(marshall(values[i]));
527 } catch (final Exception e) {
528 handleSerializationException(e, i, keys[i]);
529 s.writeObject(null);
530 }
531 }
532 }
533 }
534
535 private static byte[] marshall(final Object obj) throws IOException {
536 if (obj == null) {
537 return null;
538 }
539 final ByteArrayOutputStream bout = new ByteArrayOutputStream();
540 try (ObjectOutputStream oos = new ObjectOutputStream(bout)) {
541 oos.writeObject(obj);
542 oos.flush();
543 return bout.toByteArray();
544 }
545 }
546
547 private static Object unmarshall(final byte[] data, final ObjectInputStream inputStream)
548 throws IOException, ClassNotFoundException {
549 final ByteArrayInputStream bin = new ByteArrayInputStream(data);
550 Collection<String> allowedClasses = null;
551 ObjectInputStream ois;
552 if (inputStream instanceof FilteredObjectInputStream) {
553 allowedClasses = ((FilteredObjectInputStream) inputStream).getAllowedClasses();
554 ois = new FilteredObjectInputStream(bin, allowedClasses);
555 } else {
556 try {
557 final Object obj = getObjectInputFilter.invoke(inputStream);
558 final Object filter = newObjectInputFilter.invoke(null, obj);
559 ois = new ObjectInputStream(bin);
560 setObjectInputFilter.invoke(ois, filter);
561 } catch (IllegalAccessException | InvocationTargetException ex) {
562 throw new StreamCorruptedException("Unable to set ObjectInputFilter on stream");
563 }
564 }
565 try {
566 return ois.readObject();
567 } finally {
568 ois.close();
569 }
570 }
571
572
573
574
575
576
577
578
579
580 private static int ceilingNextPowerOfTwo(final int x) {
581 final int BITS_PER_INT = 32;
582 return 1 << (BITS_PER_INT - Integer.numberOfLeadingZeros(x - 1));
583 }
584
585
586
587
588
589 private void readObject(final java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
590 if (!(s instanceof FilteredObjectInputStream) && setObjectInputFilter == null) {
591 throw new IllegalArgumentException("readObject requires a FilteredObjectInputStream or an ObjectInputStream that accepts an ObjectInputFilter");
592 }
593
594 s.defaultReadObject();
595
596
597 keys = EMPTY;
598 values = EMPTY;
599
600
601 final int capacity = s.readInt();
602 if (capacity < 0) {
603 throw new InvalidObjectException("Illegal capacity: " + capacity);
604 }
605
606
607 final int mappings = s.readInt();
608 if (mappings < 0) {
609 throw new InvalidObjectException("Illegal mappings count: " + mappings);
610 }
611
612
613 if (mappings > 0) {
614 inflateTable(capacity);
615 } else {
616 threshold = capacity;
617 }
618
619
620 for (int i = 0; i < mappings; i++) {
621 keys[i] = (String) s.readObject();
622 try {
623 final byte[] marshalledObject = (byte[]) s.readObject();
624 values[i] = marshalledObject == null ? null : unmarshall(marshalledObject, s);
625 } catch (final Exception | LinkageError error) {
626 handleSerializationException(error, i, keys[i]);
627 values[i] = null;
628 }
629 }
630 size = mappings;
631 }
632
633 private void handleSerializationException(final Throwable t, final int i, final String key) {
634 StatusLogger.getLogger().warn("Ignoring {} for key[{}] ('{}')", String.valueOf(t), i, keys[i]);
635 }
636 }