/**
 * 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 android.annotation.TargetApi;
import android.content.ContextWrapper;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.core.CoreConstants;

/**
 * This class provides utility methods to get common directory paths and
 * context data (such as package name).
 *
 * @author Anthony Trinh
 * @since 1.0.8-1
 */
public class AndroidContextUtil {
  private ContextWrapper context;

  public AndroidContextUtil() {
    this(getContext());
  }

  public AndroidContextUtil(ContextWrapper contextWrapper) {
    this.context = contextWrapper;
  }

  /**
   * Sets properties for use in configs
   * @param context logger context whose property map is updated
   */
  public void setupProperties(LoggerContext context) {
    // legacy properties
    Properties props = new Properties();
    props.setProperty(CoreConstants.DATA_DIR_KEY, getFilesDirectoryPath());
    final String extDir = getMountedExternalStorageDirectoryPath();
    if (extDir != null) {
      props.setProperty(CoreConstants.EXT_DIR_KEY, extDir);
    }
    props.setProperty(CoreConstants.PACKAGE_NAME_KEY, getPackageName());
    props.setProperty(CoreConstants.VERSION_CODE_KEY, getVersionCode());
    props.setProperty(CoreConstants.VERSION_NAME_KEY, getVersionName());

    context.putProperties(props);
  }

  private static ContextWrapper getContext() {
    try {
      Class<?> c = Class.forName("android.app.AppGlobals");
      Method method = c.getDeclaredMethod("getInitialApplication");
      return (ContextWrapper)method.invoke(c);
    } catch (ClassNotFoundException e) {
      //e.printStackTrace();
    } catch (NoSuchMethodException e) {
      //e.printStackTrace();
    } catch (IllegalAccessException e) {
      //e.printStackTrace();
    } catch (InvocationTargetException e) {
      //e.printStackTrace();
    }
    return null;
  }

  /**
   * Gets the path to the external storage directory only if
   * mounted.
   *
   * @return the absolute path to the external storage directory;
   * or {@code null} if not mounted.
   */
  public String getMountedExternalStorageDirectoryPath() {
    String path = null;
    String state = Environment.getExternalStorageState();
    if (state.equals(Environment.MEDIA_MOUNTED) ||
        state.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
      path = absPath(Environment.getExternalStorageDirectory());
    }
    return path;
  }

  /**
   * Gets the path to the external storage directory
   *
   * @return the absolute path to the external storage directory
   */
  public String getExternalStorageDirectoryPath() {
    return Environment.getExternalStorageDirectory().getAbsolutePath();
  }

  public String getExternalFilesDirectoryPath() {
    return this.context != null
            ? absPath(this.context.getExternalFilesDir(null))
            : "";
  }

  public String getCacheDirectoryPath() {
    return this.context != null
            ? absPath(this.context.getCacheDir())
            : "";
  }

  public String getExternalCacheDirectoryPath() {
    return this.context != null
            ? absPath(this.context.getExternalCacheDir())
            : "";
  }

  public String getPackageName() {
    return this.context != null
            ? this.context.getPackageName()
            : "";
  }

  /**
   * Returns the absolute path to the directory on the Android
   * filesystem where files are stored for the current application.
   * Unlike the equivalent function in Android, this function does
   * not create the directory if it's non-existent.
   *
   * @return the absolute path to the files directory
   * (example: "/data/data/com.example/files")
   */
  public String getFilesDirectoryPath() {
    return this.context != null
            ? absPath(this.context.getFilesDir())
            : "";
  }

  /**
   * Returns the absolute path to the directory on the Android
   * filesystem similar to {@link #getFilesDirectoryPath()}.
   * The difference is these files are excluded from automatic
   * backup to remote storage by {@code android.app.backup.BackupAgent}.
   * This API is only available on SDK 21+. On older versions,
   * this function returns an empty string.
   *
   * @return the absolute path to the files directory
   * (example: "/data/data/com.example/nobackup/files")
   */
  @TargetApi(21)
  public String getNoBackupFilesDirectoryPath() {
    return Build.VERSION.SDK_INT >= 21 &&
            this.context != null
            ? absPath(this.context.getNoBackupFilesDir())
            : "";
  }

  /**
   * Returns the absolute path to the directory on the Android
   * filesystem where databases are stored for the current application.
   *
   * @return the absolute path to the databases directory
   * (example: "/data/data/com.example/databases")
   */
  public String getDatabaseDirectoryPath() {
    return this.context != null
            && this.context.getDatabasePath("x") != null
            ? this.context.getDatabasePath("x").getParent()
            : "";
  }

  public String getDatabasePath(String databaseName) {
    return this.context != null
            ? absPath(this.context.getDatabasePath(databaseName))
            : "";
  }

  public String getVersionCode() {
    String versionCode = "";
    if (this.context != null) {
      try {
        PackageInfo pkgInfo = this.context.getPackageManager().getPackageInfo(getPackageName(), 0);
        versionCode = "" + pkgInfo.versionCode;
      } catch (PackageManager.NameNotFoundException e) {
      }
    }
    return versionCode;
  }

  public String getVersionName() {
    String versionName = "";
    if (this.context != null) {
      try {
        PackageInfo pkgInfo = this.context.getPackageManager().getPackageInfo(getPackageName(), 0);
        versionName = pkgInfo.versionName;
      } catch (PackageManager.NameNotFoundException e) {
      }
    }
    return versionName != null ? versionName : "";
  }

  private String absPath(File file) {
    return file != null ? file.getAbsolutePath() : "";
  }
}


/**
 * 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.File;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.Map;

import ch.qos.logback.core.recovery.ResilientFileOutputStream;
import ch.qos.logback.core.util.ContextUtil;
import ch.qos.logback.core.util.EnvUtil;
import ch.qos.logback.core.util.FileSize;
import ch.qos.logback.core.util.FileUtil;

/**
 * FileAppender appends log events to a file.
 *
 * For more information about this appender, please refer to the online manual
 * at http://logback.qos.ch/manual/appenders.html#FileAppender
 *
 * @author Ceki Gülcü
 */
public class FileAppender<E> extends OutputStreamAppender<E> {

  public static final long DEFAULT_BUFFER_SIZE = 8192;

  static protected String COLLISION_WITH_EARLIER_APPENDER_URL = CoreConstants.CODES_URL + "#earlier_fa_collision";

  /**
   * Append to or truncate the file? The default value for this variable is
   * true, meaning that by default a FileAppender will
   * append to an existing file and not truncate it.
   */
  protected boolean append = true;

  /**
   * The name of the active log file.
   */
  protected String fileName = null;

  private boolean prudent = false;
  private boolean initialized = false;
  private boolean lazyInit = false;

  private FileSize bufferSize = new FileSize(DEFAULT_BUFFER_SIZE);

  /**
   * The File property takes a string value which should be the name of
   * the file to append to.
   * @param file path to destination log file
   */
  public void setFile(String file) {
    if (file == null) {
      fileName = null;
    } else {
      // Trim spaces from both ends. The users probably does not want
      // trailing spaces in file names.
      fileName = file.trim();
    }
  }

  /**
   * Returns the value of the Append property.
   * @return true if file should be appended to instead of overwritten
   */
  public boolean isAppend() {
    return append;
  }

  /**
   * This method is used by derived classes to obtain the raw file property.
   * Regular users should not be calling this method. Note that RollingFilePolicyBase
   * requires public getter for this property.
   *
   * @return the value of the file property
   */
  final public String rawFileProperty() {
    return fileName;
  }

  /**
   * Returns the value of the File property.
   * @return the path to the destination log file
   */
  public String getFile() {
    return fileName;
  }

  /**
   * If the value of File is not null, then
   * {@link #openFile} is called with the values of File and
   * Append properties.
   */
  public void start() {
    int errors = 0;

    // Use getFile() instead of direct access to fileName because
    // the function is overridden in RollingFileAppender, which
    // returns a value that doesn't necessarily match fileName.
    String file = getFile();

    if (file != null) {
      file = getAbsoluteFilePath(file);
      addInfo("File property is set to [" + file + "]");

      if (prudent) {
        if (!isAppend()) {
          setAppend(true);
          addWarn("Setting \"Append\" property to true on account of \"Prudent\" mode");
        }
      }

      if (!lazyInit) {
        if (checkForFileCollisionInPreviousFileAppenders()) {
          addError("Collisions detected with FileAppender/RollingAppender instances defined earlier. Aborting.");
          addError(COLLISION_WITH_EARLIER_APPENDER_URL);
          errors++;
        } else {
          // file should be opened only if collision free
          try {
            openFile(file);
          } catch (IOException e) {
            errors++;
            addError("openFile(" + file + "," + append + ") failed", e);
          }
        }
      } else {
        // We'll initialize the file output stream later. Use a dummy for now
        // to satisfy OutputStreamAppender.start().
        setOutputStream(new NOPOutputStream());
      }
    } else {
      errors++;
      addError("\"File\" property not set for appender named [" + name + "]");
    }
    if (errors == 0) {
      super.start();
    }
  }

  @Override
  public void stop() {
    super.stop();

    Map<String, String> map = ContextUtil.getFilenameCollisionMap(context);
    if (map == null || getName() == null)
      return;

    map.remove(getName());
  }

  protected boolean checkForFileCollisionInPreviousFileAppenders() {
    boolean collisionsDetected = false;
    if (fileName == null) {
      return false;
    }
    @SuppressWarnings("unchecked")
    Map<String, String> map = (Map<String, String>) context.getObject(CoreConstants.FA_FILENAME_COLLISION_MAP);
    if (map == null) {
      return collisionsDetected;
    }
    for (Map.Entry<String, String> entry : map.entrySet()) {
      if (fileName.equals(entry.getValue())) {
        addErrorForCollision("File", entry.getValue(), entry.getKey());
        collisionsDetected = true;
      }
    }
    if (name != null) {
      map.put(getName(), fileName);
    }
    return collisionsDetected;
  }

