/**
 * Copyright 2019 Anthony Trinh
 *
 * 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
 *
 *    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 ch.qos.logback.core.pattern;

/**
 * FormattingInfo instances contain the information obtained when parsing
 * formatting modifiers in conversion modifiers.
 *
 * @author Ceki Gülcü
 */
public class FormatInfo {
  private int min = Integer.MIN_VALUE;
  private int max = Integer.MAX_VALUE;
  private boolean leftPad = true;
  private boolean leftTruncate = true;

  public FormatInfo() {
  }

  public FormatInfo(int min, int max) {
    this.min = min;
    this.max = max;
  }

  public FormatInfo(int min, int max, boolean leftPad, boolean leftTruncate) {
    this.min = min;
    this.max = max;
    this.leftPad = leftPad;
    this.leftTruncate = leftTruncate;
  }

  /**
   * This method is used to parse a string such as "5", ".7", "5.7" or "-5.7" into
   * a FormatInfo.
   *
   * @param str A String to convert into a FormatInfo object
   * @return A newly created and appropriately initialized FormatInfo object.
   * @throws IllegalArgumentException string is null or ends with '.'
   */
  public static FormatInfo valueOf(String str) throws IllegalArgumentException {
    if (str == null) {
      throw new NullPointerException("Argument cannot be null");
    }

    FormatInfo fi = new FormatInfo();

    int indexOfDot = str.indexOf('.');
    String minPart = null;
    String maxPart = null;
    if (indexOfDot != -1) {
      minPart = str.substring(0, indexOfDot);
      if (indexOfDot + 1 == str.length()) {
        throw new IllegalArgumentException("Formatting string [" + str
            + "] should not end with '.'");
      } else {
        maxPart = str.substring(indexOfDot + 1);
      }
    } else {
      minPart = str;
    }

    if (minPart != null && minPart.length() > 0) {
      int min = Integer.parseInt(minPart);
      if (min >= 0) {
        fi.min = min;
      } else {
        fi.min = -min;
        fi.leftPad = false;
      }
    }

    if (maxPart != null && maxPart.length() > 0) {
      int max = Integer.parseInt(maxPart);
      if (max >= 0) {
        fi.max = max;
      } else {
        fi.max = -max;
        fi.leftTruncate = false;
      }
    }

    return fi;

  }

  public boolean isLeftPad() {
    return leftPad;
  }

  public void setLeftPad(boolean leftAlign) {
    this.leftPad = leftAlign;
  }

  public int getMax() {
    return max;
  }

  public void setMax(int max) {
    this.max = max;
  }

  public int getMin() {
    return min;
  }

  public void setMin(int min) {
    this.min = min;
  }

  public boolean isLeftTruncate() {
    return leftTruncate;
  }

  public void setLeftTruncate(boolean leftTruncate) {
    this.leftTruncate = leftTruncate;
  }

  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (!(o instanceof FormatInfo)) {
      return false;
    }
    FormatInfo r = (FormatInfo) o;

    return (min == r.min) && (max == r.max) && (leftPad == r.leftPad)
        && (leftTruncate == r.leftTruncate);
  }

  @Override
  public int hashCode() {
    int result = min;
    result = 31 * result + max;
    result = 31 * result + (leftPad ? 1 : 0);
    result = 31 * result + (leftTruncate ? 1 : 0);
    return result;
  }

  public String toString() {
    return "FormatInfo(" + min + ", " + max + ", " + leftPad + ", "
        + leftTruncate + ")";
  }
}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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
 *
 *    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 ch.qos.logback.core.rolling.helper;

import java.io.File;

import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.RolloverFailure;
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.util.EnvUtil;
import ch.qos.logback.core.util.FileUtil;

/**
 * Utility class to help solving problems encountered while renaming files.
 *
 * @author Ceki Gulcu
 */
public class RenameUtil extends ContextAwareBase {

  static String RENAMING_ERROR_URL = CoreConstants.CODES_URL + "#renamingError";

  /**
   * A relatively robust file renaming method which in case of failure due to
   * src and target being on different volumes, falls back onto
   * renaming by copying.
   *
   * @param src
   * @param target
   * @throws RolloverFailure
   */
  public void rename(String src, String target) throws RolloverFailure {
    if (src.equals(target)) {
      addWarn("Source and target files are the same [" + src + "]. Skipping.");
      return;
    }
    File srcFile = new File(src);

    if (srcFile.exists()) {
      File targetFile = new File(target);
      createMissingTargetDirsIfNecessary(targetFile);

      addInfo("Renaming file [" + srcFile + "] to [" + targetFile + "]");

      boolean result = srcFile.renameTo(targetFile);

      if (!result) {
        addWarn("Failed to rename file [" + srcFile + "] as [" + targetFile + "].");

        Boolean areOnDifferentVolumes = areOnDifferentVolumes(srcFile, targetFile);
        if (Boolean.TRUE.equals(areOnDifferentVolumes)) {
          addWarn("Detected different file systems for source [" + src + "] and target [" + target + "]. Attempting rename by copying.");
          renameByCopying(src, target);
          return;
        } else {
          addWarn("Please consider leaving the [file] option of " + RollingFileAppender.class.getSimpleName() + " empty.");
          addWarn("See also " + RENAMING_ERROR_URL);
        }
      }
    } else {
      throw new RolloverFailure("File [" + src + "] does not exist.");
    }
  }

  /**
   * Attempts tp determine whether both files are on different volumes. Returns true if we could determine that
   * the files are on different volumes. Returns false otherwise or if an error occurred while doing the check.
   *
   * @param srcFile
   * @param targetFile
   * @return true if on different volumes, false otherwise or if an error occurred
   */
   Boolean areOnDifferentVolumes(File srcFile, File targetFile) throws RolloverFailure {
    if (!EnvUtil.isJDK7OrHigher()) {
      return false;
    }

    // target file is not certain to exist but its parent has to exist given the call hierarchy of this method
    File parentOfTarget = targetFile.getAbsoluteFile().getParentFile();

    if (parentOfTarget == null) {
      addWarn("Parent of target file ["+targetFile+"] is null");
      return null;
    }
    if (!parentOfTarget.exists()) {
      addWarn("Parent of target file [" + targetFile + "] does not exist");
      return null;
    }

    try {
      boolean onSameFileStore = FileStoreUtil.areOnSameFileStore(srcFile, parentOfTarget);
      return !onSameFileStore;
    } catch (RolloverFailure rf) {
      addWarn("Error while checking file store equality", rf);
      return null;
    }
  }

  public void renameByCopying(String src, String target)
          throws RolloverFailure {

    FileUtil fileUtil = new FileUtil(getContext());
    fileUtil.copy(src, target);

    File srcFile = new File(src);
   if (!srcFile.delete()) {
      addWarn("Could not delete " + src);
    }

  }

  void createMissingTargetDirsIfNecessary(File toFile) throws RolloverFailure {
    boolean result = FileUtil.createMissingParentDirectories(toFile);
    if (!result) {
      throw new RolloverFailure("Failed to create parent directories for ["
              + toFile.getAbsolutePath() + "]");
    }
  }

  @Override
  public String toString() {
    return "c.q.l.co.rolling.helper.RenameUtil";
  }
}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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
 *
 *    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 ch.qos.logback.core.util;

import java.lang.reflect.Constructor;
import java.util.Properties;

import ch.qos.logback.core.Context;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.android.SystemPropertiesProxy;
import ch.qos.logback.core.spi.ContextAware;
import ch.qos.logback.core.spi.PropertyContainer;
import ch.qos.logback.core.spi.ScanException;
import ch.qos.logback.core.subst.NodeToStringTransformer;

/**
 * @author Ceki Gulcu
 */
public class OptionHelper {

  public static Object instantiateByClassName(String className,
                                              Class<?> superClass, Context context) throws IncompatibleClassException,
          DynamicClassLoadingException {
    ClassLoader classLoader = Loader.getClassLoaderOfObject(context);
    return instantiateByClassName(className, superClass, classLoader);
  }

  public static Object instantiateByClassNameAndParameter(String className,
                                                          Class<?> superClass, Context context, Class<?> type, Object param) throws IncompatibleClassException,
          DynamicClassLoadingException {
    ClassLoader classLoader = Loader.getClassLoaderOfObject(context);
    return instantiateByClassNameAndParameter(className, superClass, classLoader, type, param);
  }

  public static Object instantiateByClassName(String className,
                                              Class<?> superClass, ClassLoader classLoader)
          throws IncompatibleClassException, DynamicClassLoadingException {
    return instantiateByClassNameAndParameter(className, superClass, classLoader, null, null);
  }

  public static Object instantiateByClassNameAndParameter(String className,
                                                          Class<?> superClass, ClassLoader classLoader, Class<?> type, Object parameter)
          throws IncompatibleClassException, DynamicClassLoadingException {

    if (className == null) {
      throw new NullPointerException();
    }
    try {
      Class<?> classObj = null;
      classObj = classLoader.loadClass(className);
      if (!superClass.isAssignableFrom(classObj)) {
        throw new IncompatibleClassException(superClass, classObj);
      }
      if (type == null) {
        return classObj.getConstructor().newInstance();
      } else {
        Constructor<?> constructor = classObj.getConstructor(type);
        return constructor.newInstance(parameter);
      }
    } catch (IncompatibleClassException ice) {
      throw ice;
    } catch (Throwable t) {
      throw new DynamicClassLoadingException("Failed to instantiate type "
              + className, t);
    }
  }

  /**
   * Find the value corresponding to key in props.
   * Then perform variable substitution on the found value.
   */
  // public static String findAndSubst(String key, Properties props) {
  // String value = props.getProperty(key);
  //
  // if (value == null) {
  // return null;
  // }
  //
  // try {
  // return substVars(value, props);
  // } catch (IllegalArgumentException e) {
  // return value;
  // }
  // }
  final static String DELIM_START = "${";
  final static char DELIM_STOP = '}';
  final static String DELIM_DEFAULT = ":-";

  final static int DELIM_START_LEN = 2;
  final static int DELIM_STOP_LEN = 1;
  final static int DELIM_DEFAULT_LEN = 2;

  final static String _IS_UNDEFINED = "_IS_UNDEFINED";

