/FileExtraction/Java_unseen/dlitz_resin/modules/kernel/src/com/caucho/log/EnvironmentLogger.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.log;

import com.caucho.loader.ClassLoaderListener;
import com.caucho.loader.DynamicClassLoader;
import com.caucho.loader.Environment;
import com.caucho.loader.EnvironmentClassLoader;
import com.caucho.loader.EnvironmentLocal;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

/**
 * Proxy logger that understands the environment.
 */
class EnvironmentLogger extends Logger implements ClassLoaderListener {
  private static final Handler[] EMPTY_HANDLERS = new Handler[0];
  
  private static ClassLoader _systemClassLoader;
  
  // The custom local handlers
  private final EnvironmentLocal<Logger> _localLoggers
    = new EnvironmentLocal<Logger>();
  
  // The environment handlers for the Logger
  private final EnvironmentLocal<Handler[]> _localHandlers
    = new EnvironmentLocal<Handler[]>();
  
  // The environment handlers owned by the Logger
  private final EnvironmentLocal<HandlerEntry> _ownHandlers
    = new EnvironmentLocal<HandlerEntry>();

  // The use-parent-handlers value
  private final EnvironmentLocal<Boolean> _useParentHandlers
    = new EnvironmentLocal<Boolean>();
  
  // Application level override
  private EnvironmentLocal<Level> _localLevel;
  private Level _systemLevel = null;

  private EnvironmentLogger _parent;

  // Can be a weak reference because any configuration in an
  // environment will be held in the EnvironmentLocal.
  private final ArrayList<WeakReference<EnvironmentLogger>> _children
    = new ArrayList<WeakReference<EnvironmentLogger>>();
  
  // Weak list of all the class loaders
  private final ArrayList<WeakReference<ClassLoader>> _loaders
    = new ArrayList<WeakReference<ClassLoader>>();
  
  // The local effective level
  private EnvironmentLocal<Integer> _localEffectiveLevel;

  private boolean _hasLocalEffectiveLevel;
  
  private Level _finestEffectiveLevel = Level.INFO;
  private int _finestEffectiveLevelValue = _finestEffectiveLevel.intValue();
  private int _systemEffectiveLevelValue = Level.INFO.intValue();
  
  public EnvironmentLogger(String name, String resourceBundleName)
  {
    super(name, resourceBundleName);
  }

  /**
   * Sets the logger's parent.  This should only be called by the LogManager
   * code.
   */
  @Override
  public void setParent(Logger parent)
  {
    if (parent.equals(_parent))
      return;
    
    super.setParent(parent);

    if (parent instanceof EnvironmentLogger) {
      _parent = (EnvironmentLogger) parent;

      _parent.addChild(this);
    }
    
    updateEffectiveLevel(_systemClassLoader);
  }
  
  //
  // levels
  //

  /**
   * Returns the logger's assigned level.
   */
  @Override
  public Level getLevel()
  {
    if (_localLevel != null) {
      Level level = _localLevel.get();

      if (level != null) {
        return level;
      }
    }

    return _systemLevel;
  }

  /**
   * Application API to set the level.
   *
   * @param level the logging level to set for the logger.
   */
  @Override
  public void setLevel(Level level)
  {
    ClassLoader loader = Thread.currentThread().getContextClassLoader();
    
    if (loader == null)
      loader = _systemClassLoader;

    if (loader != _systemClassLoader) {
      if (_localLevel == null)
        _localLevel = new EnvironmentLocal<Level>();
      
      _localLevel.set(level);
      
      if (level != null) {
        addLoader(loader);
      }
    }
    else {
      _systemLevel = level;
    }
    
    updateEffectiveLevel(loader);
  }
  
  //
  // handlers
  //

  /**
   * Returns the handlers.
   */
  @Override
  public Handler []getHandlers()
  {
    Handler []handlers = _localHandlers.get();
    
    if (handlers != null)
      return handlers;
    else
      return EMPTY_HANDLERS;
  }