  protected void addErrorForCollision(String optionName, String optionValue, String appenderName) {
    addError("'"+optionName+"' option has the same value \""+optionValue+"\" as that given for appender [" + appenderName+"] defined earlier.");
  }

  /**
   *

   * Sets and opens the file where the log output will go. The specified
   * file must be writable.
   *
   *

   * If there was already an opened file, then the previous file is closed
   * first.
   *
   *

   * Do not use this method directly. To configure a FileAppender or one of
   * its subclasses, set its properties one by one and then call start().
   *
   * @param filename
   *          The path to the log file.
   *
   * @return true if successful; false otherwise
   * @throws IOException file could not be opened
   */
  protected boolean openFile(String filename) throws IOException {
    boolean successful = false;
    filename = getAbsoluteFilePath(filename);
    lock.lock();
    try {
      File file = new File(filename);
      boolean result = FileUtil.createMissingParentDirectories(file);
      if (!result) {
        addError("Failed to create parent directories for ["
                + file.getAbsolutePath() + "]");
      }

      ResilientFileOutputStream resilientFos = new ResilientFileOutputStream(file, append, bufferSize.getSize());
      resilientFos.setContext(context);
      setOutputStream(resilientFos);
      successful = true;
    } finally {
      lock.unlock();
    }
    return successful;
  }

  /**
   * @see #setPrudent(boolean)
   *
   * @return true if in prudent mode
   */
  public boolean isPrudent() {
    return prudent;
  }

  /**
   * When prudent is set to true, file appenders from multiple JVMs can safely
   * write to the same file.
   *
   * @param prudent whether to enable prudent mode
   */
  public void setPrudent(boolean prudent) {
    this.prudent = prudent;
  }

  public void setAppend(boolean append) {
    this.append = append;
  }

  /**
   * Gets the enable status of lazy initialization of the file output
   * stream
   *
   * @return true if enabled; false otherwise
   */
  public boolean getLazy() {
    return lazyInit;
  }

  /**
   * Enables/disables lazy initialization of the file output stream.
   * This defers the file creation until the first outgoing message.
   *
   * @param enable true to enable lazy initialization; false otherwise
   */
  public void setLazy(boolean enable) {
    lazyInit = enable;
  }

  public void setBufferSize(FileSize bufferSize) {
    addInfo("Setting bufferSize to ["+bufferSize.toString()+"]");
    this.bufferSize = bufferSize;
  }

  private void safeWrite(E event) throws IOException {
    ResilientFileOutputStream resilientFOS = (ResilientFileOutputStream) getOutputStream();
    FileChannel fileChannel = resilientFOS.getChannel();
    if (fileChannel == null) {
      return;
    }

    // Clear any current interrupt (see LOGBACK-875)
    boolean interrupted = Thread.interrupted();

    FileLock fileLock = null;
    try {
      fileLock = fileChannel.lock();
      long position = fileChannel.position();
      long size = fileChannel.size();
      if (size != position) {
        fileChannel.position(size);
      }
      super.writeOut(event);
    } catch (IOException e) {
      // Mainly to catch FileLockInterruptionExceptions (see LOGBACK-875)
      resilientFOS.postIOFailure(e);
    } finally {
      if (fileLock != null && fileLock.isValid()) {
        fileLock.release();
      }

      // Re-interrupt if we started in an interrupted state (see LOGBACK-875)
      if (interrupted) {
        Thread.currentThread().interrupt();
      }
    }
  }

  @Override
  protected void writeOut(E event) throws IOException {
    if (prudent) {
      safeWrite(event);
    } else {
      super.writeOut(event);
    }
  }

  @Override
  protected void subAppend(E event) {
    if (!initialized && lazyInit) {
      initialized = true;

      if (checkForFileCollisionInPreviousFileAppenders()) {
        addError("Collisions detected with FileAppender/RollingAppender instances defined earlier. Aborting.");
        addError(COLLISION_WITH_EARLIER_APPENDER_URL);
      } else {
        try {
          openFile(getFile());
          super.start();
        } catch (IOException e) {
          this.started = false;
          addError("openFile(" + fileName + "," + append + ") failed", e);
        }
      }
    }

    super.subAppend(event);
  }

  /**
   * Gets the absolute path to the filename, starting from the app's
   * "files" directory, if it is not already an absolute path
   *
   * @param filename filename to evaluate
   * @return absolute path to the filename
   */
  private String getAbsoluteFilePath(String filename) {
    // In Android, relative paths created with File() are relative
    // to root, so fix it by prefixing the path to the app's "files"
    // directory.
    // This transformation is rather expensive, since it involves loading the
    // Android manifest from the APK (which is a ZIP file), and parsing it to
    // retrieve the application package name. This should be avoided if
    // possible as it may perceptibly delay the app launch time.
    if (EnvUtil.isAndroidOS() && !new File(filename).isAbsolute()) {
      String dataDir = context.getProperty(CoreConstants.DATA_DIR_KEY);
      filename = FileUtil.prefixRelativePath(dataDir, filename);
    }
    return filename;
  }
}


/**
 * 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.boolex;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.boolex.EvaluationException;
import ch.qos.logback.core.boolex.EventEvaluatorBase;

/**
 * Evaluates to true when the logging event passed as parameter has level ERROR
 * or higher.
 *
 * @author Ceki Gülcü
 *
 */
public class OnErrorEvaluator extends EventEvaluatorBase<ILoggingEvent> {

  /**
   * Return true if event passed as parameter has level ERROR or higher, returns
   * false otherwise.
   */
  public boolean evaluate(ILoggingEvent event) throws NullPointerException,
      EvaluationException {
    return event.getLevel().levelInt >= Level.ERROR_INT;
  }
}


/**
 * 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.sift;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.sift.AbstractDiscriminator;
import ch.qos.logback.core.util.OptionHelper;

import java.util.Map;

/**
 * MDCBasedDiscriminator essentially returns the value mapped to an MDC key. If
 * the said value is null, then a default value is returned.
 *

 *
Both Key and the DefaultValue are user specified properties.
 *
 * @author Ceki Gülcü
 */
public class MDCBasedDiscriminator extends AbstractDiscriminator<ILoggingEvent> {

  private String key;
  private String defaultValue;

  /**
   * Return the value associated with an MDC entry designated by the Key
   * property. If that value is null, then return the value assigned to the
   * DefaultValue property.
   */
  public String getDiscriminatingValue(ILoggingEvent event) {
    // http://jira.qos.ch/browse/LBCLASSIC-213
    Map<String, String> mdcMap = event.getMDCPropertyMap();
    if (mdcMap == null) {
      return defaultValue;
    }
    String mdcValue = mdcMap.get(key);
    if (mdcValue == null) {
      return defaultValue;
    } else {
      return mdcValue;
    }
  }

  @Override
  public void start() {
    int errors = 0;
    if (OptionHelper.isEmpty(key)) {
      errors++;
      addError("The \"Key\" property must be set");
    }
    if (OptionHelper.isEmpty(defaultValue)) {
      errors++;
      addError("The \"DefaultValue\" property must be set");
    }
    if (errors == 0) {
      started = true;
    }
  }

  public String getKey() {
    return key;
  }

  public void setKey(String key) {
    this.key = key;
  }

  /**
   * @return The default MDC value in case the MDC is not set for
   * {@link #setKey(String) mdcKey}.
   */
  public String getDefaultValue() {
    return defaultValue;
  }

  /**
   * The default MDC value in case the MDC is not set for
   * {@link #setKey(String) mdcKey}.
   *

   *
 For example, if {@link #setKey(String) Key} is set to the value
   * "someKey", and the MDC is not set for "someKey", then this appender will
   * use the default value, which you can set with the help of this method.
   *
   * @param defaultValue
   */
  public void setDefaultValue(String defaultValue) {
    this.defaultValue = defaultValue;
  }
}


/**
 * 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.turbo;

import java.io.File;
import java.net.URL;
import java.util.List;

import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.android.AndroidContextUtil;
import ch.qos.logback.core.joran.event.SaxEvent;
import ch.qos.logback.core.joran.spi.ConfigurationWatchList;
import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;
import ch.qos.logback.core.status.StatusUtil;
import org.slf4j.Marker;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.spi.FilterReply;

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

/**
 * Reconfigure a LoggerContext when the configuration file changes.
 *
 * @author Ceki Gulcu
 */
public class ReconfigureOnChangeFilter extends TurboFilter {

  /**
   * Scan for changes in configuration file once every minute.
   */
  // 1 minute - value mentioned in documentation
  public final static long DEFAULT_REFRESH_PERIOD = 60 * MILLIS_IN_ONE_SECOND;

  long refreshPeriod = DEFAULT_REFRESH_PERIOD;
  URL mainConfigurationURL;
  protected volatile long nextCheck;

  ConfigurationWatchList configurationWatchList;

  @Override
  public void start() {
    configurationWatchList = ConfigurationWatchListUtil.getConfigurationWatchList(context);
    if (configurationWatchList != null) {
      mainConfigurationURL = configurationWatchList.getMainURL();
      if(mainConfigurationURL == null) {
        addWarn("Due to missing top level configuration file, automatic reconfiguration is impossible.");
        return;
      }
      List<File> watchList = configurationWatchList.getCopyOfFileWatchList();
      long inSeconds = refreshPeriod / 1000;
      addInfo("Will scan for changes in [" + watchList + "] every "
              + inSeconds + " seconds. ");
      synchronized (configurationWatchList) {
        updateNextCheck(System.currentTimeMillis());
      }
      super.start();
    } else {
      addWarn("Empty ConfigurationWatchList in context");
    }
  }

  @Override
  public String toString() {
    return "ReconfigureOnChangeFilter{" +
            "invocationCounter=" + invocationCounter +
            '}';
  }

