Console.java revision 3dafbce3b8d025f465ebf0a4c6d018c7dc31654e
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package vogar;
18
19import java.util.List;
20
21/**
22 * Controls, formats and emits output to the command line.
23 */
24public class Console {
25    private static final Console INSTANCE = new Console();
26
27    private boolean stream;
28    private boolean color;
29    private boolean verbose;
30    private String indent;
31
32    private String currentName;
33    private CurrentLine currentLine = CurrentLine.NEW;
34    private final StringBuilder bufferedOutput = new StringBuilder();
35
36    private Console() {}
37
38    public static Console getInstance() {
39        return INSTANCE;
40    }
41
42    public void setStream(boolean stream) {
43        this.stream = stream;
44    }
45
46    public void setIndent(String indent) {
47        this.indent = indent;
48    }
49
50    public void setColor(boolean color) {
51        this.color = color;
52    }
53
54    public void setVerbose(boolean verbose) {
55        this.verbose = verbose;
56    }
57
58    public synchronized void verbose(String s) {
59        newLine();
60        System.out.print(s);
61        System.out.flush();
62        currentLine = CurrentLine.VERBOSE;
63    }
64
65    public synchronized void warn(String message) {
66        newLine();
67        System.out.println(yellow("Warning: " + message));
68    }
69
70    /**
71     * Warns, and also puts a list of strings afterwards.
72     */
73    public synchronized void warn(String message, List<String> list) {
74        newLine();
75        System.out.println(yellow("Warning: " + message));
76        for (String item : list) {
77            System.out.println(yellow(indent + item));
78        }
79    }
80
81    public synchronized void info(String s) {
82        newLine();
83        System.out.println(s);
84    }
85
86    public synchronized void info(String message, Throwable throwable) {
87        newLine();
88        System.out.println(message);
89        throwable.printStackTrace(System.out);
90    }
91
92    public synchronized void action(String name) {
93        newLine();
94        System.out.print("Action " + name);
95        System.out.flush();
96        currentName = name;
97        currentLine = CurrentLine.NAME;
98    }
99
100    /**
101     * Prints the beginning of the named outcome.
102     */
103    public synchronized void outcome(String name) {
104        // if the outcome and action names are the same, omit the outcome name
105        if (name.equals(currentName)) {
106            return;
107        }
108
109        currentName = name;
110        newLine();
111        System.out.print(indent + name);
112        System.out.flush();
113        currentLine = CurrentLine.NAME;
114    }
115
116    /**
117     * Appends the action output immediately to the stream when streaming is on,
118     * or to a buffer when streaming is off. Buffered output will be held and
119     * printed only if the outcome is unsuccessful.
120     */
121    public synchronized void streamOutput(String output) {
122        if (stream) {
123            printOutput(output);
124        } else {
125            bufferedOutput.append(output);
126        }
127    }
128
129    /**
130     * Writes the action's outcome.
131     */
132    public synchronized void printResult(Result result, ResultValue resultValue) {
133        if (resultValue == ResultValue.OK) {
134            String prefix = (currentLine == CurrentLine.NAME) ? " " : "\n" + indent;
135            System.out.println(prefix + green("OK (" + result + ")"));
136
137        } else if (resultValue == ResultValue.FAIL) {
138            if (bufferedOutput.length() > 0) {
139                printOutput(bufferedOutput.toString());
140                bufferedOutput.delete(0, bufferedOutput.length());
141            }
142
143            newLine();
144            System.out.println(indent + red("FAIL (" + result + ")"));
145        } else if (resultValue == ResultValue.IGNORE) {
146            if (bufferedOutput.length() > 0) {
147                printOutput(bufferedOutput.toString());
148                bufferedOutput.delete(0, bufferedOutput.length());
149            }
150
151            newLine();
152            System.out.println(indent + yellow("SKIP (" + result + ")"));
153        }
154
155        currentName = null;
156        currentLine = CurrentLine.NEW;
157    }
158
159    public synchronized void summarizeFailures(List<String> failureNames) {
160        newLine();
161        System.out.println("Failure summary:");
162        for (String failureName : failureNames) {
163            System.out.println(red(failureName));
164        }
165    }
166
167    public synchronized void summarizeSkips(List<String> skippedNames) {
168        newLine();
169        System.out.println("Skip summary:");
170        for (String skippedName : skippedNames) {
171            System.out.println(yellow(skippedName));
172        }
173    }
174
175    /**
176     * Prints the action output with appropriate indentation.
177     */
178    private void printOutput(String streamedOutput) {
179        if (streamedOutput.length() == 0) {
180            return;
181        }
182
183        String[] lines = messageToLines(streamedOutput);
184
185        if (currentLine != CurrentLine.STREAMED_OUTPUT) {
186            newLine();
187            System.out.print(indent);
188            System.out.print(indent);
189        }
190        System.out.print(lines[0]);
191        currentLine = CurrentLine.STREAMED_OUTPUT;
192
193        for (int i = 1; i < lines.length; i++) {
194            newLine();
195
196            if (lines[i].length() > 0) {
197                System.out.print(indent);
198                System.out.print(indent);
199                System.out.print(lines[i]);
200                currentLine = CurrentLine.STREAMED_OUTPUT;
201            }
202        }
203    }
204
205    /**
206     * Inserts a linebreak if necessary.
207     */
208    private void newLine() {
209        if (currentLine == CurrentLine.NEW) {
210            return;
211        } else if (currentLine == CurrentLine.VERBOSE) {
212            // --verbose means "leave all the verbose output on the screen".
213            if (!verbose) {
214                // Otherwise we overwrite verbose output whenever something new arrives.
215                eraseCurrentLine();
216                currentLine = CurrentLine.NEW;
217                return;
218            }
219        }
220
221        System.out.println();
222        currentLine = CurrentLine.NEW;
223    }
224
225    /**
226     * Status of a currently-in-progress line of output.
227     */
228    enum CurrentLine {
229
230        /**
231         * The line is blank.
232         */
233        NEW,
234
235        /**
236         * The line contains streamed application output. Additional streamed
237         * output may be appended without additional line separators or
238         * indentation.
239         */
240        STREAMED_OUTPUT,
241
242        /**
243         * The line contains the name of an action or outcome. The outcome's
244         * result (such as "OK") can be appended without additional line
245         * separators or indentation.
246         */
247        NAME,
248
249        /**
250         * The line contains verbose output, and may be overwritten.
251         */
252        VERBOSE,
253    }
254
255    /**
256     * Returns an array containing the lines of the given text.
257     */
258    private String[] messageToLines(String message) {
259        // pass Integer.MAX_VALUE so split doesn't trim trailing empty strings.
260        return message.split("\r\n|\r|\n", Integer.MAX_VALUE);
261    }
262
263    private String green(String message) {
264        return color ? ("\u001b[32;1m" + message + "\u001b[0m") : message;
265    }
266
267    private String red(String message) {
268        return color ? ("\u001b[31;1m" + message + "\u001b[0m") : message;
269    }
270
271    private String yellow(String message) {
272        return color ? ("\u001b[33;1m" + message + "\u001b[0m") : message;
273    }
274
275    private void eraseCurrentLine() {
276        System.out.print(color ? "\u001b[2K\r" : "\n");
277        System.out.flush();
278    }
279}
280