-
Notifications
You must be signed in to change notification settings - Fork 109
/
Copy pathClassUtilities.java
1767 lines (1568 loc) · 73 KB
/
ClassUtilities.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package com.cedarsoftware.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.io.UncheckedIOException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.MonthDay;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Period;
import java.time.Year;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Currency;
import java.util.Date;
import java.util.Deque;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.PriorityQueue;
import java.util.Properties;
import java.util.Queue;
import java.util.RandomAccess;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.Stack;
import java.util.StringJoiner;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import java.util.Vector;
import java.util.WeakHashMap;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
import com.cedarsoftware.util.convert.Converter;
import static com.cedarsoftware.util.ExceptionUtilities.safelyIgnoreException;
/**
* A utility class providing various methods for working with Java {@link Class} objects and related operations.
* <p>
* {@code ClassUtilities} includes functionalities such as:
* </p>
* <ul>
* <li>Determining inheritance distance between two classes or interfaces ({@link #computeInheritanceDistance}).</li>
* <li>Checking if a class is primitive or a primitive wrapper ({@link #isPrimitive}).</li>
* <li>Converting between primitive types and their wrapper classes ({@link #toPrimitiveWrapperClass}).</li>
* <li>Loading resources from the classpath as strings or byte arrays ({@link #loadResourceAsString} and {@link #loadResourceAsBytes}).</li>
* <li>Providing custom mappings for class aliases ({@link #addPermanentClassAlias} and {@link #removePermanentClassAlias}).</li>
* <li>Identifying whether all constructors in a class are private ({@link #areAllConstructorsPrivate}).</li>
* <li>Finding the most specific matching class in an inheritance hierarchy ({@link #findClosest}).</li>
* </ul>
*
* <h2>Inheritance Distance</h2>
* <p>
* The {@link #computeInheritanceDistance(Class, Class)} method calculates the number of inheritance steps
* between two classes or interfaces. If there is no relationship, it returns {@code -1}.
* </p>
*
* <h2>Primitive and Wrapper Handling</h2>
* <ul>
* <li>Supports identification of primitive types and their wrappers.</li>
* <li>Handles conversions between primitive types and their wrapper classes.</li>
* <li>Considers primitive types and their wrappers interchangeable for certain operations.</li>
* </ul>
*
* <h2>Resource Loading</h2>
* <p>
* Includes methods for loading resources from the classpath as strings or byte arrays, throwing appropriate
* exceptions if the resource cannot be found or read.
* </p>
*
* <h2>OSGi and JPMS ClassLoader Support</h2>
* <p>
* Detects and supports environments such as OSGi or JPMS for proper class loading. Uses caching
* for efficient retrieval of class loaders in these environments.
* </p>
*
* <h2>Design Notes</h2>
* <ul>
* <li>This class is designed to be a static utility class and should not be instantiated.</li>
* <li>It uses internal caching for operations like class aliasing and OSGi class loading to optimize performance.</li>
* </ul>
*
* <h2>Usage Example</h2>
* <pre>{@code
* // Compute inheritance distance
* int distance = ClassUtilities.computeInheritanceDistance(ArrayList.class, List.class); // Outputs 1
*
* // Check if a class is primitive
* boolean isPrimitive = ClassUtilities.isPrimitive(int.class); // Outputs true
*
* // Load a resource as a string
* String resourceContent = ClassUtilities.loadResourceAsString("example.txt");
* }</pre>
*
* @see Class
* @see ClassLoader
* @see Modifier
*
* @author John DeRegnaucourt ([email protected])
* <br>
* Copyright (c) Cedar Software LLC
* <br><br>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <br><br>
* <a href="http://www.apache.org/licenses/LICENSE-2.0">License</a>
* <br><br>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public class ClassUtilities {
private ClassUtilities() {
}
private static final Map<String, Class<?>> nameToClass = new ConcurrentHashMap<>();
private static final Map<Class<?>, Class<?>> wrapperMap;
private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_WRAPPER = new ClassValueMap<>();
private static final Map<Class<?>, Class<?>> WRAPPER_TO_PRIMITIVE = new ClassValueMap<>();
// Cache for OSGi ClassLoader to avoid repeated reflection calls
private static final Map<Class<?>, ClassLoader> osgiClassLoaders = new ClassValueMap<>();
private static final ClassLoader SYSTEM_LOADER = ClassLoader.getSystemClassLoader();
private static volatile boolean useUnsafe = false;
private static volatile Unsafe unsafe;
private static final Map<Class<?>, Supplier<Object>> DIRECT_CLASS_MAPPING = new ClassValueMap<>();
private static final Map<Class<?>, Supplier<Object>> ASSIGNABLE_CLASS_MAPPING = new ClassValueMap<>();
/**
* A cache that maps a Class<?> to its associated enum type (if any).
*/
private static final ClassValue<Class<?>> ENUM_CLASS_CACHE = new ClassValue<Class<?>>() {
@Override
protected Class<?> computeValue(Class<?> type) {
return computeEnum(type);
}
};
/**
* Add a cache for successful constructor selections
*/
private static final Map<Class<?>, Constructor<?>> SUCCESSFUL_CONSTRUCTOR_CACHE = new ClassValueMap<>();
/**
* Cache for class hierarchy information
*/
private static final Map<Class<?>, ClassHierarchyInfo> CLASS_HIERARCHY_CACHE = new ClassValueMap<>();
static {
// DIRECT_CLASS_MAPPING for concrete types
DIRECT_CLASS_MAPPING.put(Date.class, Date::new);
DIRECT_CLASS_MAPPING.put(StringBuilder.class, StringBuilder::new);
DIRECT_CLASS_MAPPING.put(StringBuffer.class, StringBuffer::new);
DIRECT_CLASS_MAPPING.put(Locale.class, Locale::getDefault);
DIRECT_CLASS_MAPPING.put(TimeZone.class, TimeZone::getDefault);
DIRECT_CLASS_MAPPING.put(Timestamp.class, () -> new Timestamp(System.currentTimeMillis()));
DIRECT_CLASS_MAPPING.put(java.sql.Date.class, () -> new java.sql.Date(System.currentTimeMillis()));
DIRECT_CLASS_MAPPING.put(LocalDate.class, LocalDate::now);
DIRECT_CLASS_MAPPING.put(LocalDateTime.class, LocalDateTime::now);
DIRECT_CLASS_MAPPING.put(OffsetDateTime.class, OffsetDateTime::now);
DIRECT_CLASS_MAPPING.put(ZonedDateTime.class, ZonedDateTime::now);
DIRECT_CLASS_MAPPING.put(ZoneId.class, ZoneId::systemDefault);
DIRECT_CLASS_MAPPING.put(AtomicBoolean.class, AtomicBoolean::new);
DIRECT_CLASS_MAPPING.put(AtomicInteger.class, AtomicInteger::new);
DIRECT_CLASS_MAPPING.put(AtomicLong.class, AtomicLong::new);
DIRECT_CLASS_MAPPING.put(URL.class, () -> ExceptionUtilities.safelyIgnoreException(() -> new URL("http://localhost"), null));
DIRECT_CLASS_MAPPING.put(URI.class, () -> ExceptionUtilities.safelyIgnoreException(() -> new URI("http://localhost"), null));
DIRECT_CLASS_MAPPING.put(Object.class, Object::new);
DIRECT_CLASS_MAPPING.put(String.class, () -> "");
DIRECT_CLASS_MAPPING.put(BigInteger.class, () -> BigInteger.ZERO);
DIRECT_CLASS_MAPPING.put(BigDecimal.class, () -> BigDecimal.ZERO);
DIRECT_CLASS_MAPPING.put(Class.class, () -> String.class);
DIRECT_CLASS_MAPPING.put(Calendar.class, Calendar::getInstance);
DIRECT_CLASS_MAPPING.put(Instant.class, Instant::now);
DIRECT_CLASS_MAPPING.put(Duration.class, () -> Duration.ofSeconds(10));
DIRECT_CLASS_MAPPING.put(Period.class, () -> Period.ofDays(0));
DIRECT_CLASS_MAPPING.put(Year.class, Year::now);
DIRECT_CLASS_MAPPING.put(YearMonth.class, YearMonth::now);
DIRECT_CLASS_MAPPING.put(MonthDay.class, MonthDay::now);
DIRECT_CLASS_MAPPING.put(ZoneOffset.class, () -> ZoneOffset.UTC);
DIRECT_CLASS_MAPPING.put(OffsetTime.class, OffsetTime::now);
DIRECT_CLASS_MAPPING.put(LocalTime.class, LocalTime::now);
DIRECT_CLASS_MAPPING.put(ByteBuffer.class, () -> ByteBuffer.allocate(0));
DIRECT_CLASS_MAPPING.put(CharBuffer.class, () -> CharBuffer.allocate(0));
// Collection classes
DIRECT_CLASS_MAPPING.put(HashSet.class, HashSet::new);
DIRECT_CLASS_MAPPING.put(TreeSet.class, TreeSet::new);
DIRECT_CLASS_MAPPING.put(HashMap.class, HashMap::new);
DIRECT_CLASS_MAPPING.put(TreeMap.class, TreeMap::new);
DIRECT_CLASS_MAPPING.put(Hashtable.class, Hashtable::new);
DIRECT_CLASS_MAPPING.put(ArrayList.class, ArrayList::new);
DIRECT_CLASS_MAPPING.put(LinkedList.class, LinkedList::new);
DIRECT_CLASS_MAPPING.put(Vector.class, Vector::new);
DIRECT_CLASS_MAPPING.put(Stack.class, Stack::new);
DIRECT_CLASS_MAPPING.put(Properties.class, Properties::new);
DIRECT_CLASS_MAPPING.put(ConcurrentHashMap.class, ConcurrentHashMap::new);
DIRECT_CLASS_MAPPING.put(LinkedHashMap.class, LinkedHashMap::new);
DIRECT_CLASS_MAPPING.put(LinkedHashSet.class, LinkedHashSet::new);
DIRECT_CLASS_MAPPING.put(ArrayDeque.class, ArrayDeque::new);
DIRECT_CLASS_MAPPING.put(PriorityQueue.class, PriorityQueue::new);
// Concurrent collections
DIRECT_CLASS_MAPPING.put(CopyOnWriteArrayList.class, CopyOnWriteArrayList::new);
DIRECT_CLASS_MAPPING.put(CopyOnWriteArraySet.class, CopyOnWriteArraySet::new);
DIRECT_CLASS_MAPPING.put(LinkedBlockingQueue.class, LinkedBlockingQueue::new);
DIRECT_CLASS_MAPPING.put(LinkedBlockingDeque.class, LinkedBlockingDeque::new);
DIRECT_CLASS_MAPPING.put(ConcurrentSkipListMap.class, ConcurrentSkipListMap::new);
DIRECT_CLASS_MAPPING.put(ConcurrentSkipListSet.class, ConcurrentSkipListSet::new);
// Additional Map implementations
DIRECT_CLASS_MAPPING.put(WeakHashMap.class, WeakHashMap::new);
DIRECT_CLASS_MAPPING.put(IdentityHashMap.class, IdentityHashMap::new);
DIRECT_CLASS_MAPPING.put(EnumMap.class, () -> new EnumMap<>(TimeUnit.class));
// Utility classes
DIRECT_CLASS_MAPPING.put(UUID.class, UUID::randomUUID);
DIRECT_CLASS_MAPPING.put(Currency.class, () -> Currency.getInstance(Locale.getDefault()));
DIRECT_CLASS_MAPPING.put(Pattern.class, () -> Pattern.compile(".*"));
DIRECT_CLASS_MAPPING.put(BitSet.class, BitSet::new);
DIRECT_CLASS_MAPPING.put(StringJoiner.class, () -> new StringJoiner(","));
// Optional types
DIRECT_CLASS_MAPPING.put(Optional.class, Optional::empty);
DIRECT_CLASS_MAPPING.put(OptionalInt.class, OptionalInt::empty);
DIRECT_CLASS_MAPPING.put(OptionalLong.class, OptionalLong::empty);
DIRECT_CLASS_MAPPING.put(OptionalDouble.class, OptionalDouble::empty);
// Stream types
DIRECT_CLASS_MAPPING.put(Stream.class, Stream::empty);
DIRECT_CLASS_MAPPING.put(IntStream.class, IntStream::empty);
DIRECT_CLASS_MAPPING.put(LongStream.class, LongStream::empty);
DIRECT_CLASS_MAPPING.put(DoubleStream.class, DoubleStream::empty);
// Primitive arrays
DIRECT_CLASS_MAPPING.put(boolean[].class, () -> new boolean[0]);
DIRECT_CLASS_MAPPING.put(byte[].class, () -> new byte[0]);
DIRECT_CLASS_MAPPING.put(short[].class, () -> new short[0]);
DIRECT_CLASS_MAPPING.put(int[].class, () -> new int[0]);
DIRECT_CLASS_MAPPING.put(long[].class, () -> new long[0]);
DIRECT_CLASS_MAPPING.put(float[].class, () -> new float[0]);
DIRECT_CLASS_MAPPING.put(double[].class, () -> new double[0]);
DIRECT_CLASS_MAPPING.put(char[].class, () -> new char[0]);
DIRECT_CLASS_MAPPING.put(Object[].class, () -> ArrayUtilities.EMPTY_OBJECT_ARRAY);
// Boxed primitive arrays
DIRECT_CLASS_MAPPING.put(Boolean[].class, () -> new Boolean[0]);
DIRECT_CLASS_MAPPING.put(Byte[].class, () -> new Byte[0]);
DIRECT_CLASS_MAPPING.put(Short[].class, () -> new Short[0]);
DIRECT_CLASS_MAPPING.put(Integer[].class, () -> new Integer[0]);
DIRECT_CLASS_MAPPING.put(Long[].class, () -> new Long[0]);
DIRECT_CLASS_MAPPING.put(Float[].class, () -> new Float[0]);
DIRECT_CLASS_MAPPING.put(Double[].class, () -> new Double[0]);
DIRECT_CLASS_MAPPING.put(Character[].class, () -> new Character[0]);
// ASSIGNABLE_CLASS_MAPPING for interfaces and abstract classes
// Order from most specific to most general
ASSIGNABLE_CLASS_MAPPING.put(EnumSet.class, () -> null);
// Specific collection types
ASSIGNABLE_CLASS_MAPPING.put(BlockingDeque.class, LinkedBlockingDeque::new);
ASSIGNABLE_CLASS_MAPPING.put(Deque.class, ArrayDeque::new);
ASSIGNABLE_CLASS_MAPPING.put(BlockingQueue.class, LinkedBlockingQueue::new);
ASSIGNABLE_CLASS_MAPPING.put(Queue.class, LinkedList::new);
// Specific set types
ASSIGNABLE_CLASS_MAPPING.put(NavigableSet.class, TreeSet::new);
ASSIGNABLE_CLASS_MAPPING.put(SortedSet.class, TreeSet::new);
ASSIGNABLE_CLASS_MAPPING.put(Set.class, LinkedHashSet::new);
// Specific map types
ASSIGNABLE_CLASS_MAPPING.put(ConcurrentMap.class, ConcurrentHashMap::new);
ASSIGNABLE_CLASS_MAPPING.put(NavigableMap.class, TreeMap::new);
ASSIGNABLE_CLASS_MAPPING.put(SortedMap.class, TreeMap::new);
ASSIGNABLE_CLASS_MAPPING.put(Map.class, LinkedHashMap::new);
// List and more general collection types
ASSIGNABLE_CLASS_MAPPING.put(List.class, ArrayList::new);
ASSIGNABLE_CLASS_MAPPING.put(Collection.class, ArrayList::new);
// Iterators and enumerations
ASSIGNABLE_CLASS_MAPPING.put(ListIterator.class, () -> new ArrayList<>().listIterator());
ASSIGNABLE_CLASS_MAPPING.put(Iterator.class, Collections::emptyIterator);
ASSIGNABLE_CLASS_MAPPING.put(Enumeration.class, Collections::emptyEnumeration);
// Other interfaces
ASSIGNABLE_CLASS_MAPPING.put(RandomAccess.class, ArrayList::new);
ASSIGNABLE_CLASS_MAPPING.put(CharSequence.class, StringBuilder::new);
ASSIGNABLE_CLASS_MAPPING.put(Comparable.class, () -> ""); // String implements Comparable
ASSIGNABLE_CLASS_MAPPING.put(Cloneable.class, ArrayList::new); // ArrayList implements Cloneable
ASSIGNABLE_CLASS_MAPPING.put(AutoCloseable.class, () -> new ByteArrayInputStream(new byte[0]));
// Most general
ASSIGNABLE_CLASS_MAPPING.put(Iterable.class, ArrayList::new);
nameToClass.put("boolean", Boolean.TYPE);
nameToClass.put("char", Character.TYPE);
nameToClass.put("byte", Byte.TYPE);
nameToClass.put("short", Short.TYPE);
nameToClass.put("int", Integer.TYPE);
nameToClass.put("long", Long.TYPE);
nameToClass.put("float", Float.TYPE);
nameToClass.put("double", Double.TYPE);
nameToClass.put("string", String.class);
nameToClass.put("date", Date.class);
nameToClass.put("class", Class.class);
PRIMITIVE_TO_WRAPPER.put(int.class, Integer.class);
PRIMITIVE_TO_WRAPPER.put(long.class, Long.class);
PRIMITIVE_TO_WRAPPER.put(double.class, Double.class);
PRIMITIVE_TO_WRAPPER.put(float.class, Float.class);
PRIMITIVE_TO_WRAPPER.put(boolean.class, Boolean.class);
PRIMITIVE_TO_WRAPPER.put(char.class, Character.class);
PRIMITIVE_TO_WRAPPER.put(byte.class, Byte.class);
PRIMITIVE_TO_WRAPPER.put(short.class, Short.class);
PRIMITIVE_TO_WRAPPER.put(void.class, Void.class);
// Initialize wrapper mappings
WRAPPER_TO_PRIMITIVE.put(Boolean.class, boolean.class);
WRAPPER_TO_PRIMITIVE.put(Byte.class, byte.class);
WRAPPER_TO_PRIMITIVE.put(Character.class, char.class);
WRAPPER_TO_PRIMITIVE.put(Short.class, short.class);
WRAPPER_TO_PRIMITIVE.put(Integer.class, int.class);
WRAPPER_TO_PRIMITIVE.put(Long.class, long.class);
WRAPPER_TO_PRIMITIVE.put(Float.class, float.class);
WRAPPER_TO_PRIMITIVE.put(Double.class, double.class);
WRAPPER_TO_PRIMITIVE.put(Void.class, void.class);
Map<Class<?>, Class<?>> map = new ClassValueMap<>();
map.putAll(PRIMITIVE_TO_WRAPPER);
map.putAll(WRAPPER_TO_PRIMITIVE);
wrapperMap = Collections.unmodifiableMap(map);
}
/**
* Converts a wrapper class to its corresponding primitive type.
*
* @param toType The wrapper class to convert to its primitive equivalent.
* Must be one of the standard Java wrapper classes (e.g., Integer.class, Boolean.class).
* @return The primitive class corresponding to the provided wrapper class or null if toType is not a primitive wrapper.
* @throws IllegalArgumentException If toType is null
*/
public static Class<?> getPrimitiveFromWrapper(Class<?> toType) {
Convention.throwIfNull(toType, "toType cannot be null");
return WRAPPER_TO_PRIMITIVE.get(toType);
}
/**
* Container for class hierarchy information to avoid redundant calculations
* Not considered API. Do not use this class in your code.
*/
public static class ClassHierarchyInfo {
private final Set<Class<?>> allSupertypes;
private final Map<Class<?>, Integer> distanceMap;
private final int depth; // Store depth as a field
ClassHierarchyInfo(Set<Class<?>> supertypes, Map<Class<?>, Integer> distances, Class<?> sourceClass) {
this.allSupertypes = Collections.unmodifiableSet(supertypes);
this.distanceMap = Collections.unmodifiableMap(distances);
// Calculate the depth during construction
int maxDepth = 0;
Class<?> current = sourceClass;
while (current != null) {
current = current.getSuperclass();
maxDepth++;
}
this.depth = maxDepth - 1; // -1 because we counted steps, not classes
}
public Map<Class<?>, Integer> getDistanceMap() {
return distanceMap;
}
Set<Class<?>> getAllSupertypes() {
return allSupertypes;
}
int getDistance(Class<?> type) {
return distanceMap.getOrDefault(type, -1);
}
public int getDepth() {
return depth;
}
}
/**
* Registers a permanent alias name for a class to support Class.forName() lookups.
*
* @param clazz the class to alias
* @param alias the alternative name for the class
*/
public static void addPermanentClassAlias(Class<?> clazz, String alias) {
nameToClass.put(alias, clazz);
}
/**
* Removes a previously registered class alias.
*
* @param alias the alias name to remove
*/
public static void removePermanentClassAlias(String alias) {
nameToClass.remove(alias);
}
/**
* Computes the inheritance distance between two classes/interfaces/primitive types.
* Results are cached for performance.
*
* @param source The source class, interface, or primitive type.
* @param destination The destination class, interface, or primitive type.
* @return The number of steps from the source to the destination, or -1 if no path exists.
*/
public static int computeInheritanceDistance(Class<?> source, Class<?> destination) {
if (source == null || destination == null) {
return -1;
}
if (source.equals(destination)) {
return 0;
}
// Handle primitives specially
if (source.isPrimitive() || isPrimitive(source)) {
if (destination.isPrimitive() || isPrimitive(destination)) {
return areSamePrimitiveType(source, destination) ? 0 : -1;
}
}
// Use the cached hierarchy info for non-primitive cases
return getClassHierarchyInfo(source).getDistance(destination);
}
/**
* Determines if two primitive or wrapper types represent the same primitive type.
*
* @param source The source type to compare
* @param destination The destination type to compare
* @return true if both types represent the same primitive type, false otherwise
*/
private static boolean areSamePrimitiveType(Class<?> source, Class<?> destination) {
// If both are primitive, they must be exactly the same type
if (source.isPrimitive() && destination.isPrimitive()) {
return source.equals(destination);
}
// Get normalized primitive types (if they are wrappers, get the primitive equivalent)
Class<?> sourcePrimitive = source.isPrimitive() ? source : WRAPPER_TO_PRIMITIVE.get(source);
Class<?> destPrimitive = destination.isPrimitive() ? destination : WRAPPER_TO_PRIMITIVE.get(destination);
// If either conversion failed, they're not compatible
if (sourcePrimitive == null || destPrimitive == null) {
return false;
}
// Check if they represent the same primitive type (e.g., int.class and Integer.class)
return sourcePrimitive.equals(destPrimitive);
}
/**
* @param c Class to test
* @return boolean true if the passed in class is a Java primitive, false otherwise. The Wrapper classes
* Integer, Long, Boolean, etc. are considered primitives by this method.
*/
public static boolean isPrimitive(Class<?> c) {
return c.isPrimitive() || WRAPPER_TO_PRIMITIVE.containsKey(c);
}
/**
* Given the passed in String class name, return the named JVM class.
*
* @param name String name of a JVM class.
* @param classLoader ClassLoader to use when searching for JVM classes.
* @return Class instance of the named JVM class or null if not found.
*/
public static Class<?> forName(String name, ClassLoader classLoader) {
if (StringUtilities.isEmpty(name)) {
return null;
}
try {
return internalClassForName(name, classLoader);
} catch (SecurityException e) {
throw new IllegalArgumentException("Security exception, classForName() call on: " + name, e);
} catch (Exception e) {
return null;
}
}
/**
* Used internally to load a class by name, and takes care of caching name mappings for speed.
*
* @param name String name of a JVM class.
* @param classLoader ClassLoader to use when searching for JVM classes.
* @return Class instance of the named JVM class
*/
private static Class<?> internalClassForName(String name, ClassLoader classLoader) throws ClassNotFoundException {
Class<?> c = nameToClass.get(name);
if (c != null) {
return c;
}
// Check name before loading (quick rejection)
if (SecurityChecker.isSecurityBlockedName(name)) {
throw new SecurityException("For security reasons, cannot load: " + name);
}
c = loadClass(name, classLoader);
// Perform full security check on loaded class
SecurityChecker.verifyClass(c);
nameToClass.put(name, c);
return c;
}
/**
* loadClass() provided by: Thomas Margreiter
* <p>
* Loads a class using the specified ClassLoader, with recursive handling for array types
* and primitive arrays.
*
* @param name the fully qualified class name or array type descriptor
* @param classLoader the ClassLoader to use
* @return the loaded Class object
* @throws ClassNotFoundException if the class cannot be found
*/
private static Class<?> loadClass(String name, ClassLoader classLoader) throws ClassNotFoundException {
String className = name;
boolean arrayType = false;
Class<?> primitiveArray = null;
while (className.startsWith("[")) {
arrayType = true;
if (className.endsWith(";")) {
className = className.substring(0, className.length() - 1);
}
switch (className) {
case "[B":
primitiveArray = byte[].class;
break;
case "[S":
primitiveArray = short[].class;
break;
case "[I":
primitiveArray = int[].class;
break;
case "[J":
primitiveArray = long[].class;
break;
case "[F":
primitiveArray = float[].class;
break;
case "[D":
primitiveArray = double[].class;
break;
case "[Z":
primitiveArray = boolean[].class;
break;
case "[C":
primitiveArray = char[].class;
break;
}
int startpos = className.startsWith("[L") ? 2 : 1;
className = className.substring(startpos);
}
Class<?> currentClass = null;
if (null == primitiveArray) {
try {
currentClass = classLoader.loadClass(className);
} catch (ClassNotFoundException e) {
currentClass = Thread.currentThread().getContextClassLoader().loadClass(className);
}
}
if (arrayType) {
currentClass = (null != primitiveArray) ? primitiveArray : Array.newInstance(currentClass, 0).getClass();
while (name.startsWith("[[")) {
currentClass = Array.newInstance(currentClass, 0).getClass();
name = name.substring(1);
}
}
return currentClass;
}
/**
* Determines if a class is declared as final.
* <p>
* Checks if the class has the {@code final} modifier, indicating that it cannot be subclassed.
* </p>
*
* <p><strong>Example:</strong></p>
* <pre>{@code
* boolean isFinal = ClassUtilities.isClassFinal(String.class); // Returns true
* boolean notFinal = ClassUtilities.isClassFinal(ArrayList.class); // Returns false
* }</pre>
*
* @param c the class to check, must not be null
* @return true if the class is final, false otherwise
* @throws NullPointerException if the input class is null
*/
public static boolean isClassFinal(Class<?> c) {
return (c.getModifiers() & Modifier.FINAL) != 0;
}
/**
* Determines if all constructors in a class are declared as private.
* <p>
* This method is useful for identifying classes that enforce singleton patterns
* or utility classes that should not be instantiated.
* </p>
*
* <p><strong>Example:</strong></p>
* <pre>{@code
* // Utility class with private constructor
* public final class Utils {
* private Utils() {}
* }
*
* boolean isPrivate = ClassUtilities.areAllConstructorsPrivate(Utils.class); // Returns true
* boolean notPrivate = ClassUtilities.areAllConstructorsPrivate(String.class); // Returns false
* }</pre>
*
* @param c the class to check, must not be null
* @return true if all constructors in the class are private, false if any constructor is non-private
* @throws NullPointerException if the input class is null
*/
public static boolean areAllConstructorsPrivate(Class<?> c) {
Constructor<?>[] constructors = c.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
if ((constructor.getModifiers() & Modifier.PRIVATE) == 0) {
return false;
}
}
return true;
}
/**
* Converts a primitive class to its corresponding wrapper class.
* <p>
* If the input class is already a non-primitive type, it is returned unchanged.
* For primitive types, returns the corresponding wrapper class (e.g., {@code int.class} → {@code Integer.class}).
* </p>
*
* <p><strong>Examples:</strong></p>
* <pre>{@code
* Class<?> intWrapper = ClassUtilities.toPrimitiveWrapperClass(int.class); // Returns Integer.class
* Class<?> boolWrapper = ClassUtilities.toPrimitiveWrapperClass(boolean.class); // Returns Boolean.class
* Class<?> sameClass = ClassUtilities.toPrimitiveWrapperClass(String.class); // Returns String.class
* }</pre>
*
* <p><strong>Supported Primitive Types:</strong></p>
* <ul>
* <li>{@code boolean.class} → {@code Boolean.class}</li>
* <li>{@code byte.class} → {@code Byte.class}</li>
* <li>{@code char.class} → {@code Character.class}</li>
* <li>{@code double.class} → {@code Double.class}</li>
* <li>{@code float.class} → {@code Float.class}</li>
* <li>{@code int.class} → {@code Integer.class}</li>
* <li>{@code long.class} → {@code Long.class}</li>
* <li>{@code short.class} → {@code Short.class}</li>
* <li>{@code void.class} → {@code Void.class}</li>
* </ul>
*
* @param primitiveClass the class to convert, must not be null
* @return the wrapper class if the input is primitive, otherwise the input class itself
* @throws NullPointerException if the input class is null
* @throws IllegalArgumentException if the input class is not a recognized primitive type
*/
public static Class<?> toPrimitiveWrapperClass(Class<?> primitiveClass) {
if (!primitiveClass.isPrimitive()) {
return primitiveClass;
}
Class<?> c = PRIMITIVE_TO_WRAPPER.get(primitiveClass);
if (c == null) {
throw new IllegalArgumentException("Passed in class: " + primitiveClass + " is not a primitive class");
}
return c;
}
/**
* Determines if one class is the wrapper type of the other.
* <p>
* This method checks if there is a primitive-wrapper relationship between two classes.
* For example, {@code Integer.class} wraps {@code int.class} and vice versa.
* </p>
*
* <p><strong>Examples:</strong></p>
* <pre>{@code
* boolean wraps = ClassUtilities.doesOneWrapTheOther(Integer.class, int.class); // Returns true
* boolean wraps2 = ClassUtilities.doesOneWrapTheOther(int.class, Integer.class); // Returns true
* boolean noWrap = ClassUtilities.doesOneWrapTheOther(Integer.class, long.class); // Returns false
* }</pre>
*
* <p><strong>Supported Wrapper Pairs:</strong></p>
* <ul>
* <li>{@code Boolean.class} ↔ {@code boolean.class}</li>
* <li>{@code Byte.class} ↔ {@code byte.class}</li>
* <li>{@code Character.class} ↔ {@code char.class}</li>
* <li>{@code Double.class} ↔ {@code double.class}</li>
* <li>{@code Float.class} ↔ {@code float.class}</li>
* <li>{@code Integer.class} ↔ {@code int.class}</li>
* <li>{@code Long.class} ↔ {@code long.class}</li>
* <li>{@code Short.class} ↔ {@code short.class}</li>
* </ul>
*
* @param x first class to check
* @param y second class to check
* @return true if one class is the wrapper of the other, false otherwise
* @throws NullPointerException if either input class is null
*/
public static boolean doesOneWrapTheOther(Class<?> x, Class<?> y) {
return wrapperMap.get(x) == y;
}
/**
* Obtains the appropriate ClassLoader depending on whether the environment is OSGi, JPMS, or neither.
*
* @return the appropriate ClassLoader
*/
public static ClassLoader getClassLoader() {
return getClassLoader(ClassUtilities.class);
}
/**
* Obtains the appropriate ClassLoader depending on whether the environment is OSGi, JPMS, or neither.
*
* @param anchorClass the class to use as reference for loading
* @return the appropriate ClassLoader
*/
public static ClassLoader getClassLoader(final Class<?> anchorClass) {
if (anchorClass == null) {
throw new IllegalArgumentException("Anchor class cannot be null");
}
checkSecurityAccess();
// Try OSGi first
ClassLoader cl = getOSGiClassLoader(anchorClass);
if (cl != null) {
return cl;
}
// Try context class loader
cl = Thread.currentThread().getContextClassLoader();
if (cl != null) {
return cl;
}
// Try anchor class loader
cl = anchorClass.getClassLoader();
if (cl != null) {
return cl;
}
// Last resort
return SYSTEM_LOADER;
}
/**
* Checks if the current security manager allows class loader access.
*/
private static void checkSecurityAccess() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("getClassLoader"));
}
}
/**
* Attempts to retrieve the OSGi Bundle's ClassLoader.
*
* @param classFromBundle the class from which to get the bundle
* @return the OSGi Bundle's ClassLoader if in an OSGi environment; otherwise, null
*/
private static ClassLoader getOSGiClassLoader(final Class<?> classFromBundle) {
return osgiClassLoaders.computeIfAbsent(classFromBundle, ClassUtilities::getOSGiClassLoader0);
}
/**
* Internal method to retrieve the OSGi Bundle's ClassLoader using reflection.
*
* @param classFromBundle the class from which to get the bundle
* @return the OSGi Bundle's ClassLoader if in an OSGi environment; otherwise, null
*/
private static ClassLoader getOSGiClassLoader0(final Class<?> classFromBundle) {
try {
// Load the FrameworkUtil class from OSGi
Class<?> frameworkUtilClass = Class.forName("org.osgi.framework.FrameworkUtil");
// Get the getBundle(Class<?>) method
Method getBundleMethod = frameworkUtilClass.getMethod("getBundle", Class.class);
// Invoke FrameworkUtil.getBundle(classFromBundle) to get the Bundle instance
Object bundle = getBundleMethod.invoke(null, classFromBundle);
if (bundle != null) {
// Get BundleWiring class
Class<?> bundleWiringClass = Class.forName("org.osgi.framework.wiring.BundleWiring");
// Get the adapt(Class) method
Method adaptMethod = bundle.getClass().getMethod("adapt", Class.class);
// Invoke bundle.adapt(BundleWiring.class) to get the BundleWiring instance
Object bundleWiring = adaptMethod.invoke(bundle, bundleWiringClass);
if (bundleWiring != null) {
// Get the getClassLoader() method from BundleWiring
Method getClassLoaderMethod = bundleWiringClass.getMethod("getClassLoader");
// Invoke getClassLoader() to obtain the ClassLoader
Object classLoader = getClassLoaderMethod.invoke(bundleWiring);
if (classLoader instanceof ClassLoader) {
return (ClassLoader) classLoader;
}
}
}
} catch (Exception e) {
// OSGi environment not detected or an error occurred
// Silently ignore as this is expected in non-OSGi environments
}
return null;
}
/**
* Finds the closest matching class in an inheritance hierarchy from a map of candidate classes.
* <p>
* This method searches through a map of candidate classes to find the one that is most closely
* related to the input class in terms of inheritance distance. The search prioritizes:
* <ul>
* <li>Exact class match (returns immediately)</li>
* <li>Closest superclass/interface in the inheritance hierarchy</li>
* </ul>
* <p>
* This method is typically used for cache misses when looking up class-specific handlers
* or processors.
*
* @param <T> The type of value stored in the candidateClasses map
* @param clazz The class to find a match for (must not be null)
* @param candidateClasses Map of candidate classes and their associated values (must not be null)
* @param defaultClass Default value to return if no suitable match is found
* @return The value associated with the closest matching class, or defaultClass if no match found
* @throws NullPointerException if clazz or candidateClasses is null
*
* @see ClassUtilities#computeInheritanceDistance(Class, Class)
*/
public static <T> T findClosest(Class<?> clazz, Map<Class<?>, T> candidateClasses, T defaultClass) {
Convention.throwIfNull(clazz, "Source class cannot be null");
Convention.throwIfNull(candidateClasses, "Candidate classes Map cannot be null");
// First try exact match
T exactMatch = candidateClasses.get(clazz);
if (exactMatch != null) {
return exactMatch;
}
// If no exact match, then look for closest inheritance match
T closest = defaultClass;
int minDistance = Integer.MAX_VALUE;
Class<?> closestClass = null;
for (Map.Entry<Class<?>, T> entry : candidateClasses.entrySet()) {
Class<?> candidateClass = entry.getKey();
int distance = ClassUtilities.computeInheritanceDistance(clazz, candidateClass);
if (distance != -1 && (distance < minDistance ||
(distance == minDistance && shouldPreferNewCandidate(candidateClass, closestClass)))) {
minDistance = distance;
closest = entry.getValue();
closestClass = candidateClass;
}
}
return closest;
}
/**
* Determines if a new candidate class should be preferred over the current closest class when
* they have equal inheritance distances.
* <p>
* The selection logic follows these rules in order:
* <ol>
* <li>If there is no current class (null), the new candidate is preferred</li>
* <li>Classes are preferred over interfaces</li>
* <li>When both are classes or both are interfaces, the more specific type is preferred</li>
* </ol>
*
* @param newClass the candidate class being evaluated (must not be null)
* @param currentClass the current closest matching class (may be null)
* @return true if newClass should be preferred over currentClass, false otherwise
*/
private static boolean shouldPreferNewCandidate(Class<?> newClass, Class<?> currentClass) {
if (currentClass == null) return true;
// Prefer classes to interfaces
if (newClass.isInterface() != currentClass.isInterface()) {
return !newClass.isInterface();
}
// If both are classes or both are interfaces, prefer the more specific one
return newClass.isAssignableFrom(currentClass);
}
/**
* Loads resource content as a String.
* @param resourceName Name of the resource file.
* @return Content of the resource file as a String.
*/
public static String loadResourceAsString(String resourceName) {
byte[] resourceBytes = loadResourceAsBytes(resourceName);
return new String(resourceBytes, StandardCharsets.UTF_8);
}
/**
* Loads resource content as a byte[].
* @param resourceName Name of the resource file.
* @return Content of the resource file as a byte[].
* @throws IllegalArgumentException if the resource cannot be found
* @throws UncheckedIOException if there is an error reading the resource
* @throws NullPointerException if resourceName is null