Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
ListMapIterator |
|
| 0.0;0 |
1 | /* | |
2 | * Licensed under the MIT License | |
3 | * | |
4 | * Copyright 2010 (c) Flávio Silva, http://flsilva.com | |
5 | * | |
6 | * Permission is hereby granted, free of charge, to any person | |
7 | * obtaining a copy of this software and associated documentation | |
8 | * files (the "Software"), to deal in the Software without | |
9 | * restriction, including without limitation the rights to use, | |
10 | * copy, modify, merge, publish, distribute, sublicense, and/or sell | |
11 | * copies of the Software, and to permit persons to whom the | |
12 | * Software is furnished to do so, subject to the following | |
13 | * conditions: | |
14 | * | |
15 | * The above copyright notice and this permission notice shall be | |
16 | * included in all copies or substantial portions of the Software. | |
17 | * | |
18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
19 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | |
20 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
21 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |
22 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
23 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
24 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
25 | * OTHER DEALINGS IN THE SOFTWARE. | |
26 | * | |
27 | * http://www.opensource.org/licenses/mit-license.php | |
28 | */ | |
29 | ||
30 | 1 | package org.as3collections.iterators |
31 | { | |
32 | import org.as3collections.IListMap; | |
33 | import org.as3collections.IListMapIterator; | |
34 | import org.as3collections.errors.ConcurrentModificationError; | |
35 | import org.as3collections.errors.IndexOutOfBoundsError; | |
36 | import org.as3collections.errors.NoSuchElementError; | |
37 | import org.as3coreaddendum.errors.IllegalStateError; | |
38 | ||
39 | /** | |
40 | * An iterator to iterate over implementations of <code>IListMap</code> interface. | |
41 | * <code>ListMapIterator</code> allows to traverse the map in either direction. | |
42 | * <p><b>IMPORTANT:</b></p> | |
43 | * <p>A <code>ListMapIterator</code> has no current mapping; its cursor position always lies between the mapping that would be returned by a call to <code>previous()</code> and the mapping that would be returned by a call to <code>next()</code>. | |
44 | * An iterator for a map of length <code>n</code> has <code>n+1</code> possible cursor positions, as illustrated by the carets (^) below:</p> | |
45 | * <p>                                 | |
46 | * Element(0)        | |
47 | * Element(1)        | |
48 | * Element(2)        | |
49 | * ... Element(n-1)</p> | |
50 | * <p>cursor positions: | |
51 | *     | |
52 | * ^                     | |
53 | * ^                      | |
54 | * ^                     | |
55 | * ^                             | |
56 | * ^</p> | |
57 | * <p>Note that the <code>remove()</code> and <code>set()</code> methods are <em>not</em> defined in terms of the cursor position; they are defined to operate on the last mapping returned by a call to <code>next()</code> or <code>previous()</code>.</p> | |
58 | * <p>For further information do not hesitate to see the examples at the end of the page.</p> | |
59 | * <p>This documentation is partially based in the <em>Java Collections Framework</em> JavaDoc documentation. | |
60 | * For further information see <a href="http://download.oracle.com/javase/6/docs/technotes/guides/collections/index.html" target="_blank">Java Collections Framework</a></p> | |
61 | * | |
62 | * @example | |
63 | * | |
64 | * <b>Example 1</b> | |
65 | * | |
66 | * <listing version="3.0"> | |
67 | * import org.as3collections.IListMap; | |
68 | * import org.as3collections.IListMapIterator; | |
69 | * import org.as3collections.maps.ArrayListMap; | |
70 | * | |
71 | * var map1:IListMap = new ArrayListMap(); | |
72 | * map1.put("element-1", 1); | |
73 | * map1.put("element-3", 3); | |
74 | * map1.put("element-5", 5); | |
75 | * | |
76 | * map1 // ["element-1"=1,"element-3"=3,"element-5"=5] | |
77 | * | |
78 | * var it:IListMapIterator = map1.listMapIterator(); | |
79 | * var e:int; | |
80 | * | |
81 | * while (it.hasNext()) | |
82 | * { | |
83 | * | |
84 | * ITERATION N.1 | |
85 | * | |
86 | * it.pointer() // null | |
87 | * it.nextIndex() // 0 | |
88 | * it.previousIndex() // -1 | |
89 | * | |
90 | * e = it.next(); | |
91 | * e // 1 | |
92 | * | |
93 | * it.pointer() // "element-1" | |
94 | * it.nextIndex() // 1 | |
95 | * it.previousIndex() // 0 | |
96 | * | |
97 | * ITERATION N.2 | |
98 | * | |
99 | * it.pointer() // "element-1" | |
100 | * it.nextIndex() // 1 | |
101 | * it.previousIndex() // 0 | |
102 | * | |
103 | * e = it.next(); | |
104 | * e // 3 | |
105 | * | |
106 | * it.pointer() // "element-3" | |
107 | * it.nextIndex() // 2 | |
108 | * it.previousIndex() // 1 | |
109 | * | |
110 | * if (e == 3) | |
111 | * { | |
112 | * //map1.put("element-4", 4) // ConcurrentModificationError: During the iteration, the map was changed directly (without use the iterator). | |
113 | * it.put("element-4", 4); | |
114 | * map1 // ["element-1"=1,"element-3"=3,"element-4"=4,"element-5"=5] | |
115 | * } | |
116 | * | |
117 | * ITERATION N.3 | |
118 | * | |
119 | * it.pointer() // "element-4" | |
120 | * it.nextIndex() // 3 | |
121 | * it.previousIndex() // 2 | |
122 | * | |
123 | * e = it.next(); | |
124 | * e // 5 | |
125 | * | |
126 | * it.pointer() // "element-5" | |
127 | * it.nextIndex() // 4 | |
128 | * it.previousIndex() // 3 | |
129 | * | |
130 | * if (e == 5) | |
131 | * { | |
132 | * it.remove(); | |
133 | * map1 // ["element-1"=1,"element-3"=3,"element-4"=4] | |
134 | * } | |
135 | * } | |
136 | * </listing> | |
137 | * | |
138 | * <b>Example 2</b> | |
139 | * | |
140 | * <listing version="3.0"> | |
141 | * import org.as3collections.IListMap; | |
142 | * import org.as3collections.IListMapIterator; | |
143 | * import org.as3collections.maps.ArrayListMap; | |
144 | * | |
145 | * var map1:IListMap = new ArrayListMap(); | |
146 | * map1.put("element-1", 1); | |
147 | * map1.put("element-3", 3); | |
148 | * map1.put("element-5", 5); | |
149 | * | |
150 | * map1 // ["element-1"=1,"element-3"=3,"element-5"=5] | |
151 | * | |
152 | * var it:IListMapIterator = map1.listIterator(map1.size()); | |
153 | * var e:int; | |
154 | * | |
155 | * while (it.hasPrevious()) | |
156 | * | |
157 | * { | |
158 | * | |
159 | * ITERATION N.1 | |
160 | * | |
161 | * it.pointer() // "element-5" | |
162 | * it.nextIndex() // 3 | |
163 | * it.previousIndex() // 2 | |
164 | * | |
165 | * e = it.previous(); | |
166 | * e // 5 | |
167 | * | |
168 | * it.pointer() // "element-3" | |
169 | * it.nextIndex() // 2 | |
170 | * it.previousIndex() // 1 | |
171 | * | |
172 | * if (e == 5) | |
173 | * { | |
174 | * it.remove() | |
175 | * map1 // ["element-1"=1,"element-3"=3] | |
176 | * } | |
177 | * | |
178 | * ITERATION N.2 | |
179 | * | |
180 | * it.pointer() // "element-3" | |
181 | * it.nextIndex() // 2 | |
182 | * it.previousIndex() // 1 | |
183 | * | |
184 | * e = it.previous(); | |
185 | * e // 3 | |
186 | * | |
187 | * it.pointer() // "element-1" | |
188 | * it.nextIndex() // 1 | |
189 | * it.previousIndex() // 0 | |
190 | * | |
191 | * if (e == 3) | |
192 | * { | |
193 | * //map1.put("element-4", 4); // ConcurrentModificationError: During the iteration, the map was changed directly (without use the iterator). | |
194 | * it.put("element-4", 4); | |
195 | * map1 // [1,4,3] | |
196 | * } | |
197 | * | |
198 | * ITERATION N.3 | |
199 | * | |
200 | * it.pointer() // "element-3" | |
201 | * it.nextIndex() // 2 | |
202 | * it.previousIndex() // 1 | |
203 | * | |
204 | * e = it.previous(); | |
205 | * e // 4 | |
206 | * | |
207 | * it.pointer() // "element-1" | |
208 | * it.nextIndex() // 1 | |
209 | * it.previousIndex() // 0 | |
210 | * | |
211 | * ITERATION N.4 | |
212 | * | |
213 | * it.pointer() // "element-1" | |
214 | * it.nextIndex() // 1 | |
215 | * it.previousIndex() // 0 | |
216 | * | |
217 | * e = it.previous(); | |
218 | * e // 1 | |
219 | * | |
220 | * it.pointer() // null | |
221 | * it.nextIndex() // 0 | |
222 | * it.previousIndex() // -1 | |
223 | * } | |
224 | * </listing> | |
225 | * | |
226 | * @author Flávio Silva | |
227 | */ | |
228 | public class ListMapIterator implements IListMapIterator | |
229 | { | |
230 | private var _allowModification: Boolean; | |
231 | private var _modCount: int; | |
232 | private var _pointer: int = -1; | |
233 | private var _removePointer: int; | |
234 | private var _source: IListMap; | |
235 | ||
236 | /** | |
237 | * Constructor, creates a new <code>ListMapIterator</code> object. | |
238 | * | |
239 | * @param source the source <code>ListMapIterator</code> to iterate over. | |
240 | * @param position indicates the first mapping that would be returned by an initial call to <code>next</code>. An initial call to <code>previous</code> would return the mapping with the specified position minus one. | |
241 | * @throws ArgumentError if the <code>source</code> argument is <code>null</code>. | |
242 | */ | |
243 | public function ListMapIterator(source:IListMap, position:int = 0) | |
244 | 1 | { |
245 | 1 | if (!source) throw new ArgumentError("The 'source' argument must not be 'null'."); |
246 | 1 | if (position < 0 || position > source.size()) throw new IndexOutOfBoundsError("The 'position' argument is out of bounds: " + position + " (min: 0, max: " + source.size() + ")"); |
247 | ||
248 | 1 | _source = source; |
249 | 1 | _modCount = _source.modCount; |
250 | 1 | _pointer += position; |
251 | 1 | } |
252 | ||
253 | /** | |
254 | * @inheritDoc | |
255 | */ | |
256 | public function hasNext(): Boolean | |
257 | { | |
258 | 1 | return _pointer < _source.size() - 1; |
259 | } | |
260 | ||
261 | /** | |
262 | * @inheritDoc | |
263 | */ | |
264 | public function hasPrevious(): Boolean | |
265 | { | |
266 | 1 | return _pointer >= 0; |
267 | } | |
268 | ||
269 | /** | |
270 | * Returns the next <code>value</code> in the iteration. | |
271 | * The <code>pointer</code> operation returns the <code>key</code> associated with the returned <code>value</code>. | |
272 | * | |
273 | * @throws org.as3collections.errors.NoSuchElementError if the iteration has no more mappings. | |
274 | * @throws org.as3collections.errors.ConcurrentModificationError if the map was changed directly (without using the iterator) during iteration. | |
275 | */ | |
276 | public function next(): * | |
277 | { | |
278 | 1 | if (!hasNext()) throw new NoSuchElementError("Iterator has no next mapping. Call hasNext() method before."); |
279 | ||
280 | 1 | checkConcurrentModificationError(); |
281 | 1 | _allowModification = true; |
282 | 1 | _pointer++; |
283 | 1 | _removePointer = _pointer; |
284 | 1 | return _source.getValueAt(_pointer); |
285 | } | |
286 | ||
287 | /** | |
288 | * @inheritDoc | |
289 | */ | |
290 | public function nextIndex(): int | |
291 | { | |
292 | 1 | return _pointer + 1; |
293 | } | |
294 | ||
295 | /** | |
296 | * Returns the internal pointer of the iteration. | |
297 | * <p>In this implementation the pointer is a <code>key</code>.</p> | |
298 | * | |
299 | * @return the internal pointer of the iteration. | |
300 | */ | |
301 | public function pointer(): * | |
302 | { | |
303 | 1 | if (_pointer < 0) return null; |
304 | 1 | return _source.getKeyAt(_pointer); |
305 | } | |
306 | ||
307 | /** | |
308 | * Returns the previous <code>value</code> in the iteration. | |
309 | * The <code>pointer</code> operation returns the <code>key</code> associated with the returned <code>value</code>. | |
310 | * | |
311 | * @throws org.as3collections.errors.NoSuchElementError if the iteration has no previous mappings. | |
312 | * @throws org.as3collections.errors.ConcurrentModificationError if the map was changed directly (without using the iterator) during iteration. | |
313 | */ | |
314 | public function previous(): * | |
315 | { | |
316 | 1 | if (!hasPrevious()) throw new NoSuchElementError("Iterator has no previous mapping. Call hasPrevious() method before."); |
317 | ||
318 | 1 | checkConcurrentModificationError(); |
319 | 1 | _allowModification = true; |
320 | 1 | _removePointer = _pointer; |
321 | 1 | return _source.getValueAt(_pointer--); |
322 | } | |
323 | ||
324 | /** | |
325 | * @inheritDoc | |
326 | */ | |
327 | public function previousIndex(): int | |
328 | { | |
329 | 1 | return _pointer; |
330 | } | |
331 | ||
332 | /** | |
333 | * Associates the specified value with the specified key in this map. | |
334 | * The mapping is inserted immediately before the next mapping that would be returned by <code>next</code>, if any, and after the next mapping that would be returned by <code>previous</code>, if any. | |
335 | * (If the map contains no mappings, the new mapping becomes the sole mapping on the map.) | |
336 | * The new mapping is inserted before the implicit cursor: a subsequent call to <code>next</code> would be unaffected, and a subsequent call to <code>previous</code> would return the new mapping. | |
337 | * (This call increases by one the value that would be returned by a call to <code>nextIndex</code> or <code>previousIndex</code>.) | |
338 | * | |
339 | * @param key key with which the specified value is to be associated. | |
340 | * @param value value to be associated with the specified key. | |
341 | * @throws org.as3collections.errors.ConcurrentModificationError if the map was changed directly (without using the iterator) during iteration. | |
342 | * @throws ArgumentError if the map already contains the specified key. | |
343 | */ | |
344 | public function put(key:*, value:*): void | |
345 | { | |
346 | 1 | checkConcurrentModificationError(); |
347 | ||
348 | 1 | _source.putAt(_pointer + 1, key, value); |
349 | ||
350 | 1 | if (_modCount != _source.modCount) _pointer++; |
351 | 1 | _modCount = _source.modCount; |
352 | 1 | _allowModification = false; |
353 | 1 | } |
354 | ||
355 | /** | |
356 | * Removes from the map the last mapping that was returned by <code>next</code> or <code>previous</code>. | |
357 | * This call can only be made once per call to <code>next</code> or <code>previous</code>. | |
358 | * It can be made only if <code>IListMapIterator.add</code> has not been called after the last call to <code>next</code> or <code>previous</code>. | |
359 | * | |
360 | * @throws org.as3coreaddendum.errors.UnsupportedOperationError if the <code>remove</code> operation is not supported by this iterator. | |
361 | * @throws org.as3coreaddendum.errors.IllegalStateError if the <code>next</code> method has not yet been called, or the <code>remove</code> method has already been called after the last call to the <code>next</code> method. | |
362 | * @throws org.as3collections.errors.ConcurrentModificationError if the map was changed directly (without using the iterator) during iteration. | |
363 | */ | |
364 | public function remove(): void | |
365 | { | |
366 | 1 | checkConcurrentModificationError(); |
367 | ||
368 | 1 | if (!_allowModification) throw new IllegalStateError("The next or previous method has not yet been called or the add or remove method has already been called after the last call to the next or previous method."); |
369 | ||
370 | 1 | _source.removeAt(_removePointer); |
371 | 1 | _modCount = _source.modCount; |
372 | 1 | _allowModification = false; |
373 | 1 | if (_removePointer == _pointer) _pointer--; |
374 | 1 | } |
375 | ||
376 | /** | |
377 | * @inheritDoc | |
378 | */ | |
379 | public function reset(): void | |
380 | { | |
381 | 1 | _pointer = -1; |
382 | 1 | } |
383 | ||
384 | /** | |
385 | * Replaces the last mapping returned by <code>next</code> or <code>previous</code> with the specified mapping. | |
386 | * This call can be made only if neither <code>IListMapIterator.remove</code> nor <code>IListMapIterator.add</code> have been called after the last call to <code>next</code> or <code>previous</code>. | |
387 | * | |
388 | * @param key key with which the specified value is to be associated. | |
389 | * @param value value to be associated with the specified key. | |
390 | * @throws org.as3coreaddendum.errors.UnsupportedOperationError if the <code>set</code> operation is not supported by this iterator. | |
391 | * @throws org.as3coreaddendum.errors.ClassCastError if the class of the specified key or value prevents it from being added to this map. | |
392 | * @throws org.as3coreaddendum.errors.IllegalStateError if neither <code>next</code> or <code>previous</code> have been called, or <code>remove</code> or <code>add</code> have been called after the last call to <code>next</code> or <code>previous</code>. | |
393 | * @throws ArgumentError if the map already contains the specified key and it is not the replaced key. | |
394 | */ | |
395 | public function set(key:*, value:*): void | |
396 | { | |
397 | 1 | checkConcurrentModificationError(); |
398 | ||
399 | 1 | if (!_allowModification) throw new IllegalStateError("The next or previous method has not yet been called or the add or remove method has already been called after the last call to the next or previous method."); |
400 | ||
401 | 1 | var setIndex:int = (_removePointer > _pointer) ? _pointer + 1 : _pointer; |
402 | ||
403 | 1 | _source.setKeyAt(setIndex, key); |
404 | 1 | _source.setValueAt(setIndex, value); |
405 | 1 | _modCount = _source.modCount; |
406 | 1 | } |
407 | ||
408 | /** | |
409 | * @private | |
410 | */ | |
411 | public function checkConcurrentModificationError(): void | |
412 | { | |
413 | 1 | if (_modCount != _source.modCount) throw new ConcurrentModificationError("During the iteration, the map was changed directly (without use the iterator)."); |
414 | 1 | } |
415 | ||
416 | } | |
417 | ||
418 | } |