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