View Javadoc
1   package org.apache.maven.plugin.surefire.report;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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   * A deferred file output stream decorator that encodes the string from the VM to UTF-8.
41   * <br>
42   * The deferred file is temporary file, and it is created at the first {@link #write(String, boolean) write}.
43   * The {@link #writeTo(OutputStream) reads} can be called anytime.
44   * It is highly recommended to {@link #commit() commit} the cache which would close the file stream
45   * and subsequent reads may continue.
46   * The {@link #free()} method would commit and delete the deferred file.
47   *
48   * @author Andreas Gudian
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                     //todo delete( file ); uncomment L485 assertThat( file ).doesNotExist() in StatelessXmlReporterTest
215                 }
216                 catch ( /*todo uncomment IOException |*/ UncheckedIOException e )
217                 {
218                     /*todo uncomment file.toFile()
219                         .deleteOnExit();*/
220                 }
221                 finally
222                 {
223                     // todo should be removed after uncommented delete( file )
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             // the data that you wrote with the mode "rw" may still only be kept in memory and may be read back
253             // storage.getFD().sync();
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 }