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.cli.jansi; 20 21 import org.apache.maven.api.services.MessageBuilder; 22 import org.apache.maven.internal.impl.DefaultMessageBuilder; 23 import org.fusesource.jansi.Ansi; 24 import org.fusesource.jansi.AnsiConsole; 25 import org.fusesource.jansi.AnsiMode; 26 27 /** 28 * Colored message utils, to manage colors. This is the core implementation of the 29 * {@link JansiMessageBuilderFactory} and {@link JansiMessageBuilder} classes. 30 * This class should not be used outside of maven-embedder and the public 31 * {@link org.apache.maven.api.services.MessageBuilderFactory} should be used instead. 32 * <p> 33 * Internally, <a href="http://fusesource.github.io/jansi/">Jansi</a> is used to render 34 * <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#Colors">ANSI colors</a> on any platform. 35 * <p> 36 * 37 * @see MessageBuilder 38 * @see org.apache.maven.api.services.MessageBuilderFactory 39 * @see JansiMessageBuilderFactory 40 * @see JansiMessageBuilder 41 * @since 4.0.0 42 */ 43 public class MessageUtils { 44 private static final boolean JANSI; 45 46 /** Reference to the JVM shutdown hook, if registered */ 47 private static Thread shutdownHook; 48 49 /** Synchronization monitor for the "uninstall" */ 50 private static final Object STARTUP_SHUTDOWN_MONITOR = new Object(); 51 52 static { 53 boolean jansi = true; 54 try { 55 // Jansi is provided by Maven core since 3.5.0 56 Class.forName("org.fusesource.jansi.Ansi"); 57 } catch (ClassNotFoundException cnfe) { 58 jansi = false; 59 } 60 JANSI = jansi; 61 } 62 63 /** 64 * Install color support. 65 * This method is called by Maven core, and calling it is not necessary in plugins. 66 */ 67 public static void systemInstall() { 68 if (JANSI) { 69 AnsiConsole.systemInstall(); 70 } 71 } 72 73 /** 74 * Undo a previous {@link #systemInstall()}. If {@link #systemInstall()} was called 75 * multiple times, {@link #systemUninstall()} must be called call the same number of times before 76 * it is actually uninstalled. 77 */ 78 public static void systemUninstall() { 79 synchronized (STARTUP_SHUTDOWN_MONITOR) { 80 doSystemUninstall(); 81 82 // hook can only set when Jansi is true 83 if (shutdownHook != null) { 84 try { 85 Runtime.getRuntime().removeShutdownHook(shutdownHook); 86 } catch (IllegalStateException ex) { 87 // ignore - VM is already shutting down 88 } 89 } 90 } 91 } 92 93 private static void doSystemUninstall() { 94 if (JANSI) { 95 AnsiConsole.systemUninstall(); 96 } 97 } 98 99 /** 100 * Enables message color (if Jansi is available). 101 * @param flag to enable Jansi 102 */ 103 public static void setColorEnabled(boolean flag) { 104 if (JANSI) { 105 AnsiConsole.out().setMode(flag ? AnsiMode.Force : AnsiMode.Strip); 106 Ansi.setEnabled(flag); 107 System.setProperty( 108 AnsiConsole.JANSI_MODE, flag ? AnsiConsole.JANSI_MODE_FORCE : AnsiConsole.JANSI_MODE_STRIP); 109 boolean installed = AnsiConsole.isInstalled(); 110 while (AnsiConsole.isInstalled()) { 111 AnsiConsole.systemUninstall(); 112 } 113 if (installed) { 114 AnsiConsole.systemInstall(); 115 } 116 } 117 } 118 119 /** 120 * Is message color enabled: requires Jansi available (through Maven) and the color has not been disabled. 121 * @return whether colored messages are enabled 122 */ 123 public static boolean isColorEnabled() { 124 return JANSI ? Ansi.isEnabled() : false; 125 } 126 127 /** 128 * Create a default message buffer. 129 * @return a new buffer 130 */ 131 public static MessageBuilder builder() { 132 return builder(new StringBuilder()); 133 } 134 135 /** 136 * Create a message buffer with an internal buffer of defined size. 137 * @param size size of the buffer 138 * @return a new buffer 139 */ 140 public static MessageBuilder builder(int size) { 141 return builder(new StringBuilder(size)); 142 } 143 144 /** 145 * Create a message buffer with defined String builder. 146 * @param builder initial content of the message buffer 147 * @return a new buffer 148 */ 149 public static MessageBuilder builder(StringBuilder builder) { 150 return JANSI && isColorEnabled() ? new JansiMessageBuilder(builder) : new DefaultMessageBuilder(builder); 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 }