  /**
   * Adds a handler.
   */
  @Override
  public void addHandler(Handler handler)
  {
    synchronized (this) {
      ClassLoader loader = Thread.currentThread().getContextClassLoader();
      
      if (loader == null)
        loader = _systemClassLoader;

      boolean hasLoader = false;
      for (int i = _loaders.size() - 1; i >= 0; i--) {
        WeakReference<ClassLoader> ref = _loaders.get(i);
        ClassLoader refLoader = ref.get();

        if (refLoader == null)
          _loaders.remove(i);

        if (refLoader == loader)
          hasLoader = true;

        if (isParentLoader(loader, refLoader))
          addHandler(handler, refLoader);
      }

      if (! hasLoader) {
        _loaders.add(new WeakReference<ClassLoader>(loader));
        addHandler(handler, loader);
        Environment.addClassLoaderListener(this, loader);
      }

      HandlerEntry ownHandlers = _ownHandlers.get();
      if (ownHandlers == null) {
        ownHandlers = new HandlerEntry(this);
        _ownHandlers.set(ownHandlers);
      }
    
      ownHandlers.addHandler(handler);
    }
  }

  /**
   * Removes a handler.
   */
  @Override
  public void removeHandler(Handler handler)
  {
    synchronized (this) {
      ClassLoader loader = Thread.currentThread().getContextClassLoader();
      
      if (loader == null)
        loader = _systemClassLoader;

      for (int i = _loaders.size() - 1; i >= 0; i--) {
        WeakReference<ClassLoader> ref = _loaders.get(i);
        ClassLoader refLoader = ref.get();

        if (refLoader == null)
          _loaders.remove(i);

        if (isParentLoader(loader, refLoader))
          removeHandler(handler, refLoader);
      }

      HandlerEntry ownHandlers = _ownHandlers.get();
      if (ownHandlers != null)
        ownHandlers.removeHandler(handler);
    }
  }
  
  //
  // logging
  //
  
  /**
   * True if the level is loggable
   */
  @Override
  public final boolean isLoggable(Level level)
  {
    if (level == null)
      return false;

    int intValue = level.intValue();
    
    if (intValue < _finestEffectiveLevelValue)
      return false;
    else if (! _hasLocalEffectiveLevel) {
      return true;
    }
    else {
      Integer localValue = _localEffectiveLevel.get();
      
      if (localValue != null) {
        int localIntValue = localValue.intValue();
        
        if (localIntValue == Level.OFF.intValue())
          return false;
        else
          return localIntValue <= intValue;
      }
      else {
        if (_systemEffectiveLevelValue == Level.OFF.intValue())
          return false;
        else
          return _systemEffectiveLevelValue <= intValue;
      }
    }
  }

  /**
   * Returns the use-parent-handlers
   */
  @Override
  public boolean getUseParentHandlers()
  {
    Boolean value = _useParentHandlers.get();

    if (value != null)
      return Boolean.TRUE.equals(value);
    else
      return true;
  }

  /**
   * Sets the use-parent-handlers
   */
  @Override
  public void setUseParentHandlers(boolean useParentHandlers)
  {
    _useParentHandlers.set(new Boolean(useParentHandlers));
  }

  /**
   * Logs the message.
   */
  @Override
  public void log(LogRecord record)
  {
    if (record == null)
      return;
    
    Level recordLevel = record.getLevel();

    if (! isLoggable(recordLevel))
      return;

    for (Logger ptr = this; ptr != null; ptr = ptr.getParent()) {
      Handler handlers[] = ptr.getHandlers();

      if (handlers != null) {
        for (int i = 0; i < handlers.length; i++) {
          handlers[i].publish(record);
        }
      }

      if (! ptr.getUseParentHandlers())
        break;
    }
  }
  
  //
  // implementation methods
  //

  /**
   * Adds a new logger as a child, triggered by a setParent.
   */
  void addChild(EnvironmentLogger child)
  {
    _children.add(new WeakReference<EnvironmentLogger>(child));
    
    updateChildren();
  }

