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