  // The next fields counts the number of time the decide method is called
  //
  // IMPORTANT: This field can be updated by multiple threads. It follows that
  // its values may *not* be incremented sequentially. However, we don't care
  // about the actual value of the field except that from time to time the
  // expression (invocationCounter++ & mask) == mask) should be true.
  private long invocationCounter = 0;

  private volatile long mask = 0xF;
  private volatile long lastMaskCheck = System.currentTimeMillis();

  @Override
  public FilterReply decide(Marker marker, Logger logger, Level level,
                            String format, Object[] params, Throwable t) {
    if (!isStarted()) {
      return FilterReply.NEUTRAL;
    }

    // for performance reasons, skip change detection (MASK-1) times out of MASK.
    // Only once every MASK calls is change detection code executed
    // Note that MASK is a variable itself.
    if (((invocationCounter++) & mask) != mask) {
      return FilterReply.NEUTRAL;
    }

    long now = System.currentTimeMillis();

    synchronized (configurationWatchList) {
      updateMaskIfNecessary(now);
      if (changeDetected(now)) {
        // Even though reconfiguration involves resetting the loggerContext,
        // which clears the list of turbo filters including this instance, it is
        // still possible for this instance to be subsequently invoked by another
        // thread if it was already executing when the context was reset.
        disableSubsequentReconfiguration();
        detachReconfigurationToNewThread();
      }
    }

    return FilterReply.NEUTRAL;
  }

  // experiments indicate that even for CPU intensive applications with 200 or more threads MASK
  // values in the order of 0xFFFF is appropriate
  private static final int MAX_MASK = 0xFFFF;

  // if less  than MASK_INCREASE_THRESHOLD milliseconds elapse between invocations of updateMaskIfNecessary() method,
  // then the mask should be increased
  private static final long MASK_INCREASE_THRESHOLD = 100;

  // if more than MASK_DECREASE_THRESHOLD milliseconds elapse between invocations of updateMaskIfNecessary() method,
  // then the mask should be decreased
  private static final long MASK_DECREASE_THRESHOLD = MASK_INCREASE_THRESHOLD*8;

  // update the mask so as to execute change detection code about once every 100 to 8000 milliseconds.
  private void updateMaskIfNecessary(long now) {
    final long timeElapsedSinceLastMaskUpdateCheck = now - lastMaskCheck;
    lastMaskCheck = now;
    if (timeElapsedSinceLastMaskUpdateCheck < MASK_INCREASE_THRESHOLD && (mask < MAX_MASK)) {
        mask = (mask << 1) | 1;
    } else if (timeElapsedSinceLastMaskUpdateCheck > MASK_DECREASE_THRESHOLD) {
      mask = mask >>> 2;
    }
  }

  // by detaching reconfiguration to a new thread, we release the various
  // locks held by the current thread, in particular, the AppenderAttachable
  // reader lock.
  void detachReconfigurationToNewThread() {
    addInfo("Detected change in [" + configurationWatchList.getCopyOfFileWatchList() + "]");
    context.getScheduledExecutorService().submit(new ReconfiguringThread());
  }

  void updateNextCheck(long now) {
    nextCheck = now + refreshPeriod;
  }

  protected boolean changeDetected(long now) {
    if (now >= nextCheck) {
      updateNextCheck(now);
      return configurationWatchList.changeDetected();
    }
    return false;
  }

  void disableSubsequentReconfiguration() {
    nextCheck = Long.MAX_VALUE;
  }

  public long getRefreshPeriod() {
    return refreshPeriod;
  }

  public void setRefreshPeriod(long refreshPeriod) {
    this.refreshPeriod = refreshPeriod;
  }

  class ReconfiguringThread implements Runnable {
    public void run() {
      if (mainConfigurationURL == null) {
        addInfo("Due to missing top level configuration file, skipping reconfiguration");
        return;
      }
      LoggerContext lc = (LoggerContext) context;
      addInfo(CoreConstants.RESET_MSG_PREFIX + "named [" + context.getName() + "]");
      if (mainConfigurationURL.toString().endsWith("xml")) {
        performXMLConfiguration(lc);
      }
    }

    private void performXMLConfiguration(LoggerContext lc) {
      JoranConfigurator jc = new JoranConfigurator();
      jc.setContext(lc);
      StatusUtil statusUtil = new StatusUtil(lc);
      List<SaxEvent> eventList = jc.recallSafeConfiguration();
      URL mainURL = ConfigurationWatchListUtil.getMainWatchURL(lc);
      lc.reset();
      new AndroidContextUtil().setupProperties(lc);
      long threshold = System.currentTimeMillis();
      try {
        jc.doConfigure(mainConfigurationURL);
        if (statusUtil.hasXMLParsingErrors(threshold)) {
          fallbackConfiguration(lc, eventList, mainURL);
        }
      } catch (JoranException e) {
        fallbackConfiguration(lc, eventList, mainURL);
      }
    }

    private void fallbackConfiguration(LoggerContext lc, List<SaxEvent> eventList, URL mainURL) {
      JoranConfigurator joranConfigurator = new JoranConfigurator();
      joranConfigurator.setContext(lc);
      if (eventList != null) {
        addWarn("Falling back to previously registered safe configuration.");
        try {
          lc.reset();
          new AndroidContextUtil().setupProperties(lc);
          JoranConfigurator.informContextOfURLUsedForConfiguration(lc, mainURL);
          joranConfigurator.doConfigure(eventList);
          addInfo("Re-registering previous fallback configuration once more as a fallback configuration point");
          joranConfigurator.registerSafeConfiguration(eventList);
        } catch (JoranException e) {
          addError("Unexpected exception thrown by a configuration considered safe.", e);
        }
      } else {
        addWarn("No previous configuration to fall back on.");
      }
    }
  }
}


/**
 * 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.spi;

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

import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.util.COWArrayList;

/**
 * Implementation of FilterAttachable.
 *
 * @author Ceki Gülcü
 */
final public class FilterAttachableImpl<E> implements FilterAttachable<E> {

  @SuppressWarnings("unchecked")
  COWArrayList<Filter<E>> filterList = new COWArrayList<Filter<E>>(new Filter[0]);

  /**
   * Add a filter to end of the filter list.
   */
  public void addFilter(Filter<E> newFilter) {
    filterList.add(newFilter);
  }

  /**
   * Clear the filter chain
   */
  public void clearAllFilters() {
    filterList.clear();
  }

  /**
   * Loop through the filters in the list. As soon as a filter decides on
   * ACCEPT or DENY, then that value is returned. If all of the filters return
   * NEUTRAL, then NEUTRAL is returned.
   */
  public FilterReply getFilterChainDecision(E event) {
    final Filter<E>[] filterArrray = filterList.asTypedArray();
    final int len = filterArrray.length;

    for (int i = 0; i < len; i++) {
      final FilterReply r = filterArrray[i].decide(event);
      if (r == FilterReply.DENY || r == FilterReply.ACCEPT) {
        return r;
      }
    }

    // no decision
    return FilterReply.NEUTRAL;
  }

  public List<Filter<E>> getCopyOfAttachedFiltersList() {
    return new ArrayList<Filter<E>>(filterList);
  }
}


/**
 * 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.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.Socket;
import java.net.SocketException;
import java.util.concurrent.BlockingQueue;

import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.util.CloseUtil;

/**
 * A {@link RemoteReceiverClient} that writes serialized logging events to an
 * {@link OutputStream}.
 *
 * @author Carl Harris
 */
class RemoteReceiverStreamClient
    extends ContextAwareBase implements RemoteReceiverClient {

  private final String clientId;
  private final Socket socket;
  private final OutputStream outputStream;

  private BlockingQueue<Serializable> queue;

  /**
   * Constructs a new client.
   * @param id identifier string for the client
   * @param socket socket to which logging events will be written
   */
  public RemoteReceiverStreamClient(String id, Socket socket) {
    this.clientId = "client " + id + ": ";
    this.socket = socket;
    this.outputStream = null;
  }

  /**
   * Constructs a new client.
   * <p>
   * This constructor exists primarily to support unit tests where it
   * is inconvenient to have to create a socket for the test.
   *
   * @param id identifier string for the client
   * @param outputStream output stream to which logging Events will be written
   */
  RemoteReceiverStreamClient(String id, OutputStream outputStream) {
    this.clientId = "client " + id + ": ";
    this.socket = null;
    this.outputStream = outputStream;
  }

  /**
   * {@inheritDoc}
   */
  public void setQueue(BlockingQueue<Serializable> queue) {
    this.queue = queue;
  }

  /**
   * {@inheritDoc}
   */
  public boolean offer(Serializable event) {
    if (queue == null) {
      throw new IllegalStateException("client has no event queue");
    }
    return queue.offer(event);
  }

  /**
   * {@inheritDoc}
   */
  public void close() {
    if (socket == null) return;
    CloseUtil.closeQuietly(socket);
  }

  /**
   * {@inheritDoc}
   */
  public void run() {
    addInfo(clientId + "connected");

    ObjectOutputStream oos = null;
    try {
      int counter = 0;
      oos = createObjectOutputStream();
      while (!Thread.currentThread().isInterrupted()) {
        try {
          Serializable event = queue.take();
          oos.writeObject(event);
          oos.flush();
          if (++counter >= CoreConstants.OOS_RESET_FREQUENCY) {
            // failing to reset the stream periodically will result in a
            // serious memory leak (as noted in AbstractSocketAppender)
            counter = 0;
            oos.reset();
          }
        }
        catch (InterruptedException ex) {
          Thread.currentThread().interrupt();
        }
      }
    }
    catch (SocketException ex) {
      addInfo(clientId + ex);
    }
    catch (IOException ex) {
      addError(clientId + ex);
    }
    catch (RuntimeException ex) {
      addError(clientId + ex);
    }
    finally {
      if (oos != null) {
        CloseUtil.closeQuietly(oos);
      }
      close();
      addInfo(clientId + "connection closed");
    }
  }

  private ObjectOutputStream createObjectOutputStream() throws IOException {
    if (socket == null) {
      return new ObjectOutputStream(outputStream);
    }
    return new ObjectOutputStream(socket.getOutputStream());
  }
}


