MalLogger.java

/*
 * Copyright 2019-2022 Foreseeti AB <https://foreseeti.com>
 *
 * Licensed 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
 *
 *     https://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.mal_lang.lib;

import java.util.Set;
import java.util.TreeSet;
import java.util.logging.ConsoleHandler;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

public class MalLogger extends Logger {
  @SuppressWarnings("serial")
  private static class MalLevel extends Level {
    public static final Level DEBUG = new MalLevel("DEBUG", Level.SEVERE.intValue() + 1);
    public static final Level INFO = new MalLevel("INFO", Level.SEVERE.intValue() + 2);
    public static final Level WARNING = new MalLevel("WARNING", Level.SEVERE.intValue() + 3);
    public static final Level ERROR = new MalLevel("ERROR", Level.SEVERE.intValue() + 4);

    private MalLevel(String name, int value) {
      super(name, value);
    }
  }

  private static class MalFormatter extends Formatter {
    @Override
    public String format(LogRecord record) {
      StringBuffer sb = new StringBuffer();
      sb.append(printLevel(record.getLoggerName(), record.getLevel()));
      sb.append(" ");
      sb.append(record.getMessage());
      sb.append(String.format("%n"));
      return sb.toString();
    }

    private String printLevel(String loggerName, Level level) {
      String colorInit = "";
      String colorClear = "";

      if (System.console() != null) {
        switch (level.getName()) {
          case "ERROR":
            colorInit = "\u001B[1;31m";
            break;
          case "WARNING":
            colorInit = "\u001B[1;33m";
            break;
          case "INFO":
            colorInit = "\u001B[1;34m";
            break;
          case "DEBUG":
            colorInit = "\u001B[1;36m";
            break;
          default:
        }
        colorClear = "\u001B[m";
      }
      return String.format("[%s%s %s%s]", colorInit, loggerName, level.getName(), colorClear);
    }
  }

  private class LogMessage implements Comparable<LogMessage> {
    public final Level level;
    public final String message;

    public LogMessage(Level level, String message) {
      this.level = level;
      this.message = message;
    }

    @Override
    public int compareTo(LogMessage o) {
      if (o instanceof LogMessagePosition) {
        return -1;
      }
      int cmp = Integer.compare(this.level.intValue(), o.level.intValue());
      if (cmp != 0) {
        return cmp;
      }
      return this.message.compareTo(o.message);
    }

    @Override
    public String toString() {
      return message;
    }
  }

  private class LogMessagePosition extends LogMessage {
    public final Position position;

    public LogMessagePosition(Level level, String message, Position position) {
      super(level, message);
      this.position = position;
    }

    @Override
    public int compareTo(LogMessage o) {
      if (!(o instanceof LogMessagePosition)) {
        return 1;
      }
      var other = (LogMessagePosition) o;
      int cmp = this.position.compareTo(other.position);
      if (cmp != 0) {
        return cmp;
      }
      cmp = Integer.compare(this.level.intValue(), other.level.intValue());
      if (cmp != 0) {
        return cmp;
      }
      return this.message.compareTo(other.message);
    }

    @Override
    public String toString() {
      return String.format("%s %s", position.posString(), message);
    }
  }

  private boolean verbose;
  private boolean debug;
  private boolean isBuffered;

  private Set<LogMessage> logMessages = new TreeSet<>();

  public MalLogger(String name) {
    this(name, false, false);
  }

  public MalLogger(String name, boolean verbose, boolean debug) {
    this(name, verbose, debug, true);
  }

  public MalLogger(String name, boolean verbose, boolean debug, boolean isBuffered) {
    this(name, null);
    this.verbose = verbose;
    this.debug = debug;
    this.isBuffered = isBuffered;
    if (debug) {
      setLevel(MalLevel.DEBUG);
    } else if (verbose) {
      setLevel(MalLevel.INFO);
    } else {
      setLevel(MalLevel.WARNING);
    }
  }

  private MalLogger(String name, String resourceBundleName) {
    super(name, resourceBundleName);
    setUseParentHandlers(false);
    ConsoleHandler handler = new ConsoleHandler();
    handler.setFormatter(new MalFormatter());
    addHandler(handler);
  }

  public boolean isVerbose() {
    return this.verbose;
  }

  public boolean isDebug() {
    return this.debug;
  }

  public boolean isBuffered() {
    return isBuffered;
  }

  private void log(LogMessage logMessage) {
    if (isBuffered) {
      logMessages.add(logMessage);
    } else {
      log(logMessage.level, logMessage.toString());
    }
  }

  public void debug(Position pos, String msg) {
    log(new LogMessagePosition(MalLevel.DEBUG, msg, pos));
  }

  public void debug(String msg) {
    log(new LogMessage(MalLevel.DEBUG, msg));
  }

  public void info(Position pos, String msg) {
    log(new LogMessagePosition(MalLevel.INFO, msg, pos));
  }

  @Override
  public void info(String msg) {
    log(new LogMessage(MalLevel.INFO, msg));
  }

  public void warning(Position pos, String msg) {
    log(new LogMessagePosition(MalLevel.WARNING, msg, pos));
  }

  @Override
  public void warning(String msg) {
    log(new LogMessage(MalLevel.WARNING, msg));
  }

  public void error(Position pos, String msg) {
    log(new LogMessagePosition(MalLevel.ERROR, msg, pos));
  }

  public void error(String msg) {
    log(new LogMessage(MalLevel.ERROR, msg));
  }

  public void print() {
    for (var logMessage : logMessages) {
      log(logMessage.level, logMessage.toString());
    }
  }
}