1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v2.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.classic.spi;
15  
16  import ch.qos.logback.core.CoreConstants;
17  
18  /**
19   * Convert a throwable into an array of ThrowableDataPoint objects.
20   *
21   *
22   * @author Ceki Gülcü
23   */
24  public class ThrowableProxyUtil {
25  
26      public static final int REGULAR_EXCEPTION_INDENT = 1;
27      public static final int SUPPRESSED_EXCEPTION_INDENT = 1;
28      private static final int BUILDER_CAPACITY = 2048;
29  
30      public static void build(ThrowableProxy nestedTP, Throwable nestedThrowable, ThrowableProxy parentTP) {
31  
32          StackTraceElement[] nestedSTE = nestedThrowable.getStackTrace();
33  
34          int commonFramesCount = -1;
35          if (parentTP != null) {
36              commonFramesCount = findNumberOfCommonFrames(nestedSTE, parentTP.getStackTraceElementProxyArray());
37          }
38  
39          nestedTP.commonFrames = commonFramesCount;
40          nestedTP.stackTraceElementProxyArray = steArrayToStepArray(nestedSTE);
41      }
42  
43      static StackTraceElementProxy[] steArrayToStepArray(StackTraceElement[] stea) {
44          if (stea == null) {
45              return new StackTraceElementProxy[0];
46          }
47          StackTraceElementProxy[] stepa = new StackTraceElementProxy[stea.length];
48          for (int i = 0; i < stepa.length; i++) {
49              stepa[i] = new StackTraceElementProxy(stea[i]);
50          }
51          return stepa;
52      }
53  
54      static int findNumberOfCommonFrames(StackTraceElement[] steArray, StackTraceElementProxy[] parentSTEPArray) {
55          if (parentSTEPArray == null || steArray == null) {
56              return 0;
57          }
58  
59          int steIndex = steArray.length - 1;
60          int parentIndex = parentSTEPArray.length - 1;
61          int count = 0;
62          while (steIndex >= 0 && parentIndex >= 0) {
63              StackTraceElement ste = steArray[steIndex];
64              StackTraceElement otherSte = parentSTEPArray[parentIndex].ste;
65              if (ste.equals(otherSte)) {
66                  count++;
67              } else {
68                  break;
69              }
70              steIndex--;
71              parentIndex--;
72          }
73          return count;
74      }
75  
76      public static void appendNominalFirstLine(StringBuilder buf, String classname, String message) {
77          buf.append(classname).append(": ").append(message);
78      }
79  
80      public static String asString(IThrowableProxy tp) {
81          StringBuilder sb = new StringBuilder(BUILDER_CAPACITY);
82  
83          recursiveAppend(sb, null, REGULAR_EXCEPTION_INDENT, tp);
84  
85          return sb.toString();
86      }
87  
88      private static void recursiveAppend(StringBuilder sb, String prefix, int indent, IThrowableProxy tp) {
89          if (tp == null)
90              return;
91          subjoinFirstLine(sb, prefix, indent, tp);
92          sb.append(CoreConstants.LINE_SEPARATOR);
93          subjoinSTEPArray(sb, indent, tp);
94          IThrowableProxy[] suppressed = tp.getSuppressed();
95          if (suppressed != null) {
96              for (IThrowableProxy current : suppressed) {
97                  recursiveAppend(sb, CoreConstants.SUPPRESSED, indent + SUPPRESSED_EXCEPTION_INDENT, current);
98              }
99          }
100         recursiveAppend(sb, CoreConstants.CAUSED_BY, indent, tp.getCause());
101     }
102 
103     public static void indent(StringBuilder buf, int indent) {
104         for (int j = 0; j < indent; j++) {
105             buf.append(CoreConstants.TAB);
106         }
107     }
108 
109     private static void subjoinFirstLine(StringBuilder buf, String prefix, int indent, IThrowableProxy tp) {
110         indent(buf, indent - 1);
111         if (prefix != null) {
112             buf.append(prefix);
113         }
114         subjoinExceptionMessage(buf, tp);
115     }
116 
117     public static void subjoinPackagingData(StringBuilder builder, StackTraceElementProxy step) {
118         if (step != null) {
119             ClassPackagingData cpd = step.getClassPackagingData();
120             if (cpd != null) {
121                 if (!cpd.isExact()) {
122                     builder.append(" ~[");
123                 } else {
124                     builder.append(" [");
125                 }
126 
127                 builder.append(cpd.getCodeLocation()).append(':').append(cpd.getVersion()).append(']');
128             }
129         }
130     }
131 
132     public static void subjoinSTEP(StringBuilder sb, StackTraceElementProxy step) {
133         sb.append(step.toString());
134         subjoinPackagingData(sb, step);
135     }
136 
137     /**
138      * @param sb The StringBuilder the STEPs are appended to.
139      * @param tp the IThrowableProxy containing the STEPs.
140      * @deprecated Use subjoinSTEPArray(StringBuilder sb, int indentLevel,
141      *             IThrowableProxy tp) instead.
142      */
143     public static void subjoinSTEPArray(StringBuilder sb, IThrowableProxy tp) {
144         // not called anymore - but it is public
145         subjoinSTEPArray(sb, REGULAR_EXCEPTION_INDENT, tp);
146     }
147 
148     /**
149      * @param sb          The StringBuilder the STEPs are appended to.
150      * @param indentLevel indentation level used for the STEPs, usually
151      *                    REGULAR_EXCEPTION_INDENT.
152      * @param tp          the IThrowableProxy containing the STEPs.
153      */
154     public static void subjoinSTEPArray(StringBuilder sb, int indentLevel, IThrowableProxy tp) {
155         StackTraceElementProxy[] stepArray = tp.getStackTraceElementProxyArray();
156         int commonFrames = tp.getCommonFrames();
157 
158         for (int i = 0; i < stepArray.length - commonFrames; i++) {
159             StackTraceElementProxy step = stepArray[i];
160             indent(sb, indentLevel);
161             subjoinSTEP(sb, step);
162             sb.append(CoreConstants.LINE_SEPARATOR);
163         }
164 
165         if (commonFrames > 0) {
166             indent(sb, indentLevel);
167             sb.append("... ").append(commonFrames).append(" common frames omitted")
168                     .append(CoreConstants.LINE_SEPARATOR);
169         }
170 
171     }
172 
173     public static void subjoinFirstLine(StringBuilder buf, IThrowableProxy tp) {
174         int commonFrames = tp.getCommonFrames();
175         if (commonFrames > 0) {
176             buf.append(CoreConstants.CAUSED_BY);
177         }
178         subjoinExceptionMessage(buf, tp);
179     }
180 
181     public static void subjoinFirstLineRootCauseFirst(StringBuilder buf, IThrowableProxy tp) {
182         if (tp.getCause() != null) {
183             buf.append(CoreConstants.WRAPPED_BY);
184         }
185         subjoinExceptionMessage(buf, tp);
186     }
187 
188     public static void subjoinExceptionMessage(StringBuilder stringBuilder, IThrowableProxy tp) {
189         if (tp.isCyclic()) {
190             stringBuilder.append("[CIRCULAR REFERENCE: ");
191             appendNominalOrOverridingMessage(stringBuilder, tp);
192             stringBuilder.append(']');
193         } else {
194             appendNominalOrOverridingMessage(stringBuilder, tp);
195         }
196     }
197 
198     private static void appendNominalOrOverridingMessage(StringBuilder stringBuilder, IThrowableProxy tp) {
199         if(tp.getOverridingMessage() == null) {
200             appendNominalFirstLine(stringBuilder, tp.getClassName(), tp.getMessage());
201         } else {
202             stringBuilder.append(tp.getOverridingMessage());
203         }
204     }
205 }