/**
 * 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.ssl;

import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;

/**
 * A factory bean for a JCA {@link SecureRandom} generator.
 * <p>
 * This object holds the configurable properties of a secure random generator
 * and uses them to create and load a {@link SecureRandom} instance.
 *
 * @author Carl Harris
 */
public class SecureRandomFactoryBean {

  private String algorithm;
  private String provider;

  /**
   * Creates a new {@link SecureRandom} generator using the receiver's
   * configuration.
   * @return secure random generator instance
   * @throws NoSuchProviderException if the provider name specified by
   *    {@link #setProvider(String)} is not known to the platform
   * @throws NoSuchAlgorithmException if the algorithm name specified by
   *    {@link #setAlgorithm(String)} is not recognized by the specified
   *    provider (or the platform's default provider if the provider isn't
   *    specified)
   */
  public SecureRandom createSecureRandom() throws NoSuchProviderException,
      NoSuchAlgorithmException {
    try {
      return getProvider() != null ?
          SecureRandom.getInstance(getAlgorithm(), getProvider())
          : SecureRandom.getInstance(getAlgorithm());
    }
    catch (NoSuchProviderException ex) {
      throw new NoSuchProviderException("no such secure random provider: "
          + getProvider());
    }
    catch (NoSuchAlgorithmException ex) {
      throw new NoSuchAlgorithmException("no such secure random algorithm: "
          + getAlgorithm());
    }
  }

  /**
   * Gets the secure random generator algorithm name.
   * @return an algorithm name (e.g. {@code SHA1PRNG}); the
   *    {@link SSL#DEFAULT_SECURE_RANDOM_ALGORITHM} is returned if no algorithm has been
   *    specified
   */
  public String getAlgorithm() {
    if (algorithm == null) {
      return SSL.DEFAULT_SECURE_RANDOM_ALGORITHM;
    }
    return algorithm;
  }

  /**
   * Sets the secure random generator algorithm name.
   * @param algorithm an algorithm name, which must be recognized by the
   *    provider specified via {@link #setProvider(String)} or by the
   *    platform's default provider if no provider is specified.
   */
  public void setAlgorithm(String algorithm) {
    this.algorithm = algorithm;
  }

  /**
   * Gets the JCA provider name for the secure random generator.
   * @return provider name
   */
  public String getProvider() {
    return provider;
  }

  /**
   * Sets the JCA provider name for the secure random generator.
   * @param provider name of the JCA provider to utilize in creating the
   *    secure random generator
   */
  public void setProvider(String provider) {
    this.provider = provider;
  }

}


/**
 * 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 org.xml.sax.Attributes;

import ch.qos.logback.core.joran.spi.InterpretationContext;
import ch.qos.logback.core.joran.spi.ElementSelector;
import ch.qos.logback.core.util.OptionHelper;

public class NewRuleAction extends Action {
  boolean inError = false;

  /**
   * Instantiates an layout of the given class and sets its name.
   */
  public void begin(InterpretationContext ec, String localName, Attributes attributes) {
    // Let us forget about previous errors (in this object)
    inError = false;
    String errorMsg;
    String pattern = attributes.getValue(Action.PATTERN_ATTRIBUTE);
    String actionClass = attributes.getValue(Action.ACTION_CLASS_ATTRIBUTE);

    if (OptionHelper.isEmpty(pattern)) {
      inError = true;
      errorMsg = "No 'pattern' attribute in <newRule>";
      addError(errorMsg);
      return;
    }

    if (OptionHelper.isEmpty(actionClass)) {
      inError = true;
      errorMsg = "No 'actionClass' attribute in <newRule>";
      addError(errorMsg);
      return;
    }

    try {
      addInfo("About to add new Joran parsing rule [" + pattern + ","
          + actionClass + "].");
      ec.getJoranInterpreter().getRuleStore().addRule(new ElementSelector(pattern),
          actionClass);
    } catch (Exception oops) {
      inError = true;
      errorMsg = "Could not add new Joran parsing rule [" + pattern + ","
          + actionClass + "]";
      addError(errorMsg);
    }
  }

  /**
   * Once the children elements are also parsed, now is the time to activate the
   * appender options.
   */
  public void end(InterpretationContext ec, String n) {
  }

  public void finish(InterpretationContext ec) {
  }
}


/**
 * 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.ssl;

import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

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

/**
 * A factory bean for a JSSE {@link SSLContext}.
 * <p>
 * This object holds the configurable properties for an SSL context and uses
 * them to create an {@link SSLContext} instance.
 *
 * @author Carl Harris
 */
public class SSLContextFactoryBean {

  private static final String JSSE_KEY_STORE_PROPERTY = "javax.net.ssl.keyStore";
  private static final String JSSE_TRUST_STORE_PROPERTY = "javax.net.ssl.trustStore";

  private KeyStoreFactoryBean keyStore;
  private KeyStoreFactoryBean trustStore;
  private SecureRandomFactoryBean secureRandom;
  private KeyManagerFactoryFactoryBean keyManagerFactory;
  private TrustManagerFactoryFactoryBean trustManagerFactory;
  private String protocol;
  private String provider;

  /**
   * Creates a new {@link SSLContext} using the receiver's configuration.
   * @param context context for status messages
   * @return {@link SSLContext} object
   * @throws NoSuchProviderException if a provider specified for one of the
   *    JCA or JSSE components utilized in creating the context is not
   *    known to the platform
   * @throws NoSuchAlgorithmException if a JCA or JSSE algorithm, protocol,
   *    or type name specified for one of the context's components is not
   *    known to a given provider (or platform default provider for the
   *    component)
   * @throws KeyManagementException if an error occurs in creating a
   *    {@link KeyManager} for the context
   * @throws UnrecoverableKeyException if a private key needed by a
   *    {@link KeyManager} cannot be obtained from a key store
   * @throws KeyStoreException if an error occurs in reading the
   *    contents of a key store
   * @throws CertificateException if an error occurs in reading the
   *    contents of a certificate
   */
  public SSLContext createContext(ContextAware context) throws NoSuchProviderException,
      NoSuchAlgorithmException, KeyManagementException,
      UnrecoverableKeyException, KeyStoreException, CertificateException {

    SSLContext sslContext = getProvider() != null ?
        SSLContext.getInstance(getProtocol(), getProvider())
        : SSLContext.getInstance(getProtocol());

    context.addInfo("SSL protocol '" + sslContext.getProtocol()
        + "' provider '" + sslContext.getProvider() + "'");

    KeyManager[] keyManagers = createKeyManagers(context);
    TrustManager[] trustManagers = createTrustManagers(context);
    SecureRandom secureRandom = createSecureRandom(context);
    sslContext.init(keyManagers, trustManagers, secureRandom);
    return sslContext;
  }

  /**
   * Creates key managers using the receiver's key store configuration.
   * @param context context for status messages
   * @return an array of key managers or {@code null} if no key store
   *    configuration was provided
   * @throws NoSuchProviderException if a provider specified for one
   *    of the key manager components is not known to the platform
   * @throws NoSuchAlgorithmException if an algorithm specified for
   *    one of the key manager components is not known to the relevant
   *    provider
   * @throws KeyStoreException if an error occurs in reading a key store
   */
  private KeyManager[] createKeyManagers(ContextAware context)
      throws NoSuchProviderException, NoSuchAlgorithmException,
      UnrecoverableKeyException, KeyStoreException {

    if (getKeyStore() == null) return null;

    KeyStore keyStore = getKeyStore().createKeyStore();
    context.addInfo(
        "key store of type '" + keyStore.getType()
        + "' provider '" + keyStore.getProvider()
        + "': " + getKeyStore().getLocation());

    KeyManagerFactory kmf = getKeyManagerFactory().createKeyManagerFactory();
    context.addInfo("key manager algorithm '" + kmf.getAlgorithm()
        + "' provider '" + kmf.getProvider() + "'");

    char[] passphrase = getKeyStore().getPassword().toCharArray();
    kmf.init(keyStore, passphrase);
    return kmf.getKeyManagers();
  }

  /**
   * Creates trust managers using the receiver's trust store configuration.
   * @param context context for status messages
   * @return an array of trust managers or {@code null} if no trust store
   *    configuration was provided
   * @throws NoSuchProviderException if a provider specified for one
   *    of the trust manager components is not known to the platform
   * @throws NoSuchAlgorithmException if an algorithm specified for
   *    one of the trust manager components is not known to the relevant
   *    provider
   * @throws KeyStoreException if an error occurs in reading a key
   *    store containing trust anchors
   */
  private TrustManager[] createTrustManagers(ContextAware context)
      throws NoSuchProviderException, NoSuchAlgorithmException,
      KeyStoreException {

    if (getTrustStore() == null) return null;

    KeyStore trustStore = getTrustStore().createKeyStore();
    context.addInfo(
        "trust store of type '" + trustStore.getType()
        + "' provider '" + trustStore.getProvider()
        + "': " + getTrustStore().getLocation());

    TrustManagerFactory tmf = getTrustManagerFactory()
        .createTrustManagerFactory();
    context.addInfo("trust manager algorithm '" + tmf.getAlgorithm()
        + "' provider '" + tmf.getProvider() + "'");

    tmf.init(trustStore);
    return tmf.getTrustManagers();
  }

  private SecureRandom createSecureRandom(ContextAware context)
      throws NoSuchProviderException, NoSuchAlgorithmException {

    SecureRandom secureRandom = getSecureRandom().createSecureRandom();
    context.addInfo("secure random algorithm '" + secureRandom.getAlgorithm()
        + "' provider '" + secureRandom.getProvider() + "'");

    return secureRandom;
  }

  /**
   * Gets the key store configuration.
   * @return key store factory bean or {@code null} if no key store
   *    configuration was provided
   */
  public KeyStoreFactoryBean getKeyStore() {
    if (keyStore == null) {
      keyStore = keyStoreFromSystemProperties(JSSE_KEY_STORE_PROPERTY);
    }
    return keyStore;
  }

