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.overload.Overload;
    19  import org.as2lib.data.holder.List;
    20  import org.as2lib.util.Stringifier;
    21  import org.as2lib.data.holder.list.ListStringifier;
    22  import org.as2lib.data.holder.list.SubList;
    23  import org.as2lib.data.holder.IndexOutOfBoundsException;
    24  
    25  /**
    26   * {@code AbstractList} provides common implementations of methods needed by
    27   * implementations of the {@link List} interface.
    28   * 
    29   * @author Simon Wacker
    30   */
    31  class org.as2lib.data.holder.list.AbstractList extends BasicClass {
    32  	
    33  	/** Stringifies lists. */
    34  	private static var stringifier:Stringifier;
    35  	
    36  	/**
    37  	 * Returns the stringifier to stringify lists.
    38  	 *
    39  	 * @return the list stringifier
    40  	 */
    41  	public static function getStringifier(Void):Stringifier {
    42  		if (!stringifier) stringifier = new ListStringifier();
    43  		return stringifier;
    44  	}
    45  	
    46  	/**
    47  	 * Sets the stringifier to stringify lists.
    48  	 * 
    49  	 * @param listStringifier the stringifier to stringify lists
    50  	 */
    51  	public static function setStringifier(listStringifier:Stringifier):Void {
    52  		stringifier = listStringifier;
    53  	}
    54  	
    55  	/** This instance casted to interface {@code List}. */
    56  	private var thiz:List;
    57  	
    58  	/**
    59  	 * Constructs a new {@code AbstractList} instance.
    60  	 */
    61  	private function AbstractList(Void) {
    62  		thiz = List(this);
    63  	}
    64  	
    65  	/**
    66  	 * @overload #insertByValue
    67  	 * @overload insertByIndexAndValue
    68  	 */
    69  	public function insert():Void {
    70  		var o:Overload = new Overload(this);
    71  		o.addHandler([Object], insertByValue);
    72  		o.addHandler([Number, Object], thiz.insertByIndexAndValue);
    73  		o.forward(arguments);
    74  	}
    75  	
    76  	/**
    77  	 * Inserts {@code value} at the end of this list.
    78  	 * 
    79  	 * @param value the value to insert
    80  	 * @see #insertLast
    81  	 */
    82  	public function insertByValue(value):Void {
    83  		insertLast(value);
    84  	}
    85  	
    86  	/**
    87  	 * Inserts {@code value} at the beginning of this list.
    88  	 * 
    89  	 * @param value the value to insert
    90  	 */
    91  	public function insertFirst(value):Void {
    92  		thiz.insertByIndexAndValue(0, value);
    93  	}
    94  	
    95  	/**
    96  	 * Inserts {@code value} at the end of this list.
    97  	 * 
    98  	 * @param value the value to insert
    99  	 * @see #insert
   100  	 */
   101  	public function insertLast(value):Void {
   102  		thiz.insertByIndexAndValue(thiz.size(), value);
   103  	}
   104  	
   105  	/**
   106  	 * @overload #insertAllByList
   107  	 * @overload #insertAllByIndexAndList
   108  	 */
   109  	public function insertAll():Void {
   110  		var o:Overload = new Overload(this);
   111  		o.addHandler([List], insertAllByList);
   112  		o.addHandler([Number, List], insertAllByIndexAndList);
   113  		o.forward(arguments);
   114  	}
   115  	
   116  	/**
   117  	 * Inserts all values contained in {@code list} to the end of this list.
   118  	 * 
   119  	 * @param list the values to insert
   120  	 */
   121  	public function insertAllByList(list:List):Void {
   122  		var v:Array = list.toArray();
   123  		var l:Number = v.length;
   124  		for (var i:Number = 0; i < l; i++) {
   125  			thiz.insertLast(v[i]);
   126  		}
   127  	}
   128  	
   129  	/**
   130  	 * Inserts all values contained in {@code list} to this list, starting at the
   131  	 * specified {@code index}.
   132  	 * 
   133  	 * <p>Elements that are at an affected index are shifted to the right by the size
   134  	 * of the given {@code list}.
   135  	 * 
   136  	 * @param index the index to start the insertion at
   137  	 * @param list the values to insert
   138  	 * @throws IndexOutOfBoundsException if the given {@code index} is not in range,
   139  	 * this is less than 0 or greater than this list's size
   140  	 */
   141  	public function insertAllByIndexAndList(index:Number, list:List):Void {
   142  		if (index < 0 || index > thiz.size()) {
   143  			throw new IndexOutOfBoundsException("Argument 'index' [" + index + "] is out of range, this is less than 0 or greater than this list's size [" + thiz.size() + "].", this, arguments);
   144  		}
   145  		var v:Array = list.toArray();
   146  		var l:Number = v.length;
   147  		for (var i:Number = 0; i < l; i++) {
   148  			thiz.insertByIndexAndValue(i + index, v[i]);
   149  		}
   150  	}
   151  	
   152  	/**
   153  	 * @overload #removeByValue
   154  	 * @overload removeByIndex
   155  	 */
   156  	public function remove() {
   157  		var o:Overload = new Overload(this);
   158  		o.addHandler([Object], removeByValue);
   159  		o.addHandler([Number], thiz.removeByIndex);
   160  		return o.forward(arguments);
   161  	}
   162  	
   163  	/**
   164  	 * Removes {@code value} from this list if it exists.
   165  	 * 
   166  	 * @param value the value to remove
   167  	 */
   168  	public function removeByValue(value):Number {
   169  		var result:Number = indexOf(value);
   170  		if (result > -1) {
   171  			thiz.removeByIndex(indexOf(value));
   172  		}
   173  		return result;
   174  	}
   175  	
   176  	/**
   177  	 * Removes the value at the beginning of this list.
   178  	 * 
   179  	 * @return the removed value
   180  	 */
   181  	public function removeFirst(Void) {
   182  		return thiz.removeByIndex(0);
   183  	}
   184  	
   185  	/**
   186  	 * Removes the value at the end of this list.
   187  	 * 
   188  	 * @return the removed value
   189  	 */
   190  	public function removeLast(Void) {
   191  		return thiz.removeByIndex(thiz.size() - 1);
   192  	}
   193  	
   194  	/**
   195  	 * Removes all values contained in {@code list}.
   196  	 * 
   197  	 * @param list the values to remove
   198  	 */
   199  	public function removeAll(list:List):Void {
   200  		var v:Array = list.toArray();
   201  		var l:Number = v.length;
   202  		for (var i:Number = 0; i < l; i++) {
   203  			removeByValue(v[i]);
   204  		}
   205  	}
   206  	
   207  	/**
   208  	 * Sets all values contained in {@code list} to this list, starting from given
   209  	 * {@code index}. They values that were originally at the given {@code index}
   210  	 * and following indices will be overwritten.
   211  	 * 
   212  	 * <p>This method only overwrites existing index-value pairs. If an affected index
   213  	 * is equal to or greater than this list's size, which would mean that this list's
   214  	 * size had to be expanded, an {@code IndexOutOfBoundsException} will be thrown. In
   215  	 * such a case use the {@link #insertAll} method instead, which expands this list
   216  	 * dynamically.
   217  	 * 
   218  	 * @param index the index to start at
   219  	 * @param list the values to set
   220  	 * @throws IndexOutOfBoundsException if given {@code index} is less than 0 or if
   221  	 * any affected index, that is the given {@code index} plus the index of the
   222  	 * specific value in the given {@code list}, is equal to or greater than this list's
   223  	 * size
   224  	 */
   225  	public function setAll(index:Number, list:List):Void {
   226  		if (index < 0 || index + list.size() > thiz.size()) {
   227  			throw new IndexOutOfBoundsException("Argument 'index' [" + index + "] is out of range, this is less than 0 or the 'index' plus the size of the given 'list' [" + list.size() + "] is greater than this list's size [" + thiz.size() + "].", this, arguments);
   228  		}
   229  		var v:Array = list.toArray();
   230  		var l:Number = v.length;
   231  		for (var i:Number = 0; i < l; i++) {
   232  			thiz.set(index++, v[i]);
   233  		}
   234  	}
   235  	
   236  	/**
   237  	 * Retains all values the are contained in {@code list} and removes all others.
   238  	 * 
   239  	 * @param list the list of values to retain
   240  	 */
   241  	public function retainAll(list:List):Void {
   242  		var i:Number = thiz.size();
   243  		while(--i-(-1)) {
   244  			if (!list.contains(thiz.get(i))) {
   245  				thiz.removeByIndex(i);
   246  			}
   247  		}
   248  	}
   249  	
   250  	/**
   251  	 * Checks whether {@code value} is contained in this list.
   252  	 * 
   253  	 * @param value the value to check whether it is contained
   254  	 * @return {@code true} if {@code value} is contained else {@code false}
   255  	 */
   256  	public function contains(value):Boolean {
   257  		return (indexOf(value) > -1);
   258  	}
   259  	
   260  	/**
   261  	 * Checks whether all values of {@code list} are contained in this list.
   262  	 * 
   263  	 * @param list the values to check whether they are contained
   264  	 * @return {@code true} if all values of {@code list} are contained else
   265  	 * {@code false}
   266  	 */
   267  	public function containsAll(list:List):Boolean {
   268  		var v:Array = list.toArray();
   269  		var l:Number = v.length;
   270  		for (var i:Number = 0; i < l; i++) {
   271  			if (!contains(v[i])) {
   272  				return false;
   273  			}
   274  		}
   275  		return true;
   276  	}
   277  	
   278  	/**
   279  	 * Returns a view of the portion of this list between the specified {@code fromIndex},
   280  	 * inclusive, and {@code toIndex}, exclusive.
   281  	 * 
   282  	 * <p>If {@code fromIndex} and {@code toIndex} are equal an empty list is returned.
   283  	 * 
   284  	 * <p>The returned list is backed by this list, so changes in the returned list are
   285  	 * reflected in this list, and vice-versa.
   286  	 * 
   287  	 * @param fromIndex the index from which the sub-list starts (inclusive)
   288  	 * @param toIndex the index specifying the end of the sub-list (exclusive)
   289  	 * @return a view of the specified range within this list
   290  	 * @throws IndexOutOfBoundsException if argument {@code fromIndex} is less than 0
   291  	 * @throws IndexOutOfBoundsException if argument {@code toIndex} is greater than
   292  	 * the size of this list
   293  	 * @throws IndexOutOfBoundsException if argument {@code fromIndex} is greater than
   294  	 * {@code toIndex}
   295  	 */
   296  	public function subList(fromIndex:Number, toIndex:Number):List {
   297  		return new SubList(thiz, fromIndex, toIndex);
   298  	}
   299  	
   300  	/**
   301  	 * Returns the index of {@code value}.
   302  	 * 
   303  	 * @param value the value to return the index of
   304  	 * @return the index of {@code value}
   305  	 */
   306  	public function indexOf(value):Number {
   307  		var l:Number = thiz.size();
   308  		while (--l > -1 && thiz.get(l) !== value);
   309  		return l;
   310  	}
   311  	
   312  	/**
   313  	 * Returns whether this list is empty.
   314  	 * 
   315  	 * <p>This list is empty if it has no values assigned to it.
   316  	 * 
   317  	 * @return {@code true} if this list is empty else {@code false}
   318  	 */
   319  	public function isEmpty(Void):Boolean {
   320  		return (thiz.size() < 1);
   321  	}
   322  	
   323  	/**
   324  	 * Returns the string representation of this list.
   325  	 * 
   326  	 * <p>The string representation is obtained via the stringifier returned by the
   327  	 * static {@link #getStringifier} method.
   328  	 * 
   329  	 * @return the string representation of this list
   330  	 */
   331  	public function toString():String {
   332  		return getStringifier().execute(this);
   333  	}
   334  	
   335  }