Skip to content

Commit 7f91d05

Browse files
committed
Add tree-like output of nested exceptions to MultitargetException (for Throwable.printStackTrace)
1 parent 0dd7e99 commit 7f91d05

File tree

4 files changed

+299
-5
lines changed

4 files changed

+299
-5
lines changed

src/main/java/net/tascalate/concurrent/MultitargetException.java

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,31 @@
1515
*/
1616
package net.tascalate.concurrent;
1717

18+
import java.io.IOException;
19+
import java.io.PrintStream;
20+
import java.io.PrintWriter;
21+
1822
import java.util.Collections;
1923
import java.util.List;
2024
import java.util.Optional;
2125

26+
import java.util.function.BiConsumer;
27+
import java.util.function.Consumer;
28+
2229
public class MultitargetException extends Exception {
23-
final private static long serialVersionUID = 1L;
30+
private final static long serialVersionUID = 1L;
2431

25-
final private List<Throwable> exceptions;
32+
private final List<Throwable> exceptions;
2633

27-
public MultitargetException(final List<Throwable> exceptions) {
28-
this.exceptions = exceptions;
34+
public MultitargetException(List<Throwable> exceptions) {
35+
this.exceptions = exceptions == null ?
36+
Collections.emptyList()
37+
:
38+
Collections.unmodifiableList(exceptions);
2939
}
3040

3141
public List<Throwable> getExceptions() {
32-
return Collections.unmodifiableList(exceptions);
42+
return exceptions;
3343
}
3444

3545
Optional<Throwable> getFirstException() {
@@ -39,4 +49,55 @@ Optional<Throwable> getFirstException() {
3949
public static MultitargetException of(final Throwable exception) {
4050
return new MultitargetException(Collections.singletonList(exception));
4151
}
52+
53+
@Override
54+
public void printStackTrace(PrintStream s) {
55+
super.printStackTrace(s);
56+
printExceptions(s, (ex, padding) -> {
57+
PrintStream ps = new PrintStream(new PaddedOutputStream(s, padding));
58+
ex.printStackTrace(ps);
59+
});
60+
}
61+
62+
@Override
63+
public void printStackTrace(PrintWriter w) {
64+
super.printStackTrace(w);
65+
printExceptions(w, (ex, padding) -> {
66+
PrintWriter pw = new PrintWriter(new PaddedWriter(w, padding), true);
67+
ex.printStackTrace(pw);
68+
});
69+
}
70+
71+
private <O extends Appendable> void printExceptions(O out, BiConsumer<Throwable, String> nestedExceptionPrinter) {
72+
int idx = 0;
73+
int n = ((int)Math.log10(exceptions.size()) + 1);
74+
String idxPadder = "%0" + n + "d";
75+
String padding = String.format("\t %1$-" + n + "s ... ", "");
76+
Consumer<Throwable> printer = ex -> nestedExceptionPrinter.accept(ex, padding);
77+
for (Throwable e : exceptions) {
78+
if (null == e) {
79+
idx++;
80+
continue;
81+
}
82+
try {
83+
printException(String.format(idxPadder, idx++), e, out, printer);
84+
} catch (IOException ex) {
85+
throw new RuntimeException(ex);
86+
}
87+
}
88+
}
89+
90+
private static <O extends Appendable> void printException(String idx, Throwable ex, O out, Consumer<Throwable> nestedExceptionPrinter) throws IOException {
91+
out.append("\t[");
92+
out.append(idx);
93+
out.append("] -> ");
94+
if (null == ex) {
95+
out.append("<NO ERROR>");
96+
out.append(NEW_LINE);
97+
} else {
98+
nestedExceptionPrinter.accept(ex);
99+
}
100+
}
101+
102+
private static final String NEW_LINE = System.lineSeparator();
42103
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* Copyright 2015-2019 Valery Silaev (http://vsilaev.com)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package net.tascalate.concurrent;
17+
18+
import java.io.IOException;
19+
import java.io.OutputStream;
20+
import java.nio.charset.Charset;
21+
22+
class PaddedOutputStream extends OutputStream {
23+
private final OutputStream delegate;
24+
private final byte[] padding;
25+
26+
private volatile boolean needPadding = false;
27+
28+
public PaddedOutputStream(OutputStream delegate, String padding) {
29+
this.delegate = delegate;
30+
this.padding = padding.getBytes(Charset.defaultCharset());
31+
}
32+
33+
private void writePaddingIfNecessary() throws IOException {
34+
if (needPadding) {
35+
delegate.write(padding);
36+
needPadding = false;
37+
}
38+
}
39+
40+
@Override
41+
public void write(int b) throws IOException {
42+
if (b != 0xD && b!= 0xA) {
43+
writePaddingIfNecessary();
44+
}
45+
if (b == 0xD || b == 0xA) {
46+
needPadding = true;
47+
}
48+
delegate.write(b);
49+
}
50+
51+
@Override
52+
public void write(byte[] b, int off, int len) throws IOException {
53+
writePaddingIfNecessary();
54+
int from = off;
55+
boolean needPadding = false;
56+
for (int idx = off; idx < len; idx++) {
57+
if (b[idx] == 0xD || b[idx] == 0xA) {
58+
needPadding = true;
59+
} else if (needPadding) {
60+
if (from < idx) {
61+
delegate.write(b, from, idx - from + 1);
62+
}
63+
delegate.write(padding);
64+
needPadding = false;
65+
from = idx + 1;
66+
}
67+
}
68+
if (from < len) {
69+
delegate.write(b, from, len);
70+
}
71+
this.needPadding = needPadding;
72+
}
73+
74+
@Override
75+
public void flush() throws IOException {
76+
delegate.flush();
77+
}
78+
79+
@Override
80+
public void close() throws IOException {
81+
try (OutputStream stream = delegate) {
82+
flush();
83+
}
84+
}
85+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* Copyright 2015-2019 Valery Silaev (http://vsilaev.com)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package net.tascalate.concurrent;
17+
18+
import java.io.IOException;
19+
import java.io.Writer;
20+
21+
public class PaddedWriter extends Writer {
22+
private final Writer delegate;
23+
private final char[] padding;
24+
25+
private volatile boolean needPadding = false;
26+
27+
public PaddedWriter(Writer delegate, String padding) {
28+
this.delegate = delegate;
29+
this.padding = padding.toCharArray();
30+
}
31+
32+
private void writePaddingIfNecessary() throws IOException {
33+
if (needPadding) {
34+
delegate.write(padding);
35+
needPadding = false;
36+
}
37+
}
38+
39+
@Override
40+
public void write(int b) throws IOException {
41+
if (b != 0xD && b!= 0xA) {
42+
writePaddingIfNecessary();
43+
}
44+
if (b == 0xD || b == 0xA) {
45+
needPadding = true;
46+
}
47+
delegate.write(b);
48+
}
49+
50+
@Override
51+
public void write(char[] b, int off, int len) throws IOException {
52+
writePaddingIfNecessary();
53+
int from = off;
54+
boolean needPadding = false;
55+
for (int idx = off; idx < len; idx++) {
56+
if (b[idx] == 0xD || b[idx] == 0xA) {
57+
needPadding = true;
58+
} else if (needPadding) {
59+
if (from < idx) {
60+
delegate.write(b, from, idx - from + 1);
61+
}
62+
delegate.write(padding);
63+
needPadding = false;
64+
from = idx + 1;
65+
}
66+
}
67+
if (from < len) {
68+
delegate.write(b, from, len);
69+
}
70+
this.needPadding = needPadding;
71+
}
72+
73+
@Override
74+
public void flush() throws IOException {
75+
delegate.flush();
76+
}
77+
78+
@Override
79+
public void close() throws IOException {
80+
try (Writer writer = delegate) {
81+
flush();
82+
}
83+
}
84+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package net.tascalate.concurrent;
2+
3+
import java.io.PrintWriter;
4+
import java.util.Arrays;
5+
6+
public class TestMultitargetExceptionTraces {
7+
8+
9+
public static void main(String[] argv) {
10+
Exception e = err();
11+
e.printStackTrace(new PrintWriter(System.err, true));
12+
}
13+
14+
static Exception err() {
15+
return err_1();
16+
}
17+
18+
static Exception err_1() {
19+
MultitargetException e = new MultitargetException(Arrays.asList(
20+
null,
21+
null,
22+
b(), null,
23+
a(), null,
24+
new MultitargetException(Arrays.asList(c(), b()))
25+
));
26+
e.fillInStackTrace();
27+
return e;
28+
}
29+
30+
static Throwable a() {
31+
Exception e = new IllegalArgumentException("Something wrong", b());
32+
e.fillInStackTrace();
33+
return e;
34+
}
35+
36+
static Throwable b() {
37+
return b_1();
38+
}
39+
40+
static Throwable b_1() {
41+
return b_2();
42+
}
43+
44+
static Throwable b_2() {
45+
Throwable e = new NoSuchMethodError("Data not found");
46+
e.fillInStackTrace();
47+
return e;
48+
}
49+
50+
static Throwable c() {
51+
return c_1();
52+
}
53+
54+
static Throwable c_1() {
55+
return c_2();
56+
}
57+
58+
static Exception c_2() {
59+
Exception e = new IllegalStateException("State is forbidden");
60+
e.fillInStackTrace();
61+
return e;
62+
}
63+
64+
}

0 commit comments

Comments
 (0)