+ * Internally, Jansi is used to render + * ANSI colors on any platform. + * @since 3.1.0 + */ +public class MessageUtils { + private static final boolean JANSI; + + /** Reference to the JVM shutdown hook, if registered */ + private static Thread shutdownHook; + + /** Synchronization monitor for the "uninstall" */ + private static final Object STARTUP_SHUTDOWN_MONITOR = new Object(); + + static { + boolean jansi = true; + try { + // Jansi is provided by Maven core since 3.5.0 + Class.forName("org.fusesource.jansi.Ansi"); + } catch (ClassNotFoundException cnfe) { + jansi = false; + } + JANSI = jansi; + } + + /** + * Install color support. + * This method is called by Maven core, and calling it is not necessary in plugins. + */ + public static void systemInstall() { + if (JANSI) { + AnsiConsole.systemInstall(); + } + } + + /** + * Undo a previous {@link #systemInstall()}. If {@link #systemInstall()} was called + * multiple times, {@link #systemUninstall()} must be called call the same number of times before + * it is actually uninstalled. + */ + public static void systemUninstall() { + synchronized (STARTUP_SHUTDOWN_MONITOR) { + doSystemUninstall(); + + // hook can only set when Jansi is true + if (shutdownHook != null) { + try { + Runtime.getRuntime().removeShutdownHook(shutdownHook); + } catch (IllegalStateException ex) { + // ignore - VM is already shutting down + } + } + } + } + + private static void doSystemUninstall() { + if (JANSI) { + AnsiConsole.systemUninstall(); + } + } + + /** + * Enables message color (if Jansi is available). + * @param flag to enable Jansi + */ + public static void setColorEnabled(boolean flag) { + if (JANSI) { + AnsiConsole.out().setMode(flag ? AnsiMode.Force : AnsiMode.Strip); + Ansi.setEnabled(flag); + System.setProperty( + AnsiConsole.JANSI_MODE, flag ? AnsiConsole.JANSI_MODE_FORCE : AnsiConsole.JANSI_MODE_STRIP); + boolean installed = AnsiConsole.isInstalled(); + while (AnsiConsole.isInstalled()) { + AnsiConsole.systemUninstall(); + } + if (installed) { + AnsiConsole.systemInstall(); + } + } + } + + /** + * Is message color enabled: requires Jansi available (through Maven) and the color has not been disabled. + * @return whether colored messages are enabled + */ + public static boolean isColorEnabled() { + return JANSI ? Ansi.isEnabled() : false; + } + + /** + * Create a default message buffer. + * @return a new buffer + */ + public static MessageBuilder builder() { + return builder(new StringBuilder()); + } + + /** + * Create a message buffer with an internal buffer of defined size. + * @param size size of the buffer + * @return a new buffer + */ + public static MessageBuilder builder(int size) { + return builder(new StringBuilder(size)); + } + + /** + * Create a message buffer with defined String builder. + * @param builder initial content of the message buffer + * @return a new buffer + */ + public static MessageBuilder builder(StringBuilder builder) { + return JANSI && isColorEnabled() ? new JansiMessageBuilder(builder) : new DefaultMessageBuilder(builder); + } + + /** + * Remove any ANSI code from a message (colors or other escape sequences). + * @param msg message eventually containing ANSI codes + * @return the message with ANSI codes removed + */ + public static String stripAnsiCodes(String msg) { + return msg.replaceAll("\u001B\\[[;\\d]*[ -/]*[@-~]", ""); + } + + /** + * Register a shutdown hook with the JVM runtime, uninstalling Ansi support on + * JVM shutdown unless is has already been uninstalled at that time. + *
Delegates to {@link #doSystemUninstall()} for the actual uninstall procedure
+ *
+ * @see Runtime#addShutdownHook(Thread)
+ * @see MessageUtils#systemUninstall()
+ * @see #doSystemUninstall()
+ */
+ public static void registerShutdownHook() {
+ if (JANSI && shutdownHook == null) {
+ // No shutdown hook registered yet.
+ shutdownHook = new Thread() {
+ @Override
+ public void run() {
+ synchronized (STARTUP_SHUTDOWN_MONITOR) {
+ while (AnsiConsole.isInstalled()) {
+ doSystemUninstall();
+ }
+ }
+ }
+ };
+ Runtime.getRuntime().addShutdownHook(shutdownHook);
+ }
+ }
+
+ /**
+ * Get the terminal width or -1 if the width cannot be determined.
+ *
+ * @return the terminal width
+ */
+ public static int getTerminalWidth() {
+ if (JANSI) {
+ int width = AnsiConsole.getTerminalWidth();
+ return width > 0 ? width : -1;
+ } else {
+ return -1;
+ }
+ }
+}
diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/jansi/Style.java b/maven-embedder/src/main/java/org/apache/maven/cli/jansi/Style.java
new file mode 100644
index 0000000000..194bf64b55
--- /dev/null
+++ b/maven-embedder/src/main/java/org/apache/maven/cli/jansi/Style.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.cli.jansi;
+
+import java.util.Locale;
+
+import org.fusesource.jansi.Ansi;
+import org.fusesource.jansi.Ansi.Color;
+
+/**
+ * Configurable message styles.
+ * @since 3.1.0
+ */
+enum Style {
+ DEBUG("bold,cyan"),
+ INFO("bold,blue"),
+ WARNING("bold,yellow"),
+ ERROR("bold,red"),
+ SUCCESS("bold,green"),
+ FAILURE("bold,red"),
+ STRONG("bold"),
+ MOJO("green"),
+ PROJECT("cyan");
+
+ private final boolean bold;
+
+ private final boolean bright;
+
+ private final Color color;
+
+ private final boolean bgBright;
+
+ private final Color bgColor;
+
+ Style(String defaultValue) {
+ boolean currentBold = false;
+ boolean currentBright = false;
+ Color currentColor = null;
+ boolean currentBgBright = false;
+ Color currentBgColor = null;
+
+ String value = System.getProperty("style." + name().toLowerCase(Locale.ENGLISH), defaultValue)
+ .toLowerCase(Locale.ENGLISH);
+
+ for (String token : value.split(",")) {
+ if ("bold".equals(token)) {
+ currentBold = true;
+ } else if (token.startsWith("bg")) {
+ token = token.substring(2);
+ if (token.startsWith("bright")) {
+ currentBgBright = true;
+ token = token.substring(6);
+ }
+ currentBgColor = toColor(token);
+ } else {
+ if (token.startsWith("bright")) {
+ currentBright = true;
+ token = token.substring(6);
+ }
+ currentColor = toColor(token);
+ }
+ }
+
+ this.bold = currentBold;
+ this.bright = currentBright;
+ this.color = currentColor;
+ this.bgBright = currentBgBright;
+ this.bgColor = currentBgColor;
+ }
+
+ private static Color toColor(String token) {
+ for (Color color : Color.values()) {
+ if (color.toString().equalsIgnoreCase(token)) {
+ return color;
+ }
+ }
+ return null;
+ }
+
+ Ansi apply(Ansi ansi) {
+ if (bold) {
+ ansi.bold();
+ }
+ if (color != null) {
+ if (bright) {
+ ansi.fgBright(color);
+ } else {
+ ansi.fg(color);
+ }
+ }
+ if (bgColor != null) {
+ if (bgBright) {
+ ansi.bgBright(bgColor);
+ } else {
+ ansi.bg(bgColor);
+ }
+ }
+ return ansi;
+ }
+
+ @Override
+ public String toString() {
+ if (!bold && color == null && bgColor == null) {
+ return name();
+ }
+ StringBuilder sb = new StringBuilder(name() + '=');
+ if (bold) {
+ sb.append("bold");
+ }
+ if (color != null) {
+ if (sb.length() > 0) {
+ sb.append(',');
+ }
+ if (bright) {
+ sb.append("bright");
+ }
+ sb.append(color.name());
+ }
+ if (bgColor != null) {
+ if (sb.length() > 0) {
+ sb.append(',');
+ }
+ sb.append("bg");
+ if (bgBright) {
+ sb.append("bright");
+ }
+ sb.append(bgColor.name());
+ }
+ return sb.toString();
+ }
+}
diff --git a/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java b/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
index 7ba238dbc1..a68976bb37 100644
--- a/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
+++ b/maven-embedder/src/test/java/org/apache/maven/cli/MavenCliTest.java
@@ -35,6 +35,7 @@
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.maven.Maven;
+import org.apache.maven.cli.jansi.MessageUtils;
import org.apache.maven.cli.transfer.ConsoleMavenTransferListener;
import org.apache.maven.cli.transfer.QuietMavenTransferListener;
import org.apache.maven.cli.transfer.Slf4jMavenTransferListener;
@@ -44,7 +45,6 @@
import org.apache.maven.execution.ProjectActivation;
import org.apache.maven.model.root.DefaultRootLocator;
import org.apache.maven.project.MavenProject;
-import org.apache.maven.shared.utils.logging.MessageUtils;
import org.apache.maven.toolchain.building.ToolchainsBuildingRequest;
import org.apache.maven.toolchain.building.ToolchainsBuildingResult;
import org.codehaus.plexus.DefaultPlexusContainer;
diff --git a/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java b/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java
index 59b8dc282a..3e260124b5 100644
--- a/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java
+++ b/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java
@@ -22,10 +22,11 @@
import com.google.common.collect.ImmutableList;
import org.apache.commons.io.FilenameUtils;
+import org.apache.maven.cli.jansi.JansiMessageBuilderFactory;
+import org.apache.maven.cli.jansi.MessageUtils;
import org.apache.maven.execution.ExecutionEvent;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.project.MavenProject;
-import org.apache.maven.shared.utils.logging.MessageUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
@@ -44,6 +45,7 @@ class ExecutionEventLoggerTest {
private Logger logger;
private ExecutionEventLogger executionEventLogger;
+ private JansiMessageBuilderFactory messageBuilderFactory = new JansiMessageBuilderFactory();
@BeforeAll
static void setUp() {
@@ -59,7 +61,7 @@ static void tearDown() {
void beforeEach() {
logger = mock(Logger.class);
when(logger.isInfoEnabled()).thenReturn(true);
- executionEventLogger = new ExecutionEventLogger(logger);
+ executionEventLogger = new ExecutionEventLogger(messageBuilderFactory, logger);
}
@Test
@@ -143,25 +145,25 @@ void testTerminalWidth() {
when(event.getProject()).thenReturn(project);
// default width
- new ExecutionEventLogger(logger, -1).projectStarted(event);
+ new ExecutionEventLogger(messageBuilderFactory, logger, -1).projectStarted(event);
Mockito.verify(logger).info("----------------------------[ maven-plugin ]----------------------------");
// terminal width: 30
- new ExecutionEventLogger(logger, 30).projectStarted(event);
+ new ExecutionEventLogger(messageBuilderFactory, logger, 30).projectStarted(event);
Mockito.verify(logger).info("------------------[ maven-plugin ]------------------");
// terminal width: 70
- new ExecutionEventLogger(logger, 70).projectStarted(event);
+ new ExecutionEventLogger(messageBuilderFactory, logger, 70).projectStarted(event);
Mockito.verify(logger).info("-----------------------[ maven-plugin ]-----------------------");
// terminal width: 110
- new ExecutionEventLogger(logger, 110).projectStarted(event);
+ new ExecutionEventLogger(messageBuilderFactory, logger, 110).projectStarted(event);
Mockito.verify(logger)
.info(
"-------------------------------------------[ maven-plugin ]-------------------------------------------");
// terminal width: 200
- new ExecutionEventLogger(logger, 200).projectStarted(event);
+ new ExecutionEventLogger(messageBuilderFactory, logger, 200).projectStarted(event);
Mockito.verify(logger)
.info(
"-----------------------------------------------------[ maven-plugin ]-----------------------------------------------------");
diff --git a/maven-slf4j-provider/pom.xml b/maven-slf4j-provider/pom.xml
index 5721c27316..dcc964f387 100644
--- a/maven-slf4j-provider/pom.xml
+++ b/maven-slf4j-provider/pom.xml
@@ -38,12 +38,16 @@ under the License.