  /**
   * @see #substVars(String, PropertyContainer, PropertyContainer)
   * @param val string to be evaluated
   * @param pc1 property container
   * @return the substituted string
   */
  public static String substVars(String val, PropertyContainer pc1) {
    return substVars(val, pc1, null);
  }

  /**
   * See  http://logback.qos.ch/manual/configuration.html#variableSubstitution
   * @param input string to be evaluated
   * @param pc0 primary property container
   * @param pc1 secondary property container
   * @return the substituted string
   */
  public static String substVars(String input, PropertyContainer pc0, PropertyContainer pc1) {
    try {
      return NodeToStringTransformer.substituteVariable(input, pc0, pc1);
    } catch (ScanException e) {
      throw new IllegalArgumentException("Failed to parse input [" + input + "]", e);
    }
  }

  public static String propertyLookup(String key, PropertyContainer pc1,
                                      PropertyContainer pc2) {
    String value = null;
    // first try the props passed as parameter
    value = pc1.getProperty(key);

    // then try  the pc2
    if (value == null && pc2 != null) {
      value = pc2.getProperty(key);
    }
    // then try in System properties
    if (value == null) {
      value = getSystemProperty(key, null);
    }
    if (value == null) {
      value = getEnv(key);
    }
    return value;
  }

  /**
   * Very similar to System.getProperty except that the
   * {@link SecurityException} is absorbed.
   *
   * @param key The key to search for.
   * @param def The default value to return.
   * @return the string value of the system property, or the default value if
   *         there is no property with that key.
   */
  public static String getSystemProperty(String key, String def) {
    try {
      return System.getProperty(key, def);
    } catch (SecurityException e) {
      return def;
    }
  }

  /**
   * Lookup a key from the environment.
   *
   * @param key the environment variable's key
   * @return value corresponding to key from the OS environment
   */
  public static String getEnv(String key) {
    try {
      return System.getenv(key);
    } catch (SecurityException e) {
      return null;
    }
  }

  /**
   * Gets an Android system property
   *
   * @param key The key to search for
   * @return the string value of the system property
   */
  public static String getAndroidSystemProperty(String key) {
    try {
      return SystemPropertiesProxy.getInstance().get(key, null);
    } catch (IllegalArgumentException e) {
      return null;
    }
  }

  /**
   * Very similar to System.getProperty except that the
   * {@link SecurityException} is absorbed. Also checks Android
   * system properties as a fallback.
   *
   * @param key The key to search for.
   * @return the string value of the system property.
   */
  public static String getSystemProperty(String key) {
    try {
      String prop = System.getProperty(key);
      return (prop == null) ? getAndroidSystemProperty(key) : prop;
    } catch (SecurityException e) {
      return null;
    }
  }

  public static void setSystemProperties(ContextAware contextAware, Properties props) {
    for (Object o : props.keySet()) {
      String key = (String) o;
      String value = props.getProperty(key);
      setSystemProperty(contextAware, key, value);
    }
  }

  public static void setSystemProperty(ContextAware contextAware, String key, String value) {
    try {
      System.setProperty(key, value);
    } catch (SecurityException e) {
      contextAware.addError("Failed to set system property [" + key + "]", e);
    }
  }

  /**
   * Very similar to {@link System#getProperties()} except that the
   * {@link SecurityException} is absorbed.
   *
   * @return the system properties
   */
  public static Properties getSystemProperties() {
    try {
      return System.getProperties();
    } catch (SecurityException e) {
      return new Properties();
    }
  }

  /**
   * Return a String[] of size two. The first item containing the key part and the second item
   * containing a default value specified by the user. The second item will be null if no default value
   * is specified.
   *
   * @param key
   * @return array, where item 0 is the key, and item 1 is the default value
   */
  static public String[] extractDefaultReplacement(String key) {
    String[] result = new String[2];
    if(key == null)
      return result;

    result[0] = key;
    int d = key.indexOf(DELIM_DEFAULT);
    if (d != -1) {
      result[0] = key.substring(0, d);
      result[1] = key.substring(d + DELIM_DEFAULT_LEN);
    }
    return result;
  }

  /**
   * If value is "true", then true is returned. If
   * value is "false", then true is returned.
   * Otherwise, default is returned.
   *

   * Case of value is unimportant.
   * @param value string to be evaluated
   * @param defaultValue default value if value could not be converted
   * @return the equivalent boolean value
   */
  public static boolean toBoolean(String value, boolean defaultValue) {
    if (value == null) {
      return defaultValue;
    }

    String trimmedVal = value.trim();

    if ("true".equalsIgnoreCase(trimmedVal)) {
      return true;
    }

    if ("false".equalsIgnoreCase(trimmedVal)) {
      return false;
    }

    return defaultValue;
  }

  public static boolean isEmpty(String str) {
    return ((str == null) || CoreConstants.EMPTY_STRING.equals(str));
  }

}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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
 *
 *    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 ch.qos.logback.classic;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

import ch.qos.logback.classic.util.LoggerNameUtil;
import org.slf4j.ILoggerFactory;
import org.slf4j.Marker;

import ch.qos.logback.classic.spi.LoggerComparator;
import ch.qos.logback.classic.spi.LoggerContextListener;
import ch.qos.logback.classic.spi.LoggerContextVO;
import ch.qos.logback.classic.spi.TurboFilterList;
import ch.qos.logback.classic.turbo.TurboFilter;
import ch.qos.logback.core.ContextBase;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.boolex.EventEvaluator;
import ch.qos.logback.core.spi.FilterReply;
import ch.qos.logback.core.spi.LifeCycle;
import ch.qos.logback.core.status.StatusListener;
import ch.qos.logback.core.status.StatusManager;
import ch.qos.logback.core.status.WarnStatus;

/**
 * LoggerContext glues many of the logback-classic components together. In
 * principle, every logback-classic component instance is attached either
 * directly or indirectly to a LoggerContext instance. Just as importantly
 * LoggerContext implements the {@link ILoggerFactory} acting as the
 * manufacturing source of {@link Logger} instances.
 *
 * @author Ceki Gulcu
 */