  /**
   * Sets the key store configuration.
   * @param keyStore the key store factory bean to set
   */
  public void setKeyStore(KeyStoreFactoryBean keyStore) {
    this.keyStore = keyStore;
  }

  /**
   * Gets the trust store configuration.
   * @return trust store factory bean or {@code null} if no trust store
   *    configuration was provided
   */
  public KeyStoreFactoryBean getTrustStore() {
    if (trustStore == null) {
      trustStore = keyStoreFromSystemProperties(JSSE_TRUST_STORE_PROPERTY);
    }
    return trustStore;
  }

  /**
   * Sets the trust store configuration.
   * @param trustStore the trust store factory bean to set
   */
  public void setTrustStore(KeyStoreFactoryBean trustStore) {
    this.trustStore = trustStore;
  }

  /**
   * Constructs a key store factory bean using JSSE system properties.
   * @param property base property name (e.g. {@code javax.net.ssl.keyStore})
   * @return key store or {@code null} if no value is defined for the
   *    base system property name
   */
  private KeyStoreFactoryBean keyStoreFromSystemProperties(String property) {
    if (System.getProperty(property) == null) return null;
    KeyStoreFactoryBean keyStore = new KeyStoreFactoryBean();
    keyStore.setLocation(locationFromSystemProperty(property));
    keyStore.setProvider(System.getProperty(property + "Provider"));
    keyStore.setPassword(System.getProperty(property + "Password"));
    keyStore.setType(System.getProperty(property + "Type"));
    return keyStore;
  }

  /**
   * Constructs a resource location from a JSSE system property.
   * @param name property name (e.g. {@code javax.net.ssl.keyStore})
   * @return URL for the location specified in the property or {@code null}
   *    if no value is defined for the property
   */
  private String locationFromSystemProperty(String name) {
    String location = System.getProperty(name);
    if (location != null && !location.startsWith("file:")) {
      location = "file:" + location;
    }
    return location;
  }

  /**
   * Gets the secure random generator configuration.
   * @return secure random factory bean; if no secure random generator
   *    configuration has been set, a default factory bean is returned
   */
  public SecureRandomFactoryBean getSecureRandom() {
    if (secureRandom == null) {
      return new SecureRandomFactoryBean();
    }
    return secureRandom;
  }

  /**
   * Sets the secure random generator configuration.
   * @param secureRandom the secure random factory bean to set
   */
  public void setSecureRandom(SecureRandomFactoryBean secureRandom) {
    this.secureRandom = secureRandom;
  }

  /**
   * Gets the key manager factory configuration.
   * @return factory bean; if no key manager factory
   *    configuration has been set, a default factory bean is returned
   */
  public KeyManagerFactoryFactoryBean getKeyManagerFactory() {
    if (keyManagerFactory == null) {
      return new KeyManagerFactoryFactoryBean();
    }
    return keyManagerFactory;
  }

  /**
   * Sets the key manager factory configuration.
   * @param keyManagerFactory the key manager factory factory bean to set
   */
  public void setKeyManagerFactory(
      KeyManagerFactoryFactoryBean keyManagerFactory) {
    this.keyManagerFactory = keyManagerFactory;
  }

  /**
   * Gets the trust manager factory configuration.
   * @return factory bean; if no trust manager factory
   *    configuration has been set, a default factory bean is returned
   */
  public TrustManagerFactoryFactoryBean getTrustManagerFactory() {
    if (trustManagerFactory == null) {
      return new TrustManagerFactoryFactoryBean();
    }
    return trustManagerFactory;
  }

  /**
   * Sets the trust manager factory configuration.
   * @param trustManagerFactory the factory bean to set
   */
  public void setTrustManagerFactory(
      TrustManagerFactoryFactoryBean trustManagerFactory) {
    this.trustManagerFactory = trustManagerFactory;
  }

  /**
   * Gets the secure transport protocol name.
   * @return protocol name (e.g. {@code SSL}, {@code TLS}); the
   *    {@link SSL#DEFAULT_PROTOCOL} is returned if no protocol has been
   *    configured
   */
  public String getProtocol() {
    if (protocol == null) {
      return SSL.DEFAULT_PROTOCOL;
    }
    return protocol;
  }

  /**
   * Sets the secure transport protocol name.
   * @param protocol a protocol name, which must be recognized by the provider
   *    specified by {@link #setProvider(String)} or by the platform's
   *    default provider if no platform was specified.
   */
  public void setProtocol(String protocol) {
    this.protocol = protocol;
  }

  /**
   * Gets the JSSE provider name for the SSL context.
   * @return JSSE provider name
   */
  public String getProvider() {
    return provider;
  }

  /**
   * Sets the JSSE provider name for the SSL context.
   * @param provider name of the JSSE provider to use in creating the
   *    SSL context
   */
  public void setProvider(String provider) {
    this.provider = provider;
  }

}


/**
 * 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.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * A static utility method that converts a string that describes the
 * location of a resource into a {@link URL} object.
 *
 * @author Carl Harris
 */
public class LocationUtil {

  /** Regex pattern for a URL scheme (reference RFC 2396 section 3) */
  public static final String SCHEME_PATTERN =
      "^\\p{Alpha}[\\p{Alnum}+.-]*:.*$";

  /** Scheme name for a classpath resource */
  public static final String CLASSPATH_SCHEME = "classpath:";

  /**
   * Converts a string describing the location of a resource into a URL object.
   * @param location String describing the location
   * @return URL object that refers to {@code location}
   * @throws MalformedURLException if {@code location} is not a syntatically
   *    valid URL
   * @throws FileNotFoundException if {@code location} specifies a non-existent
   *    classpath resource
   * @throws NullPointerException if {@code location} is {@code null}
   */
  public static URL urlForResource(String location)
      throws MalformedURLException, FileNotFoundException {
    if (location == null) {
      throw new NullPointerException("location is required");
    }
    URL url = null;
    if (!location.matches(SCHEME_PATTERN)) {
      url = Loader.getResourceBySelfClassLoader(location);
    }
    else if (location.startsWith(CLASSPATH_SCHEME)) {
      String path = location.substring(CLASSPATH_SCHEME.length());
      if (path.startsWith("/")) {
        path = path.substring(1);
      }
      if (path.length() == 0) {
        throw new MalformedURLException("path is required");
      }
      url = Loader.getResourceBySelfClassLoader(path);
    }
    else {
      url = new URL(location);
    }
    if (url == null) {
      throw new FileNotFoundException(location);
    }
    return url;
  }
}


/**
 * 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 java.util.HashMap;
import java.util.Map;

import org.xml.sax.Attributes;

import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.joran.spi.InterpretationContext;
import ch.qos.logback.core.util.OptionHelper;

public class ConversionRuleAction extends Action {
  boolean inError = false;

  /**
   * Instantiates an layout of the given class and sets its name.
   *
   */
  @SuppressWarnings("unchecked")
  public void begin(InterpretationContext ec, String localName, Attributes attributes) {
    // Let us forget about previous errors (in this object)
    inError = false;

    String errorMsg;
    String conversionWord =
      attributes.getValue(ActionConst.CONVERSION_WORD_ATTRIBUTE);
    String converterClass =
      attributes.getValue(ActionConst.CONVERTER_CLASS_ATTRIBUTE);

    if (OptionHelper.isEmpty(conversionWord)) {
      inError = true;
      errorMsg = "No 'conversionWord' attribute in <conversionRule>";
      addError(errorMsg);

      return;
    }

    if (OptionHelper.isEmpty(converterClass)) {
      inError = true;
      errorMsg = "No 'converterClass' attribute in <conversionRule>";
      ec.addError(errorMsg);

      return;
    }

    try {
      Map<String, String> ruleRegistry = (Map<String, String>) context.getObject(CoreConstants.PATTERN_RULE_REGISTRY);
      if(ruleRegistry == null) {
        ruleRegistry = new HashMap<String, String>();
        context.putObject(CoreConstants.PATTERN_RULE_REGISTRY, ruleRegistry);
      }
      // put the new rule into the rule registry
      addInfo("registering conversion word "+conversionWord+" with class ["+converterClass+"]");
      ruleRegistry.put(conversionWord, converterClass);
    } catch (Exception oops) {
      inError = true;
      errorMsg = "Could not add conversion rule to PatternLayout.";
      addError(errorMsg);
    }
  }

  /**
   * Once the children elements are also parsed, now is the time to activate
   * the appender options.
   */
  public void end(InterpretationContext ec, String n) {
  }

  public void finish(InterpretationContext ec) {
  }
}


/**
 * 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.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.pattern.Converter;
import ch.qos.logback.core.pattern.ConverterUtil;
import ch.qos.logback.core.pattern.PostCompileProcessor;

public class EnsureExceptionHandling implements
    PostCompileProcessor<ILoggingEvent> {

  /**
   * This implementation checks if any of the converters in the chain handles
   * exceptions. If not, then this method adds a
   * {@link ThrowableProxyConverter} instance to the end of the chain.
   *

   * This allows appenders using this layout to output exception information
   * event if the user forgets to add %ex to the pattern. Note that the
   * appenders defined in the Core package are not aware of exceptions nor
   * LoggingEvents.
   *

   * If for some reason the user wishes to NOT print exceptions, then she can
   * add %nopex to the pattern.
   *
   *
   */
  public void process(Context context, Converter<ILoggingEvent> head) {
    if(head == null) {
      // this should never happen
      throw new IllegalArgumentException("cannot process empty chain");
    }
    if (!chainHandlesThrowable(head)) {
      Converter<ILoggingEvent> tail = ConverterUtil.findTail(head);
      Converter<ILoggingEvent> exConverter = null;
      LoggerContext loggerContext = (LoggerContext) context;
      if (loggerContext.isPackagingDataEnabled()) {
        exConverter = new ExtendedThrowableProxyConverter();
      } else {
        exConverter = new ThrowableProxyConverter();
      }
      tail.setNext(exConverter);
    }
  }

  /**
   * This method computes whether a chain of converters handles exceptions or
   * not.
   *
   * @param head
   *                The first element of the chain
   * @return true if can handle throwables contained in logging events
   */
  public boolean chainHandlesThrowable(Converter<ILoggingEvent> head) {
    Converter<ILoggingEvent> c = head;
    while (c != null) {
      if (c instanceof ThrowableHandlingConverter) {
        return true;
      }
      c = c.getNext();
    }
    return false;
  }
}