  /**
   * Adds a new handler with a given classloader context.
   */
  private void addHandler(Handler handler, ClassLoader loader)
  {
    // handlers ordered by level
    ArrayList<Handler> handlers = new ArrayList<Handler>();

    handlers.add(handler);

    for (ClassLoader ptr = loader; ptr != null; ptr = ptr.getParent()) {
      Handler []localHandlers = _localHandlers.getLevel(ptr);

      if (localHandlers != null) {
        for (int i = 0; i < localHandlers.length; i++) {
          int p = handlers.indexOf(localHandlers[i]);

          if (p < 0) {
            handlers.add(localHandlers[i]);
          }
          else {
            Handler oldHandler = handlers.get(p);

            if (localHandlers[i].getLevel().intValue()
                < oldHandler.getLevel().intValue()) {
              handlers.set(p, localHandlers[i]);
            }
          }
        }
      }
    }

    Handler []newHandlers = new Handler[handlers.size()];
    handlers.toArray(newHandlers);
    
    if (loader == _systemClassLoader)
      loader = null;

    _localHandlers.set(newHandlers, loader);
  }

  private void removeHandler(Handler handler, ClassLoader loader)
  {
    ArrayList<Handler> handlers = new ArrayList<Handler>();

    for (ClassLoader ptr = loader; ptr != null; ptr = ptr.getParent()) {
      Handler []localHandlers = _localHandlers.getLevel(ptr);

      if (localHandlers != null) {
        for (int i = 0; i < localHandlers.length; i++) {
          if (! localHandlers[i].equals(handler)) {
            int p = handlers.indexOf(localHandlers[i]);

            if (p < 0) {
              handlers.add(localHandlers[i]);
            }
            else {
              Handler oldHandler = handlers.get(p);

              if (localHandlers[i].getLevel().intValue()
                  < oldHandler.getLevel().intValue()) {
                handlers.set(p, localHandlers[i]);
              }
            }
          }
        }
      }
    }

    Handler []newHandlers = new Handler[handlers.size()];
    handlers.toArray(newHandlers);

    _localHandlers.set(newHandlers, loader);
  }

  /**
   * Returns true if 'parent' is a parent classloader of 'child'.
   *
   * @param parent the classloader to test as a parent.
   * @param child the classloader to test as a child.
   */
  private boolean isParentLoader(ClassLoader parent, ClassLoader child)
  {
    for (; child != null; child = child.getParent()) {
      if (child == parent)
        return true;
    }

    return false;
  }

  /**
   * Sets a custom logger if possible
   */
  boolean addCustomLogger(Logger logger)
  {
    if (logger.getClass().getName().startsWith("java"))
      return false;
    
    Logger oldLogger = _localLoggers.get();

    if (oldLogger != null)
      return false;

    _localLoggers.set(logger);
    
    if (_parent != null) {
      logger.setParent(_parent);
    }

    return true;
  }

  /**
   * Gets the custom logger if possible
   */
  Logger getLogger()
  {
    return _localLoggers.get();
  }

  /**
   * Adds a class loader to the list of dependency  loaders.
   */
  private void addLoader(ClassLoader loader)
  {
    for (int i = _loaders.size() - 1; i >= 0; i--) {
      WeakReference<ClassLoader> ref = _loaders.get(i);
      ClassLoader refLoader = ref.get();

      if (refLoader == null)
        _loaders.remove(i);

      if (refLoader == loader)
        return;
    }

    _loaders.add(new WeakReference<ClassLoader>(loader));
    Environment.addClassLoaderListener(this, loader);
  }

  /**
   * Returns the assigned level, calculated through the normal
   * Logger rules, i.e. if unassigned, use the parent's value.
   */
  /*
  private Level getAssignedLevel()
  {
    for (Logger log = this; log != null; log = log.getParent()) {
      Level level = log.getLevel();

      if (level != null)
        return level;
    }

    return Level.INFO;
  }
  */