public class LoggerContext extends ContextBase implements ILoggerFactory,
        LifeCycle {

  /** Default setting of packaging data in stack traces */
  public static final boolean DEFAULT_PACKAGING_DATA = false;

  final Logger root;
  private int size;
  private int noAppenderWarning = 0;
  final private List<LoggerContextListener> loggerContextListenerList = new ArrayList<LoggerContextListener>();

  private Map<String, Logger> loggerCache;

  private LoggerContextVO loggerContextRemoteView;
  private final TurboFilterList turboFilterList = new TurboFilterList();
  private boolean packagingDataEnabled = DEFAULT_PACKAGING_DATA;

  private int maxCallerDataDepth = ClassicConstants.DEFAULT_MAX_CALLEDER_DATA_DEPTH;

  int resetCount = 0;
  private List<String> frameworkPackages;

  public LoggerContext() {
    super();
    this.loggerCache = new ConcurrentHashMap<String, Logger>();

    this.loggerContextRemoteView = new LoggerContextVO(this);
    this.root = new Logger(Logger.ROOT_LOGGER_NAME, null, this);
    this.root.setLevel(Level.DEBUG);
    loggerCache.put(Logger.ROOT_LOGGER_NAME, root);
    initEvaluatorMap();
    size = 1;
    this.frameworkPackages = new ArrayList<String>();
  }

  void initEvaluatorMap() {
    putObject(CoreConstants.EVALUATOR_MAP, new HashMap<String, EventEvaluator<?>>());
  }

  /**
   * A new instance of LoggerContextRemoteView needs to be created each time the
   * name or propertyMap (including keys or values) changes.
   */
  private void updateLoggerContextVO() {
    loggerContextRemoteView = new LoggerContextVO(this);
  }

  @Override
  public void putProperty(String key, String val) {
    super.putProperty(key, val);
    updateLoggerContextVO();
  }

  public void putProperties(Properties props) {
    for (String key : props.stringPropertyNames()) {
      super.putProperty(key, props.getProperty(key));
    }
    updateLoggerContextVO();
  }

  @Override
  public void setName(String name) {
    super.setName(name);
    updateLoggerContextVO();
  }

  public final Logger getLogger(final Class<?> clazz) {
    return getLogger(clazz.getName());
  }

  public Logger getLogger(final String name) {

    if (name == null) {
      throw new IllegalArgumentException("name argument cannot be null");
    }

    // if we are asking for the root logger, then let us return it without
    // wasting time
    if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
      return root;
    }

    int i = 0;
    Logger logger = root;

    // check if the desired logger exists, if it does, return it
    // without further ado.
    Logger childLogger = (Logger) loggerCache.get(name);
    // if we have the child, then let us return it without wasting time
    if (childLogger != null) {
      return childLogger;
    }

    // if the desired logger does not exist, them create all the loggers
    // in between as well (if they don't already exist)
    String childName;
    while (true) {
      int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
      if (h == -1) {
        childName = name;
      } else {
        childName = name.substring(0, h);
      }
      // move i left of the last point
      i = h + 1;
      synchronized (logger) {
        childLogger = logger.getChildByName(childName);
        if (childLogger == null) {
          childLogger = logger.createChildByName(childName);
          loggerCache.put(childName, childLogger);
          incSize();
        }
      }
      logger = childLogger;
      if (h == -1) {
        return childLogger;
      }
    }
  }

  private void incSize() {
    size++;
  }

  int size() {
    return size;
  }

  /**
   * Check if the named logger exists in the hierarchy. If so return its
   * reference, otherwise returns null.
   *
   * @param name the name of the logger to search for.
   */
  public Logger exists(String name) {
    return (Logger) loggerCache.get(name);
  }

  final void noAppenderDefinedWarning(final Logger logger) {
    if (noAppenderWarning++ == 0) {
      getStatusManager().add(
              new WarnStatus("No appenders present in context [" + getName()
                      + "] for logger [" + logger.getName() + "].", logger));
    }
  }

  public List<Logger> getLoggerList() {
    Collection<Logger> collection = loggerCache.values();
    List<Logger> loggerList = new ArrayList<Logger>(collection);
    Collections.sort(loggerList, new LoggerComparator());
    return loggerList;
  }

  public LoggerContextVO getLoggerContextRemoteView() {
    return loggerContextRemoteView;
  }

  public void setPackagingDataEnabled(boolean packagingDataEnabled) {
    this.packagingDataEnabled = packagingDataEnabled;
  }

  public boolean isPackagingDataEnabled() {
    return packagingDataEnabled;
  }

  /**
   * This method clears all internal properties, except internal status messages,
   * closes all appenders, removes any turboFilters, fires an OnReset event,
   * removes all status listeners, removes all context listeners
   * (except those which are reset resistant).
   *

   * As mentioned above, internal status messages survive resets.
   */
  @Override
  public void reset() {
    resetCount++;
    super.reset();
    initEvaluatorMap();
    initCollisionMaps();
    root.recursiveReset();
    resetTurboFilterList();
    cancelScheduledTasks();
    fireOnReset();
    resetListenersExceptResetResistant();
    resetStatusListeners();
  }

  private void cancelScheduledTasks() {
    for (ScheduledFuture<?> sf : scheduledFutures) {
      sf.cancel(false);
    }
    scheduledFutures.clear();
  }

  private void resetStatusListeners() {
    StatusManager sm = getStatusManager();
    for (StatusListener sl : sm.getCopyOfStatusListenerList()) {
      sm.remove(sl);
    }
  }

  public TurboFilterList getTurboFilterList() {
    return turboFilterList;
  }

  public void addTurboFilter(TurboFilter newFilter) {
    turboFilterList.add(newFilter);
  }

  /**
   * First processPriorToRemoval all registered turbo filters and then clear the registration
   * list.
   */
  public void resetTurboFilterList() {
    for (TurboFilter tf : turboFilterList) {
      tf.stop();
    }
    turboFilterList.clear();
  }

  final FilterReply getTurboFilterChainDecision_0_3OrMore(final Marker marker,
                                                          final Logger logger, final Level level, final String format,
                                                          final Object[] params, final Throwable t) {
    if (turboFilterList.size() == 0) {
      return FilterReply.NEUTRAL;
    }
    return turboFilterList.getTurboFilterChainDecision(marker, logger, level,
            format, params, t);
  }

  final FilterReply getTurboFilterChainDecision_1(final Marker marker,
                                                  final Logger logger, final Level level, final String format,
                                                  final Object param, final Throwable t) {
    if (turboFilterList.size() == 0) {
      return FilterReply.NEUTRAL;
    }
    return turboFilterList.getTurboFilterChainDecision(marker, logger, level,
            format, new Object[]{param}, t);
  }

  final FilterReply getTurboFilterChainDecision_2(final Marker marker,
                                                  final Logger logger, final Level level, final String format,
                                                  final Object param1, final Object param2, final Throwable t) {
    if (turboFilterList.size() == 0) {
      return FilterReply.NEUTRAL;
    }
    return turboFilterList.getTurboFilterChainDecision(marker, logger, level,
            format, new Object[]{param1, param2}, t);
  }

  // === start listeners ==============================================
  public void addListener(LoggerContextListener listener) {
    loggerContextListenerList.add(listener);
  }

  public void removeListener(LoggerContextListener listener) {
    loggerContextListenerList.remove(listener);
  }

  private void resetListenersExceptResetResistant() {
    List<LoggerContextListener> toRetain = new ArrayList<LoggerContextListener>();

    for (LoggerContextListener lcl : loggerContextListenerList) {
      if (lcl.isResetResistant()) {
        toRetain.add(lcl);
      }
    }
    loggerContextListenerList.retainAll(toRetain);
  }

  private void resetAllListeners() {
    loggerContextListenerList.clear();
  }

  public List<LoggerContextListener> getCopyOfListenerList() {
    return new ArrayList<LoggerContextListener>(loggerContextListenerList);
  }

  void fireOnLevelChange(Logger logger, Level level) {
    for (LoggerContextListener listener : loggerContextListenerList) {
      listener.onLevelChange(logger, level);
    }
  }

  private void fireOnReset() {
    for (LoggerContextListener listener : loggerContextListenerList) {
      listener.onReset(this);
    }
  }

  private void fireOnStart() {
    for (LoggerContextListener listener : loggerContextListenerList) {
      listener.onStart(this);
    }
  }

  private void fireOnStop() {
    for (LoggerContextListener listener : loggerContextListenerList) {
      listener.onStop(this);
    }
  }

  // === end listeners ==============================================

  public void start() {
    super.start();
    fireOnStart();
  }

  public void stop() {
    reset();
    fireOnStop();
    resetAllListeners();
    super.stop();
  }

  @Override
  public String toString() {
    return this.getClass().getName() + "[" + getName() + "]";
  }

  public int getMaxCallerDataDepth() {
    return maxCallerDataDepth;
  }

  public void setMaxCallerDataDepth(int maxCallerDataDepth) {
    this.maxCallerDataDepth = maxCallerDataDepth;
  }

  /**
   * List of packages considered part of the logging framework such that they are never considered
   * as callers of the logging framework. This list used to compute the caller for logging events.
   *

   * To designate package "com.foo" as well as all its subpackages as being part of the logging framework, simply add
   * "com.foo" to this list.
   *
   * @return list of framework packages
   */
  public List<String> getFrameworkPackages() {
    return frameworkPackages;
  }
}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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
 *
 *    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 ch.qos.logback.classic.spi;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Collections;
import java.util.Map;

import org.slf4j.MDC;
import org.slf4j.Marker;
import org.slf4j.helpers.MessageFormatter;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.util.LogbackMDCAdapter;
import org.slf4j.spi.MDCAdapter;

/**
 * The internal representation of logging events. When an affirmative decision
 * is made to log then a LoggingEvent instance is created. This
 * instance is passed around to the different logback-classic components.
 *

 * Writers of logback-classic components such as appenders should be aware of
 * that some of the LoggingEvent fields are initialized lazily. Therefore, an
 * appender wishing to output data to be later correctly read by a receiver,
 * must initialize "lazy" fields prior to writing them out. See the
 * {@link #prepareForDeferredProcessing()} method for the exact list.
 *

 *
 * @author Ceki Gülcü
 * @author Sébastien Pennec
 */
public class LoggingEvent implements ILoggingEvent {

  /**
   * Fully qualified name of the calling Logger class. This field does not
   * survive serialization.
   *

   *

   * Note that the getCallerInformation() method relies on this fact.
   */
  transient String fqnOfLoggerClass;

  /**
   * The name of thread in which this logging event was generated.
   */
  private String threadName;

  private String loggerName;
  private LoggerContext loggerContext;
  private LoggerContextVO loggerContextVO;

  /**
   * Level of logging event.
   *

   *

   * This field should not be accessed directly. You should use the
   * {@link #getLevel} method instead.
   *

   */
  private transient Level level;

  private String message;

  // we gain significant space at serialization time by marking
  // formattedMessage as transient and constructing it lazily in
  // getFormattedMessage()
  transient String formattedMessage;

  private transient Object[] argumentArray;

  private ThrowableProxy throwableProxy;

  private StackTraceElement[] callerDataArray;

  private Marker marker;

  private Map<String, String> mdcPropertyMap;

  /**
   * The number of milliseconds elapsed from 1/1/1970 until logging event was
   * created.
   */
  private long timeStamp;

  public LoggingEvent() {
  }

  public LoggingEvent(String fqcn, Logger logger, Level level, String message,
                      Throwable throwable, Object[] argArray) {
    this.fqnOfLoggerClass = fqcn;
    this.loggerName = logger.getName();
    this.loggerContext = logger.getLoggerContext();
    this.loggerContextVO = loggerContext.getLoggerContextRemoteView();
    this.level = level;

    this.message = message;
    this.argumentArray = argArray;

    if(throwable == null) {
      throwable = extractThrowableAnRearrangeArguments(argArray);
    }

    if (throwable != null) {
      this.throwableProxy = new ThrowableProxy(throwable);
      LoggerContext lc = logger.getLoggerContext();
      if (lc.isPackagingDataEnabled()) {
        this.throwableProxy.calculatePackagingData();
      }
    }

    timeStamp = System.currentTimeMillis();
  }

  private Throwable extractThrowableAnRearrangeArguments(Object[] argArray) {
    Throwable extractedThrowable = EventArgUtil.extractThrowable(argArray);
    if(EventArgUtil.successfulExtraction(extractedThrowable)) {
      this.argumentArray = EventArgUtil.trimmedCopy(argArray);
    }
    return extractedThrowable;
  }

  public void setArgumentArray(Object[] argArray) {
    if (this.argumentArray != null) {
      throw new IllegalStateException("argArray has been already set");
    }
    this.argumentArray = argArray;
  }

  public Object[] getArgumentArray() {
    return this.argumentArray;
  }

  public Level getLevel() {
    return level;
  }

  public String getLoggerName() {
    return loggerName;
  }

  public void setLoggerName(String loggerName) {
    this.loggerName = loggerName;
  }

  public String getThreadName() {
    if (threadName == null) {
      threadName = (Thread.currentThread()).getName();
    }
    return threadName;
  }

  /**
   * @param threadName The threadName to set.
   * @throws IllegalStateException If threadName has been already set.
   */
  public void setThreadName(String threadName) throws IllegalStateException {
    if (this.threadName != null) {
      throw new IllegalStateException("threadName has been already set");
    }
    this.threadName = threadName;
  }

  /**
   * Returns the throwable information contained within this event. May be
   * null if there is no such information.
   */
  public IThrowableProxy getThrowableProxy() {
    return throwableProxy;
  }

  /**
   * Set this event's throwable information.
   * @param tp desired throwable proxy info
   */
  public void setThrowableProxy(ThrowableProxy tp) {
    if (throwableProxy != null) {
      throw new IllegalStateException("ThrowableProxy has been already set.");
    } else {
      throwableProxy = tp;
    }
  }

  /**
   * This method should be called prior to serializing an event. It should also
   * be called when using asynchronous or deferred logging.
   *

   * Note that due to performance concerns, this method does NOT extract caller
   * data. It is the responsibility of the caller to extract caller information.
   */
  public void prepareForDeferredProcessing() {
    this.getFormattedMessage();
    this.getThreadName();
    // fixes http://jira.qos.ch/browse/LBCLASSIC-104
    this.getMDCPropertyMap();
  }

  public LoggerContextVO getLoggerContextVO() {
    return loggerContextVO;
  }

  public void setLoggerContextRemoteView(LoggerContextVO loggerContextVO) {
    this.loggerContextVO = loggerContextVO;
  }