/**
 * 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.hook;

import ch.qos.logback.core.Context;
import ch.qos.logback.core.ContextBase;
import ch.qos.logback.core.spi.ContextAwareBase;

/**
 * Base class for classes implementing a Logback ShutdownHook via extension
 *
 * @author Mike Reinhold
 */
public abstract class ShutdownHookBase extends ContextAwareBase implements ShutdownHook {

    public ShutdownHookBase() {
    }

    /**
     * Default method for stopping the Logback context
     */
    protected void stop() {
        addInfo("Logback context being closed via shutdown hook");

        Context hookContext = getContext();
        if (hookContext instanceof ContextBase) {
            ContextBase context = (ContextBase) hookContext;
            context.stop();
        }
    }
}


/**
 * 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.io.PrintStream;
import java.util.Iterator;
import java.util.List;

import ch.qos.logback.core.Context;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.helpers.ThrowableToStringArray;
import ch.qos.logback.core.status.*;

import static ch.qos.logback.core.status.StatusUtil.filterStatusListByTimeThreshold;

public class StatusPrinter {

  private static PrintStream ps = System.out;

  static CachingDateFormatter cachingDateFormat = new CachingDateFormatter(
      "HH:mm:ss,SSS");

  public static void setPrintStream(PrintStream printStream) {
    ps = printStream;
  }

  /**
   * Print the contents of the context statuses, but only if they contain
   * warnings or errors.
   *
   * @param context
   */
  public static void printInCaseOfErrorsOrWarnings(Context context) {
    printInCaseOfErrorsOrWarnings(context, 0);
  }

  /**
   * Print the contents of the context status, but only if they contain
   * warnings or errors occurring later then the threshold.
   *
   * @param context
   */
  public static void printInCaseOfErrorsOrWarnings(Context context, long threshold) {
    if (context == null) {
      throw new IllegalArgumentException("Context argument cannot be null");
    }

    StatusManager sm = context.getStatusManager();
    if (sm == null) {
      ps.println("WARN: Context named \"" + context.getName()
          + "\" has no status manager");
    } else {
      StatusUtil statusUtil = new StatusUtil(context);
      if (statusUtil.getHighestLevel(threshold) >= ErrorStatus.WARN) {
        print(sm, threshold);
      }
    }
  }

  /**
   * Print the contents of the context statuses, but only if they contain
   * errors.
   *
   * @param context
   */
  public static void printIfErrorsOccured(Context context) {
    if (context == null) {
      throw new IllegalArgumentException("Context argument cannot be null");
    }

    StatusManager sm = context.getStatusManager();
    if (sm == null) {
      ps.println("WARN: Context named \"" + context.getName()
          + "\" has no status manager");
    } else {
      StatusUtil statusUtil = new StatusUtil(context);
      if (statusUtil.getHighestLevel(0) == ErrorStatus.ERROR) {
        print(sm);
      }
    }
  }

  /**
   * Print the contents of the context's status data.
   *
   * @param context
   */
  public static void print(Context context) {
    print(context, 0);
  }

   /**
   * Print context's status data with a timestamp higher than the threshold.
   * @param context
   */
   public static void print(Context context, long threshold) {
     if (context == null) {
       throw new IllegalArgumentException("Context argument cannot be null");
     }

     StatusManager sm = context.getStatusManager();
     if (sm == null) {
       ps.println("WARN: Context named \"" + context.getName()
           + "\" has no status manager");
     } else {
       print(sm, threshold);
     }
   }

  public static void print(StatusManager sm) {
    print(sm, 0);
  }

  public static void print(StatusManager sm, long threshold) {
    StringBuilder sb = new StringBuilder();
    List<Status> filteredList = filterStatusListByTimeThreshold(sm.getCopyOfStatusList(), threshold);
    buildStrFromStatusList(sb, filteredList);
    ps.println(sb.toString());
  }

  public static void print(List<Status> statusList) {
    StringBuilder sb = new StringBuilder();
    buildStrFromStatusList(sb, statusList);
    ps.println(sb.toString());
  }

  private static void buildStrFromStatusList(StringBuilder sb, List<Status> statusList) {
    if(statusList == null)
      return;
    for(Status s : statusList) {
      buildStr(sb, "", s);
    }
  }

//  private static void buildStrFromStatusManager(StringBuilder sb, StatusManager sm) {
//  }

  private static void appendThrowable(StringBuilder sb, Throwable t) {
    String[] stringRep = ThrowableToStringArray.convert(t);

    for (String s : stringRep) {
      if (s.startsWith(CoreConstants.CAUSED_BY)) {
        // nothing
      } else if (Character.isDigit(s.charAt(0))) {
        // if line resembles "48 common frames omitted"
        sb.append("\t... ");
      } else {
        // most of the time. just add a tab+"at"
        sb.append("\tat ");
      }
      sb.append(s).append(CoreConstants.LINE_SEPARATOR);
    }
  }

  public static void buildStr(StringBuilder sb, String indentation, Status s) {
    String prefix;
    if (s.hasChildren()) {
      prefix = indentation + "+ ";
    } else {
      prefix = indentation + "|-";
    }

    if (cachingDateFormat != null) {
      String dateStr = cachingDateFormat.format(s.getDate());
      sb.append(dateStr).append(" ");
    }
    sb.append(prefix).append(s).append(CoreConstants.LINE_SEPARATOR);

    if (s.getThrowable() != null) {
      appendThrowable(sb, s.getThrowable());
    }
    if (s.hasChildren()) {
      Iterator<Status> ite = s.iterator();
      while (ite.hasNext()) {
        Status child = ite.next();
        buildStr(sb, indentation + "  ", child);
      }
    }
  }

}


/**
 * 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.
 */
// Contributors: Dan MacDonald <dan@redknee.com>
package ch.qos.logback.core.net;

import java.io.IOException;
import java.io.Serializable;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import javax.net.SocketFactory;

import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.spi.PreSerializationTransformer;
import ch.qos.logback.core.util.CloseUtil;
import ch.qos.logback.core.util.Duration;

/**
 * An abstract base for module specific {@code SocketAppender}
 * implementations in other logback modules.
 *
 * @author Ceki Gülcü
 * @author Sébastien Pennec
 * @author Carl Harris
 * @author Sebastian Gröbler
 */

