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 }