View Javadoc
1   package com.github.triceo.splitlog.splitters.exceptions;
2   
3   import java.util.ArrayList;
4   import java.util.Arrays;
5   import java.util.Collection;
6   import java.util.Collections;
7   import java.util.LinkedHashMap;
8   import java.util.List;
9   import java.util.Map;
10  
11  import com.github.triceo.splitlog.api.ExceptionDescriptor;
12  
13  /**
14   * A class that describes an exception instance.
15   * 
16   */
17  public class DefaultExceptionDescriptor implements ExceptionDescriptor {
18  
19      private static class Builder {
20  
21          private final Map<CauseLine, List<StackTraceElement>> causes = new LinkedHashMap<CauseLine, List<StackTraceElement>>();
22          private List<StackTraceElement> currentlyUsedStackTrace;
23  
24          public void addLine(final ExceptionLine line) {
25              if (line instanceof CauseLine) {
26                  // start a new exception
27                  this.causes.put((CauseLine) line, new ArrayList<StackTraceElement>());
28                  this.currentlyUsedStackTrace = this.causes.get(line);
29              } else if (line instanceof StackTraceLine) {
30                  // add a stack trace element to the current exception
31                  final StackTraceLine l = (StackTraceLine) line;
32                  final String fqMethodName = l.getMethodName();
33                  final int index = fqMethodName.lastIndexOf('.');
34                  final StackTraceElement e = new StackTraceElement(fqMethodName.substring(0, index),
35                          fqMethodName.substring(index + 1), l.getClassName(), l.getLineInCode());
36                  this.currentlyUsedStackTrace.add(e);
37              } else {
38                  // we don't care about any other kind of information
39              }
40          }
41  
42          public ExceptionDescriptor build() {
43              final List<CauseLine> properlyOrdered = new ArrayList<CauseLine>(this.causes.keySet());
44              ExceptionDescriptor previousException = null;
45              for (int i = properlyOrdered.size() - 1; i >= 0; i--) {
46                  final CauseLine cause = properlyOrdered.get(i);
47                  final List<StackTraceElement> stackTrace = this.causes.get(cause);
48                  previousException = new DefaultExceptionDescriptor(cause.getClassName(), cause.getMessage(),
49                          stackTrace, previousException);
50              }
51              return previousException;
52          }
53  
54      }
55  
56      /**
57       * Take a chunk of log and try to parse an exception out of it.
58       * 
59       * @param lines
60       *            Any random log, separated into lines.
61       * @return First exception found, including full stack trace with causes, or
62       *         null if none identified.
63       */
64      public static ExceptionDescriptor parseStackTrace(final Collection<String> lines) {
65          if ((lines == null) || lines.isEmpty()) {
66              throw new IllegalArgumentException("No stack trace provided.");
67          }
68          try {
69              final Builder b = new Builder();
70              final Collection<ExceptionLine> parsedLines = new ExceptionParser().parse(lines);
71              for (final ExceptionLine line : parsedLines) {
72                  b.addLine(line);
73              }
74              return b.build();
75          } catch (final ExceptionParseException e) {
76              // FIXME provides no information as to why there's no exception
77              // found
78              return null;
79          }
80      }
81  
82      private final ExceptionDescriptor cause;
83  
84      private final String exceptionClassName, message;
85      private final StackTraceElement[] stackTrace;
86  
87      private DefaultExceptionDescriptor(final String className, final String message,
88              final List<StackTraceElement> elements, final ExceptionDescriptor cause) {
89          this.exceptionClassName = className;
90          this.message = message;
91          this.stackTrace = elements.toArray(new StackTraceElement[elements.size()]);
92          this.cause = cause;
93      }
94  
95      @Override
96      public boolean equals(final Object obj) {
97          if (this == obj) {
98              return true;
99          }
100         if (obj == null) {
101             return false;
102         }
103         if (this.getClass() != obj.getClass()) {
104             return false;
105         }
106         final DefaultExceptionDescriptor other = (DefaultExceptionDescriptor) obj;
107         if (this.cause == null) {
108             if (other.cause != null) {
109                 return false;
110             }
111         } else if (!this.cause.equals(other.cause)) {
112             return false;
113         }
114         if (this.exceptionClassName == null) {
115             if (other.exceptionClassName != null) {
116                 return false;
117             }
118         } else if (!this.exceptionClassName.equals(other.exceptionClassName)) {
119             return false;
120         }
121         if (this.message == null) {
122             if (other.message != null) {
123                 return false;
124             }
125         } else if (!this.message.equals(other.message)) {
126             return false;
127         }
128         if (!Arrays.equals(this.stackTrace, other.stackTrace)) {
129             return false;
130         }
131         return true;
132     }
133 
134     @Override
135     public ExceptionDescriptor getCause() {
136         return this.cause;
137     }
138 
139     @Override
140     @SuppressWarnings("unchecked")
141     public Class<? extends Throwable> getExceptionClass() {
142         try {
143             return (Class<? extends Throwable>) Class.forName(this.getExceptionClassName());
144         } catch (final ClassNotFoundException e) {
145             // the exception in the log came from code that is not on known here
146             return null;
147         }
148     }
149 
150     @Override
151     public String getExceptionClassName() {
152         return this.exceptionClassName;
153     }
154 
155     @Override
156     public String getMessage() {
157         return this.message;
158     }
159 
160     @Override
161     public List<StackTraceElement> getStackTrace() {
162         return Collections.unmodifiableList(Arrays.asList(this.stackTrace));
163     }
164 
165     @Override
166     public int hashCode() {
167         final int prime = 31;
168         int result = 1;
169         result = (prime * result) + ((this.cause == null) ? 0 : this.cause.hashCode());
170         result = (prime * result) + ((this.exceptionClassName == null) ? 0 : this.exceptionClassName.hashCode());
171         result = (prime * result) + ((this.message == null) ? 0 : this.message.hashCode());
172         result = (prime * result) + Arrays.hashCode(this.stackTrace);
173         return result;
174     }
175 
176     @Override
177     public boolean isRootCause() {
178         return this.cause == null;
179     }
180 
181     @Override
182     public String toString() {
183         final StringBuilder builder2 = new StringBuilder();
184         builder2.append("ExceptionDescriptor [");
185         if (this.stackTrace != null) {
186             builder2.append("stackTrace=").append(Arrays.toString(this.stackTrace)).append(", ");
187         }
188         if (this.exceptionClassName != null) {
189             builder2.append("exceptionClassName=").append(this.exceptionClassName).append(", ");
190         }
191         if (this.message != null) {
192             builder2.append("message=").append(this.message).append(", ");
193         }
194         if (this.cause != null) {
195             builder2.append("cause=").append(this.cause);
196         }
197         builder2.append("]");
198         return builder2.toString();
199     }
200 
201 }