1  /**
     2   * com.sekati.log.Out
     3   * @version 1.9.1
     4   * @author jason m horwitz | sekati.com
     5   * Copyright (C) 2007  jason m horwitz, Sekat LLC. All Rights Reserved.
     6   * Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
     7   */
     8  
     9  import com.sekati.log.OutPanel;
    10  import com.sekati.log.Inspector;
    11  import com.sekati.events.Dispatcher;
    12  import com.sekati.events.Event;
    13  import com.sekati.time.StopWatch;
    14  /**
    15   * Out is a multi-tiered trace replacement which uses debug levels and
    16   * object/instance filters to assist and clarify the debugging process.
    17   * New level method calls can be created dynamically "overloading" 
    18   * the class via __resolve and proxy.
    19   * 
    20   * {@code Usage:
    21   * var out:Out = Out.getInstance();
    22   * out.createPanel(640,480); // optionally create an outPanel in the swf
    23   * out.trace(this,"hello world"); // simple hello world
    24   * out.setFilter(this,true); // filters this object from further output
    25   * out.info(this,"hello world"); // wont display due to filter
    26   * out.setFilter(this,false); // unfilter this object
    27   * out.setLevel("trace",false); // disable level trace
    28   * out.trace(this,"hello world"); // wont display due to disabled level
    29   * out.setLevel("debug",true); // creates a new level named "debug" & enable it
    30   * out.debug(this,"hello world"); // displays within the new debug level
    31   * out.object(this,{foo:5, bar:1}); // special object level does basic object recursion
    32   * }
    33   */
    34  class com.sekati.log.Out {
    35  	private static var _instance:Out;
    36  	private static var _levels:Object = {TRACE:true, INFO:true, STATUS:true, WARN:true, ERROR:true, FATAL:true, OBJECT:true};
    37  	private static var _filters:Array = new Array( );
    38  	private static var _proxyObj:Object = new Object( );
    39  	private static var _isEnabled:Boolean = true;
    40  	private static var _isPanel:Boolean = false;
    41  	private static var _benchwatch:StopWatch = new StopWatch( false );
    42  	// default level stubs
    43  	public var trace:Function;
    44  	public var info:Function;
    45  	public var status:Function;
    46  	public var warn:Function;
    47  	public var error:Function;
    48  	public var fatal:Function;
    49  	/**
    50  	 * Singleton Private Constructor
    51  	 */
    52  	private function Out() {
    53  	}
    54  	/**
    55  	 * Singleton Accessor
    56  	 * @return Out
    57  	 */	
    58  	public static function getInstance():Out {
    59  		if (!_instance) _instance = new Out( );
    60  		return _instance;
    61  	}
    62  	/**
    63  	 * shorthand singleton accessor getter
    64  	 */
    65  	public static function get $():Out {
    66  		return Out.getInstance( );	
    67  	}	
    68  	/**
    69  	 * wrapper to draw a panel to swf for output via {@link OutPanel}
    70  	 * @param w (Number) panel width
    71  	 * @param h (Number) panel height
    72  	 * @return Void
    73  	 */
    74  	public function createPanel(w:Number, h:Number):Void {
    75  		_isPanel = true;
    76  		OutPanel.getInstance( w, h );
    77  	}
    78  	// Core controllers
    79  	/**
    80  	 * Out enabled setter
    81  	 * @param b (Boolean) enable or disable Out
    82  	 */
    83  	public function set enabled(b:Boolean):Void {
    84  		_isEnabled = b;
    85  	}
    86  	/**
    87  	 * Out enabled getter
    88  	 * @return Boolean
    89  	 */	
    90  	public function get enabled():Boolean {
    91  		return _isEnabled;
    92  	}
    93  	/**
    94  	 * level and filter _status getter
    95  	 * @return String
    96  	 */
    97  	public function get _status():String {
    98  		return getLevels( ) + "\n" + getFilters( );
    99  	}
   100  	/**
   101  	 * reset Out to default levels and filters
   102  	 * @return Void
   103  	 */
   104  	public function reset():Void {
   105  		setAllLevels( true );
   106  		resetFilters( );
   107  	}
   108  	// Level Handlers
   109  	/**
   110  	 * set a levels enabled status or create a new level
   111  	 * @param level (String) level name
   112  	 * @param isEnabled (Boolean)
   113  	 * @return Void
   114  	 */
   115  	public function setLevel(level:String, isEnabled:Boolean):Void {
   116  		_levels[level.toUpperCase( )] = isEnabled;
   117  	}
   118  	/**
   119  	 * set all levels status
   120  	 * @param isEnabled (Boolean)
   121  	 * @return Void
   122  	 */
   123  	public function setAllLevels(isEnabled:Boolean):Void {
   124  		for (var i in _levels) {
   125  			setLevel( _levels[i], isEnabled );
   126  		}
   127  	}
   128  	/**
   129  	 * returns a string of all levels for overview/status purposes
   130  	 * @return String
   131  	 */
   132  	public function getLevels():String {
   133  		var a:Array = new Array( );
   134  		for (var i in _levels) {
   135  			a.push( i + ":" + _levels[i].toString( ) );
   136  		}
   137  		return "_levels={" + a.toString( ) + "};";
   138  	}
   139  	// Object Filters
   140  	/**
   141  	 * set a filters enabled status or create a new filter
   142  	 * @param origin (Object) object to set filter on
   143  	 * @param isFiltered (Boolean)
   144  	 * @return Void
   145  	 */
   146  	public function setFilter(origin:Object, isFiltered:Boolean):Void {
   147  		if(isFiltered) {
   148  			filter( origin );
   149  		} else {
   150  			unfilter( origin );
   151  		}
   152  	}
   153  	/**
   154  	 * return a string of all filters for overview/status purposes
   155  	 * @return String
   156  	 */
   157  	public function getFilters():String {
   158  		return "_filters=[" + _filters.toString( ) + "];";
   159  	}
   160  	/**
   161  	 * reset all filters
   162  	 */
   163  	public function resetFilters():Void {
   164  		_filters = [];
   165  	}
   166  	private function filter(origin:Object):Void {
   167  		var o:String = String( origin );
   168  		if (!isFiltered( o )) {
   169  			_filters.push( o );
   170  		}
   171  	}
   172  	private function unfilter(origin:Object):Void {
   173  		var o:String = String( origin );
   174  		for (var i:Number = 0; i < _filters.length ; i++) {
   175  			if (_filters[i] == o) {
   176  				_filters.splice( i, 1 );
   177  				break;
   178  			}
   179  		}
   180  	}
   181  	public function isFiltered(origin:Object):Boolean {
   182  		var o:String = String( origin );
   183  		for (var i:Number = 0; i < _filters.length ; i++) {
   184  			if (_filters[i] == o) {
   185  				return true;
   186  			}
   187  		}
   188  		return false;
   189  	}
   190  	// currently unused - as3 has getNameSpace - for now maybe better to keep filtering on object/string rather than class?
   191  	private function resolveOrigin($origin:Object):Void {
   192  		var o:Object = (typeof ($origin) == "string") ? $origin : $origin._classname;
   193  		if (!o) {
   194  			o = $origin;
   195  		}
   196  		trace( o );
   197  	}
   198  	// Output methods
   199  	private function _output(level:String, origin:Object, msg:Object):Void {
   200  		if (!_isEnabled || !_levels[level] || isFiltered( origin )) {
   201  			return;
   202  		}
   203  		var o:String = level + ":[" + String( origin ) + "]: " + msg;
   204  		if(!_isPanel) {
   205  			trace( o );
   206  		} else {
   207  			var e:Event = new Event( "OUT_EVENT", this, {message:o, level:level} );
   208  			Dispatcher.$.dispatchEvent( e );
   209  		}
   210  	}
   211  	// __resolve catches all wrapper & invented levels and processes them thru the proxy to _output: cool!
   212  	private function __resolve(name:String):Function {
   213  		if (name.indexOf( "OUT_EVENT" ) > 1) {
   214  			return;
   215  		}
   216  		var f:Function = function ():Object {
   217  			arguments.unshift( name );
   218  			return __proxy.apply( _proxyObj, arguments );
   219  		};
   220  		_proxyObj[name] = f;
   221  		return f;
   222  	}
   223  	private function __proxy(name:String):Void {
   224  		arguments.shift( );
   225  		var n:String = String( name ).toUpperCase( );
   226  		var o:String = String( arguments[0] );
   227  		var s:String = String( arguments[1] );
   228  		_instance._output( n, o, s );
   229  	}
   230  	/**
   231  	 * object is a special level (method) which handles object recursion via {@link com.sekati.log.Inspector}
   232  	 * @param origin (Object) origin for filtering purposes
   233  	 * @param obj (Object) object to be recursed thru Out
   234  	 * @return Void 
   235  	 */
   236  	public function object(origin:Object, obj:Object):Void {
   237  		var insp:Inspector = new Inspector( obj, origin );
   238  		_output( "OBJECT", origin, insp );
   239  	}
   240  	/**
   241  	 * benchmark is a special level (method) which handles benchmarking of call execution
   242  	 * @param origin (Object) origin for filtering purposes
   243  	 * @param msg (String) any extraneous name or message info you wish to output before the benchmark results
   244  	 * @param mode (String) valid modes are: "start", "stop", "lap"
   245  	 * @return Void
   246  	 * @throws Error on invalid 'mode' argument
   247  	 */
   248  	public function benchmark(origin:Object, msg:String, mode:String):Void {
   249  		//trace("bench call");
   250  		/*
   251  		if (mode.toLowerCase() != "start" && mode.toLowerCase() != "stop" && mode.toLowerCase() != "lap") {
   252  		throw new Error ("@@@ com.sekati.log.Out Error: bench() expects mode param: 'start', 'stop' or 'lap'.");
   253  		return;
   254  		}
   255  		 */
   256  		var b:Number;
   257  		switch (mode.toLowerCase( )) { 
   258  			case "start" : 
   259  				b = _benchwatch.start( );
   260  				break; 
   261  			case "stop" : 
   262  				b = _benchwatch.stop( ); 
   263  				break; 
   264  			case "lap" : 
   265  				b = _benchwatch.lap( ); 
   266  				break; 						
   267  			default : 
   268  				throw new Error( "@@@ com.sekati.log.Out Error: bench() expects mode param: 'start', 'stop' or 'lap'." );
   269  				return;
   270  			//break; 
   271  		}  
   272  		//var b:Number = _benchwatch[mode]();
   273  		var bs:String = String( _benchwatch.read( ) );
   274  		var str:String = String( msg + " | benchmark (" + mode + "): " + bs + "ms" );
   275  		//trace(str);
   276  		_output( "BENCHMARK", origin, str );
   277  	}
   278  }