  public String getMessage() {
    return message;
  }

  public void setMessage(String message) {
    if (this.message != null) {
      throw new IllegalStateException(
              "The message for this event has been set already.");
    }
    this.message = message;
  }

  public long getTimeStamp() {
    return timeStamp;
  }

  public void setTimeStamp(long timeStamp) {
    this.timeStamp = timeStamp;
  }

  public void setLevel(Level level) {
    if (this.level != null) {
      throw new IllegalStateException(
              "The level has been already set for this event.");
    }
    this.level = level;
  }

  /**
   * Get the caller information for this logging event. If caller information is
   * null at the time of its invocation, this method extracts location
   * information. The collected information is cached for future use.
   *

   * Note that after serialization it is impossible to correctly extract caller
   * information.
   *

   */
  public StackTraceElement[] getCallerData() {
    if (callerDataArray == null) {
      callerDataArray = CallerData.extract(new Throwable(), fqnOfLoggerClass,
              loggerContext.getMaxCallerDataDepth(), loggerContext.getFrameworkPackages());
    }
    return callerDataArray;
  }

  public boolean hasCallerData() {
    return (callerDataArray != null);
  }

  public void setCallerData(StackTraceElement[] callerDataArray) {
    this.callerDataArray = callerDataArray;
  }

  public Marker getMarker() {
    return marker;
  }

  public void setMarker(Marker marker) {
    if (this.marker != null) {
      throw new IllegalStateException(
              "The marker has been already set for this event.");
    }
    this.marker = marker;
  }

  public long getContextBirthTime() {
    return loggerContextVO.getBirthTime();
  }

  // lazy computation as suggested in LOGBACK-495
  public String getFormattedMessage() {
    if (formattedMessage != null) {
      return formattedMessage;
    }
    if (argumentArray != null) {
      formattedMessage = MessageFormatter.arrayFormat(message, argumentArray)
              .getMessage();
    } else {
      formattedMessage = message;
    }

    return formattedMessage;
  }

  public Map<String, String> getMDCPropertyMap() {
    // populate mdcPropertyMap if null
    if (mdcPropertyMap == null) {
      MDCAdapter mdc = MDC.getMDCAdapter();
      if (mdc instanceof LogbackMDCAdapter)
        mdcPropertyMap = ((LogbackMDCAdapter) mdc).getPropertyMap();
      else
        mdcPropertyMap = mdc.getCopyOfContextMap();
    }
    // mdcPropertyMap still null, use emptyMap()
    if (mdcPropertyMap == null)
      mdcPropertyMap = Collections.emptyMap();

    return mdcPropertyMap;
  }

  /**
   * Set the MDC map for this event.
   *
   * @param map map of MDC properties to values
   * @since 1.0.8
   */
  public void setMDCPropertyMap(Map<String, String> map) {
    if (mdcPropertyMap != null) {
      throw new IllegalStateException(
              "The MDCPropertyMap has been already set for this event.");
    }
    this.mdcPropertyMap = map;

  }

  /**
   * Synonym for [@link #getMDCPropertyMap}.
   *
   * @deprecated Replaced by [@link #getMDCPropertyMap}
   */
  public Map<String, String> getMdc() {
    return getMDCPropertyMap();
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append('[');
    sb.append(level).append("] ");
    sb.append(getFormattedMessage());
    return sb.toString();
  }

  /**
   * LoggerEventVO instances should be used for serialization. Use
   * {@link LoggingEventVO#build(ILoggingEvent) build} method to create the LoggerEventVO instance.
   *
   * @since 1.0.11
   */
  private void writeObject(ObjectOutputStream out) throws IOException {
    throw new UnsupportedOperationException(this.getClass() + " does not support serialization. " +
            "Use LoggerEventVO instance instead. See also LoggerEventVO.build method.");
  }

}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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
 *
 *    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 ch.qos.logback.classic.pattern;

import ch.qos.logback.classic.spi.ILoggingEvent;

import java.util.Map;

import static ch.qos.logback.core.util.OptionHelper.extractDefaultReplacement;

public class MDCConverter extends ClassicConverter {

  private String key;
  private String defaultValue = "";

  @Override
  public void start() {
		String[] keyInfo = extractDefaultReplacement(getFirstOption());
		key = keyInfo[0];
    if (keyInfo[1] != null) {
      defaultValue = keyInfo[1];
    }
    super.start();
  }

  @Override
  public void stop() {
    key = null;
    super.stop();
  }

  @Override
  public String convert(ILoggingEvent event) {
    Map<String, String> mdcPropertyMap = event.getMDCPropertyMap();

    if (mdcPropertyMap == null) {
      return defaultValue;
    }

    if (key == null) {
      return outputMDCForAllKeys(mdcPropertyMap);
    } else {

      String value = mdcPropertyMap.get(key);
      if (value != null) {
        return value;
      } else {
        return defaultValue;
      }
    }
  }

  /**
   * if no key is specified, return all the values present in the MDC, in the format "k1=v1, k2=v2, ..."
   */
  private String outputMDCForAllKeys(Map<String, String> mdcPropertyMap) {
    StringBuilder buf = new StringBuilder();
    boolean first = true;
    for (Map.Entry<String, String> entry : mdcPropertyMap.entrySet()) {
      if (first) {
        first = false;
      } else {
        buf.append(", ");
      }
      //format: key0=value0, key1=value1
      buf.append(entry.getKey()).append('=').append(entry.getValue());
    }
    return buf.toString();
  }
}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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
 *
 *    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 ch.qos.logback.core.property;

import ch.qos.logback.core.PropertyDefinerBase;
import ch.qos.logback.core.util.OptionHelper;

import java.io.File;

/**
 * In conjunction with {@link ch.qos.logback.core.joran.action.PropertyAction} sets
 * the named variable to "true" if the file specified by {@link #setPath(String) path}
 * property exists, to "false" otherwise.
 *
 * @see #getPropertyValue()
 *
 * @author Ceki Gücü
 */
public class FileExistsPropertyDefiner extends PropertyDefinerBase {

  String path;

  public String getPath() {
    return path;
  }

  /**
   * Sets the path for the file to search for.
   *
   * @param path the file path
   */
  public void setPath(String path) {
    this.path = path;
  }

  /**
   * Returns "true" if the file specified by {@link #setPath(String) path} property exists.
   * Returns "false" otherwise.
   *
   * @return "true"|"false" depending on the existence of file
   */
  public String getPropertyValue() {
    if (OptionHelper.isEmpty(path)) {
      addError("The \"path\" property must be set.");
      return null;
    }

    File file = new File(path);
    return booleanAsStr(file.exists());
  }
}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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
 *
 *    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 ch.qos.logback.core.net.server;

import java.io.Serializable;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executor;

/**
 * A {@link ServerRunner} that listens for connections from remote receiver
 * component clients and delivers logging events to all connected clients.
 *
 * @author Carl Harris
 */
