1 | /* |
2 | Copyright - 2017 2023 - wwwouaiebe - Contact: https://www.ouaie.be/ |
3 | |
4 | This program is free software; |
5 | you can redistribute it and/or modify it under the terms of the |
6 | GNU General Public License as published by the Free Software Foundation; |
7 | either version 3 of the License, or any later version. |
8 | This program is distributed in the hope that it will be useful, |
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | GNU General Public License for more details. |
12 | You should have received a copy of the GNU General Public License |
13 | along with this program; if not, write to the Free Software |
14 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
15 | */ |
16 | /* |
17 | Changes: |
18 | - v4.0.0: |
19 | - created from v3.6.0 |
20 | Doc reviewed 202208 |
21 | */ |
22 | |
23 | import { ZERO, ONE, NEXT, PREVIOUS, TWO, NOT_FOUND } from '../main/Constants.js'; |
24 | import CollectionIterator from './CollectionIterator.js'; |
25 | |
26 | /* ------------------------------------------------------------------------------------------------------------------------- */ |
27 | /** |
28 | Class used to store objects in an iterable |
29 | */ |
30 | /* ------------------------------------------------------------------------------------------------------------------------- */ |
31 | |
32 | class Collection { |
33 | |
34 | /** |
35 | The array where objects are stored |
36 | @type {Array.<Object>} |
37 | */ |
38 | |
39 | #array; |
40 | |
41 | /** |
42 | The class name of objects stored in the collection |
43 | @type {String} |
44 | */ |
45 | |
46 | #objName; |
47 | |
48 | /** |
49 | The class definition of objects stored in the collection |
50 | @type {Class} |
51 | */ |
52 | |
53 | #classCollection; |
54 | |
55 | /** |
56 | Return the position of an object in the Collection |
57 | @param {Number} objId The objId of the object to locate |
58 | @return {Number} the position of the object in the Collection |
59 | */ |
60 | |
61 | #indexOfObjId ( objId ) { |
62 | return this.#array.findIndex ( |
63 | element => element.objId === objId |
64 | ); |
65 | } |
66 | |
67 | /** |
68 | Gives the previous or next object in the collection that fullfil a given condition |
69 | @param {Number} objId The objId of the object from witch the search start |
70 | @param {?function} condition A fonction used to compare the objects. If null, ( ) => true is used |
71 | @param {Number} direction The direction to follow. Must be NEXT or PREVIOUS |
72 | @return {?Object} An object or null if nothing found |
73 | */ |
74 | |
75 | #nextOrPrevious ( objId, condition, direction ) { |
76 | let index = this.#indexOfObjId ( objId ); |
77 | if ( NOT_FOUND === index ) { |
78 | throw new Error ( 'invalid objId for next or previous function' ); |
79 | } |
80 | if ( direction !== NEXT && direction !== PREVIOUS ) { |
81 | throw new Error ( 'invalid direction' ); |
82 | } |
83 | |
84 | let otherCondition = condition; |
85 | if ( ! otherCondition ) { |
86 | otherCondition = ( ) => true; |
87 | } |
88 | index += direction; |
89 | |
90 | while ( ( NOT_FOUND < index ) && ( index < this.#array.length ) && ! otherCondition ( this.#array [ index ] ) ) { |
91 | index += direction; |
92 | } |
93 | if ( NOT_FOUND === index || this.#array.length === index ) { |
94 | return null; |
95 | } |
96 | |
97 | return this.#array [ index ]; |
98 | } |
99 | |
100 | /** |
101 | The constructor |
102 | @param {class} classCollection The class of objects that have to be stored in the collection |
103 | */ |
104 | |
105 | constructor ( classCollection ) { |
106 | Object.freeze ( this ); |
107 | this.#array = []; |
108 | this.#classCollection = classCollection; |
109 | const tmpObject = new classCollection ( ); |
110 | if ( ( ! tmpObject.objType ) || ( ! tmpObject.objType.name ) ) { |
111 | throw new Error ( 'invalid object name for collection' ); |
112 | } |
113 | this.#objName = tmpObject.objType.name; |
114 | } |
115 | |
116 | /** |
117 | Add an object at the end of the collection |
118 | @param {TravelObject} object The object to add |
119 | */ |
120 | |
121 | add ( object ) { |
122 | if ( ( ! object.objType ) || ( ! object.objType.name ) || ( object.objType.name !== this.#objName ) ) { |
123 | throw new Error ( 'invalid object name for add function' ); |
124 | } |
125 | this.#array.push ( object ); |
126 | } |
127 | |
128 | /** |
129 | Search an object in the collection with the index |
130 | @param {Number} index The position of the desired object in the array |
131 | @return {?Object} The object at the position or null if not found |
132 | */ |
133 | |
134 | at ( index ) { |
135 | return ( index < this.#array.length && index > NOT_FOUND ) ? this.#array [ index ] : null; |
136 | } |
137 | |
138 | /** |
139 | Executes a function on each object of the Collection and returns the final result |
140 | @param {function} funct The function to execute |
141 | */ |
142 | |
143 | forEach ( funct ) { |
144 | let result = null; |
145 | const iterator = this.iterator; |
146 | while ( ! iterator.done ) { |
147 | result = funct ( iterator.value, result ); |
148 | } |
149 | return result; |
150 | } |
151 | |
152 | /** |
153 | Search an object in the Collection |
154 | @param {Number} objId The objId of the object to search |
155 | @return {TravelObject} the object with the given objId or null when the object is not found |
156 | */ |
157 | |
158 | getAt ( objId ) { |
159 | const index = this.#indexOfObjId ( objId ); |
160 | return NOT_FOUND === index ? null : this.#array [ index ]; |
161 | } |
162 | |
163 | /** |
164 | Move an object near another object in the Collection |
165 | @param {Number} objId The objId of the object to move |
166 | @param {Number} targetObjId The objId of the object near witch the object will be moved |
167 | @param {Boolean} moveBefore When true, the object is moved before the target, when false after the target |
168 | */ |
169 | |
170 | moveTo ( objId, targetObjId, moveBefore ) { |
171 | let oldPosition = this.#indexOfObjId ( objId ); |
172 | let newPosition = this.#indexOfObjId ( targetObjId ); |
173 | if ( NOT_FOUND === oldPosition || NOT_FOUND === newPosition ) { |
174 | throw new Error ( 'invalid objId for function myMoveTo' ); |
175 | } |
176 | if ( ! moveBefore ) { |
177 | newPosition ++; |
178 | } |
179 | this.#array.splice ( newPosition, ZERO, this.#array [ oldPosition ] ); |
180 | if ( newPosition < oldPosition ) { |
181 | oldPosition ++; |
182 | } |
183 | this.#array.splice ( oldPosition, ONE ); |
184 | } |
185 | |
186 | /** |
187 | gives the next object in the collection that fullfil a given condition |
188 | @param {Number} objId The objId of the object from witch the search start |
189 | @param {?function} condition A fonction used to compare the objects. If null, ( ) => true is used |
190 | @return {?Object} An object or null if nothing found |
191 | */ |
192 | |
193 | next ( objId, condition ) { return this.#nextOrPrevious ( objId, condition, NEXT ); } |
194 | |
195 | /** |
196 | gives the previous object in the collection that fullfil a given condition |
197 | @param {Number} objId The objId of the object from witch the search start |
198 | @param {?function} condition A fonction used to compare the objects. If null, ( ) => true is used |
199 | @return {?Object} An object or null if nothing found |
200 | */ |
201 | |
202 | previous ( objId, condition ) { return this.#nextOrPrevious ( objId, condition, PREVIOUS ); } |
203 | |
204 | /** |
205 | Remove an object from the Collection |
206 | @param {Number} objId The objId of the object to remove |
207 | */ |
208 | |
209 | remove ( objId ) { |
210 | const index = this.#indexOfObjId ( objId ); |
211 | if ( NOT_FOUND === index ) { |
212 | throw new Error ( 'invalid objId for remove function' ); |
213 | } |
214 | this.#array.splice ( index, ONE ); |
215 | } |
216 | |
217 | /** |
218 | Remove all objects from the Collection |
219 | @param {?boolean} exceptFirstLast When true, first and last objects are not removed |
220 | */ |
221 | |
222 | removeAll ( exceptFirstLast ) { |
223 | if ( exceptFirstLast ) { |
224 | this.#array.splice ( ONE, this.#array.length - TWO ); |
225 | } |
226 | else { |
227 | this.#array.length = ZERO; |
228 | } |
229 | } |
230 | |
231 | /** |
232 | Replace an object in the Collection with another object |
233 | @param {Number} oldObjId the objId of the object to replace |
234 | @param {TravelObject} newObject The new object |
235 | */ |
236 | |
237 | replace ( oldObjId, newObject ) { |
238 | const index = this.#indexOfObjId ( oldObjId ); |
239 | if ( NOT_FOUND === index ) { |
240 | throw new Error ( 'invalid objId for replace function' ); |
241 | } |
242 | if ( ( ! newObject.objType ) || ( ! newObject.objType.name ) || ( newObject.objType.name !== this.#objName ) ) { |
243 | throw new Error ( 'invalid object name for replace function' ); |
244 | } |
245 | this.#array [ index ] = newObject; |
246 | } |
247 | |
248 | /** |
249 | Reverse the objects in the collection |
250 | */ |
251 | |
252 | reverse ( ) { this.#array.reverse ( ); } |
253 | |
254 | /** |
255 | Sort the collection, using a function |
256 | @param {function} compareFunction The function to use to compare objects in the Collection |
257 | */ |
258 | |
259 | sort ( compareFunction ) { this.#array.sort ( compareFunction ); } |
260 | |
261 | /** |
262 | Reverse an Object with the previous or next object in the Collection |
263 | @param {Number} objId The objId of the object to swap |
264 | @param {Boolean} swapUp When true the object is swapped with the previous one, |
265 | when false with the next one |
266 | */ |
267 | |
268 | swap ( objId, swapUp ) { |
269 | const index = this.#indexOfObjId ( objId ); |
270 | if ( |
271 | ( NOT_FOUND === index ) |
272 | || |
273 | ( ( ZERO === index ) && swapUp ) |
274 | || |
275 | ( ( this.#array.length - ONE === index ) && ( ! swapUp ) ) |
276 | ) { |
277 | throw new Error ( 'invalid objId for swap function' ); |
278 | } |
279 | const swap = swapUp ? PREVIOUS : NEXT; |
280 | const tmp = this.#array [ index ]; |
281 | this.#array [ index ] = this.#array [ index + swap ]; |
282 | this.#array [ index + swap ] = tmp; |
283 | } |
284 | |
285 | /** |
286 | The first object of the Collection |
287 | @type {TravelObject} |
288 | */ |
289 | |
290 | get first ( ) { return this.#array [ ZERO ]; } |
291 | |
292 | /** |
293 | An iterator on the Collection. See CollectionIterator |
294 | @type {CollectionIterator} |
295 | |
296 | */ |
297 | |
298 | get iterator ( ) { |
299 | return new CollectionIterator ( this ); |
300 | } |
301 | |
302 | /** |
303 | The last object of the Collection |
304 | @type {TravelObject} |
305 | */ |
306 | |
307 | get last ( ) { return this.#array [ this.#array.length - ONE ]; } |
308 | |
309 | /** |
310 | The length of the Collection |
311 | @type {Number} |
312 | */ |
313 | |
314 | get length ( ) { return this.#array.length; } |
315 | |
316 | /** |
317 | an Array with the objects in the collection |
318 | @type {Array} |
319 | */ |
320 | |
321 | get jsonObject ( ) { |
322 | const array = [ ]; |
323 | const iterator = this.iterator; |
324 | while ( ! iterator.done ) { |
325 | array.push ( iterator.value.jsonObject ); |
326 | } |
327 | |
328 | return array; |
329 | } |
330 | |
331 | set jsonObject ( something ) { |
332 | this.#array.length = ZERO; |
333 | |
334 | if ( ! Array.isArray ( something ) ) { |
335 | return; |
336 | } |
337 | |
338 | something.forEach ( |
339 | arrayObject => { |
340 | const newObject = new this.#classCollection ( ); |
341 | newObject.jsonObject = arrayObject; |
342 | this.add ( newObject ); |
343 | } |
344 | ); |
345 | } |
346 | } |
347 | |
348 | export default Collection; |
349 | |
350 | /* --- End of file --------------------------------------------------------------------------------------------------------- */ |
351 |