package org.apache.logging.log4j.core;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.net.URI;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationListener;
import org.apache.logging.log4j.core.config.DefaultConfiguration;
import org.apache.logging.log4j.core.config.NullConfiguration;
import org.apache.logging.log4j.core.config.Reconfigurable;
import org.apache.logging.log4j.core.jmx.Server;
import org.apache.logging.log4j.core.util.Assert;
import org.apache.logging.log4j.core.util.NetUtils;
import org.apache.logging.log4j.message.MessageFactory;
import org.apache.logging.log4j.spi.AbstractLogger;
import org.apache.logging.log4j.util.PropertiesUtil;

public class LoggerContext extends AbstractLifeCycle implements org.apache.logging.log4j.spi.LoggerContext, ConfigurationListener {
   private static final boolean SHUTDOWN_HOOK_ENABLED = PropertiesUtil.getProperties().getBooleanProperty("log4j.shutdownHookEnabled", true);
   public static final String PROPERTY_CONFIG = "config";
   private static final Marker SHUTDOWN_HOOK = MarkerManager.getMarker("SHUTDOWN HOOK");
   private static final Configuration NULL_CONFIGURATION = new NullConfiguration();
   private final ConcurrentMap<String, Logger> loggers;
   private final CopyOnWriteArrayList<PropertyChangeListener> propertyChangeListeners;
   private volatile Configuration config;
   private Object externalContext;
   private final String name;
   private URI configLocation;
   private Reference<Thread> shutdownThread;
   private final Lock configLock;

   public LoggerContext(String name) {
      this(name, (Object)null, (URI)((URI)null));
   }

   public LoggerContext(String name, Object externalContext) {
      this(name, externalContext, (URI)null);
   }

   public LoggerContext(String name, Object externalContext, URI configLocn) {
      this.loggers = new ConcurrentHashMap();
      this.propertyChangeListeners = new CopyOnWriteArrayList();
      this.config = new DefaultConfiguration();
      this.configLock = new ReentrantLock();
      this.name = name;
      this.externalContext = externalContext;
      this.configLocation = configLocn;
   }

   public LoggerContext(String name, Object externalContext, String configLocn) {
      this.loggers = new ConcurrentHashMap();
      this.propertyChangeListeners = new CopyOnWriteArrayList();
      this.config = new DefaultConfiguration();
      this.configLock = new ReentrantLock();
      this.name = name;
      this.externalContext = externalContext;
      if (configLocn != null) {
         URI uri;
         try {
            uri = (new File(configLocn)).toURI();
         } catch (Exception var6) {
            uri = null;
         }

         this.configLocation = uri;
      } else {
         this.configLocation = null;
      }

   }

   public void start() {
      LOGGER.debug("Starting LoggerContext[name={}, {}]...", this.getName(), this);
      if (this.configLock.tryLock()) {
         try {
            if (this.isInitialized() || this.isStopped()) {
               this.setStarting();
               this.reconfigure();
               this.setUpShutdownHook();
               this.setStarted();
            }
         } finally {
            this.configLock.unlock();
         }
      }

      LOGGER.debug("LoggerContext[name={}, {}] started OK.", this.getName(), this);
   }

   public void start(Configuration config) {
      LOGGER.debug("Starting LoggerContext[name={}, {}] with configuration {}...", this.getName(), this, config);
      if (this.configLock.tryLock()) {
         try {
            if (this.isInitialized() || this.isStopped()) {
               this.setUpShutdownHook();
               this.setStarted();
            }
         } finally {
            this.configLock.unlock();
         }
      }

      this.setConfiguration(config);
      LOGGER.debug("LoggerContext[name={}, {}] started OK with configuration {}.", this.getName(), this, config);
   }

   private void setUpShutdownHook() {
      if (this.config.isShutdownHookEnabled() && SHUTDOWN_HOOK_ENABLED) {
         LOGGER.debug(SHUTDOWN_HOOK, "Shutdown hook enabled. Registering a new one.");
         this.shutdownThread = new SoftReference(new Thread(new LoggerContext.ShutdownThread(this), "log4j-shutdown"));
         this.addShutdownHook();
      }

   }

   private void addShutdownHook() {
      Thread hook = this.getShutdownThread();
      if (hook != null) {
         try {
            Runtime.getRuntime().addShutdownHook(hook);
         } catch (IllegalStateException var3) {
            LOGGER.warn(SHUTDOWN_HOOK, "Unable to register shutdown hook due to JVM state");
         } catch (SecurityException var4) {
            LOGGER.warn(SHUTDOWN_HOOK, "Unable to register shutdown hook due to security restrictions");
         }
      }

   }

   private Thread getShutdownThread() {
      return this.shutdownThread == null ? null : (Thread)this.shutdownThread.get();
   }

   public void stop() {
      LOGGER.debug("Stopping LoggerContext[name={}, {}]...", this.getName(), this);
      this.configLock.lock();

      try {
         if (this.isStopped()) {
            return;
         }

         this.setStopping();
         this.tearDownShutdownHook();
         Configuration prev = this.config;
         this.config = NULL_CONFIGURATION;
         this.updateLoggers();
         prev.stop();
         this.externalContext = null;
         LogManager.getFactory().removeContext(this);
         this.setStopped();
      } finally {
         this.configLock.unlock();
         Server.unregisterLoggerContext(this.getName());
      }

      LOGGER.debug("Stopped LoggerContext[name={}, {}]...", this.getName(), this);
   }