public abstract class AbstractSocketAppender<E> extends AppenderBase<E>
    implements SocketConnector.ExceptionHandler {

  /**
   * The default port number of remote logging server (4560).
   */
  public static final int DEFAULT_PORT = 4560;

  /**
   * The default reconnection delay (30000 milliseconds or 30 seconds).
   */
  public static final int DEFAULT_RECONNECTION_DELAY = 30000;

  /**
   * Default size of the deque used to hold logging events that are destined
   * for the remote peer.
   */
  public static final int DEFAULT_QUEUE_SIZE = 128;

  /**
   * Default timeout when waiting for the remote server to accept our
   * connection.
   */
  private static final int DEFAULT_ACCEPT_CONNECTION_DELAY = 5000;

  /**
   * Default timeout for how long to wait when inserting an event into
   * the BlockingQueue.
   */
  private static final int DEFAULT_EVENT_DELAY_TIMEOUT = 100;

  private final ObjectWriterFactory objectWriterFactory;
  private final QueueFactory queueFactory;

  private String remoteHost;
  private int port = DEFAULT_PORT;
  private InetAddress address;
  private Duration reconnectionDelay = new Duration(DEFAULT_RECONNECTION_DELAY);
  private int queueSize = DEFAULT_QUEUE_SIZE;
  private int acceptConnectionTimeout = DEFAULT_ACCEPT_CONNECTION_DELAY;
  private Duration eventDelayLimit = new Duration(DEFAULT_EVENT_DELAY_TIMEOUT);

  private BlockingDeque<E> deque;
  private String peerId;
  private SocketConnector connector;
  private Future<?> task;

  private volatile Socket socket;

  /**
   * Constructs a new appender.
   */
  protected AbstractSocketAppender() {
    this(new QueueFactory(), new ObjectWriterFactory());
  }

  /**
   * Constructs a new appender using the given {@link QueueFactory} and {@link ObjectWriterFactory}.
   */
  AbstractSocketAppender(QueueFactory queueFactory, ObjectWriterFactory objectWriterFactory) {
    this.objectWriterFactory = objectWriterFactory;
    this.queueFactory = queueFactory;
  }

  /**
   * {@inheritDoc}
   */
  public void start() {
    if (isStarted()) return;
    int errorCount = 0;
    if (port <= 0) {
      errorCount++;
      addError("No port was configured for appender"
          + name
          + " For more information, please visit http://logback.qos.ch/codes.html#socket_no_port");
    }

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

    if (queueSize == 0) {
      addWarn("Queue size of zero is deprecated, use a size of one to indicate synchronous processing");
    }

    if (queueSize < 0) {
      errorCount++;
      addError("Queue size must be greater than zero");
    }

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

    if (errorCount == 0) {
      deque = queueFactory.newLinkedBlockingDeque(queueSize);
      peerId = "remote peer " + remoteHost + ":" + port + ": ";
      connector = createConnector(address, port, 0, reconnectionDelay.getMilliseconds());
      task = getContext().getScheduledExecutorService().submit(new Runnable() {
        public void run() {
          connectSocketAndDispatchEvents();
        }
      });
      super.start();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void stop() {
    if (!isStarted()) return;
    CloseUtil.closeQuietly(socket);
    task.cancel(true);
    super.stop();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void append(E event) {
    if (event == null || !isStarted()) return;

    try {
      final boolean inserted = deque.offer(event, eventDelayLimit.getMilliseconds(), TimeUnit.MILLISECONDS);
      if (!inserted) {
        addInfo("Dropping event due to timeout limit of [" + eventDelayLimit + "] being exceeded");
      }
    } catch (InterruptedException e) {
      addError("Interrupted while appending event to SocketAppender", e);
    }
  }

  private void connectSocketAndDispatchEvents() {
    try {
      while (socketConnectionCouldBeEstablished()) {
        try {
          ObjectWriter objectWriter = createObjectWriterForSocket();
          addInfo(peerId + "connection established");
          dispatchEvents(objectWriter);
        } catch (IOException ex) {
          addInfo(peerId + "connection failed: " + ex);
        } finally {
          CloseUtil.closeQuietly(socket);
          socket = null;
          addInfo(peerId + "connection closed");
        }
      }
    } catch (InterruptedException ex) {
      assert true;    // ok... we'll exit now
    }
    addInfo("shutting down");
  }

  private boolean socketConnectionCouldBeEstablished() throws InterruptedException {
    return (socket = connector.call()) != null;
  }

  private ObjectWriter createObjectWriterForSocket() throws IOException {
    socket.setSoTimeout(acceptConnectionTimeout);
    ObjectWriter objectWriter = objectWriterFactory.newAutoFlushingObjectWriter(socket.getOutputStream());
    socket.setSoTimeout(0);
    return objectWriter;
  }

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

  private void dispatchEvents(ObjectWriter objectWriter) throws InterruptedException, IOException {
    while (true) {
      E event = deque.takeFirst();
      postProcessEvent(event);
      Serializable serializableEvent = getPST().transform(event);
      try {
        objectWriter.write(serializableEvent);
      } catch (IOException e) {
        tryReAddingEventToFrontOfQueue(event);
        throw e;
      }
    }
  }

  private void tryReAddingEventToFrontOfQueue(E event) {
    final boolean wasInserted = deque.offerFirst(event);
    if (!wasInserted) {
      addInfo("Dropping event due to socket connection error and maxed out deque capacity");
    }
  }

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

  /**
   * Creates a new {@link SocketConnector}.
   *

   * The default implementation creates an instance of {@link DefaultSocketConnector}.
   * A subclass may override to provide a different {@link SocketConnector}
   * implementation.
   *
   * @param address target remote address
   * @param port target remote port
   * @param initialDelay delay before the first connection attempt
   * @param retryDelay delay before a reconnection attempt
   * @return socket connector
   */
  protected SocketConnector newConnector(InetAddress address, int port, long initialDelay, long retryDelay) {
    return new DefaultSocketConnector(address, port, initialDelay, retryDelay);
  }

  /**
   * Gets the default {@link SocketFactory} for the platform.
   *

   * Subclasses may override to provide a custom socket factory.
   * @return the socket factory
   */
  protected SocketFactory getSocketFactory() {
    return SocketFactory.getDefault();
  }

  /**
   * Post-processes an event before it is serialized for delivery to the
   * remote receiver.
   * @param event the event to post-process
   */
  protected abstract void postProcessEvent(E event);

  /**
   * Get the pre-serialization transformer that will be used to transform
   * each event into a Serializable object before delivery to the remote
   * receiver.
   * @return transformer object
   */
  protected abstract PreSerializationTransformer<E> getPST();

  /**
   * The RemoteHost property takes the name of of the host where a corresponding server is running.
   * @param host the remote hostname
   */
  public void setRemoteHost(String host) {
    remoteHost = host;
  }

  /**
   * Returns value of the RemoteHost property.
   * @return the RemoteHost value
   */
  public String getRemoteHost() {
    return remoteHost;
  }

  /**
   * The Port property takes a positive integer representing the port
   * where the server is waiting for connections.
   * @param port the desired port
   */
  public void setPort(int port) {
    this.port = port;
  }

  /**
   * Returns value of the Port property.
   * @return the Port value
   */
  public int getPort() {
    return port;
  }

  /**
   * The reconnectionDelay property takes a positive {@link Duration} value
   * representing the time to wait between each failed connection attempt
   * to the server. The default value of this option is to 30 seconds.
   *
   *

   * Setting this option to zero turns off reconnection capability.
   * @param delay the desired reconnection delay
   */
  public void setReconnectionDelay(Duration delay) {
    this.reconnectionDelay = delay;
  }

  /**
   * Returns value of the reconnectionDelay property.
   * @return the reconnection delay
   */
  public Duration getReconnectionDelay() {
    return reconnectionDelay;
  }

  /**
   * The queueSize property takes a non-negative integer representing
   * the number of logging events to retain for delivery to the remote receiver.
   * When the deque size is zero, event delivery to the remote receiver is
   * synchronous.  When the deque size is greater than zero, the
   * {@link #append(Object)} method returns immediately after enqueing the
   * event, assuming that there is space available in the deque.  Using a
   * non-zero deque length can improve performance by eliminating delays
   * caused by transient network delays.
   *
   * @param queueSize the deque size to set.
   */
  public void setQueueSize(int queueSize) {
    this.queueSize = queueSize;
  }

  /**
   * Returns the value of the queueSize property.
   * @return the size of the queue
   */
  public int getQueueSize() {
    return queueSize;
  }

  /**
   * The eventDelayLimit takes a non-negative integer representing the
   * number of milliseconds to allow the appender to block if the underlying
   * BlockingQueue is full. Once this limit is reached, the event is dropped.
   *
   * @param eventDelayLimit the event delay limit
   */
  public void setEventDelayLimit(Duration eventDelayLimit) {
    this.eventDelayLimit = eventDelayLimit;
  }

  /**
   * Returns the value of the eventDelayLimit property.
   * @return the event delay limit in ms
   */
  public Duration getEventDelayLimit() {
    return eventDelayLimit;
  }

  /**
   * Sets the timeout that controls how long we'll wait for the remote
   * peer to accept our connection attempt.
   *

   * This property is configurable primarily to support instrumentation
   * for unit testing.
   *
   * @param acceptConnectionTimeout timeout value in milliseconds
   */
  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.classic.net;

import java.io.IOException;
import java.io.OutputStream;
import java.net.SocketException;
import java.net.UnknownHostException;

import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.pattern.SyslogStartConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.classic.util.LevelToSyslogSeverity;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.Layout;
import ch.qos.logback.core.net.SyslogAppenderBase;
import ch.qos.logback.core.net.SyslogOutputStream;

/**
 * This appender can be used to send messages to a remote syslog daemon. <p> For
 * more information about this appender, please refer to the online manual at
 * http://logback.qos.ch/manual/appenders.html#SyslogAppender
 *
 * @author Ceki Gülcü
 */
public class SyslogAppender extends SyslogAppenderBase<ILoggingEvent> {

  static final public String DEFAULT_SUFFIX_PATTERN = "[%thread] %logger %msg";
  static final public String DEFAULT_STACKTRACE_PATTERN = "" + CoreConstants.TAB;

  PatternLayout stackTraceLayout = new PatternLayout();
  String stackTracePattern = DEFAULT_STACKTRACE_PATTERN;

  boolean throwableExcluded = false;

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

  String getPrefixPattern() {
    return "%syslogStart{" + getFacility() + "}%nopex{}";
  }

  @Override
  public SyslogOutputStream createOutputStream() throws SocketException, UnknownHostException {
    return new SyslogOutputStream(getSyslogHost(), getPort());
  }

  /**
   * Convert a level to equivalent syslog severity. Only levels for printing
   * methods i.e DEBUG, WARN, INFO and ERROR are converted.
   *
   * @see ch.qos.logback.core.net.SyslogAppenderBase#getSeverityForEvent(java.lang.Object)
   */
  @Override
  public int getSeverityForEvent(Object eventObject) {
    ILoggingEvent event = (ILoggingEvent) eventObject;
    return LevelToSyslogSeverity.convert(event);
  }

  @Override
  protected void postProcess(Object eventObject, OutputStream sw) {
    if (throwableExcluded)
      return;

    ILoggingEvent event = (ILoggingEvent) eventObject;
    IThrowableProxy tp = event.getThrowableProxy();

    if (tp == null)
      return;

    String stackTracePrefix = stackTraceLayout.doLayout(event);
    boolean isRootException = true;
    while (tp != null) {
      StackTraceElementProxy[] stepArray = tp.getStackTraceElementProxyArray();
      try {
        handleThrowableFirstLine(sw, tp, stackTracePrefix, isRootException);
        isRootException = false;
        for (StackTraceElementProxy step : stepArray) {
          StringBuilder sb = new StringBuilder();
          sb.append(stackTracePrefix).append(step);
          sw.write(sb.toString().getBytes());
          sw.flush();
        }
      } catch (IOException e) {
        break;
      }
      tp = tp.getCause();
    }
  }

  // LOGBACK-411 and  LOGBACK-750
  private void handleThrowableFirstLine(OutputStream sw, IThrowableProxy tp, String stackTracePrefix, boolean isRootException) throws IOException {
    StringBuilder sb = new StringBuilder().append(stackTracePrefix);

    if (!isRootException) {
      sb.append(CoreConstants.CAUSED_BY);
    }
    sb.append(tp.getClassName()).append(": ").append(tp.getMessage());
    sw.write(sb.toString().getBytes());
    sw.flush();
  }

  boolean stackTraceHeaderLine(StringBuilder sb, boolean topException) {

    return false;
  }

  public Layout<ILoggingEvent> buildLayout() {
    PatternLayout layout = new PatternLayout();
    layout.getInstanceConverterMap().put("syslogStart",
            SyslogStartConverter.class.getName());
    if (suffixPattern == null) {
      suffixPattern = DEFAULT_SUFFIX_PATTERN;
    }
    layout.setPattern(getPrefixPattern() + suffixPattern);
    layout.setContext(getContext());
    layout.start();
    return layout;
  }

  private void setupStackTraceLayout() {
    stackTraceLayout.getInstanceConverterMap().put("syslogStart",
            SyslogStartConverter.class.getName());

    stackTraceLayout.setPattern(getPrefixPattern() + stackTracePattern);
    stackTraceLayout.setContext(getContext());
    stackTraceLayout.start();
  }

  public boolean isThrowableExcluded() {
    return throwableExcluded;
  }

  /**
   * Setting throwableExcluded to true causes no Throwable's stack trace data to be sent to
   * the syslog daemon. By default, stack trace data is sent to syslog daemon.
   *
   * @param throwableExcluded
   * @since 1.0.4
   */
  public void setThrowableExcluded(boolean throwableExcluded) {
    this.throwableExcluded = throwableExcluded;
  }

  /**
   * See {@link #setStackTracePattern(String)}.
   *
   * @return the stackTraceSuffixPattern
   * @since 1.0.4
   */
  public String getStackTracePattern() {
    return stackTracePattern;
  }

  /**
   * Stack trace lines are sent to the syslog server separately from the main message
   * For stack trace lines, the stackTracePattern is used instead of {@link #suffixPattern}.
   * The <b>stackTracePattern</b> option allows specification of a separately format for the
   * non-standardized part of stack trace lines.
   *
   * @param stackTracePattern
   * @since 1.0.4
   */
  public void setStackTracePattern(String stackTracePattern) {
    this.stackTracePattern = stackTracePattern;
  }
}


/**
 * 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.spi;

import ch.qos.logback.core.CoreConstants;

import java.util.*;

/**
 * An abstract implementation of the ComponentTracker interface. Derived classes must implement
 * {@link #buildComponent(String)}, {@link #processPriorToRemoval(Object)}, and {@link #isComponentStale(Object)}
 * methods as appropriate for their component type.
 *
 * @param <C> component type
 *
 * @author Tommy Becker
 * @author Ceki Gulcu
 * @author David Roussel
 */
abstract public class AbstractComponentTracker<C> implements ComponentTracker<C> {
  private static final boolean ACCESS_ORDERED = true;

  // Components in lingering state last 10 seconds
  final public static long LINGERING_TIMEOUT = 10 * CoreConstants.MILLIS_IN_ONE_SECOND;

  /**
   * The minimum amount of time that has to elapse between successive removal iterations.
   */
  final public static long WAIT_BETWEEN_SUCCESSIVE_REMOVAL_ITERATIONS = CoreConstants.MILLIS_IN_ONE_SECOND;

  protected int maxComponents = DEFAULT_MAX_COMPONENTS;
  protected long timeout = DEFAULT_TIMEOUT;

  // an access ordered map. Least recently accessed element will be removed after a 'timeout'
  LinkedHashMap<String, Entry<C>> liveMap = new LinkedHashMap<String, Entry<C>>(32, .75f, ACCESS_ORDERED);

  // an access ordered map. Least recently accessed element will be removed after LINGERING_TIMEOUT
  LinkedHashMap<String, Entry<C>> lingerersMap = new LinkedHashMap<String, Entry<C>>(16, .75f, ACCESS_ORDERED);
  long lastCheck = 0;

  /**
   * Stop or clean the component.
   *
   * @param component
   */
  abstract protected void processPriorToRemoval(C component);

  /**
   * Build a component based on the key.
   *
   * @param key desired key
   * @return the component
   */
  abstract protected C buildComponent(String key);

  /**
   * Components can declare themselves stale. Such components may be
   * removed before they time out.
   *
   * @param c component to evaluate
   * @return whether component is stale
   */
  protected abstract boolean isComponentStale(C c);

  public int getComponentCount() {
    return liveMap.size() + lingerersMap.size();
  }

  /**
   * Get an entry from the liveMap, if not found search the lingerersMap.
   *
   * @param key
   * @return
   */
  private Entry<C> getFromEitherMap(String key) {
    Entry<C> entry = liveMap.get(key);
    if (entry != null)
      return entry;
    else {
      return lingerersMap.get(key);
    }
  }

  /**
   * {@inheritDoc}
   *
   *
Note that this method is synchronized.

   *
   * @param key {@inheritDoc}
   * @return {@inheritDoc}
   *
   */
  public synchronized C find(String key) {
    Entry<C> entry = getFromEitherMap(key);
    if (entry == null) return null;
    else return entry.component;
  }

  /**
   *  {@inheritDoc}
   *
   *
Note that this method is atomic, i.e. synchronized.

   *
   * @param key {@inheritDoc}
   * @param timestamp {@inheritDoc}
   * @return {@inheritDoc}
   */
  public synchronized C getOrCreate(String key, long timestamp) {
    Entry<C> entry = getFromEitherMap(key);
    if (entry == null) {
      C c = buildComponent(key);
      entry = new Entry<C>(key, c, timestamp);
      // new entries go into the main map
      liveMap.put(key, entry);
    } else {
      entry.setTimestamp(timestamp);
    }
    return entry.component;
  }

  /**
   * Mark component identified by 'key' as having reached its end-of-life.
   *
   * @param key
   */
  public void endOfLife(String key) {
    Entry<C> entry = liveMap.remove(key);
    if (entry == null)
      return;
    lingerersMap.put(key, entry);
  }

  /**
   * Clear (and detach) components which are stale. Components which have not
   * been accessed for more than a user-specified duration are deemed stale.
   *
   * @param now
   */
  public synchronized void removeStaleComponents(long now) {
    if (isTooSoonForRemovalIteration(now)) return;
    removeExcedentComponents();
    removeStaleComponentsFromMainMap(now);
    removeStaleComponentsFromLingerersMap(now);
  }

  private void removeExcedentComponents() {
    genericStaleComponentRemover(liveMap, 0, byExcedent);
  }

  private void removeStaleComponentsFromMainMap(long now) {
    genericStaleComponentRemover(liveMap, now, byTimeout);
  }

  private void removeStaleComponentsFromLingerersMap(long now) {
    genericStaleComponentRemover(lingerersMap, now, byLingering);
  }

  private void genericStaleComponentRemover(LinkedHashMap<String, Entry<C>> map, long now,
                                            RemovalPredicator<C> removalPredicator) {
    Iterator<Map.Entry<String, Entry<C>>> iter = map.entrySet().iterator();
    while (iter.hasNext()) {
      Map.Entry<String, Entry<C>> mapEntry = iter.next();
      Entry<C> entry = mapEntry.getValue();
      if (removalPredicator.isSlatedForRemoval(entry, now)) {
        iter.remove();
        C c = entry.component;
        processPriorToRemoval(c);
      } else {
        break;
      }
    }
  }

  private RemovalPredicator<C> byExcedent = new RemovalPredicator<C>() {
    public boolean isSlatedForRemoval(Entry<C> entry, long timestamp) {
      return (liveMap.size() > maxComponents);
    }
  };

  private RemovalPredicator<C> byTimeout = new RemovalPredicator<C>() {
    public boolean isSlatedForRemoval(Entry<C> entry, long timestamp) {
      return isEntryStale(entry, timestamp);
    }
  };
  private RemovalPredicator<C> byLingering = new RemovalPredicator<C>() {
    public boolean isSlatedForRemoval(Entry<C> entry, long timestamp) {
      return isEntryDoneLingering(entry, timestamp);
    }
  };

  private boolean isTooSoonForRemovalIteration(long now) {
    if (lastCheck + WAIT_BETWEEN_SUCCESSIVE_REMOVAL_ITERATIONS > now) {
      return true;
    }
    lastCheck = now;
    return false;
  }

  private boolean isEntryStale(Entry<C> entry, long now) {
    // stopped or improperly started appenders are considered stale
    // see also http://jira.qos.ch/browse/LBCLASSIC-316
    C c = entry.component;
    if (isComponentStale(c))
      return true;

    return ((entry.timestamp + timeout) < now);
  }

  private boolean isEntryDoneLingering(Entry<C> entry, long now) {
    return ((entry.timestamp + LINGERING_TIMEOUT) < now);
  }

  public Set<String> allKeys() {
    HashSet<String> allKeys = new HashSet<String>(liveMap.keySet());
    allKeys.addAll(lingerersMap.keySet());
    return allKeys;
  }

  public Collection<C> allComponents() {
    List<C> allComponents = new ArrayList<C>();
    for (Entry<C> e : liveMap.values())
      allComponents.add(e.component);
    for (Entry<C> e : lingerersMap.values())
      allComponents.add(e.component);

    return allComponents;
  }

  public long getTimeout() {
    return timeout;
  }

  public void setTimeout(long timeout) {
    this.timeout = timeout;
  }

  public int getMaxComponents() {
    return maxComponents;
  }

  public void setMaxComponents(int maxComponents) {
    this.maxComponents = maxComponents;
  }

  // ================================================================
  private interface RemovalPredicator<C> {
    boolean isSlatedForRemoval(Entry<C> entry, long timestamp);
  }
  // ================================================================
  private static class Entry<C> {
    String key;
    C component;
    long timestamp;

    Entry(String k, C c, long timestamp) {
      this.key = k;
      this.component = c;
      this.timestamp = timestamp;
    }

    public void setTimestamp(long timestamp) {
      this.timestamp = timestamp;
    }

    @Override
    public int hashCode() {
      return key.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      @SuppressWarnings("unchecked")
      final Entry<C> other = (Entry<C>) obj;
      if (key == null) {
        if (other.key != null)
          return false;
      } else if (!key.equals(other.key))
        return false;
      if (component == null) {
        if (other.component != null)
          return false;
      } else if (!component.equals(other.component))
        return false;
      return true;
    }

    @Override
    public String toString() {
      return "(" + key + ", " + component + ")";
    }
  }
}