/*
 * Copyright (C) The Apache Software Foundation. All rights reserved.
 *
 * This software is published under the terms of the Apache Software
 * License version 1.1, a copy of which has been included with this
 * distribution in the LICENSE.txt file.  */

import java.util.Map;
import java.util.TreeMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;

import org.apache.log4j.Logger;
import org.apache.log4j.LogManager;
import org.apache.log4j.Level;
import org.apache.log4j.spi.LoggerRepository;

/**
  This class can be used to extract logger information for a given
  LoggerRepository.  The set of loggers can be retrieved in one of two
  ways:
  
  1) A complete list of the loggers.
  2) A list of package names, starting at a given package name pattern.
  
  If the second retrieval method is used, the caller can iteratively call
  the LoggerInfo to retrieve sub-packages and children.
  
  This class is dependent on logger names that match fully qualified class
  names.
  
  This class does not cause any side effects in the LoggerRepository.  It
  does not inadvertantly create new Loggers in the process of parsing the
  package names or accessing information.
  
  This class does not automatically keep track of changes in the given
  LoggerRepository.  The caller must call the update() method to get the
  current set of loggers.
*/
public class LoggerInfo {
  private Map loggerMap = new TreeMap();
  private Logger rootLogger;
  
  public LoggerInfo() {
  }
  
  public LoggerInfo(LoggerRepository repository) {
    update(repository);
  }
  
  /**
    Updates the LoggerInfo to the current information in the given
    LoggerRepository. */
  public void update(LoggerRepository repository) {
    // clear any old information
    loggerMap.clear();
    
    // set the root logger
    rootLogger = repository.getRootLogger();
        
    // enumerate through the current set of loggers
    // the key is the logger name, the value is the logger
    Enumeration loggerEnum = repository.getCurrentLoggers();
    while (loggerEnum.hasMoreElements()) {
      Logger logger = (Logger)loggerEnum.nextElement();
      loggerMap.put(logger.getName(), logger);
    }
  }
  
  /**
    Returns the list of all loggers, sorted by name. */
  public List getLoggerNames() {
    List loggerList = new ArrayList(loggerMap.size());
    Iterator loggerIter = loggerMap.keySet().iterator();
    while (loggerIter.hasNext()) {
      loggerList.add((String)loggerIter.next());
    }
    return loggerList;
  }
  
  /**
    Using a starting pattern, returns the next level of package names that
    start with that pattern.  Returns a list, as there can be more than one
    return value.  Passing in an empty string for the starting pattern will
    return a list of the top level package names.
    
    For example, if the following logger names were defined: org.apache.log4j
    and org.apache.log4j-extensions, then passing in an empty string would
    return one item in the list with a value of "org".  If the pattern
    "org.apache" were passed in, then the list would contain two items,
    "log4j" and "log4j-extensions". */
  public List getLoggerPackageNames(String startPattern) {
    String name = "";
    List packageList = new ArrayList(1);
    
    // iterate through the loggerMap, checking the name of each logger
    // against the starting pattern.  If name starts with pattern, then
    // add the next part of the package name to the return list.
    Iterator loggerIter = loggerMap.keySet().iterator();
    while (loggerIter.hasNext()) {
      String loggerName = (String)loggerIter.next();
      
      // does the logger name start with the startPattern
      if (loggerName.startsWith(startPattern)) {
        loggerName = loggerName.substring(startPattern.length());
        
        // is there part of the name left after the start pattern is removed?
        if (loggerName.length() > 0) {

          // if the left over string starts with '.'. remove it
          if (loggerName.startsWith(".")) {
            loggerName = loggerName.substring(1);
          }
          else if (startPattern.length() > 0) {
            break;
          }
          
          // find the next index of '.' and grab the part of the name before it
          int index = loggerName.indexOf('.');
          if (index != -1) {
            //System.out.println("found . at " + index);
            loggerName = loggerName.substring(0, index);
          }
          
          // if this is not a name we have previously encountered,
          // put it in the return list.
          if (!loggerName.equals(name)) {
            packageList.add(loggerName);
            name = loggerName;
          }
        }
      }
    }
    
    return packageList;
  }
  