  /**
   * Recalculate the dynamic assigned levels.
   */
  private synchronized void updateEffectiveLevel(ClassLoader loader)
  {
    if (loader == null)
      loader = _systemClassLoader;

    int oldEffectiveLevel = getEffectiveLevel(loader);
    
    Level newEffectiveLevel = calculateEffectiveLevel(loader);

    /*
    if (loader == _systemClassLoader) {
      _finestEffectiveLevel = newEffectiveLevel;
      _finestEffectiveLevelValue = newEffectiveLevel.intValue();
      super.setLevel(_finestEffectiveLevel);
    }
    */
    
    if (oldEffectiveLevel == newEffectiveLevel.intValue()
        && loader != _systemClassLoader)
      return;
    
    _finestEffectiveLevel = newEffectiveLevel;
    _hasLocalEffectiveLevel = false;
    
    updateEffectiveLevelPart(_systemClassLoader);
    
    updateEffectiveLevelPart(loader);
        
    for (int i = 0; i < _loaders.size(); i++) {
      WeakReference<ClassLoader> loaderRef = _loaders.get(i);
      ClassLoader classLoader = loaderRef.get();
      
      if (classLoader != null)
        updateEffectiveLevelPart(classLoader);
    }
    
    super.setLevel(_finestEffectiveLevel);
        
    _finestEffectiveLevelValue = _finestEffectiveLevel.intValue();

    updateChildren();
  }
  
  private void updateChildren()
  {
    updateChildren(_systemClassLoader);
    for (int i = _loaders.size() - 1; i >= 0; i--) {
      WeakReference<ClassLoader> loaderRef = _loaders.get(i);
      ClassLoader subLoader = loaderRef.get();
      
      if (subLoader != null)
        updateChildren(subLoader);
      else
        _loaders.remove(i);
    }
  }
  
  private void updateEffectiveLevelPart(ClassLoader loader)
  {
    Level level = getOwnEffectiveLevel(loader);
    
    if (loader == _systemClassLoader) {
      _systemEffectiveLevelValue
        = (level != null ? level.intValue() : Level.INFO.intValue());
    }
    
    if (level == null) {
      if (_localEffectiveLevel != null)
        _localEffectiveLevel.remove(loader);
      
      return;
    }
    
    if (_finestEffectiveLevel == null)
      _finestEffectiveLevel = level;
    else if (level.intValue() < _finestEffectiveLevel.intValue()) {
      _finestEffectiveLevel = level;
    }
    
    if (loader == _systemClassLoader) {
      _systemEffectiveLevelValue = level.intValue();
    }
    else {
      _hasLocalEffectiveLevel = true;
      
      addLoader(loader);
      
      if (_localEffectiveLevel == null)
        _localEffectiveLevel = new EnvironmentLocal<Integer>();
      
      _localEffectiveLevel.set(level.intValue(), loader);
    }
  }
  
  private Level getOwnEffectiveLevel(ClassLoader loader)
  {
    Level level = null;
    
    if (loader == _systemClassLoader) {
      level = _systemLevel;
    }
    else if (_localLevel != null) {
      level = _localLevel.getLevel(loader);
    }
    
    if (level != null)
      return level;
    else if (_parent != null)
      return _parent.getOwnEffectiveLevel(loader);
    else
      return null;
  }
  
  private int getEffectiveLevel(ClassLoader loader)
  {
    int oldEffectiveLevel = _systemEffectiveLevelValue;
    
    if (_localEffectiveLevel != null) {
      Integer intLevel = _localEffectiveLevel.get(loader);
      
      if (intLevel != null)
        oldEffectiveLevel = intLevel;
    }
    
    return oldEffectiveLevel;
  }
  
  private Level calculateEffectiveLevel(ClassLoader loader)
  {
    Level level = getLevel(loader);
    
    if (level != null)
      return level;
    else if (_parent != null)
      return _parent.calculateEffectiveLevel(loader);
    else
      return Level.INFO;
  }

  private Level getLevel(ClassLoader loader)
  {
    if (_localLevel != null) {
      Level level = _localLevel.get(loader);

      if (level != null) {
        return level;
      }
    }

    return _systemLevel;
  }

  /**
   * Returns the finest assigned level for any classloader environment.
   */
  private Level getFinestLevel()
  {
    Level level;
    
    if (_parent == null)
      level = Level.INFO;
    else if (_parent.isLocalLevel())
      level = selectFinestLevel(_systemLevel, _parent.getFinestLevel());
    else if (_systemLevel != null)
      level = _systemLevel;
    else
      level = _parent.getFinestLevel();
    
    if (_localLevel == null)
      return level;
    
    for (int i = _loaders.size() - 1; i >= 0; i--) {
      WeakReference<ClassLoader> ref = _loaders.get(i);
      ClassLoader loader = ref.get();

      if (loader == null)
        _loaders.remove(i);

      level = selectFinestLevel(level, _localLevel.get(loader));
    }
    
    return level;
  }
  
