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.utils.logging;
20
21 import org.fusesource.jansi.Ansi;
22 import org.fusesource.jansi.AnsiConsole;
23 import org.fusesource.jansi.AnsiMode;
24
25 /**
26 * Colored message utils, to manage colors consistently across plugins (only if Maven version is at least 3.5.0).
27 * For Maven version before 3.5.0, message built with this util will never add color.
28 * <p>
29 * Internally, <a href="http://fusesource.github.io/jansi/">Jansi</a> is used to render
30 * <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#Colors">ANSI colors</a> on any platform.
31 * @since 3.1.0
32 */
33 public class MessageUtils {
34 private static final boolean JANSI;
35
36 /** Reference to the JVM shutdown hook, if registered */
37 private static Thread shutdownHook;
38
39 /** Synchronization monitor for the "uninstall" */
40 private static final Object STARTUP_SHUTDOWN_MONITOR = new Object();
41
42 static {
43 boolean jansi = true;
44 try {
45 // Jansi is provided by Maven core since 3.5.0
46 Class.forName("org.fusesource.jansi.Ansi");
47 } catch (ClassNotFoundException cnfe) {
48 jansi = false;
49 }
50 JANSI = jansi;
51 }
52
53 /**
54 * Install color support.
55 * This method is called by Maven core, and calling it is not necessary in plugins.
56 */
57 public static void systemInstall() {
58 if (JANSI) {
59 AnsiConsole.systemInstall();
60 }
61 }
62
63 /**
64 * Undo a previous {@link #systemInstall()}. If {@link #systemInstall()} was called
65 * multiple times, {@link #systemUninstall()} must be called call the same number of times before
66 * it is actually uninstalled.
67 */
68 public static void systemUninstall() {
69 synchronized (STARTUP_SHUTDOWN_MONITOR) {
70 doSystemUninstall();
71
72 // hook can only set when Jansi is true
73 if (shutdownHook != null) {
74 try {
75 Runtime.getRuntime().removeShutdownHook(shutdownHook);
76 } catch (IllegalStateException ex) {
77 // ignore - VM is already shutting down
78 }
79 }
80 }
81 }
82
83 private static void doSystemUninstall() {
84 if (JANSI) {
85 AnsiConsole.systemUninstall();
86 }
87 }
88
89 /**
90 * Enables message color (if Jansi is available).
91 * @param flag to enable Jansi
92 */
93 public static void setColorEnabled(boolean flag) {
94 if (JANSI) {
95 AnsiConsole.out().setMode(flag ? AnsiMode.Force : AnsiMode.Strip);
96 Ansi.setEnabled(flag);
97 System.setProperty(
98 AnsiConsole.JANSI_MODE, flag ? AnsiConsole.JANSI_MODE_FORCE : AnsiConsole.JANSI_MODE_STRIP);
99 boolean installed = AnsiConsole.isInstalled();
100 while (AnsiConsole.isInstalled()) {
101 AnsiConsole.systemUninstall();
102 }
103 if (installed) {
104 AnsiConsole.systemInstall();
105 }
106 }
107 }
108
109 /**
110 * Is message color enabled: requires Jansi available (through Maven) and the color has not been disabled.
111 * @return whether colored messages are enabled
112 */
113 public static boolean isColorEnabled() {
114 return JANSI ? Ansi.isEnabled() : false;
115 }
116
117 /**
118 * Create a default message buffer.
119 * @return a new buffer
120 */
121 public static MessageBuilder buffer() {
122 return JANSI ? new AnsiMessageBuilder() : new PlainMessageBuilder();
123 }
124
125 /**
126 * Create a message buffer with defined String builder.
127 * @param builder initial content of the message buffer
128 * @return a new buffer
129 */
130 public static MessageBuilder buffer(StringBuilder builder) {
131 return JANSI ? new AnsiMessageBuilder(builder) : new PlainMessageBuilder(builder);
132 }
133
134 /**
135 * Create a message buffer with an internal buffer of defined size.
136 * @param size size of the buffer
137 * @return a new buffer
138 */
139 public static MessageBuilder buffer(int size) {
140 return JANSI ? new AnsiMessageBuilder(size) : new PlainMessageBuilder(size);
141 }
142
143 /**
144 * Create a logger level renderer.
145 * @return a logger level renderer
146 * @since 3.2.0
147 */
148 @SuppressWarnings("checkstyle:magicnumber")
149 public static LoggerLevelRenderer level() {
150 return JANSI ? new AnsiMessageBuilder(20) : new PlainMessageBuilder(7);
151 }
152
153 /**
154 * Remove any ANSI code from a message (colors or other escape sequences).
155 * @param msg message eventually containing ANSI codes
156 * @return the message with ANSI codes removed
157 */
158 public static String stripAnsiCodes(String msg) {
159 return msg.replaceAll("\u001B\\[[;\\d]*[ -/]*[@-~]", "");
160 }
161
162 /**
163 * Register a shutdown hook with the JVM runtime, uninstalling Ansi support on
164 * JVM shutdown unless is has already been uninstalled at that time.
165 * <p>Delegates to {@link #doSystemUninstall()} for the actual uninstall procedure
166 *
167 * @see Runtime#addShutdownHook(Thread)
168 * @see MessageUtils#systemUninstall()
169 * @see #doSystemUninstall()
170 */
171 public static void registerShutdownHook() {
172 if (JANSI && shutdownHook == null) {
173 // No shutdown hook registered yet.
174 shutdownHook = new Thread() {
175 @Override
176 public void run() {
177 synchronized (STARTUP_SHUTDOWN_MONITOR) {
178 while (AnsiConsole.isInstalled()) {
179 doSystemUninstall();
180 }
181 }
182 }
183 };
184 Runtime.getRuntime().addShutdownHook(shutdownHook);
185 }
186 }
187
188 /**
189 * Get the terminal width or -1 if the width cannot be determined.
190 *
191 * @return the terminal width
192 */
193 public static int getTerminalWidth() {
194 if (JANSI) {
195 int width = AnsiConsole.getTerminalWidth();
196 return width > 0 ? width : -1;
197 } else {
198 return -1;
199 }
200 }
201 }