View Javadoc
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 }