1 /**
  2  * The MIT License (MIT)
  3  *
  4  * Copyright (c) 2016 DeNA Co., Ltd.
  5  *
  6  * Permission is hereby granted, free of charge, to any person obtaining a copy
  7  * of this software and associated documentation files (the "Software"), to deal
  8  * in the Software without restriction, including without limitation the rights
  9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 10  * copies of the Software, and to permit persons to whom the Software is
 11  * furnished to do so, subject to the following conditions:
 12  *
 13  * The above copyright notice and this permission notice shall be included in
 14  * all copies or substantial portions of the Software.
 15  *
 16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 22  * SOFTWARE.
 23  */
 24 
 25 /// <reference path="base.js"/>
 26 /// <reference path="object.js"/>
 27 
 28 /**
 29  * A class that encapsulates an array that is safe to be modified in a loop. It
 30  * is not safe to modify an array in a loop, e.g. the following loop does not
 31  * iterate the second item 'b'.
 32  *
 33  *   var list = ['a', 'b', 'c'];
 34  *   for (var i = 0; i < list.length; ++i) {
 35  *     console.log(list[i]);
 36  *     if (list[i] == 'a') {
 37  *       list.splice(i, 1);
 38  *     }
 39  *   }
 40  *
 41  * Even though iterating a clone solves this problem, it is an overkill to
 42  * create a clone every time when an application iterates an array.
 43  *
 44  *   var list = ['a', 'b', 'c'];
 45  *   var clone = list.slice();
 46  *   for (var i = 0; i < clone.length; ++i) {
 47  *     console.log(clone[i]);
 48  *     if (clone[i] == 'a') {
 49  *       list.splice(i, 1);
 50  *     }
 51  *   }
 52  *
 53  * This class lazily creates a clone of its array for the first time when an
 54  * application either adds an item to the array or removes one in a loop.
 55  *
 56  *   var list = object_list.lock();
 57  *   for (var i = 0; i < list.length; ++i) {
 58  *     console.log(list[i]);
 59  *     if (list[i] == 'a') {
 60  *       object_list.removeItemAt(i);
 61  *     }
 62  *   }
 63  *   object_list.unlock();
 64  *
 65  * @constructor
 66  */
 67 createjs.ObjectList = function() {
 68   /**
 69    * A list of objects.
 70    * @type {Array.<*>}
 71    */
 72   this.items_ = [];
 73 };
 74 
 75 /**
 76  * A clone of this list.
 77  * @type {Array.<*>}
 78  */
 79 createjs.ObjectList.prototype.clone_ = null;
 80 
 81 /**
 82  * Whether this list is locked.
 83  * @type {boolean}
 84  */
 85 createjs.ObjectList.prototype.locked_ = false;
 86 
 87 /**
 88  * Retrieves the editable items of this list.
 89  * @return {Array.<*>}
 90  * @protected
 91  */
 92 createjs.ObjectList.prototype.getItems = function() {
 93   /// <returns type="Array"/>
 94   if (!this.locked_) {
 95     return this.items_;
 96   }
 97   if (!this.clone_) {
 98     this.clone_ = this.items_.slice();
 99   }
100   return this.clone_;
101 };
102 
103 /**
104  * Returns whether this list is locked.
105  * @return {boolean}
106  * @protected
107  */
108 createjs.ObjectList.prototype.isLocked = function() {
109   /// <returns type="boolean"/>
110   return this.locked_;
111 };
112 
113 /**
114  * Returns the number of items in this list.
115  * @return {number}
116  * @const
117  */
118 createjs.ObjectList.prototype.getLength = function() {
119   /// <returns type="number"/>
120   return this.items_.length;
121 };
122 
123 /**
124  * Locks this list for iteration. This method changes the state of this list to
125  * 'locked' to apply succeeding add operations and remove ones to its clone.
126  * @return {Array.<*>}
127  * @const
128  */
129 createjs.ObjectList.prototype.lock = function() {
130   /// <returns type="Array"/>
131   createjs.assert(!this.locked_);
132   this.locked_ = true;
133   return this.items_;
134 };
135 
136 /**
137  * Unlocks this list. This method changes the stage of this list to 'unlocked'
138  * and copies its clone if an application edits the list while it is locked.
139  * @const
140  */
141 createjs.ObjectList.prototype.unlock = function() {
142   createjs.assert(this.locked_);
143   this.locked_ = false;
144   if (this.clone_) {
145     this.items_ = this.clone_;
146     this.clone_ = null;
147   }
148 };
149 
150 /**
151  * Adds an item to the beginning of this list.
152  * @param {*} item
153  * @const
154  */
155 createjs.ObjectList.prototype.unshiftItem = function(item) {
156   /// <param name="item"/>
157   var list = this.getItems();
158   list.unshift(item);
159 };
160 
161 /**
162  * Adds an item to the end of this list.
163  * @param {*} item
164  * @const
165  */
166 createjs.ObjectList.prototype.pushItem = function(item) {
167   /// <param name="item"/>
168   var list = this.getItems();
169   list.push(item);
170 };
171 
172 /**
173  * Adds an item to the specified position of this list.
174  * @param {number} index
175  * @param {*} item
176  * @const
177  */
178 createjs.ObjectList.prototype.insertItem = function(index, item) {
179   /// <param type="number" name="index"/>
180   /// <param name="item"/>
181   var list = this.getItems();
182   list.splice(index, 0, item);
183 };
184 
185 /**
186  * Removes an item from this list.
187  * @param {*} item
188  * @const
189  */
190 createjs.ObjectList.prototype.removeItem = function(item) {
191   /// <param name="item"/>
192   var list = this.getItems();
193   for (var i = 0; i < list.length; ++i) {
194     if (list[i] === item) {
195       list.splice(i, 1);
196       return;
197     }
198   }
199 };
200 
201 /**
202  * Finds an item from this list.
203  * @param {*} item
204  * @return {number}
205  * @const
206  */
207 createjs.ObjectList.prototype.findItem = function(item) {
208   /// <param name="item"/>
209   /// <returns type="number"/>
210   var list = this.getItems();
211   for (var i = 0; i < list.length; ++i) {
212     if (list[i] === item) {
213       return i;
214     }
215   }
216   return -1;
217 };
218 
219 /**
220  * Adds an item to the end of this list only if this list does not have it.
221  * @param {*} item
222  * @const
223  */
224 createjs.ObjectList.prototype.pushUniqueItem = function(item) {
225   /// <param name="item"/>
226   var list = this.getItems();
227   for (var i = 0; i < list.length; ++i) {
228     if (list[i] === item) {
229       return;
230     }
231   }
232   list.push(item);
233 };
234 
235 /**
236  * Swaps two items in this list.
237  * @param {*} item1
238  * @param {*} item2
239  * @const
240  */
241 createjs.ObjectList.prototype.swapItems = function(item1, item2) {
242   /// <param name="item1"/>
243   /// <param name="item2"/>
244   var list = this.getItems();
245   var index1 = -1;
246   var index2 = -1;
247   for (var i = 0; i < list.length; ++i) {
248     var item = list[i];
249     if (item === item1) {
250       index1 = i;
251     } else if (item === item2) {
252       index2 = i;
253     }
254     if (index1 >= 0 && index2 >= 0) {
255       list[index1] = item2;
256       list[index2] = item1;
257       return;
258     }
259   }
260 };
261 
262 /**
263  * Returns an item at the specified index.
264  * @param {number} index
265  * @return {*}
266  * @const
267  */
268 createjs.ObjectList.prototype.getItemAt = function(index) {
269   /// <param type="number" name="index"/>
270   /// <returns type="Object"/>
271   var list = this.getItems();
272   return list[index];
273 };
274 
275 /**
276  * Removes an item from this list.
277  * @param {number} index
278  * @const
279  */
280 createjs.ObjectList.prototype.removeItemAt = function(index) {
281   /// <param type="number" name="index"/>
282   var list = this.getItems();
283   list.splice(index, 1);
284 };
285 
286 /**
287  * Finds an item from this list.
288  * @param {number} index1
289  * @param {number} index2
290  * @const
291  */
292 createjs.ObjectList.prototype.swapItemsAt = function(index1, index2) {
293   /// <param type="number" name="index1"/>
294   /// <param type="number" name="index2"/>
295   var list = this.getItems();
296   if (index1 >= list.length || index2 >= list.length) {
297     return;
298   }
299   var object1 = list[index1];
300   var object2 = list[index2];
301   list[index1] = object2;
302   list[index2] = object1;
303 };
304 
305 /**
306  * Removes all items from this list.
307  * @const
308  */
309 createjs.ObjectList.prototype.removeAllItems = function() {
310   if (!this.locked_) {
311     this.items_ = [];
312   } else {
313     this.clone_ = [];
314   }
315 };
316 
317 /**
318  * Returns a clone of this list.
319  * @return {Array.<*>}
320  * @const
321  */
322 createjs.ObjectList.prototype.cloneItems = function() {
323   /// <returns type="Array"/>
324   var items = this.clone_ ? this.clone_ : this.items_;
325   return items.slice();
326 };
327