1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugin.surefire.report;
20
21 import java.io.IOException;
22 import java.io.OutputStream;
23 import java.io.RandomAccessFile;
24 import java.io.UncheckedIOException;
25 import java.lang.ref.SoftReference;
26 import java.nio.Buffer;
27 import java.nio.ByteBuffer;
28 import java.nio.file.Path;
29
30 import org.apache.maven.surefire.api.util.SureFireFileManager;
31
32 import static java.nio.charset.StandardCharsets.UTF_8;
33 import static java.nio.file.Files.notExists;
34 import static java.nio.file.Files.size;
35 import static java.util.Objects.requireNonNull;
36 import static org.apache.maven.surefire.api.util.internal.StringUtils.NL;
37
38
39
40
41
42
43
44
45
46
47
48
49 final class Utf8RecodingDeferredFileOutputStream {
50 private static final byte[] NL_BYTES = NL.getBytes(UTF_8);
51 public static final int CACHE_SIZE = 64 * 1024;
52
53 private final String channel;
54 private Path file;
55 private RandomAccessFile storage;
56 private boolean closed;
57 private SoftReference<byte[]> largeCache;
58 private ByteBuffer cache;
59 private boolean isDirty;
60
61 Utf8RecodingDeferredFileOutputStream(String channel) {
62 this.channel = requireNonNull(channel);
63 }
64
65 public synchronized void write(String output, boolean newLine) throws IOException {
66 if (closed) {
67 return;
68 }
69
70 if (storage == null) {
71
72 file = SureFireFileManager.createTempFile(channel, "deferred").toPath();
73 storage = new RandomAccessFile(file.toFile(), "rw");
74 }
75
76 if (output == null) {
77 output = "null";
78 }
79
80 if (cache == null) {
81 cache = ByteBuffer.allocate(CACHE_SIZE);
82 }
83
84 isDirty = true;
85
86 byte[] decodedString = output.getBytes(UTF_8);
87 int newLineLength = newLine ? NL_BYTES.length : 0;
88 if (cache.remaining() >= decodedString.length + newLineLength) {
89 cache.put(decodedString);
90 if (newLine) {
91 cache.put(NL_BYTES);
92 }
93 } else {
94 ((Buffer) cache).flip();
95 int minLength = cache.remaining() + decodedString.length + NL_BYTES.length;
96 byte[] buffer = getLargeCache(minLength);
97 int bufferLength = 0;
98 System.arraycopy(
99 cache.array(),
100 cache.arrayOffset() + ((Buffer) cache).position(),
101 buffer,
102 bufferLength,
103 cache.remaining());
104 bufferLength += cache.remaining();
105 ((Buffer) cache).clear();
106
107 System.arraycopy(decodedString, 0, buffer, bufferLength, decodedString.length);
108 bufferLength += decodedString.length;
109
110 if (newLine) {
111 System.arraycopy(NL_BYTES, 0, buffer, bufferLength, NL_BYTES.length);
112 bufferLength += NL_BYTES.length;
113 }
114
115 storage.write(buffer, 0, bufferLength);
116 }
117 }
118
119 public synchronized long getByteCount() {
120 try {
121 sync();
122 if (storage != null && !closed) {
123 storage.getFD().sync();
124 }
125 return file == null ? 0 : size(file);
126 } catch (IOException e) {
127 return 0;
128 }
129 }
130
131 @SuppressWarnings("checkstyle:innerassignment")
132 public synchronized void writeTo(OutputStream out) throws IOException {
133 if (file != null && notExists(file)) {
134 storage = null;
135 }
136
137 if ((storage == null && file != null)
138 || (storage != null && !storage.getChannel().isOpen())) {
139 storage = new RandomAccessFile(file.toFile(), "rw");
140 }
141
142 if (storage != null) {
143 sync();
144 final long currentFilePosition = storage.getFilePointer();
145 storage.seek(0L);
146 try {
147 byte[] buffer = new byte[CACHE_SIZE];
148 for (int readCount; (readCount = storage.read(buffer)) != -1; ) {
149 out.write(buffer, 0, readCount);
150 }
151 } finally {
152 storage.seek(currentFilePosition);
153 }
154 }
155 }
156
157 public synchronized void commit() {
158 if (storage == null) {
159 return;
160 }
161
162 try {
163 sync();
164 storage.close();
165 } catch (IOException e) {
166 throw new UncheckedIOException(e);
167 } finally {
168 storage = null;
169 cache = null;
170 largeCache = null;
171 }
172 }
173
174 public synchronized void free() {
175 if (!closed) {
176 closed = true;
177 if (file != null) {
178 try {
179 commit();
180
181 } catch ( UncheckedIOException e) {
182
183
184 } finally {
185
186 file.toFile().deleteOnExit();
187 }
188
189 storage = null;
190 cache = null;
191 largeCache = null;
192 }
193 }
194 }
195
196 private void sync() throws IOException {
197 if (!isDirty) {
198 return;
199 }
200
201 isDirty = false;
202
203 if (storage != null && cache != null) {
204 ((Buffer) cache).flip();
205 byte[] array = cache.array();
206 int offset = cache.arrayOffset() + ((Buffer) cache).position();
207 int length = cache.remaining();
208 ((Buffer) cache).clear();
209 storage.write(array, offset, length);
210
211
212 }
213 }
214
215 @SuppressWarnings("checkstyle:innerassignment")
216 private byte[] getLargeCache(int minLength) {
217 byte[] buffer;
218 if (largeCache == null || (buffer = largeCache.get()) == null || buffer.length < minLength) {
219 buffer = new byte[minLength];
220 largeCache = new SoftReference<>(buffer);
221 }
222 return buffer;
223 }
224 }