AnsiMessagePrinter.java
- package ship.util;
- import static hera.util.StringUtils.removeSuffix;
- import static hera.util.ValidationUtils.assertEquals;
- import static hera.util.ValidationUtils.assertNotNull;
- import static hera.util.ValidationUtils.assertTrue;
- import static org.slf4j.LoggerFactory.getLogger;
- import java.io.IOException;
- import java.io.PrintStream;
- import java.io.StringReader;
- import java.io.StringWriter;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.Stack;
- import lombok.Getter;
- import lombok.RequiredArgsConstructor;
- import lombok.Setter;
- import org.slf4j.Logger;
- public class AnsiMessagePrinter implements MessagePrinter {
- public static final String COLOR_RESET = "\u001B[0m";
- public static final String COLOR_BLACK = "\u001B[30m";
- public static final String COLOR_RED = "\u001B[31m";
- public static final String COLOR_GREEN = "\u001B[32m";
- public static final String COLOR_YELLOW = "\u001B[33m";
- public static final String COLOR_BLUE = "\u001B[34m";
- public static final String COLOR_MAGENTA = "\u001B[35m";
- public static final String COLOR_CYAN = "\u001B[36m";
- public static final String COLOR_WHITE = "\u001B[37m";
- public static final String COLOR_BRIGHT_BLACK = removeSuffix(COLOR_BLACK, "m") + ";1m";
- public static final String COLOR_BRIGHT_RED = removeSuffix(COLOR_RED, "m") + ";1m";
- public static final String COLOR_BRIGHT_GREEN = removeSuffix(COLOR_GREEN, "m") + ";1m";
- public static final String COLOR_BRIGHT_YELLOW = removeSuffix(COLOR_YELLOW, "m") + ";1m";
- public static final String COLOR_BRIGHT_BLUE = removeSuffix(COLOR_BLUE, "m") + ";1m";
- public static final String COLOR_BRIGHT_MAGENTA = removeSuffix(COLOR_MAGENTA, "m") + ";1m";
- public static final String COLOR_BRIGHT_CYAN = removeSuffix(COLOR_CYAN, "m") + ";1m";
- public static final String COLOR_BRIGHT_WHITE = removeSuffix(COLOR_WHITE, "m") + ";1m";
- public static final String BG_BLACK = "\u001B[40m";
- public static final String BG_RED = "\u001B[41m";
- public static final String BG_GREEN = "\u001B[42m";
- public static final String BG_YELLOW = "\u001B[43m";
- public static final String BG_BLUE = "\u001B[44m";
- public static final String BG_MAGENTA = "\u001B[45m";
- public static final String BG_CYAN = "\u001B[46m";
- public static final String BG_WHITE = "\u001B[47m";
- public static final String BG_BRIGHT_BLACK = removeSuffix(BG_BLACK, "m") + ";1m";
- public static final String BG_BRIGHT_RED = removeSuffix(BG_RED, "m") + ";1m";
- public static final String BG_BRIGHT_GREEN = removeSuffix(BG_GREEN, "m") + ";1m";
- public static final String BG_BRIGHT_YELLOW = removeSuffix(BG_YELLOW, "m") + ";1m";
- public static final String BG_BRIGHT_BLUE = removeSuffix(BG_BLUE, "m") + ";1m";
- public static final String BG_BRIGHT_MAGENTA = removeSuffix(BG_MAGENTA, "m") + ";1m";
- public static final String BG_BRIGHT_CYAN = removeSuffix(BG_CYAN, "m") + ";1m";
- public static final String BG_BRIGHT_WHITE = removeSuffix(BG_WHITE, "m") + ";1m";
- protected final transient Logger logger = getLogger(getClass());
- @Getter
- @Setter
- protected String resetCode = COLOR_RESET;
- @Getter
- @Setter
- protected Map<String, String> colors = new HashMap<>();
- protected final PrintStream out;
- /**
- * Constructor with stream.
- *
- * @param out stream to print out
- */
- public AnsiMessagePrinter(final PrintStream out) {
- this.out = out;
- colors.put("black", COLOR_BLACK);
- colors.put("red", COLOR_RED);
- colors.put("green", COLOR_GREEN);
- colors.put("yellow", COLOR_YELLOW);
- colors.put("blue", COLOR_BLUE);
- colors.put("magenta", COLOR_MAGENTA);
- colors.put("cyan", COLOR_CYAN);
- colors.put("white", COLOR_WHITE);
- colors.put("bright_black", COLOR_BRIGHT_BLACK);
- colors.put("bright_red", COLOR_BRIGHT_RED);
- colors.put("bright_green", COLOR_BRIGHT_GREEN);
- colors.put("bright_yellow", COLOR_BRIGHT_YELLOW);
- colors.put("bright_blue", COLOR_BRIGHT_BLUE);
- colors.put("bright_magenta", COLOR_BRIGHT_MAGENTA);
- colors.put("bright_cyan", COLOR_BRIGHT_CYAN);
- colors.put("bright_white", COLOR_BRIGHT_WHITE);
- colors.put("bg_black", BG_BLACK);
- colors.put("bg_red", BG_RED);
- colors.put("bg_green", BG_GREEN);
- colors.put("bg_yellow", BG_YELLOW);
- colors.put("bg_blue", BG_BLUE);
- colors.put("bg_magenta", BG_MAGENTA);
- colors.put("bg_cyan", BG_CYAN);
- colors.put("bg_white", BG_WHITE);
- colors.put("bg_bright_black", BG_BRIGHT_BLACK);
- colors.put("bg_bright_red", BG_BRIGHT_RED);
- colors.put("bg_bright_green", BG_BRIGHT_GREEN);
- colors.put("bg_bright_yellow", BG_BRIGHT_YELLOW);
- colors.put("bg_bright_blue", BG_BRIGHT_BLUE);
- colors.put("bg_bright_magenta", BG_BRIGHT_MAGENTA);
- colors.put("bg_bright_cyan", BG_BRIGHT_CYAN);
- colors.put("bg_bright_white", BG_BRIGHT_WHITE);
- }
- @RequiredArgsConstructor
- abstract class State {
- @Getter
- protected final Stack<String> tags;
- @Getter
- protected final StringWriter writer;
- abstract State next(int ch);
- }
- class Normal extends State {
- protected static final int CH_TAG_START = '<';
- protected static final int CH_ESCAPE = '!';
- public Normal(final Stack<String> tags, final StringWriter writer) {
- super(tags, writer);
- }
- @Override
- public State next(int ch) {
- if (CH_ESCAPE == ch) {
- return new Escape(tags, writer);
- } else if (CH_TAG_START == ch) {
- return new TagOpen(tags, writer);
- } else {
- writer.write((char) ch);
- return this;
- }
- }
- }
- class Escape extends State {
- public Escape(final Stack<String> tags, final StringWriter writer) {
- super(tags, writer);
- }
- @Override
- public State next(int ch) {
- writer.write((char) ch);
- return new Normal(tags, writer);
- }
- }
- class TagOpen extends State {
- protected static final int CH_TAG_CLOSE = '/';
- public TagOpen(final Stack<String> tags, final StringWriter writer) {
- super(tags, writer);
- }
- @Override
- public State next(int ch) {
- if (ch == CH_TAG_CLOSE) {
- return new CloseColor(tags, writer);
- } else {
- return new OpenColor(tags, writer).next(ch);
- }
- }
- }
- class OpenColor extends State {
- protected static final int CH_TAG_END = '>';
- protected final StringBuilder color = new StringBuilder();
- public OpenColor(final Stack<String> tags, final StringWriter writer) {
- super(tags, writer);
- }
- @Override
- public State next(int ch) {
- if (ch == CH_TAG_END) {
- final String colorName = color.toString();
- // check valid
- tags.push(colorName);
- logger.debug("Tags: {}", tags);
- final String colorCode = colors.get(colorName);
- assertNotNull(colorCode, "Unknown color: " + colorName);
- writer.write(colorCode);
- return new Normal(tags, writer);
- } else {
- color.append((char) ch);
- return this;
- }
- }
- }
- class CloseColor extends State {
- protected static final int CH_TAG_END = '>';
- protected final StringBuilder color = new StringBuilder();
- public CloseColor(final Stack<String> tags, final StringWriter writer) {
- super(tags, writer);
- }
- @Override
- public State next(int ch) {
- if (ch == CH_TAG_END) {
- final String colorName = color.toString();
- // check valid
- final String openColorName = tags.pop();
- logger.debug("Tags: {}", tags);
- assertEquals(openColorName, colorName,
- "The closing tag not matched: " + openColorName + ", " + colorName);
- color.delete(0, color.length());
- if (tags.isEmpty()) {
- writer.write(resetCode);
- } else {
- writer.write(colors.get(tags.peek()));
- }
- return new Normal(tags, writer);
- } else {
- color.append((char) ch);
- return this;
- }
- }
- }
- /**
- * Format tagged message with ansi color.
- *
- * @param message message to format
- *
- * @return text with ansi code
- */
- public String format(final String message) {
- if (null == message) {
- return null;
- }
- final StringReader reader = new StringReader(message);
- int ch = 0;
- State state = new Normal(new Stack<>(), new StringWriter());
- try {
- while (0 <= (ch = reader.read())) {
- logger.trace("Char: {}", (char) ch);
- final State old = state;
- state = state.next(ch);
- if (old != state) {
- logger.debug("{} -> {}", old, state);
- }
- }
- } catch (final IOException ex) {
- throw new IllegalStateException();
- }
- logger.debug("State: {}", state);
- logger.debug("Tags: {}", state.getTags());
- assertTrue(state instanceof Normal, "Expression is invalid: " + state);
- assertTrue(state.getTags().isEmpty(), "The closing tag missed: " + state.getTags());
- return state.getWriter().toString();
- }
- public void print(final String format, Object...args) {
- out.print(this.format(String.format(format, args)));
- }
- public void println() {
- out.println();
- }
- public void println(final String format, Object... args) {
- out.println(this.format(String.format(format, args)));
- }
- @Override
- public void flush() {
- out.flush();
- }
- }