   private void tearDownShutdownHook() {
      if (this.shutdownThread != null) {
         LOGGER.debug(SHUTDOWN_HOOK, "Enqueue shutdown hook for garbage collection.");
         this.shutdownThread.enqueue();
      }

   }

   public String getName() {
      return this.name;
   }

   public void setExternalContext(Object context) {
      this.externalContext = context;
   }

   public Object getExternalContext() {
      return this.externalContext;
   }

   public Logger getLogger(String name) {
      return this.getLogger(name, (MessageFactory)null);
   }

   public Collection<Logger> getLoggers() {
      return this.loggers.values();
   }

   public Logger getLogger(String name, MessageFactory messageFactory) {
      Logger logger = (Logger)this.loggers.get(name);
      if (logger != null) {
         AbstractLogger.checkMessageFactory(logger, messageFactory);
         return logger;
      } else {
         logger = this.newInstance(this, name, messageFactory);
         Logger prev = (Logger)this.loggers.putIfAbsent(name, logger);
         return prev == null ? logger : prev;
      }
   }

   public boolean hasLogger(String name) {
      return this.loggers.containsKey(name);
   }

   public Configuration getConfiguration() {
      return this.config;
   }

   public void addFilter(Filter filter) {
      this.config.addFilter(filter);
   }

   public void removeFilter(Filter filter) {
      this.config.removeFilter(filter);
   }

   private synchronized Configuration setConfiguration(Configuration config) {
      if (config == null) {
         throw new NullPointerException("No Configuration was provided");
      } else {
         Configuration prev = this.config;
         config.addListener(this);
         ConcurrentMap map = (ConcurrentMap)config.getComponent("ContextProperties");

         try {
            map.putIfAbsent("hostName", NetUtils.getLocalHostname());
         } catch (Exception var6) {
            LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", var6.toString());
            map.putIfAbsent("hostName", "unknown");
         }

         map.putIfAbsent("contextName", this.name);
         config.start();
         this.config = config;
         this.updateLoggers();
         if (prev != null) {
            prev.removeListener(this);
            prev.stop();
         }

         this.firePropertyChangeEvent(new PropertyChangeEvent(this, "config", prev, config));

         try {
            Server.reregisterMBeansAfterReconfigure();
         } catch (Throwable var5) {
            LOGGER.error("Could not reconfigure JMX", var5);
         }

         return prev;
      }
   }

   private void firePropertyChangeEvent(PropertyChangeEvent event) {
      Iterator i$ = this.propertyChangeListeners.iterator();

      while(i$.hasNext()) {
         PropertyChangeListener listener = (PropertyChangeListener)i$.next();
         listener.propertyChange(event);
      }

   }

   public void addPropertyChangeListener(PropertyChangeListener listener) {
      this.propertyChangeListeners.add(Assert.requireNonNull(listener, "listener"));
   }

   public void removePropertyChangeListener(PropertyChangeListener listener) {
      this.propertyChangeListeners.remove(listener);
   }

   public synchronized URI getConfigLocation() {
      return this.configLocation;
   }

   public synchronized void setConfigLocation(URI configLocation) {
      this.configLocation = configLocation;
      this.reconfigure();
   }

   public synchronized void reconfigure() {
      LOGGER.debug("Reconfiguration started for context[name={}] at {} ({})", this.name, this.configLocation, this);
      Configuration instance = ConfigurationFactory.getInstance().getConfiguration(this.name, this.configLocation);
      this.setConfiguration(instance);
      LOGGER.debug("Reconfiguration complete for context[name={}] at {} ({})", this.name, this.configLocation, this);
   }

   public void updateLoggers() {
      this.updateLoggers(this.config);
   }

   public void updateLoggers(Configuration config) {
      Iterator i$ = this.loggers.values().iterator();

      while(i$.hasNext()) {
         Logger logger = (Logger)i$.next();
         logger.updateConfiguration(config);
      }

   }

   public synchronized void onChange(Reconfigurable reconfigurable) {
      LOGGER.debug("Reconfiguration started for context {} ({})", this.name, this);
      Configuration config = reconfigurable.reconfigure();
      if (config != null) {
         this.setConfiguration(config);
         LOGGER.debug("Reconfiguration completed for {} ({})", this.name, this);
      } else {
         LOGGER.debug("Reconfiguration failed for {} ({})", this.name, this);
      }

   }

   protected Logger newInstance(LoggerContext ctx, String name, MessageFactory messageFactory) {
      return new Logger(ctx, name, messageFactory);
   }

   private static class ShutdownThread implements Runnable {
      private final LoggerContext context;

      public ShutdownThread(LoggerContext context) {
         this.context = context;
      }

      public void run() {
         AbstractLifeCycle.LOGGER.debug("ShutdownThread stopping LoggerContext[name={}, {}]...", this.context.getName(), this.context);
         this.context.stop();
         AbstractLifeCycle.LOGGER.debug("ShutdownThread stopped LoggerContext[name={}, {}].", this.context.getName(), this.context);
      }
   }
}
