1818
1919import java .util .ArrayDeque ;
2020import java .util .Collections ;
21+ import java .util .Deque ;
22+ import java .util .HashMap ;
2123import java .util .HashSet ;
2224import java .util .List ;
2325import java .util .Map ;
24- import java .util .Objects ;
2526import java .util .Queue ;
2627import java .util .Set ;
27- import java .util .stream .Collectors ;
28- import java .util .stream .Stream ;
2928import org .apache .logging .log4j .util .LoaderUtil ;
3029import org .apache .logging .log4j .util .StackLocatorUtil ;
3130
@@ -74,7 +73,7 @@ void renderStackTraceElement(
7473 context .classResourceInfoByName .get (stackTraceElement .getClassName ());
7574 if (classResourceInfo != null ) {
7675 buffer .append (' ' );
77- buffer . append ( classResourceInfo );
76+ classResourceInfo . render ( buffer );
7877 }
7978 buffer .append (lineSeparator );
8079 }
@@ -102,15 +101,15 @@ private static Map<String, ClassResourceInfo> createClassResourceInfoByName(
102101 final Throwable rootThrowable , final Map <Throwable , Metadata > metadataByThrowable ) {
103102
104103 // Stack trace elements of a `Throwable` only contain the class name.
105- // But we need the associated `Class<?> ` to extract its resource information, i.e., JAR file and version.
104+ // But we need the associated `Class` to extract its resource information, i.e., JAR file and version.
106105 // We are capturing the current stack to find suitable class loaders.
107- // We will use this as a bootstrap to go from a class name in a stack trace to a `Class<?> `.
108- final Map < String , ClassResourceInfo > classResourceInfoByName =
109- StackLocatorUtil . getCurrentStackTrace (). stream ()
110- . collect ( Collectors . toMap (
111- Class :: getName ,
112- clazz -> new ClassResourceInfo ( clazz , true ),
113- ( classResourceInfo1 , classResourceInfo2 ) -> classResourceInfo1 ) );
106+ // We will use this as a bootstrap to go from a class name in a stack trace to a `Class`.
107+ final Deque < Class <?>> executionStackTrace = StackLocatorUtil . getCurrentStackTrace ();
108+
109+ // Mapping a class name to a `ClassResourceInfo` is an expensive operation.
110+ // Next to `ClassResourceInfo` allocation, it requires extraction of the associated ` Class`.
111+ // We will use this lookup table to speed things up.
112+ final Map < String , ClassResourceInfo > classResourceInfoByName = new HashMap <>( );
114113
115114 // Walk over the causal chain
116115 final Set <Throwable > visitedThrowables = new HashSet <>();
@@ -130,64 +129,78 @@ private static Map<String, ClassResourceInfo> createClassResourceInfoByName(
130129 continue ;
131130 }
132131
132+ Class <?> executionStackTraceElementClass =
133+ executionStackTrace .isEmpty () ? null : executionStackTrace .peekLast ();
133134 ClassLoader lastLoader = null ;
134135 final StackTraceElement [] stackTraceElements = throwable .getStackTrace ();
135136 for (int throwableStackIndex = metadata .stackLength - 1 ;
136137 throwableStackIndex >= 0 ;
137138 --throwableStackIndex ) {
138139
139- // Skip if the current class name is either known, or already visited and is unknown
140- final StackTraceElement stackTraceElement = stackTraceElements [throwableStackIndex ];
141- final String stackTraceElementClassName = stackTraceElement .getClassName ();
142- ClassResourceInfo classResourceInfo = classResourceInfoByName .get (stackTraceElementClassName );
140+ // Get the exception's stack trace element
141+ final StackTraceElement throwableStackTraceElement = stackTraceElements [throwableStackIndex ];
142+ final String throwableStackTraceElementClassName = throwableStackTraceElement .getClassName ();
143+
144+ // Skip if the current class name is already registered
145+ ClassResourceInfo classResourceInfo =
146+ classResourceInfoByName .get (throwableStackTraceElementClassName );
143147 if (classResourceInfo != null ) {
144148 if (classResourceInfo .clazz != null ) {
145149 lastLoader = classResourceInfo .clazz .getClassLoader ();
146150 }
147- continue ;
148151 }
149152
150- // Try to determine the stack trace element class, and register the result to the lookup table
151- final Class <?> stackTraceElementClass = loadClass (lastLoader , stackTraceElementClassName );
152- classResourceInfo = stackTraceElementClass != null
153- ? new ClassResourceInfo (stackTraceElementClass , false )
154- : ClassResourceInfo .UNKNOWN ;
155- classResourceInfoByName .put (stackTraceElementClassName , classResourceInfo );
153+ // See if we get a match from the execution stack trace
154+ else if (executionStackTraceElementClass != null
155+ && throwableStackTraceElementClassName .equals (executionStackTraceElementClass .getName ())) {
156+ classResourceInfo = new ClassResourceInfo (executionStackTraceElementClass , true );
157+ classResourceInfoByName .put (throwableStackTraceElementClassName , classResourceInfo );
158+ lastLoader = classResourceInfo .clazz .getClassLoader ();
159+ executionStackTrace .pollLast ();
160+ executionStackTraceElementClass = executionStackTrace .peekLast ();
161+ }
162+
163+ // We don't know this class name, try to load it using the last found loader
164+ else {
165+ final Class <?> stackTraceElementClass =
166+ loadClass (lastLoader , throwableStackTraceElementClassName );
167+ classResourceInfo = stackTraceElementClass != null
168+ ? new ClassResourceInfo (stackTraceElementClass , false )
169+ : ClassResourceInfo .UNKNOWN ;
170+ classResourceInfoByName .put (throwableStackTraceElementClassName , classResourceInfo );
171+ }
156172 }
157173 }
158174 return classResourceInfoByName ;
159175 }
160176
161- @ FunctionalInterface
162- private interface ThrowingSupplier <V > {
163-
164- V supply () throws Exception ;
165- }
166-
167177 private static Class <?> loadClass (final ClassLoader loader , final String className ) {
168- return Stream .<ThrowingSupplier <Class <?>>>of (
169- // 1. Try the passed class loader
170- () -> loader != null ? loader .loadClass (className ) : null ,
171- // 2. Try the `LoaderUtil` magic
172- () -> LoaderUtil .loadClass (className ),
173- // 3. Try the current class loader
174- () -> ThrowableExtendedStackTraceRenderer .class
175- .getClassLoader ()
176- .loadClass (className ))
177- .map (provider -> {
178- try {
179- final Class <?> clazz = provider .supply ();
180- if (clazz != null ) {
181- return clazz ;
182- }
183- } catch (final Exception ignored ) {
184- // Do nothing
185- }
186- return null ;
187- })
188- .filter (Objects ::nonNull )
189- .findFirst ()
190- .orElse (null );
178+ for (final ClassLoadingStrategy strategy : CLASS_LOADING_STRATEGIES ) {
179+ try {
180+ final Class <?> clazz = strategy .run (loader , className );
181+ if (clazz != null ) {
182+ return clazz ;
183+ }
184+ } catch (final Exception ignored ) {
185+ // Do nothing
186+ }
187+ }
188+ return null ;
191189 }
192190 }
191+
192+ private static final ClassLoadingStrategy [] CLASS_LOADING_STRATEGIES = {
193+ // 1. Try the passed class loader
194+ (loader , className ) -> loader != null ? loader .loadClass (className ) : null ,
195+ // 2. Try the `LoaderUtil` magic
196+ (loader , className ) -> LoaderUtil .loadClass (className ),
197+ // 3. Try the current class loader
198+ (loader , className ) ->
199+ ThrowableExtendedStackTraceRenderer .class .getClassLoader ().loadClass (className )
200+ };
201+
202+ private interface ClassLoadingStrategy {
203+
204+ Class <?> run (final ClassLoader loader , final String className ) throws Exception ;
205+ }
193206}
0 commit comments