class RemoteReceiverServerRunner
    extends ConcurrentServerRunner<RemoteReceiverClient> {

  private final int clientQueueSize;

  /**
   * Constructs a new server runner.
   * @param listener the listener from which the server will accept new
   *    clients
   * @param executor that will be used to execute asynchronous tasks
   *    on behalf of the runner.
   * @param queueSize size of the event queue that will be maintained for
   *    each client
   */
  public RemoteReceiverServerRunner(
      ServerListener<RemoteReceiverClient> listener, Executor executor,
      int clientQueueSize) {
    super(listener, executor);
    this.clientQueueSize = clientQueueSize;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected boolean configureClient(RemoteReceiverClient client) {
    client.setContext(getContext());
    client.setQueue(new ArrayBlockingQueue<Serializable>(clientQueueSize));
    return true;
  }

}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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
 *
 *    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 ch.qos.logback.classic.spi;

import ch.qos.logback.core.CoreConstants;

import java.util.List;

/**
 * This class computes caller data returning the result in the form
 * of a StackTraceElement array.
 *
 * @author Ceki Gülcü
 */
public class CallerData {

  /**
   * When caller information is not available this constant is used for file
   * name, method name, etc.
   */
  public static final String NA = "?";

  // All logger call's in log4j-over-slf4j use the Category class
  private static final String LOG4J_CATEGORY = "org.apache.log4j.Category";
  private static final String SLF4J_BOUNDARY = "org.slf4j.Logger";

  /**
   * When caller information is not available this constant is used for the line
   * number.
   */
  public static final int LINE_NA = -1;

  public static final String CALLER_DATA_NA = "?#?:?" + CoreConstants.LINE_SEPARATOR;

  /**
   * This value is returned in case no caller data could be extracted.
   */
  public static final StackTraceElement[] EMPTY_CALLER_DATA_ARRAY = new StackTraceElement[0];

  /**
   * Extract caller data information as an array based on a Throwable passed as
   * parameter
   *
   * @param t Throwable to extract data from
   * @param fqnOfInvokingClass Fully qualified name of invoking class
   * @param maxDepth maximum stack depth
   * @param frameworkPackageList list of framework package names to search
   * @return caller stack trace array
   */
  public static StackTraceElement[] extract(Throwable t,
                                            String fqnOfInvokingClass, final int maxDepth,
                                            List<String> frameworkPackageList) {
    if (t == null) {
      return null;
    }

    StackTraceElement[] steArray = t.getStackTrace();
    StackTraceElement[] callerDataArray;

    int found = LINE_NA;
    for (int i = 0; i < steArray.length; i++) {
      if (isInFrameworkSpace(steArray[i].getClassName(),
              fqnOfInvokingClass, frameworkPackageList)) {
        // the caller is assumed to be the next stack frame, hence the +1.
        found = i + 1;
      } else {
        if (found != LINE_NA) {
          break;
        }
      }
    }

    // we failed to extract caller data
    if (found == LINE_NA) {
      return EMPTY_CALLER_DATA_ARRAY;
    }

    int availableDepth = steArray.length - found;
    int desiredDepth = maxDepth < (availableDepth) ? maxDepth : availableDepth;

    callerDataArray = new StackTraceElement[desiredDepth];
    for (int i = 0; i < desiredDepth; i++) {
      callerDataArray[i] = steArray[found + i];
    }
    return callerDataArray;
  }

  static boolean isInFrameworkSpace(String currentClass,
                                    String fqnOfInvokingClass, List<String> frameworkPackageList) {
    // the check for org.apache.log4j.Category class is intended to support
    // log4j-over-slf4j. it solves http://bugzilla.slf4j.org/show_bug.cgi?id=66
    if (currentClass.equals(fqnOfInvokingClass) || currentClass.equals(LOG4J_CATEGORY)
            || currentClass.startsWith(SLF4J_BOUNDARY) || isInFrameworkSpaceList(currentClass, frameworkPackageList)) {
      return true;
    } else {
      return false;
    }
  }

  /**
   * Is currentClass present in the list of packages considered part of the logging framework?
   */
  private static boolean isInFrameworkSpaceList(String currentClass, List<String> frameworkPackageList) {
    if (frameworkPackageList == null)
      return false;

    for (String s : frameworkPackageList) {
      if (currentClass.startsWith(s))
        return true;
    }
    return false;
  }

  /**
   * Returns a StackTraceElement where all string fields are set to {@link #NA} and line number is set to {@link #LINE_NA}.
   *
   * @return StackTraceElement with values set to NA constants.
   * @since 1.0.10
   */
  public static StackTraceElement naInstance() {
    return new StackTraceElement(NA, NA, NA, LINE_NA);
  }

}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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
 *
 *    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 ch.qos.logback.core;

import java.util.ArrayList;
import java.util.List;

import ch.qos.logback.core.helpers.CyclicBuffer;
import ch.qos.logback.core.spi.LogbackLock;
import ch.qos.logback.core.status.OnConsoleStatusListener;
import ch.qos.logback.core.status.Status;
import ch.qos.logback.core.status.StatusListener;
import ch.qos.logback.core.status.StatusManager;
import ch.qos.logback.core.status.WarnStatus;

public class BasicStatusManager implements StatusManager {

  public static final int MAX_HEADER_COUNT = 150;
  public static final int TAIL_SIZE = 150;

  int count = 0;

  // protected access was requested in http://jira.qos.ch/browse/LBCORE-36
  final protected List<Status> statusList = new ArrayList<Status>();
  final protected CyclicBuffer<Status> tailBuffer = new CyclicBuffer<Status>(
      TAIL_SIZE);
  final protected LogbackLock statusListLock = new LogbackLock();

  int level = Status.INFO;

  // protected access was requested in http://jira.qos.ch/browse/LBCORE-36
  final protected List<StatusListener> statusListenerList = new ArrayList<StatusListener>();
  final protected LogbackLock statusListenerListLock = new LogbackLock();

  // Note on synchronization
  // This class contains two separate locks statusListLock and
  // statusListenerListLock guarding respectively the statusList+tailBuffer and
  // statusListenerList fields. The locks are used internally
  // without cycles. They are exposed to derived classes which should be careful
  // not to create deadlock cycles.

  /**
   * Add a new status object.
   *
   * @param newStatus
   *                the status message to add
   */
  public void add(Status newStatus) {
    // LBCORE-72: fire event before the count check
    fireStatusAddEvent(newStatus);

    count++;
    if (newStatus.getLevel() > level) {
      level = newStatus.getLevel();
    }

    synchronized (statusListLock) {
      if (statusList.size() < MAX_HEADER_COUNT) {
        statusList.add(newStatus);
      } else {
        tailBuffer.add(newStatus);
      }
    }

  }

  public List<Status> getCopyOfStatusList() {
    synchronized (statusListLock) {
      List<Status> tList = new ArrayList<Status>(statusList);
      tList.addAll(tailBuffer.asList());
      return tList;
    }
  }

  private void fireStatusAddEvent(Status status) {
    synchronized (statusListenerListLock) {
      for (StatusListener sl : statusListenerList) {
        sl.addStatusEvent(status);
      }
    }
  }

  public void clear() {
    synchronized (statusListLock) {
      count = 0;
      statusList.clear();
      tailBuffer.clear();
    }
  }

  public int getLevel() {
    return level;
  }

  public int getCount() {
    return count;
  }

  /**
   * This implementation does not allow duplicate installations of OnConsoleStatusListener
   * @param listener
   */
  public boolean add(StatusListener listener) {
    synchronized (statusListenerListLock) {
      if (listener instanceof OnConsoleStatusListener) {
        boolean alreadyPresent = checkForPresence(statusListenerList, listener.getClass());
        if (alreadyPresent) {
          return false;
        }
      }
      statusListenerList.add(listener);
    }
    return true;
  }

  private boolean checkForPresence(List<StatusListener> statusListenerList, Class<?> aClass) {
    for (StatusListener e: statusListenerList) {
      if (e.getClass() == aClass) {
        return true;
      }
    }
    return false;
  }

  public boolean addUniquely(StatusListener newListener, Object origin) {
    for (StatusListener listener : getCopyOfStatusListenerList()) {
      if (listener.getClass().isInstance(newListener)) {
        add(new WarnStatus("A previous listener of type [" + listener.getClass()
                + "] has been already registered. Skipping double registration.", origin));
        return false;
      }
    }
    add(newListener);
    return true;
  }

  public void remove(StatusListener listener) {
    synchronized (statusListenerListLock) {
      statusListenerList.remove(listener);
    }
  }

  public List<StatusListener> getCopyOfStatusListenerList() {
    synchronized (statusListenerListLock) {
      return new ArrayList<StatusListener>(statusListenerList);
    }
  }

}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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
 *
 *    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 ch.qos.logback.core.joran.spi;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.Vector;

import org.xml.sax.Attributes;
import org.xml.sax.Locator;

import ch.qos.logback.core.Context;
import ch.qos.logback.core.joran.action.Action;
import ch.qos.logback.core.joran.action.ImplicitAction;
import ch.qos.logback.core.joran.event.BodyEvent;
import ch.qos.logback.core.joran.event.EndEvent;
import ch.qos.logback.core.joran.event.StartEvent;
import ch.qos.logback.core.spi.ContextAwareImpl;

/**
 * Interpreter is Joran's main driving class. It extends SAX
 * {@link org.xml.sax.helpers.DefaultHandler DefaultHandler} which invokes
 * various {@link Action actions} according to predefined patterns.
 *
 *

 * Patterns are kept in a {@link RuleStore} which is programmed to store and
 * then later produce the applicable actions for a given pattern.
 *
 *

 * The pattern corresponding to a top level <a> element is the string
 * "a".
 *
 *

 * The pattern corresponding to an element <b> embedded within a top level
 * <a> element is the string "a/b".
 *
 *

 * The pattern corresponding to an <b> and any level of nesting is
 * "*/b. Thus, the * character placed at the beginning of a pattern
 * serves as a wildcard for the level of nesting.
 *
 * Conceptually, this is very similar to the API of commons-digester. Joran
 * offers several small advantages. First and foremost, it offers support for
 * implicit actions which result in a significant leap in flexibility. Second,
 * in our opinion better error reporting capability. Third, it is self-reliant.
 * It does not depend on other APIs, in particular commons-logging which is too
 * unreliable. Last but not least, Joran is quite tiny and is expected to remain
 * so.
 *
 * @author Ceki Gülcuü
 *
 */
public class Interpreter {
  private static List<Action> EMPTY_LIST = new Vector<Action>(0);

  final private RuleStore ruleStore;
  final private InterpretationContext interpretationContext;
  final private ArrayList<ImplicitAction> implicitActions;
  final private CAI_WithLocatorSupport cai;
  private ElementPath elementPath;
  Locator locator;
  EventPlayer eventPlayer;

  /**
   * The actionListStack contains a list of actions that are executing
   * for the given XML element.
   *
   * A list of actions is pushed by the {link #startElement} and popped by
   * {@link #endElement}.
   *
   */
  Stack<List<Action>> actionListStack;

  /**
   * If the skip nested is set, then we skip all its nested elements until it is
   * set back to null at when the element's end is reached.
   */
  ElementPath skip = null;

  public Interpreter(Context context, RuleStore rs, ElementPath initialElementPath) {
    this.cai = new CAI_WithLocatorSupport(context, this);
    ruleStore = rs;
    interpretationContext = new InterpretationContext(context, this);
    implicitActions = new ArrayList<ImplicitAction>(3);
    this.elementPath = initialElementPath;
    actionListStack = new Stack<List<Action>>();
    eventPlayer = new EventPlayer(this);
  }

  public EventPlayer getEventPlayer() {
    return eventPlayer;
  }

  public void setInterpretationContextPropertiesMap(
      Map<String, String> propertiesMap) {
    interpretationContext.setPropertiesMap(propertiesMap);
  }

  /**
   * @deprecated replaced by {@link #getInterpretationContext()}
   * @return the interpretation context of the interpreter
   */
  public InterpretationContext getExecutionContext() {
    return getInterpretationContext();
  }

  public InterpretationContext getInterpretationContext() {
    return interpretationContext;
  }

  public void startDocument() {
  }

  public void startElement(StartEvent se) {
    setDocumentLocator(se.getLocator());
    startElement(se.namespaceURI, se.localName, se.qName, se.attributes);
  }

  private void startElement(String namespaceURI, String localName,
      String qName, Attributes atts) {

    String tagName = getTagName(localName, qName);
    elementPath.push(tagName);

    if (skip != null) {
      // every startElement pushes an action list
      pushEmptyActionList();
      return;
    }

    List<Action> applicableActionList = getApplicableActionList(elementPath, atts);
    if (applicableActionList != null) {
      actionListStack.add(applicableActionList);
      callBeginAction(applicableActionList, tagName, atts);
    } else {
      // every startElement pushes an action list
      pushEmptyActionList();
      String errMsg = "no applicable action for [" + tagName
          + "], current ElementPath  is [" + elementPath + "]";
      cai.addError(errMsg);
    }
  }

  /**
   * This method is used to
   */
  private void pushEmptyActionList() {
    actionListStack.add(EMPTY_LIST);
  }

  public void characters(BodyEvent be) {

    setDocumentLocator(be.locator);

    String body = be.getText();
    List<Action> applicableActionList = actionListStack.peek();

    if (body != null) {
      body = body.trim();
      if (body.length() > 0) {
        // System.out.println("calling body method with ["+body+ "]");
        callBodyAction(applicableActionList, body);
      }
    }
  }

  public void endElement(EndEvent endEvent) {
    setDocumentLocator(endEvent.locator);
    endElement(endEvent.namespaceURI, endEvent.localName, endEvent.qName);
  }

  private void endElement(String namespaceURI, String localName, String qName) {
    // given that an action list is always pushed for every startElement, we
    // need
    // to always pop for every endElement
    List<Action> applicableActionList = (List<Action>) actionListStack.pop();

    if (skip != null) {
      if (skip.equals(elementPath)) {
        skip = null;
      }
    } else if (applicableActionList != EMPTY_LIST) {
      callEndAction(applicableActionList, getTagName(localName, qName));
    }

    // given that we always push, we must also pop the pattern
    elementPath.pop();
  }

  public Locator getLocator() {
    return locator;
  }

  public void setDocumentLocator(Locator l) {
    locator = l;
  }

  String getTagName(String localName, String qName) {
    String tagName = localName;

    if ((tagName == null) || (tagName.length() < 1)) {
      tagName = qName;
    }

    return tagName;
  }

  public void addImplicitAction(ImplicitAction ia) {
    implicitActions.add(ia);
  }

  /**
   * Check if any implicit actions are applicable. As soon as an applicable
   * action is found, it is returned. Thus, the returned list will have at most
   * one element.
   */
  List<Action> lookupImplicitAction(ElementPath elementPath, Attributes attributes,
      InterpretationContext ec) {
    int len = implicitActions.size();

    for (int i = 0; i < len; i++) {
      ImplicitAction ia = (ImplicitAction) implicitActions.get(i);

      if (ia.isApplicable(elementPath, attributes, ec)) {
        List<Action> actionList = new ArrayList<Action>(1);
        actionList.add(ia);

        return actionList;
      }
    }

    return null;
  }

  /**
   * Return the list of applicable patterns for this
   */
  List<Action> getApplicableActionList(ElementPath elementPath, Attributes attributes) {
    List<Action> applicableActionList = ruleStore.matchActions(elementPath);

    // logger.debug("set of applicable patterns: " + applicableActionList);
    if (applicableActionList == null) {
      applicableActionList = lookupImplicitAction(elementPath, attributes,
          interpretationContext);
    }

    return applicableActionList;
  }

  void callBeginAction(List<Action> applicableActionList, String tagName,
      Attributes atts) {
    if (applicableActionList == null) {
      return;
    }

    Iterator<Action> i = applicableActionList.iterator();
    while (i.hasNext()) {
      Action action = (Action) i.next();
      // now let us invoke the action. We catch and report any eventual
      // exceptions
      try {
        action.begin(interpretationContext, tagName, atts);
      } catch (ActionException e) {
        skip = elementPath.duplicate();
        cai.addError("ActionException in Action for tag [" + tagName + "]", e);
      } catch (RuntimeException e) {
        skip = elementPath.duplicate();
        cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
      }
    }
  }

  private void callBodyAction(List<Action> applicableActionList, String body) {
    if (applicableActionList == null) {
      return;
    }
    Iterator<Action> i = applicableActionList.iterator();

    while (i.hasNext()) {
      Action action = i.next();
      try {
        action.body(interpretationContext, body);
      } catch (ActionException ae) {
        cai
            .addError("Exception in end() methd for action [" + action + "]",
                ae);
      }
    }
  }

  private void callEndAction(List<Action> applicableActionList, String tagName) {
    if (applicableActionList == null) {
      return;
    }

    // logger.debug("About to call end actions on node: [" + localName + "]");
    Iterator<Action> i = applicableActionList.iterator();

    while (i.hasNext()) {
      Action action = i.next();
      // now let us invoke the end method of the action. We catch and report
      // any eventual exceptions
      try {
        action.end(interpretationContext, tagName);
      } catch (ActionException ae) {
        // at this point endAction, there is no point in skipping children as
        // they have been already processed
        cai.addError("ActionException in Action for tag [" + tagName + "]", ae);
      } catch (RuntimeException e) {
        // no point in setting skip
        cai.addError("RuntimeException in Action for tag [" + tagName + "]", e);
      }
    }
  }

  public RuleStore getRuleStore() {
    return ruleStore;
  }
}

/**
 * When {@link Interpreter} class is used as the origin of an
 * {@link ContextAwareImpl} instance, then XML locator information is lost. This
 * class preserves locator information (as a string).
 *
 * @author ceki
 */
class CAI_WithLocatorSupport extends ContextAwareImpl {

  CAI_WithLocatorSupport(Context context, Interpreter interpreter) {
    super(context, interpreter);
  }

  @Override
  protected Object getOrigin() {
    Interpreter i = (Interpreter) super.getOrigin();
    Locator locator = i.locator;
    if (locator != null) {
      return Interpreter.class.getName() + "@" + locator.getLineNumber() + ":"
          + locator.getColumnNumber();
    } else {
      return Interpreter.class.getName() + "@NA:NA";
    }
  }
}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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
 *
 *    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 ch.qos.logback.core.android;

import java.lang.reflect.Method;

/**
 * A proxy to get Android's global system properties (as opposed
 * to the default process-level system properties). Settings from
 * `adb setprop` can be accessed from this class.
 */
public class SystemPropertiesProxy {

  private static final SystemPropertiesProxy SINGLETON = new SystemPropertiesProxy(null);
  private Class<?> SystemProperties;
  private Method getString, getBoolean;

  private SystemPropertiesProxy(ClassLoader cl) {
    try {
      setClassLoader(cl);
    } catch (Exception e) {
    }
  }

  /**
   * Gets the singleton instance for this class
   *
   * @return the singleton
   */
  public static SystemPropertiesProxy getInstance() {
    return SINGLETON;
  }

  /**
   * Sets the classloader to lookup the class for android.os.SystemProperties
   *
   * @param cl desired classloader
   * @throws ClassNotFoundException android.os.SystemProperties class not found
   * @throws SecurityException security manager does not allow class loading
   * @throws NoSuchMethodException get/getBoolean method does not exist
   */
  public void setClassLoader(ClassLoader cl)
      throws ClassNotFoundException, SecurityException, NoSuchMethodException {
    if (cl == null) cl = this.getClass().getClassLoader();
    SystemProperties = cl.loadClass("android.os.SystemProperties");
    getString = SystemProperties.getMethod("get", new Class[]{ String.class, String.class });
    getBoolean = SystemProperties.getMethod("getBoolean", new Class[]{ String.class, boolean.class });
  }

  /**
   * Get the value for the given key in the Android system properties
   *
   * @param key
   *          the key to lookup
   * @param def
   *          a default value to return
   * @return an empty string if the key isn't found
   * @throws IllegalArgumentException
   *           if the key exceeds 32 characters
   */
  public String get(String key, String def)
      throws IllegalArgumentException {

    if (SystemProperties == null || getString == null) return null;

    String ret = null;
    try {
      ret = (String) getString.invoke(SystemProperties, new Object[]{ key, def });
    } catch (IllegalArgumentException e) {
      throw e;
    } catch (Exception e) {
    }
    // if return value is null or empty, use the default
    // since neither of those are valid values
    if (ret == null || ret.length() == 0) {
      ret = def;
    }
    return ret;
  }

  /**
   * Get the value for the given key in the Android system properties, returned
   * as a boolean.
   *
   * Values 'n', 'no', '0', 'false' or 'off' are considered false. Values 'y',
   * 'yes', '1', 'true' or 'on' are considered true. (case insensitive). If the
   * key does not exist, or has any other value, then the default result is
   * returned.
   *
   * @param key
   *          the key to lookup
   * @param def
   *          a default value to return
   * @return the key parsed as a boolean, or def if the key isn't found or is
   *         not able to be parsed as a boolean.
   * @throws IllegalArgumentException
   *           if the key exceeds 32 characters
   */
  public Boolean getBoolean(String key, boolean def)
      throws IllegalArgumentException {

    if (SystemProperties == null || getBoolean == null) return def;

    Boolean ret = def;
    try {
      ret = (Boolean) getBoolean.invoke(SystemProperties, new Object[]{ key, def });
    } catch (IllegalArgumentException e) {
      throw e;
    } catch (Exception e) {
    }
    return ret;
  }
}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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
 *
 *    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 ch.qos.logback.core.joran.util;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

/**
 * The Introspector class provides a standard way for tools to learn about the
 * properties, events, and methods supported by a target class.
 *
 * @author Anthony K. Trinh
 */
public class Introspector {

  /**
   * Converts a name string's first letter to lowercase
   *
   * @param name
   *          name string to evaluate
   * @return the name with its first letter in lowercase
   */
  static public String decapitalize(String name) {
    if (name == null || name.length() == 0) {
      return name;
    } else {
      String nm = name.substring(0, 1).toLowerCase(Locale.US);
      if (name.length() > 1) {
        nm += name.substring(1);
      }
      return nm;
    }
  }

  /**
   * Gets a class's method descriptors
   *
   * @param clazz
   *          class to be evaluated
   * @return method descriptors
   */
  static public MethodDescriptor[] getMethodDescriptors(Class<?> clazz) {
    ArrayList<MethodDescriptor> methods = new ArrayList<MethodDescriptor>();
    for (Method m : clazz.getMethods()) {
      methods.add(new MethodDescriptor(m.getName(), m));
    }
    return methods.toArray(new MethodDescriptor[0]);
  }

  /**
   * Gets a class's property descriptors. All properties have methods whose name
   * begins with "set" or "get". The setters must have a single parameter and
   * getters must have none.
   *
   * @param clazz
   *          class to be evaluated
   * @return property descriptors
   */
  static public PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) {
    final String SETTER_PREFIX = "set";
    final String GETTER_PREFIX = "get";
    final int LEN_PREFIX = SETTER_PREFIX.length();

    Map<String, PropertyDescriptor> map = new HashMap<String, PropertyDescriptor>();
    for (Method m : clazz.getMethods()) {
      PropertyDescriptor pd = null;
      String mName = m.getName();

      boolean isGet = mName.startsWith(GETTER_PREFIX) && (mName.length() > LEN_PREFIX);
      boolean isSet = mName.startsWith(SETTER_PREFIX) && (mName.length() > LEN_PREFIX);

      if (isGet || isSet) {
        String propName = decapitalize(mName.substring(LEN_PREFIX));

        pd = map.get(propName);
        if (pd == null) {
          pd = new PropertyDescriptor(propName);
          map.put(propName, pd);
        }

        Class<?>[] parmTypes = m.getParameterTypes();
        if (isSet) {
          if (parmTypes.length == 1) { // we only want the single-parm setter
            pd.setWriteMethod(m);
            pd.setPropertyType(parmTypes[0]);
          }
        } else if (isGet) {
          if (parmTypes.length == 0) { // we only want the zero-parm getter
            pd.setReadMethod(m);

            // let setter's type take priority
            if (pd.getPropertyType() == null) {
              pd.setPropertyType(m.getReturnType());
            }
          }
        }
      }
    }

    return map.values().toArray(new PropertyDescriptor[0]);
  }
}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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
 *
 *    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 ch.qos.logback.classic.net;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;

