1 package org.apache.maven.shared.utils.logging;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import org.fusesource.jansi.Ansi;
23 import org.fusesource.jansi.AnsiConsole;
24 import org.fusesource.jansi.AnsiMode;
25
26 /**
27 * Colored message utils, to manage colors consistently across plugins (only if Maven version is at least 3.5.0).
28 * For Maven version before 3.5.0, message built with this util will never add color.
29 * <p>
30 * Internally, <a href="http://fusesource.github.io/jansi/">Jansi</a> is used to render
31 * <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#Colors">ANSI colors</a> on any platform.
32 * @since 3.1.0
33 */
34 public class MessageUtils
35 {
36 private static final boolean JANSI;
37
38 /** Reference to the JVM shutdown hook, if registered */
39 private static Thread shutdownHook;
40
41 /** Synchronization monitor for the "uninstall" */
42 private static final Object STARTUP_SHUTDOWN_MONITOR = new Object();
43
44 static
45 {
46 boolean jansi = true;
47 try
48 {
49 // Jansi is provided by Maven core since 3.5.0
50 Class.forName( "org.fusesource.jansi.Ansi" );
51 }
52 catch ( ClassNotFoundException cnfe )
53 {
54 jansi = false;
55 }
56 JANSI = jansi;
57 }
58
59 /**
60 * Install color support.
61 * This method is called by Maven core, and calling it is not necessary in plugins.
62 */
63 public static void systemInstall()
64 {
65 if ( JANSI )
66 {
67 AnsiConsole.systemInstall();
68 }
69 }
70
71 /**
72 * Undo a previous {@link #systemInstall()}. If {@link #systemInstall()} was called
73 * multiple times, {@link #systemUninstall()} must be called call the same number of times before
74 * it is actually uninstalled.
75 */
76 public static void systemUninstall()
77 {
78 synchronized ( STARTUP_SHUTDOWN_MONITOR )
79 {
80 doSystemUninstall();
81
82 // hook can only set when Jansi is true
83 if ( shutdownHook != null )
84 {
85 try
86 {
87 Runtime.getRuntime().removeShutdownHook( shutdownHook );
88 }
89 catch ( IllegalStateException ex )
90 {
91 // ignore - VM is already shutting down
92 }
93 }
94 }
95 }
96
97 private static void doSystemUninstall()
98 {
99 if ( JANSI )
100 {
101 AnsiConsole.systemUninstall();
102 }
103 }
104
105 /**
106 * Enables message color (if Jansi is available).
107 * @param flag to enable Jansi
108 */
109 public static void setColorEnabled( boolean flag )
110 {
111 if ( JANSI )
112 {
113 AnsiConsole.out().setMode( flag ? AnsiMode.Force : AnsiMode.Strip );
114 Ansi.setEnabled( flag );
115 System.setProperty( AnsiConsole.JANSI_MODE,
116 flag ? AnsiConsole.JANSI_MODE_FORCE : AnsiConsole.JANSI_MODE_STRIP );
117 boolean installed = AnsiConsole.isInstalled();
118 while ( AnsiConsole.isInstalled() )
119 {
120 AnsiConsole.systemUninstall();
121 }
122 if ( installed )
123 {
124 AnsiConsole.systemInstall();
125 }
126 }
127 }
128
129 /**
130 * Is message color enabled: requires Jansi available (through Maven) and the color has not been disabled.
131 * @return whether colored messages are enabled
132 */
133 public static boolean isColorEnabled()
134 {
135 return JANSI ? Ansi.isEnabled() : false;
136 }
137
138 /**
139 * Create a default message buffer.
140 * @return a new buffer
141 */
142 public static MessageBuilder buffer()
143 {
144 return JANSI ? new AnsiMessageBuilder() : new PlainMessageBuilder();
145 }
146
147 /**
148 * Create a message buffer with defined String builder.
149 * @param builder initial content of the message buffer
150 * @return a new buffer
151 */
152 public static MessageBuilder buffer( StringBuilder builder )
153 {
154 return JANSI ? new AnsiMessageBuilder( builder ) : new PlainMessageBuilder( builder );
155 }
156
157 /**
158 * Create a message buffer with an internal buffer of defined size.
159 * @param size size of the buffer
160 * @return a new buffer
161 */
162 public static MessageBuilder buffer( int size )
163 {
164 return JANSI ? new AnsiMessageBuilder( size ) : new PlainMessageBuilder( size );
165 }
166
167 /**
168 * Create a logger level renderer.
169 * @return a logger level renderer
170 * @since 3.2.0
171 */
172 @SuppressWarnings( "checkstyle:magicnumber" )
173 public static LoggerLevelRenderer level()
174 {
175 return JANSI ? new AnsiMessageBuilder( 20 ) : new PlainMessageBuilder( 7 );
176 }
177
178 /**
179 * Remove any ANSI code from a message (colors or other escape sequences).
180 * @param msg message eventually containing ANSI codes
181 * @return the message with ANSI codes removed
182 */
183 public static String stripAnsiCodes( String msg )
184 {
185 return msg.replaceAll( "\u001B\\[[;\\d]*[ -/]*[@-~]", "" );
186 }
187
188 /**
189 * Register a shutdown hook with the JVM runtime, uninstalling Ansi support on
190 * JVM shutdown unless is has already been uninstalled at that time.
191 * <p>Delegates to {@link #doSystemUninstall()} for the actual uninstall procedure
192 *
193 * @see Runtime#addShutdownHook(Thread)
194 * @see MessageUtils#systemUninstall()
195 * @see #doSystemUninstall()
196 */
197 public static void registerShutdownHook()
198 {
199 if ( JANSI && shutdownHook == null )
200 {
201 // No shutdown hook registered yet.
202 shutdownHook = new Thread()
203 {
204 @Override
205 public void run()
206 {
207 synchronized ( STARTUP_SHUTDOWN_MONITOR )
208 {
209 while ( AnsiConsole.isInstalled() )
210 {
211 doSystemUninstall();
212 }
213 }
214 }
215 };
216 Runtime.getRuntime().addShutdownHook( shutdownHook );
217 }
218 }
219
220 /**
221 * Get the terminal width or -1 if the width cannot be determined.
222 *
223 * @return the terminal width
224 */
225 public static int getTerminalWidth()
226 {
227 if ( JANSI )
228 {
229 int width = AnsiConsole.getTerminalWidth();
230 return width > 0 ? width : -1;
231 }
232 else
233 {
234 return -1;
235 }
236 }
237 }