1  /*
     2   * Copyright the original author or authors.
     3   * 
     4   * Licensed under the MOZILLA PUBLIC LICENSE, Version 1.1 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   * 
     8   *      http://www.mozilla.org/MPL/MPL-1.1.html
     9   * 
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16   
    17  import org.as2lib.core.BasicClass;
    18  import org.as2lib.env.except.IllegalArgumentException;
    19  import org.as2lib.env.except.IllegalStateException;
    20  import org.as2lib.env.log.LoggerRepository;
    21  import org.as2lib.env.log.ConfigurableHierarchicalLogger;
    22  import org.as2lib.env.log.Logger;
    23  import org.as2lib.env.log.logger.SimpleHierarchicalLogger;
    24  import org.as2lib.env.log.logger.RootLogger;
    25  import org.as2lib.env.log.level.AbstractLogLevel;
    26  import org.as2lib.env.log.repository.ConfigurableHierarchicalLoggerFactory;
    27  
    28  /**
    29   * {@code LoggerHierarchy} organizes loggers in a hierarchical structure.
    30   *
    31   * <p>It works only with loggers that are capable of acting properly in a hierarchy.
    32   * These loggers must implement the {@link ConfigurableHierarchicalLogger}
    33   * interface.
    34   * 
    35   * <p>The names of the loggers must be fully qualified and the differnt parts of
    36   * the preceding structure/path must be separated by periods.
    37   *
    38   * <p>This repository takes care that the parents of all loggers are correct and
    39   * updates them if necessary. The hierarchical loggers themselves are responsible
    40   * of obtaining the level and handlers from its parents if necessary and desired.
    41   *
    42   * <p>Example:
    43   * <code>
    44   *   var repository:LoggerHierarchy = new LoggerHierarchy();
    45   *   LogManager.setLoggerRepository(repository);
    46   *   var traceLogger:SimpleHierarchicalLogger = new SimpleHierarchicalLogger("org.as2lib");
    47   *   traceLogger.addHandler(new TraceHandler());
    48   *   repository.addLogger(traceLogger);
    49   *   // in some other class or something
    50   *   var myLogger:Logger = LogManager.getLogger("org.as2lib.mypackage.MyClass");
    51   *   myLogger.warning("Someone did something he should not do.");
    52   * </code>
    53   * 
    54   * <p>The message is traced because the namespace of {@code myLogger} is the same
    55   * as the one of {@code traceLogger}. You can of course add multiple handlers to
    56   * one logger and also multiple loggers to different namespaces.
    57   *
    58   * @author Simon Wacker
    59   */
    60  class org.as2lib.env.log.repository.LoggerHierarchy extends BasicClass implements LoggerRepository {
    61  	
    62  	/** Stores the root of this hierarchy. */
    63  	private var root:ConfigurableHierarchicalLogger;
    64  	
    65  	/** Stores added loggers. */
    66  	private var loggers:Array;
    67  	
    68  	/** This factory is used when no custom factory is specified. */
    69  	private var defaultLoggerFactory:ConfigurableHierarchicalLoggerFactory;
    70  	
    71  	/**
    72  	 * Constructs a new {@code LoggerHierarchy} instance.
    73  	 *
    74  	 * <p>Registers the root logger with name {@code "root"} if the {@code root}'s
    75  	 * {@code getName} method returns {@code null} or {@code undefined}. Otherwise it
    76  	 * will be registered with the name returned by the {@code root}'s {@code getName}
    77  	 * method.
    78  	 *
    79  	 * <p>If the passed-in {@code root} is {@code null} or {@code undefined} an
    80  	 * instance of type {@link RootLogger} with name {@code "root"} and log level
    81  	 * {@code ALL} will be used instead.
    82  	 *
    83  	 * @param root the root of the hierarchy
    84  	 */
    85  	public function LoggerHierarchy(root:ConfigurableHierarchicalLogger) {
    86  		if (!root) root = new RootLogger(AbstractLogLevel.ALL);
    87  		this.root = root;
    88  		loggers = new Array();
    89  		if (root.getName() == null) {
    90  			loggers["root"] = root;
    91  		} else {
    92  			loggers[root.getName()] = root;
    93  		}
    94  	}
    95  	
    96  	/**
    97  	 * Returns either the factory set via {@link #setDefaultLoggerFactory} or the
    98  	 * default one.
    99  	 *
   100  	 * <p>The default factory returns instances of type
   101  	 * {@link SimpleHierarchicalLogger}.
   102  	 *
   103  	 * @return the factory used as default
   104  	 */
   105  	public function getDefaultLoggerFactory(Void):ConfigurableHierarchicalLoggerFactory {
   106  		if (!defaultLoggerFactory) defaultLoggerFactory = getNormalLoggerFactory();
   107  		return defaultLoggerFactory;
   108  	}
   109  	
   110  	/**
   111  	 * Returns the normal factory that returns instances of class
   112  	 * {@link SimpleHierarchicalLogger}.
   113  	 *
   114  	 * @return the normal factory
   115  	 */
   116  	private function getNormalLoggerFactory(Void):ConfigurableHierarchicalLoggerFactory {
   117  		var result:ConfigurableHierarchicalLoggerFactory = getBlankConfigurableHierarchicalLoggerFactory();
   118  		// Not function(Void) because mtasc compiler complains with the following message:
   119  		// type error Local variable redefinition : Void
   120  		result.getLogger = function():ConfigurableHierarchicalLogger {
   121  			return new SimpleHierarchicalLogger();
   122  		};
   123  		return result;
   124  	}
   125  	
   126  	/**
   127  	 * Sets the factory used to obtain loggers that have not been set manually before.
   128  	 *
   129  	 * @param defaultLoggerFactory the factory to use as default
   130  	 */
   131  	public function setDefaultLoggerFactory(defaultLoggerFactory:ConfigurableHierarchicalLoggerFactory):Void {
   132  		this.defaultLoggerFactory = defaultLoggerFactory;
   133  	}
   134  	
   135  	/**
   136  	 * Returns the root logger of this hierarchy.
   137  	 *
   138  	 * @return the root logger of this hierarchy
   139  	 */
   140  	public function getRootLogger(Void):Logger {
   141  		return root;
   142  	}
   143  	
   144  	/**
   145  	 * Adds a new logger to this hierarchical repository.
   146  	 *
   147  	 * <p>The logger is automatically integrated into the hierarchy.
   148  	 *
   149  	 * @param logger the logger to add to this hierarchy
   150  	 * @throws IllegalArgumentException if the passed-in {@code logger} is {@code null}
   151  	 * or {@code undefined} or if the passed-in {@code logger}'s {@code getName} method
   152  	 * returns {@code null} or {@code undefined} or if a logger with the {@code logger}'s
   153  	 * name is already in use
   154  	 */
   155  	public function addLogger(logger:ConfigurableHierarchicalLogger):Void {
   156  		if (!logger) throw new IllegalArgumentException("Logger to add is not allowed to be null or undefined.", this, arguments);
   157  		var name:String = logger.getName();
   158  		if (name == null || name == "") throw new IllegalArgumentException("Name is not allowed to be null, undefined or a blank string.", this, arguments);
   159  		if (loggers[name] && loggers[name] instanceof ConfigurableHierarchicalLogger) {
   160  			throw new IllegalArgumentException("Name [" + name + "] is already in use.", this, arguments);
   161  		}
   162  		getLoggerByFactory(name, getSingletonFactory(logger));
   163  	}
   164  	
   165  	/**
   166  	 * Returns the factory used to obtain the passed-in {@code logger}.
   167  	 *
   168  	 * @param logger the logger to be returned by the returned factory
   169  	 * @return a factory that returns the passed-in {@code logger}
   170  	 */
   171  	private function getSingletonFactory(logger:ConfigurableHierarchicalLogger):ConfigurableHierarchicalLoggerFactory {
   172  		var result:ConfigurableHierarchicalLoggerFactory = getBlankConfigurableHierarchicalLoggerFactory();
   173  		result.getLogger = function(Void):ConfigurableHierarchicalLogger {
   174  			return logger;
   175  		};
   176  		return result;
   177  	}
   178  	
   179  	/**
   180  	 * Returns the logger appropriate to the given {@code name}.
   181  	 *
   182  	 * <p>The {@code name} can exist of a path as well as the actual specifier, for
   183  	 * example {@code org.as2lib.core.BasicClass}. In case no logger instance has been
   184  	 * put for the passed-in {@code name} a new will be created by the set factory,
   185  	 * that by default obtains all its configuration from the parent logger.
   186  	 *
   187  	 * <p>{@code null} will be returned if passed-in {@code name} is {@code null} or
   188  	 * {@code undefined}.
   189  	 *
   190  	 * @param name the name of the logger to obtain
   191  	 * @return the logger corresponding to the {@code name}
   192  	 */
   193  	public function getLogger(name:String):Logger {
   194  		if (name == null) return null;
   195  		return getLoggerByFactory(name, null);
   196  	}
   197  	
   198  	/**
   199  	 * Returns the logger corresponding to the passed-in {@code name}.
   200  	 *
   201  	 * <p>If a logger with the passed-in name is not explicitely registered the logger
   202  	 * returned by the factory is registered with the passed-in {@code name},
   203  	 * integrated in the hierarchy and returned.
   204  	 *
   205  	 * <p>The {@code name} can exist of a path as well as the actual specifier, for
   206  	 * example {@code org.as2lib.core.BasicClass}. In case no logger instance has been
   207  	 * put for the passed-in {@code name} a new will be created by the set factory,
   208  	 * that by default obtains all its configuration from the parent logger.
   209  	 * 
   210  	 * <p>{@code null} will be returned if the passed-in {@code name} is {@code null}
   211  	 * or {@code undefined}.
   212  	 *
   213  	 * <p>If the passed-in {@code factory} is {@code null} or {@code undefined} the
   214  	 * default one will be used.
   215  	 *
   216  	 * @param name the name of the logger to return
   217  	 * @return the logger appropriate to the passed-in {@code name}
   218  	 */
   219  	public function getLoggerByFactory(name:String, factory:ConfigurableHierarchicalLoggerFactory):Logger {
   220  		if (name == null) return null;
   221  		if (!factory) factory = getDefaultLoggerFactory();
   222  		var result = loggers[name];
   223  		if (!result) {
   224  			result = factory.getLogger();
   225  			result.setName(name);
   226  			loggers[name] = result;
   227  			updateParents(result);
   228  		} else if (result instanceof Array) {
   229  			var children:Array = result;
   230  			result = factory.getLogger();
   231  			result.setName(name);
   232  			loggers[name] = result;
   233  			updateChildren(children, result);
   234  			updateParents(result);
   235  		}
   236  		return result;
   237  	}
   238  	
   239  	/**
   240  	 * Updates the affected parents.
   241  	 *
   242  	 * @param l the newly added logger
   243  	 */
   244  	private function updateParents(l:ConfigurableHierarchicalLogger):Void {
   245  		var n:String = l.getName();
   246  		var f:Boolean = false;
   247  		var s:Number = n.length;
   248  		for (var i:Number = n.lastIndexOf(".", s-1); i >= 0; i = n.lastIndexOf(".", i-1)) {
   249  			var t:String = n.substring(0, i);
   250  			var o = loggers[t];
   251  			if (!o) {
   252  				loggers[t] = [l];
   253  			} else if (o instanceof Logger) {
   254  				f = true;
   255  				l.setParent(o);
   256  				break;
   257  			} else if (o instanceof Array) {
   258  				o.push(l);
   259  			} else {
   260  				throw new IllegalStateException("Obtained object [" + o + "] is of an unexpected type.", this, arguments);
   261  			}
   262  		}
   263  		if (!f) l.setParent(root);
   264  	}
   265  	
   266  	/**
   267  	 * Updates the affected children of the node.
   268  	 *
   269  	 * @param c the children to update
   270  	 * @param l the logger that now replaces the node
   271  	 */
   272  	private function updateChildren(c:Array, l:ConfigurableHierarchicalLogger):Void {
   273  		var s:Number = c.length;
   274  		for (var i:Number = 0; i < s; i++) {
   275  			var z:ConfigurableHierarchicalLogger = c[i];
   276  			if (z.getParent().getName().indexOf(l.getName()) != 0) {
   277  				l.setParent(z.getParent());
   278  				z.setParent(l);
   279  			}
   280  		}
   281  	}
   282  	
   283  	/**
   284  	 * Returns a blank configurable hierarchical logger factory. That is a logger
   285  	 * factory with no implemented methods.
   286  	 *
   287  	 * @return a blank configurable hierarchical logger factory
   288  	 */
   289  	private function getBlankConfigurableHierarchicalLoggerFactory(Void):ConfigurableHierarchicalLoggerFactory {
   290  		var result = new Object();
   291  		result.__proto__ = ConfigurableHierarchicalLoggerFactory["prototype"];
   292  		result.__constructor__ = ConfigurableHierarchicalLoggerFactory;
   293  		return result;
   294  	}
   295  	
   296  }