import javax.net.SocketFactory;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.net.server.HardenedLoggingEventInputStream;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.net.DefaultSocketConnector;
import ch.qos.logback.core.net.AbstractSocketAppender;
import ch.qos.logback.core.net.SocketConnector;
import ch.qos.logback.core.util.CloseUtil;

/**
 * A component that receives serialized {@link ILoggingEvent} objects from a
 * remote appender over a {@link Socket}.
 *
 * @author Carl Harris
 */
public class SocketReceiver extends ReceiverBase
        implements Runnable, SocketConnector.ExceptionHandler {

  private static final int DEFAULT_ACCEPT_CONNECTION_DELAY = 5000;

  private String remoteHost;
  private InetAddress address;
  private int port;
  private int reconnectionDelay;
  private int acceptConnectionTimeout = DEFAULT_ACCEPT_CONNECTION_DELAY;

  private String receiverId;
  private volatile Socket socket;
  private Future<Socket> connectorTask;

  /**
   * {@inheritDoc}
   */
  protected boolean shouldStart() {
    int errorCount = 0;
    if (port == 0) {
      errorCount++;
      addError("No port was configured for receiver. "
              + "For more information, please visit http://logback.qos.ch/codes.html#receiver_no_port");
    }

    if (remoteHost == null) {
      errorCount++;
      addError("No host name or address was configured for receiver. "
              + "For more information, please visit http://logback.qos.ch/codes.html#receiver_no_host");
    }

    if (reconnectionDelay == 0) {
      reconnectionDelay = AbstractSocketAppender.DEFAULT_RECONNECTION_DELAY;
    }

    if (errorCount == 0) {
      try {
        address = InetAddress.getByName(remoteHost);
      } catch (UnknownHostException ex) {
        addError("unknown host: " + remoteHost);
        errorCount++;
      }
    }

    if (errorCount == 0) {
      receiverId = "receiver " + remoteHost + ":" + port + ": ";
    }

    return errorCount == 0;
  }

  /**
   * {@inheritDoc}
   */
  protected void onStop() {
    if (socket != null) {
      CloseUtil.closeQuietly(socket);
    }
  }

  @Override
  protected Runnable getRunnableTask() {
    return this;
  }

  /**
   * {@inheritDoc}
   */
  public void run() {
    try {
      LoggerContext lc = (LoggerContext) getContext();
      while (!Thread.currentThread().isInterrupted()) {
        SocketConnector connector = createConnector(address, port, 0,
                reconnectionDelay);
        connectorTask = activateConnector(connector);
        if (connectorTask == null) {
          break;
        }
        socket = waitForConnectorToReturnASocket();
        if (socket == null)
          break;
        dispatchEvents(lc);
      }
    } catch (InterruptedException ex) {
      assert true;    // ok... we'll exit now
    }
    addInfo("shutting down");
  }

  private SocketConnector createConnector(InetAddress address, int port,
                                          int initialDelay, int retryDelay) {
    SocketConnector connector = newConnector(address, port, initialDelay,
            retryDelay);
    connector.setExceptionHandler(this);
    connector.setSocketFactory(getSocketFactory());
    return connector;
  }

  private Future<Socket> activateConnector(SocketConnector connector) {
    try {
      return getContext().getScheduledExecutorService().submit(connector);
    } catch (RejectedExecutionException ex) {
      return null;
    }
  }

  private Socket waitForConnectorToReturnASocket() throws InterruptedException {
    try {
      Socket s = connectorTask.get();
      connectorTask = null;
      return s;
    } catch (ExecutionException e) {
      return null;
    }
  }

  private void dispatchEvents(LoggerContext lc) {
    ObjectInputStream ois = null;
    try {
      socket.setSoTimeout(acceptConnectionTimeout);
      ois = new HardenedLoggingEventInputStream(socket.getInputStream());
      socket.setSoTimeout(0);
      addInfo(receiverId + "connection established");
      while (true) {
        ILoggingEvent event = (ILoggingEvent) ois.readObject();
        Logger remoteLogger = lc.getLogger(event.getLoggerName());
        if (remoteLogger.isEnabledFor(event.getLevel())) {
          remoteLogger.callAppenders(event);
        }
      }
    } catch (EOFException ex) {
      addInfo(receiverId + "end-of-stream detected");
    } catch (IOException ex) {
      addInfo(receiverId + "connection failed: " + ex);
    } catch (ClassNotFoundException ex) {
      addInfo(receiverId + "unknown event class: " + ex);
    } finally {
      CloseUtil.closeQuietly(ois);
      CloseUtil.closeQuietly(socket);
      socket = null;
      addInfo(receiverId + "connection closed");
    }
  }

  /**
   * {@inheritDoc}
   */
  public void connectionFailed(SocketConnector connector, Exception ex) {
    if (ex instanceof InterruptedException) {
      addWarn("connector interrupted", ex);
    } else if (ex instanceof ConnectException) {
      addWarn(receiverId + "connection refused", ex);
    } else {
      addWarn(receiverId + "unspecified error", ex);
    }
  }

  protected SocketConnector newConnector(InetAddress address,
                                         int port, int initialDelay, int retryDelay) {
    return new DefaultSocketConnector(address, port, initialDelay, retryDelay);
  }

  protected SocketFactory getSocketFactory() {
    return SocketFactory.getDefault();
  }

  public void setRemoteHost(String remoteHost) {
    this.remoteHost = remoteHost;
  }

  public void setPort(int port) {
    this.port = port;
  }

  public void setReconnectionDelay(int reconnectionDelay) {
    this.reconnectionDelay = reconnectionDelay;
  }

  public void setAcceptConnectionTimeout(int acceptConnectionTimeout) {
    this.acceptConnectionTimeout = acceptConnectionTimeout;
  }

}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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
 *
 *    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 ch.qos.logback.core;

