001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.impl; 018 019import java.io.Serializable; 020import java.net.URL; 021import java.security.CodeSource; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029import java.util.Stack; 030 031import org.apache.logging.log4j.core.pattern.PlainTextRenderer; 032import org.apache.logging.log4j.core.pattern.TextRenderer; 033import org.apache.logging.log4j.core.util.Loader; 034import org.apache.logging.log4j.status.StatusLogger; 035import org.apache.logging.log4j.util.LoaderUtil; 036import org.apache.logging.log4j.util.StackLocatorUtil; 037import org.apache.logging.log4j.util.Strings; 038 039/** 040 * Wraps a Throwable to add packaging information about each stack trace element. 041 * 042 * <p> 043 * A proxy is used to represent a throwable that may not exist in a different class loader or JVM. When an application 044 * deserializes a ThrowableProxy, the throwable may not be set, but the throwable's information is preserved in other 045 * fields of the proxy like the message and stack trace. 046 * </p> 047 * 048 * <p> 049 * TODO: Move this class to org.apache.logging.log4j.core because it is used from LogEvent. 050 * </p> 051 * <p> 052 * TODO: Deserialize: Try to rebuild Throwable if the target exception is in this class loader? 053 * </p> 054 */ 055public class ThrowableProxy implements Serializable { 056 057 private static final String TAB = "\t"; 058 private static final String CAUSED_BY_LABEL = "Caused by: "; 059 private static final String SUPPRESSED_LABEL = "Suppressed: "; 060 private static final String WRAPPED_BY_LABEL = "Wrapped by: "; 061 062 /** 063 * Cached StackTracePackageElement and ClassLoader. 064 * <p> 065 * Consider this class private. 066 * </p> 067 */ 068 static class CacheEntry { 069 private final ExtendedClassInfo element; 070 private final ClassLoader loader; 071 072 public CacheEntry(final ExtendedClassInfo element, final ClassLoader loader) { 073 this.element = element; 074 this.loader = loader; 075 } 076 } 077 078 private static final ThrowableProxy[] EMPTY_THROWABLE_PROXY_ARRAY = new ThrowableProxy[0]; 079 080 private static final char EOL = '\n'; 081 082 private static final String EOL_STR = String.valueOf(EOL); 083 084 private static final long serialVersionUID = -2752771578252251910L; 085 086 private final ThrowableProxy causeProxy; 087 088 private int commonElementCount; 089 090 private final ExtendedStackTraceElement[] extendedStackTrace; 091 092 private final String localizedMessage; 093 094 private final String message; 095 096 private final String name; 097 098 private final ThrowableProxy[] suppressedProxies; 099 100 private final transient Throwable throwable; 101 102 /** 103 * For JSON and XML IO via Jackson. 104 */ 105 @SuppressWarnings("unused") 106 private ThrowableProxy() { 107 this.throwable = null; 108 this.name = null; 109 this.extendedStackTrace = null; 110 this.causeProxy = null; 111 this.message = null; 112 this.localizedMessage = null; 113 this.suppressedProxies = EMPTY_THROWABLE_PROXY_ARRAY; 114 } 115 116 /** 117 * Constructs the wrapper for the Throwable that includes packaging data. 118 * 119 * @param throwable The Throwable to wrap, must not be null. 120 */ 121 public ThrowableProxy(final Throwable throwable) { 122 this(throwable, null); 123 } 124 125 /** 126 * Constructs the wrapper for the Throwable that includes packaging data. 127 * 128 * @param throwable The Throwable to wrap, must not be null. 129 * @param visited The set of visited suppressed exceptions. 130 */ 131 private ThrowableProxy(final Throwable throwable, final Set<Throwable> visited) { 132 this.throwable = throwable; 133 this.name = throwable.getClass().getName(); 134 this.message = throwable.getMessage(); 135 this.localizedMessage = throwable.getLocalizedMessage(); 136 final Map<String, CacheEntry> map = new HashMap<>(); 137 final Stack<Class<?>> stack = StackLocatorUtil.getCurrentStackTrace(); 138 this.extendedStackTrace = this.toExtendedStackTrace(stack, map, null, throwable.getStackTrace()); 139 final Throwable throwableCause = throwable.getCause(); 140 final Set<Throwable> causeVisited = new HashSet<>(1); 141 this.causeProxy = throwableCause == null ? null : new ThrowableProxy(throwable, stack, map, throwableCause, 142 visited, causeVisited); 143 this.suppressedProxies = this.toSuppressedProxies(throwable, visited); 144 } 145 146 /** 147 * Constructs the wrapper for a Throwable that is referenced as the cause by another Throwable. 148 * 149 * @param parent The Throwable referencing this Throwable. 150 * @param stack The Class stack. 151 * @param map The cache containing the packaging data. 152 * @param cause The Throwable to wrap. 153 * @param suppressedVisited TODO 154 * @param causeVisited TODO 155 */ 156 private ThrowableProxy(final Throwable parent, final Stack<Class<?>> stack, final Map<String, CacheEntry> map, 157 final Throwable cause, final Set<Throwable> suppressedVisited, 158 final Set<Throwable> causeVisited) { 159 causeVisited.add(cause); 160 this.throwable = cause; 161 this.name = cause.getClass().getName(); 162 this.message = this.throwable.getMessage(); 163 this.localizedMessage = this.throwable.getLocalizedMessage(); 164 this.extendedStackTrace = this.toExtendedStackTrace(stack, map, parent.getStackTrace(), cause.getStackTrace()); 165 final Throwable causeCause = cause.getCause(); 166 this.causeProxy = causeCause == null || causeVisited.contains(causeCause) ? null : new ThrowableProxy(parent, 167 stack, map, causeCause, suppressedVisited, causeVisited); 168 this.suppressedProxies = this.toSuppressedProxies(cause, suppressedVisited); 169 } 170 171 @Override 172 public boolean equals(final Object obj) { 173 if (this == obj) { 174 return true; 175 } 176 if (obj == null) { 177 return false; 178 } 179 if (this.getClass() != obj.getClass()) { 180 return false; 181 } 182 final ThrowableProxy other = (ThrowableProxy) obj; 183 if (this.causeProxy == null) { 184 if (other.causeProxy != null) { 185 return false; 186 } 187 } else if (!this.causeProxy.equals(other.causeProxy)) { 188 return false; 189 } 190 if (this.commonElementCount != other.commonElementCount) { 191 return false; 192 } 193 if (this.name == null) { 194 if (other.name != null) { 195 return false; 196 } 197 } else if (!this.name.equals(other.name)) { 198 return false; 199 } 200 if (!Arrays.equals(this.extendedStackTrace, other.extendedStackTrace)) { 201 return false; 202 } 203 if (!Arrays.equals(this.suppressedProxies, other.suppressedProxies)) { 204 return false; 205 } 206 return true; 207 } 208 209 private void formatCause(final StringBuilder sb, final String prefix, final ThrowableProxy cause, 210 final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) { 211 formatThrowableProxy(sb, prefix, CAUSED_BY_LABEL, cause, ignorePackages, textRenderer, suffix, lineSeparator); 212 } 213 214 private void formatThrowableProxy(final StringBuilder sb, final String prefix, final String causeLabel, 215 final ThrowableProxy throwableProxy, final List<String> ignorePackages, 216 final TextRenderer textRenderer, final String suffix, String lineSeparator) { 217 if (throwableProxy == null) { 218 return; 219 } 220 textRenderer.render(prefix, sb, "Prefix"); 221 textRenderer.render(causeLabel, sb, "CauseLabel"); 222 throwableProxy.renderOn(sb, textRenderer); 223 renderSuffix(suffix, sb, textRenderer); 224 textRenderer.render(lineSeparator, sb, "Text"); 225 this.formatElements(sb, prefix, throwableProxy.commonElementCount, 226 throwableProxy.getStackTrace(), throwableProxy.extendedStackTrace, ignorePackages, textRenderer, suffix, lineSeparator); 227 this.formatSuppressed(sb, prefix + TAB, throwableProxy.suppressedProxies, ignorePackages, textRenderer, suffix, lineSeparator); 228 this.formatCause(sb, prefix, throwableProxy.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator); 229 } 230 231 void renderOn(final StringBuilder output, final TextRenderer textRenderer) { 232 final String msg = this.message; 233 textRenderer.render(this.name, output, "Name"); 234 if (msg != null) { 235 textRenderer.render(": ", output, "NameMessageSeparator"); 236 textRenderer.render(msg, output, "Message"); 237 } 238 } 239 240 private void formatSuppressed(final StringBuilder sb, final String prefix, final ThrowableProxy[] suppressedProxies, 241 final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) { 242 if (suppressedProxies == null) { 243 return; 244 } 245 for (final ThrowableProxy suppressedProxy : suppressedProxies) { 246 formatThrowableProxy(sb, prefix, SUPPRESSED_LABEL, suppressedProxy, ignorePackages, textRenderer, suffix, lineSeparator); 247 } 248 } 249 250 private void formatElements(final StringBuilder sb, final String prefix, final int commonCount, 251 final StackTraceElement[] causedTrace, final ExtendedStackTraceElement[] extStackTrace, 252 final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) { 253 if (ignorePackages == null || ignorePackages.isEmpty()) { 254 for (final ExtendedStackTraceElement element : extStackTrace) { 255 this.formatEntry(element, sb, prefix, textRenderer, suffix, lineSeparator); 256 } 257 } else { 258 int count = 0; 259 for (int i = 0; i < extStackTrace.length; ++i) { 260 if (!this.ignoreElement(causedTrace[i], ignorePackages)) { 261 if (count > 0) { 262 appendSuppressedCount(sb, prefix, count, textRenderer, suffix, lineSeparator); 263 count = 0; 264 } 265 this.formatEntry(extStackTrace[i], sb, prefix, textRenderer, suffix, lineSeparator); 266 } else { 267 ++count; 268 } 269 } 270 if (count > 0) { 271 appendSuppressedCount(sb, prefix, count, textRenderer, suffix, lineSeparator); 272 } 273 } 274 if (commonCount != 0) { 275 textRenderer.render(prefix, sb, "Prefix"); 276 textRenderer.render("\t... ", sb, "More"); 277 textRenderer.render(Integer.toString(commonCount), sb, "More"); 278 textRenderer.render(" more", sb, "More"); 279 renderSuffix(suffix, sb, textRenderer); 280 textRenderer.render(lineSeparator, sb, "Text"); 281 } 282 } 283 284 private void renderSuffix(final String suffix, final StringBuilder sb, final TextRenderer textRenderer) { 285 if (!suffix.isEmpty()) { 286 textRenderer.render(" ", sb, "Suffix"); 287 textRenderer.render(suffix, sb, "Suffix"); 288 } 289 } 290 291 private void appendSuppressedCount(final StringBuilder sb, final String prefix, final int count, 292 final TextRenderer textRenderer, final String suffix, String lineSeparator) { 293 textRenderer.render(prefix, sb, "Prefix"); 294 if (count == 1) { 295 textRenderer.render("\t... ", sb, "Suppressed"); 296 } else { 297 textRenderer.render("\t... suppressed ", sb, "Suppressed"); 298 textRenderer.render(Integer.toString(count), sb, "Suppressed"); 299 textRenderer.render(" lines", sb, "Suppressed"); 300 } 301 renderSuffix(suffix, sb, textRenderer); 302 textRenderer.render(lineSeparator, sb, "Text"); 303 } 304 305 private void formatEntry(final ExtendedStackTraceElement extStackTraceElement, final StringBuilder sb, 306 final String prefix, final TextRenderer textRenderer, final String suffix, String lineSeparator) { 307 textRenderer.render(prefix, sb, "Prefix"); 308 textRenderer.render("\tat ", sb, "At"); 309 extStackTraceElement.renderOn(sb, textRenderer); 310 renderSuffix(suffix, sb, textRenderer); 311 textRenderer.render(lineSeparator, sb, "Text"); 312 } 313 314 /** 315 * Formats the specified Throwable. 316 * @param sb StringBuilder to contain the formatted Throwable. 317 * @param cause The Throwable to format. 318 * @param suffix 319 */ 320 public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final String suffix) { 321 this.formatWrapper(sb, cause, null, PlainTextRenderer.getInstance(), suffix); 322 } 323 324 /** 325 * Formats the specified Throwable. 326 * @param sb StringBuilder to contain the formatted Throwable. 327 * @param cause The Throwable to format. 328 * @param ignorePackages The List of packages to be suppressed from the trace. 329 * @param suffix 330 */ 331 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") 332 public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages, final String suffix) { 333 this.formatWrapper(sb, cause, ignorePackages, PlainTextRenderer.getInstance(), suffix); 334 } 335 336 /** 337 * Formats the specified Throwable. 338 * @param sb StringBuilder to contain the formatted Throwable. 339 * @param cause The Throwable to format. 340 * @param ignorePackages The List of packages to be suppressed from the stack trace. 341 * @param textRenderer The text renderer. 342 * @param suffix Append this to the end of each stack frame. 343 */ 344 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") 345 public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages, 346 final TextRenderer textRenderer, final String suffix) { 347 formatWrapper(sb, cause, ignorePackages, textRenderer, suffix, EOL_STR); 348 } 349 350 /** 351 * Formats the specified Throwable. 352 * @param sb StringBuilder to contain the formatted Throwable. 353 * @param cause The Throwable to format. 354 * @param ignorePackages The List of packages to be suppressed from the stack trace. 355 * @param textRenderer The text renderer. 356 * @param suffix Append this to the end of each stack frame. 357 * @param lineSeparator The end-of-line separator. 358 */ 359 @SuppressWarnings("ThrowableResultOfMethodCallIgnored") 360 public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages, 361 final TextRenderer textRenderer, final String suffix, final String lineSeparator) { 362 final Throwable caused = cause.getCauseProxy() != null ? cause.getCauseProxy().getThrowable() : null; 363 if (caused != null) { 364 this.formatWrapper(sb, cause.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator); 365 sb.append(WRAPPED_BY_LABEL); 366 renderSuffix(suffix, sb, textRenderer); 367 } 368 cause.renderOn(sb, textRenderer); 369 renderSuffix(suffix, sb, textRenderer); 370 textRenderer.render(lineSeparator, sb, "Text"); 371 this.formatElements(sb, Strings.EMPTY, cause.commonElementCount, 372 cause.getThrowable().getStackTrace(), cause.extendedStackTrace, ignorePackages, textRenderer, suffix, lineSeparator); 373 } 374 375 public ThrowableProxy getCauseProxy() { 376 return this.causeProxy; 377 } 378 379 /** 380 * Formats the Throwable that is the cause of this Throwable. 381 * 382 * @return The formatted Throwable that caused this Throwable. 383 * @param suffix 384 */ 385 public String getCauseStackTraceAsString(final String suffix) { 386 return this.getCauseStackTraceAsString(null, PlainTextRenderer.getInstance(), suffix, EOL_STR); 387 } 388 389 /** 390 * Formats the Throwable that is the cause of this Throwable. 391 * 392 * @param packages The List of packages to be suppressed from the trace. 393 * @param suffix Append this to the end of each stack frame. 394 * @return The formatted Throwable that caused this Throwable. 395 */ 396 public String getCauseStackTraceAsString(final List<String> packages, final String suffix) { 397 return getCauseStackTraceAsString(packages, PlainTextRenderer.getInstance(), suffix, EOL_STR); 398 } 399 400 /** 401 * Formats the Throwable that is the cause of this Throwable. 402 * 403 * @param ignorePackages The List of packages to be suppressed from the trace. 404 * @param textRenderer The text renderer. 405 * @param suffix Append this to the end of each stack frame. 406 * @return The formatted Throwable that caused this Throwable. 407 */ 408 public String getCauseStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix) { 409 return getCauseStackTraceAsString(ignorePackages, textRenderer, suffix, EOL_STR); 410 } 411 412 /** 413 * Formats the Throwable that is the cause of this Throwable. 414 * 415 * @param ignorePackages The List of packages to be suppressed from the stack trace. 416 * @param textRenderer The text renderer. 417 * @param suffix Append this to the end of each stack frame. 418 * @param lineSeparator The end-of-line separator. 419 * @return The formatted Throwable that caused this Throwable. 420 */ 421 public String getCauseStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) { 422 final StringBuilder sb = new StringBuilder(); 423 if (this.causeProxy != null) { 424 this.formatWrapper(sb, this.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator); 425 sb.append(WRAPPED_BY_LABEL); 426 renderSuffix(suffix, sb, textRenderer); 427 } 428 this.renderOn(sb, textRenderer); 429 renderSuffix(suffix, sb, textRenderer); 430 textRenderer.render(lineSeparator, sb, "Text"); 431 this.formatElements(sb, Strings.EMPTY, 0, this.throwable.getStackTrace(), this.extendedStackTrace, 432 ignorePackages, textRenderer, suffix, lineSeparator); 433 return sb.toString(); 434 } 435 436 /** 437 * Returns the number of elements that are being omitted because they are common with the parent Throwable's stack 438 * trace. 439 * 440 * @return The number of elements omitted from the stack trace. 441 */ 442 public int getCommonElementCount() { 443 return this.commonElementCount; 444 } 445 446 /** 447 * Gets the stack trace including packaging information. 448 * 449 * @return The stack trace including packaging information. 450 */ 451 public ExtendedStackTraceElement[] getExtendedStackTrace() { 452 return this.extendedStackTrace; 453 } 454 455 /** 456 * Formats the stack trace including packaging information. 457 * 458 * @return The formatted stack trace including packaging information. 459 */ 460 public String getExtendedStackTraceAsString() { 461 return this.getExtendedStackTraceAsString(null, PlainTextRenderer.getInstance(), Strings.EMPTY, EOL_STR); 462 } 463 464 /** 465 * Formats the stack trace including packaging information. 466 * 467 * @return The formatted stack trace including packaging information. 468 * @param suffix Append this to the end of each stack frame. 469 */ 470 public String getExtendedStackTraceAsString(final String suffix) { 471 return this.getExtendedStackTraceAsString(null, PlainTextRenderer.getInstance(), suffix, EOL_STR); 472 } 473 474 /** 475 * Formats the stack trace including packaging information. 476 * 477 * @param ignorePackages List of packages to be ignored in the trace. 478 * @param suffix Append this to the end of each stack frame. 479 * @return The formatted stack trace including packaging information. 480 */ 481 public String getExtendedStackTraceAsString(final List<String> ignorePackages, final String suffix) { 482 return getExtendedStackTraceAsString(ignorePackages, PlainTextRenderer.getInstance(), suffix, EOL_STR); 483 } 484 485 /** 486 * Formats the stack trace including packaging information. 487 * 488 * @param ignorePackages List of packages to be ignored in the trace. 489 * @param textRenderer The message renderer. 490 * @param suffix Append this to the end of each stack frame. 491 * @return The formatted stack trace including packaging information. 492 */ 493 public String getExtendedStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix) { 494 return getExtendedStackTraceAsString(ignorePackages, textRenderer, suffix, EOL_STR); 495 } 496 497 /** 498 * Formats the stack trace including packaging information. 499 * 500 * @param ignorePackages List of packages to be ignored in the trace. 501 * @param textRenderer The message renderer. 502 * @param suffix Append this to the end of each stack frame. 503 * @param lineSeparator The end-of-line separator. 504 * @return The formatted stack trace including packaging information. 505 */ 506 public String getExtendedStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) { 507 final StringBuilder sb = new StringBuilder(1024); 508 textRenderer.render(name, sb, "Name"); 509 textRenderer.render(": ", sb, "NameMessageSeparator"); 510 textRenderer.render(this.message, sb, "Message"); 511 renderSuffix(suffix, sb, textRenderer); 512 textRenderer.render(lineSeparator, sb, "Text"); 513 final StackTraceElement[] causedTrace = this.throwable != null ? this.throwable.getStackTrace() : null; 514 this.formatElements(sb, Strings.EMPTY, 0, causedTrace, this.extendedStackTrace, ignorePackages, textRenderer, suffix, lineSeparator); 515 this.formatSuppressed(sb, TAB, this.suppressedProxies, ignorePackages, textRenderer, suffix, lineSeparator); 516 this.formatCause(sb, Strings.EMPTY, this.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator); 517 return sb.toString(); 518 } 519 520 public String getLocalizedMessage() { 521 return this.localizedMessage; 522 } 523 524 public String getMessage() { 525 return this.message; 526 } 527 528 /** 529 * Return the FQCN of the Throwable. 530 * 531 * @return The FQCN of the Throwable. 532 */ 533 public String getName() { 534 return this.name; 535 } 536 537 public StackTraceElement[] getStackTrace() { 538 return this.throwable == null ? null : this.throwable.getStackTrace(); 539 } 540 541 /** 542 * Gets proxies for suppressed exceptions. 543 * 544 * @return proxies for suppressed exceptions. 545 */ 546 public ThrowableProxy[] getSuppressedProxies() { 547 return this.suppressedProxies; 548 } 549 550 /** 551 * Formats the suppressed Throwables. 552 * 553 * @return The formatted suppressed Throwables. 554 * @param suffix 555 */ 556 public String getSuppressedStackTrace(final String suffix) { 557 final ThrowableProxy[] suppressed = this.getSuppressedProxies(); 558 if (suppressed == null || suppressed.length == 0) { 559 return Strings.EMPTY; 560 } 561 final StringBuilder sb = new StringBuilder("Suppressed Stack Trace Elements:").append(EOL); 562 for (final ThrowableProxy proxy : suppressed) { 563 sb.append(proxy.getExtendedStackTraceAsString(suffix)); 564 } 565 return sb.toString(); 566 } 567 568 /** 569 * The throwable or null if this object is deserialized from XML or JSON. 570 * 571 * @return The throwable or null if this object is deserialized from XML or JSON. 572 */ 573 public Throwable getThrowable() { 574 return this.throwable; 575 } 576 577 @Override 578 public int hashCode() { 579 final int prime = 31; 580 int result = 1; 581 result = prime * result + (this.causeProxy == null ? 0 : this.causeProxy.hashCode()); 582 result = prime * result + this.commonElementCount; 583 result = prime * result + (this.extendedStackTrace == null ? 0 : Arrays.hashCode(this.extendedStackTrace)); 584 result = prime * result + (this.suppressedProxies == null ? 0 : Arrays.hashCode(this.suppressedProxies)); 585 result = prime * result + (this.name == null ? 0 : this.name.hashCode()); 586 return result; 587 } 588 589 private boolean ignoreElement(final StackTraceElement element, final List<String> ignorePackages) { 590 if (ignorePackages != null) { 591 final String className = element.getClassName(); 592 for (final String pkg : ignorePackages) { 593 if (className.startsWith(pkg)) { 594 return true; 595 } 596 } 597 } 598 return false; 599 } 600 601 /** 602 * Loads classes not located via Reflection.getCallerClass. 603 * 604 * @param lastLoader The ClassLoader that loaded the Class that called this Class. 605 * @param className The name of the Class. 606 * @return The Class object for the Class or null if it could not be located. 607 */ 608 private Class<?> loadClass(final ClassLoader lastLoader, final String className) { 609 // XXX: this is overly complicated 610 Class<?> clazz; 611 if (lastLoader != null) { 612 try { 613 clazz = lastLoader.loadClass(className); 614 if (clazz != null) { 615 return clazz; 616 } 617 } catch (final Throwable ignore) { 618 // Ignore exception. 619 } 620 } 621 try { 622 clazz = LoaderUtil.loadClass(className); 623 } catch (final ClassNotFoundException | NoClassDefFoundError e) { 624 return loadClass(className); 625 } catch (final SecurityException e) { 626 return null; 627 } 628 return clazz; 629 } 630 631 private Class<?> loadClass(final String className) { 632 try { 633 return Loader.loadClass(className, this.getClass().getClassLoader()); 634 } catch (final ClassNotFoundException | NoClassDefFoundError | SecurityException e) { 635 return null; 636 } 637 } 638 639 /** 640 * Construct the CacheEntry from the Class's information. 641 * 642 * @param stackTraceElement The stack trace element 643 * @param callerClass The Class. 644 * @param exact True if the class was obtained via Reflection.getCallerClass. 645 * @return The CacheEntry. 646 */ 647 private CacheEntry toCacheEntry(final StackTraceElement stackTraceElement, final Class<?> callerClass, 648 final boolean exact) { 649 String location = "?"; 650 String version = "?"; 651 ClassLoader lastLoader = null; 652 if (callerClass != null) { 653 try { 654 final CodeSource source = callerClass.getProtectionDomain().getCodeSource(); 655 if (source != null) { 656 final URL locationURL = source.getLocation(); 657 if (locationURL != null) { 658 final String str = locationURL.toString().replace('\\', '/'); 659 int index = str.lastIndexOf("/"); 660 if (index >= 0 && index == str.length() - 1) { 661 index = str.lastIndexOf("/", index - 1); 662 location = str.substring(index + 1); 663 } else { 664 location = str.substring(index + 1); 665 } 666 } 667 } 668 } catch (final Exception ex) { 669 // Ignore the exception. 670 } 671 final Package pkg = callerClass.getPackage(); 672 if (pkg != null) { 673 final String ver = pkg.getImplementationVersion(); 674 if (ver != null) { 675 version = ver; 676 } 677 } 678 try { 679 lastLoader = callerClass.getClassLoader(); 680 } catch (final SecurityException e) { 681 lastLoader = null; 682 } 683 } 684 return new CacheEntry(new ExtendedClassInfo(exact, location, version), lastLoader); 685 } 686 687 /** 688 * Resolve all the stack entries in this stack trace that are not common with the parent. 689 * 690 * @param stack The callers Class stack. 691 * @param map The cache of CacheEntry objects. 692 * @param rootTrace The first stack trace resolve or null. 693 * @param stackTrace The stack trace being resolved. 694 * @return The StackTracePackageElement array. 695 */ 696 ExtendedStackTraceElement[] toExtendedStackTrace(final Stack<Class<?>> stack, final Map<String, CacheEntry> map, 697 final StackTraceElement[] rootTrace, 698 final StackTraceElement[] stackTrace) { 699 int stackLength; 700 if (rootTrace != null) { 701 int rootIndex = rootTrace.length - 1; 702 int stackIndex = stackTrace.length - 1; 703 while (rootIndex >= 0 && stackIndex >= 0 && rootTrace[rootIndex].equals(stackTrace[stackIndex])) { 704 --rootIndex; 705 --stackIndex; 706 } 707 this.commonElementCount = stackTrace.length - 1 - stackIndex; 708 stackLength = stackIndex + 1; 709 } else { 710 this.commonElementCount = 0; 711 stackLength = stackTrace.length; 712 } 713 final ExtendedStackTraceElement[] extStackTrace = new ExtendedStackTraceElement[stackLength]; 714 Class<?> clazz = stack.isEmpty() ? null : stack.peek(); 715 ClassLoader lastLoader = null; 716 for (int i = stackLength - 1; i >= 0; --i) { 717 final StackTraceElement stackTraceElement = stackTrace[i]; 718 final String className = stackTraceElement.getClassName(); 719 // The stack returned from getCurrentStack may be missing entries for java.lang.reflect.Method.invoke() 720 // and its implementation. The Throwable might also contain stack entries that are no longer 721 // present as those methods have returned. 722 ExtendedClassInfo extClassInfo; 723 if (clazz != null && className.equals(clazz.getName())) { 724 final CacheEntry entry = this.toCacheEntry(stackTraceElement, clazz, true); 725 extClassInfo = entry.element; 726 lastLoader = entry.loader; 727 stack.pop(); 728 clazz = stack.isEmpty() ? null : stack.peek(); 729 } else { 730 final CacheEntry cacheEntry = map.get(className); 731 if (cacheEntry != null) { 732 final CacheEntry entry = cacheEntry; 733 extClassInfo = entry.element; 734 if (entry.loader != null) { 735 lastLoader = entry.loader; 736 } 737 } else { 738 final CacheEntry entry = this.toCacheEntry(stackTraceElement, 739 this.loadClass(lastLoader, className), false); 740 extClassInfo = entry.element; 741 map.put(stackTraceElement.toString(), entry); 742 if (entry.loader != null) { 743 lastLoader = entry.loader; 744 } 745 } 746 } 747 extStackTrace[i] = new ExtendedStackTraceElement(stackTraceElement, extClassInfo); 748 } 749 return extStackTrace; 750 } 751 752 @Override 753 public String toString() { 754 final String msg = this.message; 755 return msg != null ? this.name + ": " + msg : this.name; 756 } 757 758 private ThrowableProxy[] toSuppressedProxies(final Throwable thrown, Set<Throwable> suppressedVisited) { 759 try { 760 final Throwable[] suppressed = thrown.getSuppressed(); 761 if (suppressed == null) { 762 return EMPTY_THROWABLE_PROXY_ARRAY; 763 } 764 final List<ThrowableProxy> proxies = new ArrayList<>(suppressed.length); 765 if (suppressedVisited == null) { 766 suppressedVisited = new HashSet<>(proxies.size()); 767 } 768 for (int i = 0; i < suppressed.length; i++) { 769 final Throwable candidate = suppressed[i]; 770 if (!suppressedVisited.contains(candidate)) { 771 suppressedVisited.add(candidate); 772 proxies.add(new ThrowableProxy(candidate, suppressedVisited)); 773 } 774 } 775 return proxies.toArray(new ThrowableProxy[proxies.size()]); 776 } catch (final Exception e) { 777 StatusLogger.getLogger().error(e); 778 } 779 return null; 780 } 781}