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.io.file.TextFileFactory;
    18  import org.as2lib.io.file.TextFile;
    19  import org.as2lib.io.file.File;
    20  import org.as2lib.data.type.Byte;
    21  import org.as2lib.data.holder.Iterator;
    22  import org.as2lib.data.holder.Map;
    23  import org.as2lib.env.except.IllegalArgumentException;
    24  import org.as2lib.io.file.FileLoader;
    25  import org.as2lib.io.file.AbstractFileLoader;
    26  import org.as2lib.io.file.SimpleTextFileFactory;
    27  import org.as2lib.io.file.FileNotLoadedException;
    28  import org.as2lib.app.exec.Executable;
    29  
    30  /**
    31   * {@code TextFileLoader} is a implementation of {@link FileLoader} for text resources.
    32   * 
    33   * <p>Any ASCII/Unicode readable resource is ment as "Text resource". {@code TextFileLoader}
    34   * is a implementation for those resources and generates a {@code TextFile}
    35   * implementation with the loaded content.
    36   * 
    37   * <p>{@link TextFileFactory} allows to generate different {@code TextFile}
    38   * implementations for the loaded content.
    39   * 
    40   * <p>{@code TextFileLoader} represents the time consuming part of accessing files
    41   * ({@code TextFile} is the handleable part} and therefore contains a event system
    42   * to add listeners to listen to the concrete events. It is possible to add
    43   * listeners using {@code addListener}.
    44   * 
    45   * <p>Example listener:
    46   * <code>
    47   *   import org.as2lib.io.file.AbstractFileLoader;
    48   *   import org.as2lib.io.file.LoadProgressListener;
    49   *   import org.as2lib.io.file.LoadStartListener;
    50   *   import org.as2lib.io.file.LoadCompleteListener;
    51   *   import org.as2lib.io.file.LoadErrorListener;
    52   *   import org.as2lib.io.file.FileLoader;
    53   *   import org.as2lib.io.file.TextFile;
    54   *   
    55   *   class MyFileListener implements 
    56   *        LoadProgressListener, LoadStartListener,
    57   *        LoadCompleteListener, LoadErrorListener {
    58   *        
    59   *     public function onLoadComplete(fileLoader:FileLoader):Void {
    60   *       var file:TextFile = TextFile(fileLoader.getFile());
    61   *       if (file != null) {
    62   *         // Proper file available
    63   *       } else {
    64   *         // Wrong event handled
    65   *       }
    66   *     }
    67   *     
    68   *     public function onLoadError(fileLoader:FileLoader, errorCode:String, error):Void {
    69   *       if (errorCode == AbstractFileLoader.FILE_NOT_FOUND) {
    70   *         var notExistantUrl = error;
    71   *         // Use that url
    72   *       }
    73   *     }
    74   *     
    75   *     public function onLoadStart(fileLoader:FileLoader) {
    76   *       // show that this file just gets loaded
    77   *     }
    78   *     
    79   *     public function onLoadProgress(fileLoader:FileLoader) {
    80   *       // update the percentage display with resourceLoader.getPercentage();
    81   *     }
    82   *   }
    83   * </code>
    84   * 
    85   * <p>Example of the usage:
    86   * <code>
    87   *   import org.as2lib.io.file.TextFileLoader;
    88   *   
    89   *   var fileLoader:TextFileLoader = new TextFileLoader();
    90   *   fileLoader.addListener(new MyFileListener());
    91   *   fileLoader.load("test.txt");
    92   * </code>
    93   * 
    94   * @author Martin Heidegger
    95   * @version 1.1
    96   */
    97  class org.as2lib.io.file.TextFileLoader extends AbstractFileLoader implements FileLoader {
    98  	
    99  	/** {@code LoadVars} instance for loading the content. */
   100  	private var helper:LoadVars;
   101  	
   102  	/** Result of the loaded {@code uri}. */
   103  	private var textFile:TextFile;
   104  	
   105  	/** Factory to create the concrete {@code TextFile} instances. */
   106  	private var textFileFactory:TextFileFactory;
   107  	
   108  	/**
   109  	 * Constructs a new {@code TextFileLoader}.
   110  	 * 
   111  	 * @param textFileFactory (optional) {@code TextFileFactory to create the
   112  	 *        {@code TextFile} implementations, {@link SimpleTextFileFactory}
   113  	 *        gets used if no custom {@code TextFileFactory} gets passed-in
   114  	 */
   115  	public function TextFileLoader(textFileFactory:TextFileFactory) {
   116  		if (!textFileFactory) {
   117  			textFileFactory = new SimpleTextFileFactory();
   118  		}
   119  		this.textFileFactory = textFileFactory;
   120  	}
   121  	
   122  	/**
   123  	 * Loads a certain text file by a http request.
   124  	 * 
   125  	 * <p>It sends http request by using the passed-in {@code uri}, {@code method}
   126  	 * and {@code parameters}. The responding file will be passed to the set
   127  	 * {@code TextFileFactory}.
   128  	 * 
   129  	 * <p>If you only need to listen if the {@code TextFile} finished loading you can
   130  	 * apply a {@code callBack} that gets called if the {@code TextFile} is loaded.
   131  	 * 
   132  	 * @param uri location of the resource to load
   133  	 * @param parameters (optional) parameters for loading the resource
   134  	 * @param method (optional) POST/GET as method for submitting the parameters,
   135  	 *        default method used if {@code method} was not passed-in is POST.
   136  	 * @param callBack (optional) {@link Executable} to be executed after the
   137  	 *        the resource was loaded.
   138  	 */
   139  	public function load(uri:String, method:String, parameters:Map, callBack:Executable):Void {
   140  		super.load(uri, method, parameters, callBack);
   141  		initHelper();
   142  		if (uri == null) {
   143  			throw new IllegalArgumentException("Url has to be set for starting the process.", this, arguments);
   144  		} else {
   145  			if (parameters) {
   146  				if (method == "POST") {
   147  					var keys:Iterator = parameters.keyIterator();
   148  					while (keys.hasNext()) {
   149  						var key = keys.next();
   150  						helper[key.toString()] = parameters.get(key);
   151  					}
   152  					helper["sendAndLoad"](uri, this, method);
   153  				} else {
   154  					var result:String = uri;
   155  					if (uri.indexOf("?") == -1) {
   156  						result += "?";
   157  					}
   158  					var keys:Iterator = parameters.keyIterator();
   159  					while (keys.hasNext()) {
   160  						var key = keys.next();
   161  						uri += _global.encode(key.toString()) + "=" + _global.encode(parameters.get(key).toString());
   162  					}
   163  					helper.load(uri);
   164  				}
   165  			} else {
   166  				helper.load(uri);
   167  			}
   168  			sendStartEvent();
   169  		}
   170  	}
   171  	
   172  	/**
   173  	 * Returns the loaded {@code File}.
   174  	 * 
   175  	 * @return {@code File} that has been loaded
   176  	 * @throws FileNotLoadedException if the resource has not been loaded yet
   177  	 */
   178  	public function getFile(Void):File {
   179  		return getFile();
   180  	}
   181  	
   182  	/**
   183  	 * Returns the loaded {@code TextFile}.
   184  	 * 
   185  	 * @return {@code TextFile} that has been loaded
   186  	 * @throws FileNotLoadedException if the resource has not been loaded yet
   187  	 */
   188  	public function getTextFile(Void):TextFile {
   189  		if (textFile == null) {
   190  			throw new FileNotLoadedException("No File has been loaded.", this, arguments);
   191  		}
   192  		return textFile;
   193  	}
   194  	
   195  	/**
   196  	 * Prepares the helper property {@link helper} for the loading process.
   197  	 */
   198  	private function initHelper(Void):Void {
   199  		var owner:TextFileLoader = this;
   200  		helper = new LoadVars();
   201  		// Watching _bytesLoaded allows realtime events
   202  		helper.watch(
   203  			"_bytesLoaded",
   204  			function(prop, oldValue, newValue) {
   205  				// Prevent useless events.
   206  				if(newValue != oldValue && newValue > 0) {
   207  					owner["handleUpdateEvent"]();
   208  				}
   209  				return newValue;
   210  			}
   211  		);
   212  		
   213  		// Using LoadVars Template to get the onData Event.
   214  		helper.onData = function(data) {
   215  			owner["handleDataEvent"](data);
   216  		};
   217  	}
   218  	
   219  	/**
   220  	 * Returns the total amount of bytes that has been loaded.
   221  	 * 
   222  	 * <p>Returns {@code null} if its not possible to get the loaded bytes.
   223  	 * 
   224  	 * @return amount of bytes that has been loaded
   225  	 */
   226  	public function getBytesLoaded(Void):Byte {
   227  		var result:Number = helper.getBytesLoaded();
   228  		if (result >= 0) {
   229  			return new Byte(result);
   230  		}
   231  		return null;
   232  	}
   233  	
   234  	/**
   235  	 * Returns the total amount of bytes that will approximately be loaded.
   236  	 * 
   237  	 * <p>Returns {@code null} if its not possible to get the total amount of bytes.
   238  	 * 
   239  	 * @return amount of bytes to load
   240  	 */
   241  	public function getBytesTotal(Void):Byte {
   242  		var total:Number = helper.getBytesTotal();
   243  		if (total >= 0) {
   244  			return new Byte(total);
   245  		}
   246  		return null;
   247  	}
   248  	
   249  	/**
   250  	 * Handles a update event from the helper.
   251  	 * 
   252  	 * @see #initHelper
   253  	 */
   254  	private function handleUpdateEvent(Void):Void {
   255  		sendProgressEvent();
   256  	}
   257  	
   258  	/**
   259  	 * Handles a data event from the helper.
   260  	 * 
   261  	 * @see #initHelper
   262  	 */
   263  	private function handleDataEvent(data:String):Void {
   264  		finished = true;
   265  		started = false;
   266  		endTime = getTimer();
   267  		helper.onLoad = function() {};
   268  		helper.unwatch("_bytesLoaded");
   269  		// Check if the file was not available.
   270  		if(typeof data == "undefined") {
   271  			// Dispatching the event for the missing uri.
   272  			sendErrorEvent(FILE_NOT_FOUND_ERROR, uri);
   273  		} else {
   274  			// Correct replacing of special line breaks that don't match the "\n" (Windows & Mac Line Breaks).
   275  			textFile = textFileFactory.createTextFile(data, getBytesTotal(), uri);
   276  			// Dispatching the event for the loaded file.
   277  			sendCompleteEvent();
   278  		}
   279  	}
   280  }