1 package com.github.triceo.splitlog.splitters.exceptions;
2
3 import java.util.Arrays;
4 import java.util.Collection;
5 import java.util.Collections;
6 import java.util.LinkedList;
7 import java.util.List;
8 import java.util.Queue;
9
10
11
12
13
14
15
16
17
18
19
20
21 final class ExceptionParser {
22
23
24
25
26 private static enum LineType {
27 CAUSE(true, false), POST_END(false, true), PRE_START(true, false), STACK_TRACE(false, true), STACK_TRACE_END(
28 false, true), SUB_CAUSE(false, false);
29
30 private final boolean mayBeFirstLine, mayBeLastLine;
31 private ExceptionLineParser<?> parser;
32
33 LineType(final boolean mayBeginWith, final boolean mayEndWith) {
34 this.mayBeFirstLine = mayBeginWith;
35 this.mayBeLastLine = mayEndWith;
36 }
37
38 private ExceptionLineParser<?> determineParser() {
39 switch (this) {
40 case PRE_START:
41 case POST_END:
42 return null;
43 case CAUSE:
44 return new CauseParser();
45 case STACK_TRACE:
46 return new StackTraceParser();
47 case STACK_TRACE_END:
48 return new StackTraceEndParser();
49 case SUB_CAUSE:
50 return new SubCauseParser();
51 }
52 throw new IllegalStateException("This can never happen.");
53 }
54
55
56
57
58 public boolean isAcceptableAsFirstLine() {
59 return this.mayBeFirstLine;
60 }
61
62
63
64
65 public boolean isAcceptableAsLastLine() {
66 return this.mayBeLastLine;
67 }
68
69
70
71
72
73
74
75
76
77
78 public ExceptionLine parse(final String line) throws ExceptionParseException {
79 if ((this == LineType.POST_END) || (this == LineType.PRE_START)) {
80 throw new IllegalStateException("No need to parse garbage lines.");
81 } else if (this.parser == null) {
82 this.parser = this.determineParser();
83 }
84 return this.parser.parse(line);
85 }
86 }
87
88 private static String greatestCommonPrefix(final String a, final String b) {
89 final int minLength = Math.min(a.length(), b.length());
90 for (int i = 0; i < minLength; i++) {
91 if (a.charAt(i) != b.charAt(i)) {
92 return a.substring(0, i);
93 }
94 }
95 return a.substring(0, minLength);
96 }
97
98
99
100
101
102
103
104
105
106
107 private static Queue<String> removePrefix(final List<String> input) {
108 if (input.size() < 2) {
109 return new LinkedList<String>(input);
110 }
111 String resultingPrefix = "";
112 final String previousGreatestCommonPrefix = input.get(0).trim();
113 String greatestCommonPrefix = "";
114 for (int i = 1; i < input.size(); i++) {
115 final String previousLine = input.get(i - 1).trim();
116 final String currentLine = input.get(i).trim();
117 greatestCommonPrefix = ExceptionParser.greatestCommonPrefix(previousLine, currentLine);
118 resultingPrefix = ExceptionParser.greatestCommonPrefix(previousGreatestCommonPrefix, greatestCommonPrefix);
119 if (resultingPrefix.length() == 0) {
120 break;
121 }
122 }
123 final int prefixLength = resultingPrefix.length();
124 final boolean hasPrefix = prefixLength > 0;
125 final Queue<String> result = new LinkedList<String>();
126 for (final String line : input) {
127 final String line2 = line.trim();
128 if (hasPrefix) {
129 result.add(line2.substring(prefixLength));
130 } else {
131 result.add(line2);
132 }
133 }
134 return result;
135 }
136
137 private final Collection<ExceptionLine> parsedLines = new LinkedList<ExceptionLine>();
138
139
140
141
142
143
144
145
146
147
148
149
150 public synchronized Collection<ExceptionLine> parse(final Collection<String> input) throws ExceptionParseException {
151 this.parsedLines.clear();
152 final Queue<String> linesFromInput = ExceptionParser.removePrefix(new LinkedList<String>(input));
153 LineType previousLineType = LineType.PRE_START;
154 String currentLine = null;
155 boolean isFirstLine = true;
156 while (!linesFromInput.isEmpty()) {
157 currentLine = linesFromInput.poll();
158 previousLineType = this.parseLine(previousLineType, currentLine);
159 if (isFirstLine) {
160 if (!previousLineType.isAcceptableAsFirstLine()) {
161 throw new ExceptionParseException(currentLine, "Invalid line type detected at the beginning: "
162 + previousLineType);
163 }
164 isFirstLine = false;
165 }
166 }
167 if (!previousLineType.isAcceptableAsLastLine()) {
168 throw new ExceptionParseException(currentLine, "Invalid line type detected at the end: " + previousLineType);
169 }
170 return Collections.unmodifiableCollection(new LinkedList<ExceptionLine>(this.parsedLines));
171 }
172
173
174
175
176
177
178
179
180
181
182
183
184
185 private LineType parseLine(final LineType previousLineType, final String line) throws ExceptionParseException {
186 switch (previousLineType) {
187 case PRE_START:
188 return this.parseLine(line, LineType.CAUSE, LineType.PRE_START);
189 case CAUSE:
190 case SUB_CAUSE:
191 return this.parseLine(line, LineType.STACK_TRACE);
192 case STACK_TRACE:
193 return this.parseLine(line, LineType.STACK_TRACE, LineType.STACK_TRACE_END, LineType.SUB_CAUSE);
194 case STACK_TRACE_END:
195 return this.parseLine(line, LineType.SUB_CAUSE, LineType.POST_END);
196 case POST_END:
197 return this.parseLine(line, LineType.POST_END);
198 default:
199 throw new IllegalArgumentException("Unsupported line type: " + previousLineType);
200 }
201 }
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217 private LineType parseLine(final String line, final LineType... allowedLineTypes) throws ExceptionParseException {
218 for (final LineType possibleType : allowedLineTypes) {
219 if ((possibleType == LineType.POST_END) || (possibleType == LineType.PRE_START)) {
220
221 return possibleType;
222 }
223 final ExceptionLine parsedLine = possibleType.parse(line);
224 if (parsedLine != null) {
225 this.parsedLines.add(parsedLine);
226 return possibleType;
227 }
228 }
229 throw new ExceptionParseException(line, "Line not any of the expected types: "
230 + Arrays.toString(allowedLineTypes));
231 }
232
233 }