diff options
Diffstat (limited to '')
-rw-r--r-- | OpenSim/Region/ScriptEngine/XMREngine/XMRArray.cs | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMRArray.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMRArray.cs new file mode 100644 index 0000000..36d95d3 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMRArray.cs | |||
@@ -0,0 +1,534 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.IO; | ||
31 | using System.Text; | ||
32 | |||
33 | using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; | ||
34 | using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; | ||
35 | using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; | ||
36 | using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; | ||
37 | using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; | ||
38 | using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; | ||
39 | using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; | ||
40 | |||
41 | // This class exists in the main app domain | ||
42 | // | ||
43 | namespace OpenSim.Region.ScriptEngine.XMREngine | ||
44 | { | ||
45 | /** | ||
46 | * @brief Array objects. | ||
47 | */ | ||
48 | public class XMR_Array { | ||
49 | private const int EMPTYHEAP = 64; | ||
50 | private const int ENTRYHEAP = 24; | ||
51 | |||
52 | private bool enumrValid; // true: enumr set to return array[arrayValid] | ||
53 | // false: array[0..arrayValid-1] is all there is | ||
54 | private SortedDictionary<object, object> dnary; | ||
55 | private SortedDictionary<object, object>.Enumerator enumr; | ||
56 | // enumerator used to fill 'array' past arrayValid to end of dictionary | ||
57 | private int arrayValid; // number of elements in 'array' that have been filled in | ||
58 | private KeyValuePair<object, object>[] array; // list of kvp's that have been returned by ForEach() since last modification | ||
59 | private XMRInstAbstract inst; // script instance debited with heap use | ||
60 | private int heapUse; // current heap use debit amount | ||
61 | |||
62 | public static TokenTypeSDTypeDelegate countDelegate = new TokenTypeSDTypeDelegate (new TokenTypeInt (null), new TokenType[0]); | ||
63 | public static TokenTypeSDTypeDelegate clearDelegate = new TokenTypeSDTypeDelegate (new TokenTypeVoid (null), new TokenType[0]); | ||
64 | public static TokenTypeSDTypeDelegate indexDelegate = new TokenTypeSDTypeDelegate (new TokenTypeObject (null), new TokenType[] { new TokenTypeInt (null) }); | ||
65 | public static TokenTypeSDTypeDelegate valueDelegate = new TokenTypeSDTypeDelegate (new TokenTypeObject (null), new TokenType[] { new TokenTypeInt (null) }); | ||
66 | |||
67 | public XMR_Array (XMRInstAbstract inst) | ||
68 | { | ||
69 | this.inst = inst; | ||
70 | dnary = new SortedDictionary<object, object> (XMRArrayKeyComparer.singleton); | ||
71 | heapUse = inst.UpdateHeapUse (0, EMPTYHEAP); | ||
72 | } | ||
73 | |||
74 | ~XMR_Array () | ||
75 | { | ||
76 | heapUse = inst.UpdateHeapUse (heapUse, 0); | ||
77 | } | ||
78 | |||
79 | public static TokenType GetRValType (TokenName name) | ||
80 | { | ||
81 | if (name.val == "count") return new TokenTypeInt (name); | ||
82 | if (name.val == "clear") return clearDelegate; | ||
83 | if (name.val == "index") return indexDelegate; | ||
84 | if (name.val == "value") return valueDelegate; | ||
85 | return new TokenTypeVoid (name); | ||
86 | } | ||
87 | |||
88 | /** | ||
89 | * @brief Handle 'array[index]' syntax to get or set an element of the dictionary. | ||
90 | * Get returns null if element not defined, script sees type 'undef'. | ||
91 | * Setting an element to null removes it. | ||
92 | */ | ||
93 | public object GetByKey(object key) | ||
94 | { | ||
95 | object val; | ||
96 | key = FixKey (key); | ||
97 | if (!dnary.TryGetValue (key, out val)) val = null; | ||
98 | return val; | ||
99 | } | ||
100 | |||
101 | public void SetByKey(object key, object value) | ||
102 | { | ||
103 | key = FixKey (key); | ||
104 | |||
105 | /* | ||
106 | * Update heap use throwing an exception on failure | ||
107 | * before making any changes to the array. | ||
108 | */ | ||
109 | int keysize = HeapTrackerObject.Size (key); | ||
110 | int newheapuse = heapUse; | ||
111 | object oldval; | ||
112 | if (dnary.TryGetValue (key, out oldval)) { | ||
113 | newheapuse -= keysize + HeapTrackerObject.Size (oldval); | ||
114 | } | ||
115 | if (value != null) { | ||
116 | newheapuse += keysize + HeapTrackerObject.Size (value); | ||
117 | } | ||
118 | heapUse = inst.UpdateHeapUse (heapUse, newheapuse); | ||
119 | |||
120 | /* | ||
121 | * Save new value in array, replacing one of same key if there. | ||
122 | * null means remove the value, ie, script did array[key] = undef. | ||
123 | */ | ||
124 | if (value != null) { | ||
125 | dnary[key] = value; | ||
126 | } else { | ||
127 | dnary.Remove (key); | ||
128 | |||
129 | /* | ||
130 | * Shrink the enumeration array, but always leave at least one element. | ||
131 | */ | ||
132 | if ((array != null) && (dnary.Count < array.Length / 2)) { | ||
133 | Array.Resize<KeyValuePair<object, object>> (ref array, array.Length / 2); | ||
134 | } | ||
135 | } | ||
136 | |||
137 | /* | ||
138 | * The enumeration array is invalid because the dictionary has been modified. | ||
139 | * Next time a ForEach() call happens, it will repopulate 'array' as elements are retrieved. | ||
140 | */ | ||
141 | arrayValid = 0; | ||
142 | } | ||
143 | |||
144 | /** | ||
145 | * @brief Converts an 'object' type to array, key, list, string, but disallows null, | ||
146 | * as our language doesn't allow types other than 'object' to be null. | ||
147 | * Value types (float, rotation, etc) don't need explicit check for null as | ||
148 | * the C# runtime can't convert a null to a value type, and throws an exception. | ||
149 | * But for any reference type (array, key, etc) we must manually check for null. | ||
150 | */ | ||
151 | public static XMR_Array Obj2Array (object obj) | ||
152 | { | ||
153 | if (obj == null) throw new NullReferenceException (); | ||
154 | return (XMR_Array)obj; | ||
155 | } | ||
156 | public static LSL_Key Obj2Key (object obj) | ||
157 | { | ||
158 | if (obj == null) throw new NullReferenceException (); | ||
159 | return (LSL_Key)obj; | ||
160 | } | ||
161 | public static LSL_List Obj2List (object obj) | ||
162 | { | ||
163 | if (obj == null) throw new NullReferenceException (); | ||
164 | return (LSL_List)obj; | ||
165 | } | ||
166 | public static LSL_String Obj2String (object obj) | ||
167 | { | ||
168 | if (obj == null) throw new NullReferenceException (); | ||
169 | return obj.ToString (); | ||
170 | } | ||
171 | |||
172 | /** | ||
173 | * @brief remove all elements from the array. | ||
174 | * sets everything to its 'just constructed' state. | ||
175 | */ | ||
176 | public void __pub_clear () | ||
177 | { | ||
178 | heapUse = inst.UpdateHeapUse (heapUse, EMPTYHEAP); | ||
179 | dnary.Clear (); | ||
180 | enumrValid = false; | ||
181 | arrayValid = 0; | ||
182 | array = null; | ||
183 | } | ||
184 | |||
185 | /** | ||
186 | * @brief return number of elements in the array. | ||
187 | */ | ||
188 | public int __pub_count () | ||
189 | { | ||
190 | return dnary.Count; | ||
191 | } | ||
192 | |||
193 | /** | ||
194 | * @brief Retrieve index (key) of an arbitrary element. | ||
195 | * @param number = number of the element (0 based) | ||
196 | * @returns null: array doesn't have that many elements | ||
197 | * else: index (key) for that element | ||
198 | */ | ||
199 | public object __pub_index (int number) | ||
200 | { | ||
201 | return ForEach (number) ? UnfixKey (array[number].Key) : null; | ||
202 | } | ||
203 | |||
204 | /** | ||
205 | * @brief Retrieve value of an arbitrary element. | ||
206 | * @param number = number of the element (0 based) | ||
207 | * @returns null: array doesn't have that many elements | ||
208 | * else: value for that element | ||
209 | */ | ||
210 | public object __pub_value (int number) | ||
211 | { | ||
212 | return ForEach (number) ? array[number].Value : null; | ||
213 | } | ||
214 | |||
215 | /** | ||
216 | * @brief Called in each iteration of a 'foreach' statement. | ||
217 | * @param number = index of element to retrieve (0 = first one) | ||
218 | * @returns false: element does not exist | ||
219 | * true: element exists | ||
220 | */ | ||
221 | private bool ForEach (int number) | ||
222 | { | ||
223 | /* | ||
224 | * If we don't have any array, we can't have ever done | ||
225 | * any calls here before, so allocate an array big enough | ||
226 | * and set everything else to the beginning. | ||
227 | */ | ||
228 | if (array == null) { | ||
229 | array = new KeyValuePair<object, object>[dnary.Count]; | ||
230 | arrayValid = 0; | ||
231 | } | ||
232 | |||
233 | /* | ||
234 | * If dictionary modified since last enumeration, get a new enumerator. | ||
235 | */ | ||
236 | if (arrayValid == 0) { | ||
237 | enumr = dnary.GetEnumerator (); | ||
238 | enumrValid = true; | ||
239 | } | ||
240 | |||
241 | /* | ||
242 | * Make sure we have filled the array up enough for requested element. | ||
243 | */ | ||
244 | while ((arrayValid <= number) && enumrValid && enumr.MoveNext ()) { | ||
245 | if (arrayValid >= array.Length) { | ||
246 | Array.Resize<KeyValuePair<object, object>> (ref array, dnary.Count); | ||
247 | } | ||
248 | array[arrayValid++] = enumr.Current; | ||
249 | } | ||
250 | |||
251 | /* | ||
252 | * If we don't have that many elements, return end-of-array status. | ||
253 | */ | ||
254 | return number < arrayValid; | ||
255 | } | ||
256 | |||
257 | /** | ||
258 | * @brief Transmit array out in such a way that it can be reconstructed, | ||
259 | * including any in-progress ForEach() enumerations. | ||
260 | */ | ||
261 | public delegate void SendArrayObjDelegate (object graph); | ||
262 | public void SendArrayObj (SendArrayObjDelegate sendObj) | ||
263 | { | ||
264 | /* | ||
265 | * Set the count then the elements themselves. | ||
266 | * UnfixKey() because sendObj doesn't handle XMRArrayListKeys. | ||
267 | */ | ||
268 | sendObj (dnary.Count); | ||
269 | foreach (KeyValuePair<object, object> kvp in dnary) { | ||
270 | sendObj (UnfixKey (kvp.Key)); | ||
271 | sendObj (kvp.Value); | ||
272 | } | ||
273 | } | ||
274 | |||
275 | /** | ||
276 | * @brief Receive array in. Any previous contents are erased. | ||
277 | * Set up such that any enumeration in progress will resume | ||
278 | * at the exact spot and in the exact same order as they | ||
279 | * were in on the sending side. | ||
280 | */ | ||
281 | public delegate object RecvArrayObjDelegate (); | ||
282 | public void RecvArrayObj (RecvArrayObjDelegate recvObj) | ||
283 | { | ||
284 | heapUse = inst.UpdateHeapUse (heapUse, EMPTYHEAP); | ||
285 | |||
286 | /* | ||
287 | * Cause any enumeration to refill the array from the sorted dictionary. | ||
288 | * Since it is a sorted dictionary, any enumerations will be in the same | ||
289 | * order as on the sending side. | ||
290 | */ | ||
291 | arrayValid = 0; | ||
292 | enumrValid = false; | ||
293 | |||
294 | /* | ||
295 | * Fill dictionary. | ||
296 | */ | ||
297 | dnary.Clear (); | ||
298 | int count = (int)recvObj (); | ||
299 | while (-- count >= 0) { | ||
300 | object key = FixKey (recvObj ()); | ||
301 | object val = recvObj (); | ||
302 | int htuse = HeapTrackerObject.Size (key) + HeapTrackerObject.Size (val); | ||
303 | heapUse = inst.UpdateHeapUse (heapUse, heapUse + htuse); | ||
304 | dnary.Add (key, val); | ||
305 | } | ||
306 | } | ||
307 | |||
308 | /** | ||
309 | * We want our index values to be of consistent type, otherwise we get things like (LSL_Integer)1 != (int)1. | ||
310 | * So strip off any LSL-ness from the types. | ||
311 | * We also deep-strip any given lists used as keys (multi-dimensional arrays). | ||
312 | */ | ||
313 | public static object FixKey (object key) | ||
314 | { | ||
315 | if (key is LSL_Integer) return (int)(LSL_Integer)key; | ||
316 | if (key is LSL_Float) return (double)(LSL_Float)key; | ||
317 | if (key is LSL_Key) return (string)(LSL_Key)key; | ||
318 | if (key is LSL_String) return (string)(LSL_String)key; | ||
319 | if (key is LSL_List) { | ||
320 | object[] data = ((LSL_List)key).Data; | ||
321 | if (data.Length == 1) return FixKey (data[0]); | ||
322 | return new XMRArrayListKey ((LSL_List)key); | ||
323 | } | ||
324 | return key; // int, double, string, LSL_Vector, LSL_Rotation, etc are ok as is | ||
325 | } | ||
326 | |||
327 | /** | ||
328 | * @brief When returning a key, such as for array.index(), we want to return the original | ||
329 | * LSL_List, not the sanitized one, as the script compiler expects an LSL_List. | ||
330 | * Any other sanitized types can remain as is (int, string, etc). | ||
331 | */ | ||
332 | private static object UnfixKey (object key) | ||
333 | { | ||
334 | if (key is XMRArrayListKey) key = ((XMRArrayListKey)key).GetOriginal (); | ||
335 | return key; | ||
336 | } | ||
337 | } | ||
338 | |||
339 | public class XMRArrayKeyComparer : IComparer<object> { | ||
340 | |||
341 | public static XMRArrayKeyComparer singleton = new XMRArrayKeyComparer (); | ||
342 | |||
343 | /** | ||
344 | * @brief Compare two keys | ||
345 | */ | ||
346 | public int Compare (object x, object y) // IComparer<object> | ||
347 | { | ||
348 | /* | ||
349 | * Use short type name (eg, String, Int32, XMRArrayListKey) as most significant part of key. | ||
350 | */ | ||
351 | string xtn = x.GetType ().Name; | ||
352 | string ytn = y.GetType ().Name; | ||
353 | int ctn = String.CompareOrdinal (xtn, ytn); | ||
354 | if (ctn != 0) return ctn; | ||
355 | |||
356 | ComparerDelegate cd; | ||
357 | if (!comparers.TryGetValue (xtn, out cd)) { | ||
358 | throw new Exception ("unsupported key type " + xtn); | ||
359 | } | ||
360 | return cd (x, y); | ||
361 | } | ||
362 | |||
363 | private delegate int ComparerDelegate (object a, object b); | ||
364 | |||
365 | private static Dictionary<string, ComparerDelegate> comparers = BuildComparers (); | ||
366 | |||
367 | private static Dictionary<string, ComparerDelegate> BuildComparers () | ||
368 | { | ||
369 | Dictionary<string, ComparerDelegate> cmps = new Dictionary<string, ComparerDelegate> (); | ||
370 | cmps.Add (typeof (double).Name, MyFloatComparer); | ||
371 | cmps.Add (typeof (int).Name, MyIntComparer); | ||
372 | cmps.Add (typeof (XMRArrayListKey).Name, MyListKeyComparer); | ||
373 | cmps.Add (typeof (LSL_Rotation).Name, MyRotationComparer); | ||
374 | cmps.Add (typeof (string).Name, MyStringComparer); | ||
375 | cmps.Add (typeof (LSL_Vector).Name, MyVectorComparer); | ||
376 | return cmps; | ||
377 | } | ||
378 | |||
379 | private static int MyFloatComparer (object a, object b) | ||
380 | { | ||
381 | double af = (double)a; | ||
382 | double bf = (double)b; | ||
383 | if (af < bf) return -1; | ||
384 | if (af > bf) return 1; | ||
385 | return 0; | ||
386 | } | ||
387 | private static int MyIntComparer (object a, object b) | ||
388 | { | ||
389 | return (int)a - (int)b; | ||
390 | } | ||
391 | private static int MyListKeyComparer (object a, object b) | ||
392 | { | ||
393 | XMRArrayListKey alk = (XMRArrayListKey)a; | ||
394 | XMRArrayListKey blk = (XMRArrayListKey)b; | ||
395 | return XMRArrayListKey.Compare (alk, blk); | ||
396 | } | ||
397 | private static int MyRotationComparer (object a, object b) | ||
398 | { | ||
399 | LSL_Rotation ar = (LSL_Rotation)a; | ||
400 | LSL_Rotation br = (LSL_Rotation)b; | ||
401 | if (ar.x < br.x) return -1; | ||
402 | if (ar.x > br.x) return 1; | ||
403 | if (ar.y < br.y) return -1; | ||
404 | if (ar.y > br.y) return 1; | ||
405 | if (ar.z < br.z) return -1; | ||
406 | if (ar.z > br.z) return 1; | ||
407 | if (ar.s < br.s) return -1; | ||
408 | if (ar.s > br.s) return 1; | ||
409 | return 0; | ||
410 | } | ||
411 | private static int MyStringComparer (object a, object b) | ||
412 | { | ||
413 | return String.CompareOrdinal ((string)a, (string)b); | ||
414 | } | ||
415 | private static int MyVectorComparer (object a, object b) | ||
416 | { | ||
417 | LSL_Vector av = (LSL_Vector)a; | ||
418 | LSL_Vector bv = (LSL_Vector)b; | ||
419 | if (av.x < bv.x) return -1; | ||
420 | if (av.x > bv.x) return 1; | ||
421 | if (av.y < bv.y) return -1; | ||
422 | if (av.y > bv.y) return 1; | ||
423 | if (av.z < bv.z) return -1; | ||
424 | if (av.z > bv.z) return 1; | ||
425 | return 0; | ||
426 | } | ||
427 | } | ||
428 | |||
429 | /** | ||
430 | * @brief Lists used as keys must be sanitized first. | ||
431 | * List gets converted to an object[] and each element is converted from LSL_ types to system types where possible. | ||
432 | * And we also need an equality operator that compares the values of all elements of the list, not just the lengths. | ||
433 | * Note that just like LSL_Lists, we consider these objects to be immutable, so they can be directly used as keys in | ||
434 | * the dictionary as they don't ever change. | ||
435 | */ | ||
436 | public class XMRArrayListKey { | ||
437 | private LSL_List original; | ||
438 | private object[] cleaned; | ||
439 | private int length; | ||
440 | private int hashCode; | ||
441 | |||
442 | /** | ||
443 | * @brief Construct a sanitized object[] from a list. | ||
444 | * Also save the original list in case we need it later. | ||
445 | */ | ||
446 | public XMRArrayListKey (LSL_List key) | ||
447 | { | ||
448 | original = key; | ||
449 | object[] given = key.Data; | ||
450 | int len = given.Length; | ||
451 | length = len; | ||
452 | cleaned = new object[len]; | ||
453 | int hc = len; | ||
454 | for (int i = 0; i < len; i ++) { | ||
455 | object v = XMR_Array.FixKey (given[i]); | ||
456 | hc += hc + ((hc < 0) ? 1 : 0); | ||
457 | hc ^= v.GetHashCode (); | ||
458 | cleaned[i] = v; | ||
459 | } | ||
460 | hashCode = hc; | ||
461 | } | ||
462 | |||
463 | /** | ||
464 | * @brief Get heap tracking size. | ||
465 | */ | ||
466 | public int Size { | ||
467 | get { | ||
468 | return original.Size; | ||
469 | } | ||
470 | } | ||
471 | |||
472 | /** | ||
473 | * @brief See if the given object is an XMRArrayListKey and every value is equal to our own. | ||
474 | */ | ||
475 | public override bool Equals (object o) | ||
476 | { | ||
477 | if (!(o is XMRArrayListKey)) return false; | ||
478 | XMRArrayListKey a = (XMRArrayListKey)o; | ||
479 | int len = a.length; | ||
480 | if (len != length) return false; | ||
481 | if (a.hashCode != hashCode) return false; | ||
482 | for (int i = 0; i < len; i ++) { | ||
483 | if (!cleaned[i].Equals (a.cleaned[i])) return false; | ||
484 | } | ||
485 | return true; | ||
486 | } | ||
487 | |||
488 | /** | ||
489 | * @brief Get an hash code. | ||
490 | */ | ||
491 | public override int GetHashCode () | ||
492 | { | ||
493 | return hashCode; | ||
494 | } | ||
495 | |||
496 | /** | ||
497 | * @brief Compare for key sorting. | ||
498 | */ | ||
499 | public static int Compare (XMRArrayListKey x, XMRArrayListKey y) | ||
500 | { | ||
501 | int j = x.length - y.length; | ||
502 | if (j == 0) { | ||
503 | for (int i = 0; i < x.length; i ++) { | ||
504 | object xo = x.cleaned[i]; | ||
505 | object yo = y.cleaned[i]; | ||
506 | j = XMRArrayKeyComparer.singleton.Compare (xo, yo); | ||
507 | if (j != 0) break; | ||
508 | } | ||
509 | } | ||
510 | return j; | ||
511 | } | ||
512 | |||
513 | /** | ||
514 | * @brief Get the original LSL_List we were built from. | ||
515 | */ | ||
516 | public LSL_List GetOriginal () | ||
517 | { | ||
518 | return original; | ||
519 | } | ||
520 | |||
521 | /** | ||
522 | * @brief Debugging | ||
523 | */ | ||
524 | public override string ToString () | ||
525 | { | ||
526 | StringBuilder sb = new StringBuilder (); | ||
527 | for (int i = 0; i < length; i ++) { | ||
528 | if (i > 0) sb.Append (','); | ||
529 | sb.Append (cleaned[i].ToString ()); | ||
530 | } | ||
531 | return sb.ToString (); | ||
532 | } | ||
533 | } | ||
534 | } | ||