import java.io.OutputStream;
import java.util.Arrays;

import ch.qos.logback.core.joran.spi.ConsoleTarget;
import ch.qos.logback.core.status.Status;
import ch.qos.logback.core.status.WarnStatus;

/**
 * ConsoleAppender appends log events to System.out or
 * System.err using a layout specified by the user. The default
 * target is System.out.
 *

 * For more information about this appender, please refer to the online manual
 * at http://logback.qos.ch/manual/appenders.html#ConsoleAppender
 *
 * @author Ceki Gülcü
 * @author Tom SH Liu
 * @author Ruediger Dohna
 * @deprecated This appender has been deprecated for {@link ch.qos.logback.classic.android.LogcatAppender}.
 */
@Deprecated
public class ConsoleAppender<E> extends OutputStreamAppender<E> {

  @SuppressWarnings("deprecation")
  protected ConsoleTarget target = ConsoleTarget.SystemOut;

  /**
   * Sets the value of the Target option. Recognized values are
   * "System.out" and "System.err". Any other value will be ignored.
   * @param value desired console-output target
   */
  public void setTarget(String value) {
    @SuppressWarnings("deprecation")
    ConsoleTarget t = ConsoleTarget.findByName(value.trim());
    if (t == null) {
      targetWarn(value);
    } else {
      target = t;
    }
  }

  /**
   * Returns the current value of the target property. The default value
   * of the option is "System.out".
   *

   * See also {@link #setTarget}.
   * @return the console-output target's name
   */
  public String getTarget() {
    return target.getName();
  }

  private void targetWarn(String val) {
    Status status = new WarnStatus("[" + val + "] should be one of "
            + Arrays.toString(ConsoleTarget.values()), this);
    status.add(new WarnStatus(
            "Using previously set target, System.out by default.", this));
    addStatus(status);
  }

  @Override
  public void start() {
    OutputStream targetStream = target.getStream();
    setOutputStream(targetStream);
    super.start();
  }

}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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
 *
 *    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 ch.qos.logback.core;

import static ch.qos.logback.core.CoreConstants.CODES_URL;

import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.locks.ReentrantLock;

import ch.qos.logback.core.encoder.Encoder;
import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
import ch.qos.logback.core.spi.DeferredProcessingAware;
import ch.qos.logback.core.status.ErrorStatus;

/**
 * OutputStreamAppender appends events to a {@link OutputStream}. This class
 * provides basic services that other appenders build upon.
 *
 * For more information about this appender, please refer to the online manual
 * at http://logback.qos.ch/manual/appenders.html#OutputStreamAppender
 *
 * @author Ceki Gülcü
 */
public class OutputStreamAppender<E> extends UnsynchronizedAppenderBase<E> {

  /**
   * It is the encoder which is ultimately responsible for writing the event to
   * an {@link OutputStream}.
   */
  protected Encoder<E> encoder;

  /**
   * All synchronization in this class is done via the lock object.
   */
  protected final ReentrantLock lock = new ReentrantLock(false);

  /**
   * This is the {@link OutputStream outputStream} where output will be written.
   */
  private OutputStream outputStream;

  private boolean immediateFlush = true;

  /**
   * The underlying output stream used by this appender.
   *
   * @return the output stream
   */
  public OutputStream getOutputStream() {
    return outputStream;
  }

  /**
   * Checks that requires parameters are set and if everything is in order,
   * activates this appender.
   */
  public void start() {
    int errors = 0;
    if (this.encoder == null) {
      addStatus(new ErrorStatus("No encoder set for the appender named \""
          + name + "\".", this));
      errors++;
    }

    if (this.outputStream == null) {
      addStatus(new ErrorStatus(
          "No output stream set for the appender named \"" + name + "\".", this));
      errors++;
    }
    // only error free appenders should be activated
    if (errors == 0) {
      super.start();
    }
  }

