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.shared.scriptinterpreter;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.OutputStream;
24  import java.io.PrintStream;
25  import java.nio.file.Files;
26  import java.nio.file.OpenOption;
27  import java.nio.file.Path;
28  
29  /**
30   * <p>FileLogger class.</p>
31   */
32  public class FileLogger implements ExecutionLogger, AutoCloseable {
33  
34      /**
35       * The path to the log file.
36       */
37      private File file;
38  
39      /**
40       * The underlying file stream this logger writes to.
41       */
42      private PrintStream stream;
43  
44      /**
45       * Creates a new logger that writes to the specified file.
46       *
47       * @param outputFile The path to the output file, if null all message will be discarded.
48       * @throws java.io.IOException If the output file could not be created.
49       */
50      public FileLogger(File outputFile) throws IOException {
51          this(outputFile, null);
52      }
53  
54      /**
55       * Creates a new logger that writes to the specified file and optionally mirrors messages.
56       *
57       * @param outputFile    The path to the output file, if null all message will be discarded.
58       * @param mirrorHandler The class which handle mirrored message, can be <code>null</code>.
59       * @throws java.io.IOException If the output file could not be created.
60       */
61      public FileLogger(File outputFile, FileLoggerMirrorHandler mirrorHandler) throws IOException {
62          this.file = outputFile;
63  
64          OutputStream outputStream;
65  
66          if (outputFile != null) {
67              Path outputPath = outputFile.toPath();
68              Files.createDirectories(outputPath.getParent());
69              outputStream = createOutputStream(outputPath);
70          } else {
71              outputStream = new NullOutputStream();
72          }
73  
74          if (mirrorHandler != null) {
75              stream = new PrintStream(new MirrorStreamWrapper(outputStream, mirrorHandler));
76          } else {
77              stream = new PrintStream(outputStream);
78          }
79      }
80  
81      /**
82       * <p>Override this method to create a custom output stream.
83       *
84       * <p>By default, stream is created with {@link Files#newOutputStream(Path, OpenOption...)},
85       * which truncate the existing file.
86       */
87      protected OutputStream createOutputStream(Path outputPath) throws IOException {
88          return Files.newOutputStream(outputPath);
89      }
90  
91      /**
92       * Gets the path to the output file.
93       *
94       * @return The path to the output file, never <code>null</code>.
95       */
96      public File getOutputFile() {
97          return file;
98      }
99  
100     /**
101      * Gets the underlying stream used to write message to the log file.
102      *
103      * @return The underlying stream used to write message to the log file, never <code>null</code>.
104      */
105     @Override
106     public PrintStream getPrintStream() {
107         return stream;
108     }
109 
110     /**
111      * Writes the specified line to the log file
112      * and invoke {@link FileLoggerMirrorHandler#consumeOutput(String)} if is given.
113      *
114      * @param line The message to log.
115      */
116     @Override
117     public void consumeLine(String line) {
118         stream.println(line);
119         stream.flush();
120     }
121 
122     /**
123      * Closes the underlying file stream.
124      */
125     public void close() {
126         if (stream != null) {
127             stream.flush();
128             stream.close();
129             stream = null;
130         }
131     }
132 
133     private static class MirrorStreamWrapper extends OutputStream {
134         private OutputStream out;
135 
136         private final FileLoggerMirrorHandler mirrorHandler;
137 
138         private StringBuilder lineBuffer;
139 
140         MirrorStreamWrapper(OutputStream outputStream, FileLoggerMirrorHandler mirrorHandler) {
141             this.out = outputStream;
142             this.mirrorHandler = mirrorHandler;
143             this.lineBuffer = new StringBuilder();
144         }
145 
146         @Override
147         public void write(int b) throws IOException {
148             out.write(b);
149             lineBuffer.append((char) (b));
150         }
151 
152         @Override
153         public void write(byte[] b, int off, int len) throws IOException {
154             out.write(b, off, len);
155             lineBuffer.append(new String(b, off, len));
156         }
157 
158         @Override
159         public void flush() throws IOException {
160             out.flush();
161 
162             int len = lineBuffer.length();
163             if (len == 0) {
164                 // nothing to log
165                 return;
166             }
167 
168             // remove line end for log
169             while (len > 0 && (lineBuffer.charAt(len - 1) == '\n' || lineBuffer.charAt(len - 1) == '\r')) {
170                 len--;
171             }
172             lineBuffer.setLength(len);
173 
174             mirrorHandler.consumeOutput(lineBuffer.toString());
175 
176             // clear buffer
177             lineBuffer = new StringBuilder();
178         }
179 
180         @Override
181         public void close() throws IOException {
182             flush();
183             if (out != null) {
184                 out.close();
185                 out = null;
186             }
187         }
188     }
189 
190     private static class NullOutputStream extends OutputStream {
191         @Override
192         public void write(int b) {
193             // do nothing
194         }
195 
196         @Override
197         public void write(byte[] b, int off, int len) {
198             // do nothing
199         }
200     }
201 }