  private boolean isLocalLevel()
  {
    if (_localLevel != null)
      return false;
    else if (_parent != null && ! _parent.isLocalLevel())
      return false;
    else
      return true;
  }

  /**
   * Returns the finest of the two levels.
   */
  private Level selectFinestLevel(Level a, Level b)
  {
    if (a == null)
      return b;
    else if (b == null)
      return a;
    else if (b.intValue() < a.intValue())
      return b;
    else
      return a;
  }
  
  /**
   * Returns the most specific assigned level for the given classloader, i.e.
   * children override parents.
   */
  private Level getAssignedLevel(ClassLoader loader)
  {
    Level level = null;
    
    if (_localLevel != null) {
      return _localLevel.get(loader);
    }
    
    return null;
  }

  private void updateClassLoaderLevel(ClassLoader loader)
  {
    if (_localLevel == null) {
      if (_systemLevel != null)
        super.setLevel(_systemLevel);
      return;
    }
    
    Level localLevel = _localLevel.get(loader);

    if (localLevel != null) {
      if (! _hasLocalEffectiveLevel)
        super.setLevel(localLevel);
      else if (localLevel.intValue() < super.getLevel().intValue())
        super.setLevel(localLevel);

      _hasLocalEffectiveLevel = true;
    }
  }
  
  private void updateChildren(ClassLoader loader)
  {
    for (int i = _children.size() - 1; i >= 0; i--) {
      WeakReference<EnvironmentLogger> ref = _children.get(i);
      EnvironmentLogger child = ref.get();

      if (child != null)
        child.updateEffectiveLevel(loader);
      else
        _children.remove(i);
    }
  }

  /**
   * Removes the specified loader.
   */
  private synchronized void removeLoader(ClassLoader loader)
  {
    int i;
    for (i = _loaders.size() - 1; i >= 0; i--) {
      WeakReference<ClassLoader> ref = _loaders.get(i);
      ClassLoader refLoader = ref.get();

      if (refLoader == null)
        _loaders.remove(i);
      else if (refLoader == loader)
        _loaders.remove(i);
    }
  }

  /**
   * Classloader init callback
   */
  public void classLoaderInit(DynamicClassLoader env)
  {
  }

  /**
   * Classloader destroy callback
   */
  @Override
  public void classLoaderDestroy(DynamicClassLoader loader)
  {
    removeLoader(loader);

    _localHandlers.remove(loader);

    HandlerEntry ownHandlers = _ownHandlers.getLevel(loader);
    if (ownHandlers != null)
      _ownHandlers.remove(loader);

    if (ownHandlers != null)
      ownHandlers.destroy();

    if (_localLevel != null)
      _localLevel.remove(loader);

    updateEffectiveLevel(_systemClassLoader);
  }

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

  /**
   * Encapsulates the handler for this logger, keeping a reference in
   * the local environment to avoid GC.
   */
  static class HandlerEntry {
    private final EnvironmentLogger _logger;
    private ArrayList<Handler> _handlers = new ArrayList<Handler>();

    HandlerEntry(EnvironmentLogger logger)
    {
      _logger = logger;
    }

    void addHandler(Handler handler)
    {
      _handlers.add(handler);
    }

    void removeHandler(Handler handler)
    {
      _handlers.remove(handler);
    }

    void destroy()
    {
      ArrayList<Handler> handlers = _handlers;
      _handlers = null;
      
      for (int i = 0; handlers != null && i < handlers.size(); i++) {
        Handler handler = handlers.get(i);

        try {
          handler.close();
        } catch (Throwable e) {
          e.printStackTrace();
        }
      }
    }
  }
  
  static {
    try {
      _systemClassLoader = ClassLoader.getSystemClassLoader();
    } catch (Exception e) {
      // too early to log
    }
  }
}