  public void setLayout(Layout<E> layout) {
    addWarn("This appender no longer admits a layout as a sub-component, set an encoder instead.");
    addWarn("To ensure compatibility, wrapping your layout in LayoutWrappingEncoder.");
    addWarn("See also "+CODES_URL+"#layoutInsteadOfEncoder for details");
    LayoutWrappingEncoder<E> lwe = new LayoutWrappingEncoder<E>();
    lwe.setLayout(layout);
    lwe.setContext(context);
    this.encoder = lwe;
  }

  @Override
  protected void append(E eventObject) {
    if (!isStarted()) {
      return;
    }

    subAppend(eventObject);
  }

  /**
   * Stop this appender instance. The underlying stream or writer is also
   * closed.
   *
   *

   * Stopped appenders cannot be reused.
   */
  public void stop() {
    lock.lock();
    try {
      closeOutputStream();
      super.stop();
    } finally {
      lock.unlock();
    }
  }

  /**
   * Close the underlying {@link OutputStream}.
   */
  protected void closeOutputStream() {
    if (this.outputStream != null) {
      try {
        // before closing we have to output out layout's footer
        encoderClose();
        this.outputStream.close();
        this.outputStream = null;
      } catch (IOException e) {
        addStatus(new ErrorStatus(
            "Could not close output stream for OutputStreamAppender.", this, e));
      }
    }
  }

  void encoderInit() {
    if (encoder != null && this.outputStream != null) {
      try {
        byte[] header = encoder.headerBytes();
        writeBytes(header);
      } catch (IOException ioe) {
        this.started = false;
        addStatus(new ErrorStatus(
            "Failed to initialize encoder for appender named [" + name + "].",
            this, ioe));
      }
    }
  }

  void encoderClose() {
    if (encoder != null && this.outputStream != null) {
      try {
        byte[] footer = encoder.footerBytes();
        writeBytes(footer);
      } catch (IOException ioe) {
        this.started = false;
        addStatus(new ErrorStatus("Failed to write footer for appender named ["
            + name + "].", this, ioe));
      }
    }
  }

  /**
   *

   * Sets the @link OutputStream} where the log output will go. The specified
   * OutputStream must be opened by the user and be writable. The
   * OutputStream will be closed when the appender instance is
   * closed.
   *
   * @param outputStream
   *          An already opened OutputStream.
   */
  public void setOutputStream(OutputStream outputStream) {
    lock.lock();
    try {
      // close any previously opened output stream
      closeOutputStream();

      this.outputStream = outputStream;
      if (encoder == null) {
        addWarn("Encoder has not been set. Cannot invoke its init method.");
        return;
      }

      encoderInit();
    } finally {
      lock.unlock();
    }
  }

  protected void writeOut(E event) throws IOException {
    byte[] byteArray = this.encoder.encode(event);
    writeBytes(byteArray);
  }

  private void writeBytes(byte[] byteArray) throws IOException {
    if (byteArray == null || byteArray.length == 0) {
      return;
    }

    lock.lock();
    try {
      this.outputStream.write(byteArray);
      if (immediateFlush) {
        this.outputStream.flush();
      }
    } finally {
      lock.unlock();
    }
  }

  /**
   * Actual writing occurs here.
   *

   * Most subclasses of WriterAppender will need to override this
   * method.
   *
   * @param event the log event
   * @since 0.9.0
   */
  protected void subAppend(E event) {
    if (!isStarted()) {
      return;
    }
    try {
      // this step avoids LBCLASSIC-139
      if (event instanceof DeferredProcessingAware) {
        ((DeferredProcessingAware) event).prepareForDeferredProcessing();
      }
      // the synchronization prevents the OutputStream from being closed while we
      // are writing. It also prevents multiple threads from entering the same
      // converter. Converters assume that they are in a synchronized block.
      //lock.lock();
      byte[] byteArray = this.encoder.encode(event);
      writeBytes(byteArray);

    } catch (IOException ioe) {
      // as soon as an exception occurs, move to non-started state
      // and add a single ErrorStatus to the SM.
      this.started = false;
      addStatus(new ErrorStatus("IO failure in appender", this, ioe));
    }
  }

  public Encoder<E> getEncoder() {
    return encoder;
  }

  public void setEncoder(Encoder<E> encoder) {
    this.encoder = encoder;
  }

  public boolean isImmediateFlush() {
    return immediateFlush;
  }

  public void setImmediateFlush(boolean immediateFlush) {
    this.immediateFlush = immediateFlush;
  }
}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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
 *
 *    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 ch.qos.logback.core;

import java.util.HashSet;
import java.util.Set;

import ch.qos.logback.core.spi.LifeCycle;

/**
 * An object that manages a collection of components that implement the
 * {@link LifeCycle} interface.  Each component that is added to the manager
 * will be stopped and removed from the manager when the manager is reset.
 *
 * @author Carl Harris
 */
public class LifeCycleManager {

  private final Set<LifeCycle> components = new HashSet<LifeCycle>();
  /**
   * Registers a component with this manager.
   *

   * @param component the component whose life cycle is to be managed
   */
  public void register(LifeCycle component) {
    components.add(component);
  }

  /**
   * Resets this manager.
   *

   * All registered components are stopped and removed from the manager.
   */
  public void reset() {
    for (LifeCycle component : components) {
      if (component.isStarted()) {
        component.stop();
      }
    }
    components.clear();
  }
}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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
 *
 *    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 ch.qos.logback.core.util;

import ch.qos.logback.core.Context;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.spi.ContextAware;
import ch.qos.logback.core.spi.LifeCycle;
import ch.qos.logback.core.status.OnConsoleStatusListener;
import ch.qos.logback.core.status.StatusListener;

public class StatusListenerConfigHelper {

  public static void installIfAsked(Context context) {
    String slClass = OptionHelper.getSystemProperty(CoreConstants.STATUS_LISTENER_CLASS);
    if (!OptionHelper.isEmpty(slClass)) {
      addStatusListener(context, slClass);
    }
  }

  private static void addStatusListener(Context context, String listenerClassName) {
    StatusListener listener = createListenerPerClassName(context, listenerClassName);
    initAndAddListener(context, listener);
  }

  private static void initAndAddListener(Context context, StatusListener listener) {
    if (listener != null) {
      if (listener instanceof ContextAware) // LOGBACK-767
        ((ContextAware) listener).setContext(context);

      boolean effectivelyAdded = context.getStatusManager().add(listener);
      if (effectivelyAdded && (listener instanceof LifeCycle)) {
        ((LifeCycle) listener).start(); // LOGBACK-767
      }
    }
  }

  private static StatusListener createListenerPerClassName(Context context, String listenerClass) {
    try {
      return (StatusListener) OptionHelper.instantiateByClassName(listenerClass, StatusListener.class, context);
    } catch (Exception e) {
      // printing on the console is the best we can do
      e.printStackTrace();
      return null;
    }
  }

  /**
   * This utility method adds a new OnConsoleStatusListener to the context
   * passed as parameter.
   *
   * @param context
   * @since 1.0.1
   */
  static public void addOnConsoleListenerInstance(Context context, OnConsoleStatusListener onConsoleStatusListener) {
    onConsoleStatusListener.setContext(context);
    boolean effectivelyAdded = context.getStatusManager().add(onConsoleStatusListener);
    if (effectivelyAdded) {
      onConsoleStatusListener.start();
    }
  }

}


/**
 * Copyright 2019 Anthony Trinh
 *
 * 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
 *
 *    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 ch.qos.logback.core.joran.action;

import ch.qos.logback.core.joran.action.ActionUtil.Scope;
import ch.qos.logback.core.joran.spi.InterpretationContext;
import ch.qos.logback.core.joran.spi.ActionException;
import ch.qos.logback.core.spi.LifeCycle;
import ch.qos.logback.core.util.OptionHelper;
import ch.qos.logback.core.spi.PropertyDefiner;
import org.xml.sax.Attributes;

/**
 * Instantiate class for define property value. Get future property name and
 * property definer class from attributes. Some property definer properties
 * could be used. After defining put new property to context.
 *
 * @author Aleksey Didik
 */
public class DefinePropertyAction extends Action {

  String scopeStr;
  Scope scope;
  String propertyName;
  PropertyDefiner definer;
  boolean inError;

  public void begin(InterpretationContext ec, String localName,
      Attributes attributes) throws ActionException {
    // reset variables
    scopeStr = null;
    scope = null;
    propertyName = null;
    definer = null;
    inError = false;

    // read future property name
    propertyName = attributes.getValue(NAME_ATTRIBUTE);
    scopeStr = attributes.getValue(SCOPE_ATTRIBUTE);

    scope = ActionUtil.stringToScope(scopeStr);
    if (OptionHelper.isEmpty(propertyName)) {
      addError("Missing property name for property definer. Near [" + localName
          + "] line " + getLineNumber(ec));
      inError = true;
      return;
    }

    // read property definer class name
    String className = attributes.getValue(CLASS_ATTRIBUTE);
    if (OptionHelper.isEmpty(className)) {
      addError("Missing class name for property definer. Near [" + localName
          + "] line " + getLineNumber(ec));
      inError = true;
      return;
    }

    // try to instantiate property definer
    try {
      addInfo("About to instantiate property definer of type [" + className
          + "]");
      definer = (PropertyDefiner) OptionHelper.instantiateByClassName(
          className, PropertyDefiner.class, context);
      definer.setContext(context);
      if(definer instanceof LifeCycle) {
        ((LifeCycle) definer).start();
      }
      ec.pushObject(definer);
    } catch (Exception oops) {
      inError = true;
      addError("Could not create an PropertyDefiner of type [" + className
          + "].", oops);
      throw new ActionException(oops);
    }
  }

  /**
   * Now property definer is initialized by all properties and we can put
   * property value to context
   */
  public void end(InterpretationContext ec, String name) {
    if (inError) {
      return;
    }

    Object o = ec.peekObject();

    if (o != definer) {
      addWarn("The object at the of the stack is not the property definer for property named ["
          + propertyName + "] pushed earlier.");
    } else {
      addInfo("Popping property definer for property named [" + propertyName
          + "] from the object stack");
      ec.popObject();
      // let's put defined property and value to context but only if it is
      // not null
      String propertyValue = definer.getPropertyValue();
      if(propertyValue != null) {
        ActionUtil.setProperty(ec, propertyName, propertyValue, scope);
      }
    }
  }
}