  /**
    Returns true if the given package name appears to have sub-package. */
  public boolean loggerHasSubPackages(String startPattern) {
    int len = startPattern.length();
    
    // iterate through logger names and first one that starts with
    // pattern and the length is greater, return true.
    Iterator loggerIter = loggerMap.keySet().iterator();
    while (loggerIter.hasNext()) {
      String loggerName = (String)loggerIter.next();
      if (loggerName.startsWith(startPattern) && loggerName.length() > len) {
        return true;
      }
    }
    
    return false;
  }

  /**
    Returns the level for the root logger. */
  public Level getLevelForRootLogger() {
    return rootLogger.getEffectiveLevel();
  }
  
  /**
    Returns the effective level for the given package name. If no level is
    set for the given package, then search back through the package names
    until one is found that is set or return the level of the root logger. */
  public Level getLevelForPackage(String packageName) {
    String name = packageName;
    Logger logger = (Logger)loggerMap.get(packageName);
    while (logger == null && name != null) {
      int index = name.lastIndexOf('.');
      if (index != -1) {
        name = name.substring(0, index-1);
        logger = (Logger)loggerMap.get(packageName);
      } else {
        name = null;
      }
    }
    
    if (logger != null) {
      return logger.getEffectiveLevel();
    }
    else {
      return rootLogger.getEffectiveLevel();
    }
  }

  /**
    Returns true of the package has had its level set directly or
    false if the level is inherited.  */
  public boolean getLevelIsSetForPackage(String packageName) {
    String name = packageName;
    Logger logger = (Logger)loggerMap.get(packageName);
    while (logger == null && name != null) {
      int index = name.lastIndexOf('.');
      if (index != -1) {
        name = name.substring(0, index-1);
        logger = (Logger)loggerMap.get(packageName);
      } else {
        name = null;
      }
    }
    
    if (logger != null) {
      if (logger == rootLogger)
        return true;
      else
        return (logger.getLevel() != null);
    } else {
      return false;
    }
  }
  
  // here is an example of using the hierarchical version, iterating
  // through all the package names, all the loggers.
  public static void main(String[] args) {
    
    // set the root to level warn
    Logger.getRootLogger().setLevel(Level.WARN);
    
    // create some loggers
    Logger.getLogger("org.womacknet.wgntool.Researcher");
    Logger.getLogger("org.womacknet.wgntool.ResearcherList");
    Logger.getLogger("org.womacknet.util.NameUtil");
    Logger.getLogger("com.bevocal.application.platform.util.StringUtil").setLevel(Level.DEBUG);
    
    LoggerInfo info = new LoggerInfo(LogManager.getLoggerRepository());
    System.out.println("root - " + info.getLevelForRootLogger());
    iteratePackages("", 1, info);
  }
  
  // starting with a package name, iterate through all subpackages and loggers.
  static void iteratePackages(String startPackageName, int level, LoggerInfo info) {
    List packageInfo = info.getLoggerPackageNames(startPackageName);
    Iterator iter = packageInfo.iterator();
    while (iter.hasNext()) {
      String packageName = (String)iter.next();
      for (int x = 0; x < level; x++) {
        System.out.print(" ");
      }
      System.out.print(packageName);
      String subpackageName;
      if (startPackageName.length() > 0) {
        subpackageName = startPackageName + "." + packageName;
      } else {
        subpackageName = packageName;
      }
      System.out.print(" - " + info.getLevelForPackage(subpackageName));
      System.out.println((info.getLevelIsSetForPackage(subpackageName) ? "" : "*"));
      if (info.loggerHasSubPackages(subpackageName)) {
        iteratePackages(subpackageName, level+1, info);
      }
    }
  }
}
