001/* 002 * ModeShape (http://www.modeshape.org) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.modeshape.schematic.internal.document; 017 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.Date; 023import java.util.HashMap; 024import java.util.Iterator; 025import java.util.LinkedList; 026import java.util.List; 027import java.util.ListIterator; 028import java.util.Map; 029import java.util.Properties; 030import java.util.Set; 031import java.util.UUID; 032import java.util.regex.Pattern; 033import java.util.stream.Collectors; 034import org.modeshape.schematic.annotation.Immutable; 035import org.modeshape.schematic.document.Array; 036import org.modeshape.schematic.document.Binary; 037import org.modeshape.schematic.document.Bson; 038import org.modeshape.schematic.document.Code; 039import org.modeshape.schematic.document.CodeWithScope; 040import org.modeshape.schematic.document.Document; 041import org.modeshape.schematic.document.Json; 042import org.modeshape.schematic.document.MaxKey; 043import org.modeshape.schematic.document.MinKey; 044import org.modeshape.schematic.document.Null; 045import org.modeshape.schematic.document.ObjectId; 046import org.modeshape.schematic.document.Symbol; 047import org.modeshape.schematic.internal.schema.DocumentTransformer; 048 049/** 050 * A {@link Bson.Type#ARRAY ordered array of values} for use as a value within a 051 * {@link Document BSON Object}. Instances of this type are designed to be unmodifiable from a client's perspective, since clients 052 * always modify the instances using an editor. There are several <code>internal*</code> methods that do modify the contents, but 053 * these may not be used by client applications. 054 * <p> 055 * Since BSON and JSON documents can be simple arrays of values, this class implements the {@link Document} interface, where the 056 * object's names are expected to be string values of integer indexes. This class also implements {@link List} interface, but only 057 * supports the read methods. 058 * </p> 059 * 060 * @author Randall Hauch <rhauch@redhat.com> (C) 2011 Red Hat Inc. 061 */ 062public class BasicArray implements MutableArray { 063 064 private static final long serialVersionUID = 1L; 065 066 private final List<Object> values; 067 068 public BasicArray() { 069 this.values = new ArrayList<>(); 070 } 071 072 public BasicArray( int initialCapacity ) { 073 this.values = initialCapacity > 0 ? new ArrayList<>(initialCapacity) : new ArrayList<>(); 074 } 075 076 public BasicArray( List<Object> values ) { 077 this.values = values; 078 } 079 080 public BasicArray( Object... values ) { 081 this.values = new ArrayList<>(Arrays.asList(values)); 082 } 083 084 @Override 085 public Object get( String name ) { 086 int index = indexFrom(name); 087 return isValidIndex(index) ? values.get(index) : null; 088 } 089 090 @Override 091 public boolean containsField( String name ) { 092 int index = indexFrom(name); 093 return isValidIndex(index); 094 } 095 096 @Override 097 public boolean containsAll( Document document ) { 098 if (document instanceof Array) { 099 return containsAll((List<?>)document); 100 } 101 if (document != null) { 102 for (Field field : document.fields()) { 103 Object thisValue = get(field.getName()); 104 Object thatValue = field.getValue(); 105 if (!BsonUtils.valuesAreEqual(thisValue, thatValue)) { 106 return false; 107 } 108 } 109 } 110 return true; 111 } 112 113 @Override 114 public Set<String> keySet() { 115 return new IndexSequence(size()); 116 } 117 118 @Override 119 public Map<String, ?> toMap() { 120 Map<String, Object> result = new HashMap<>(); 121 int i = 0; 122 for (String index : keySet()) { 123 result.put(index, values.get(i++)); // we know that keySet().iterator() is ordered 124 } 125 return result; 126 } 127 128 @Override 129 public Iterable<Field> fields() { 130 return () -> { 131 final Iterator<String> indexIter = IndexSequence.infiniteSequence(); 132 final Iterator<Object> valueIter = values.iterator(); 133 return new Iterator<Field>() { 134 @Override 135 public boolean hasNext() { 136 return valueIter.hasNext(); 137 } 138 139 @Override 140 public Field next() { 141 return new ImmutableField(indexIter.next(), valueIter.next()); 142 } 143 144 @Override 145 public void remove() { 146 throw new UnsupportedOperationException(); 147 } 148 }; 149 }; 150 } 151 152 @Override 153 public int size() { 154 return values.size(); 155 } 156 157 @Override 158 public boolean contains( Object o ) { 159 return values.contains(o); 160 } 161 162 @Override 163 public boolean containsAll( Collection<?> c ) { 164 return values.containsAll(c); 165 } 166 167 @Override 168 public Object get( int index ) { 169 return values.get(index); 170 } 171 172 @Override 173 public int hashCode() { 174 return values.hashCode(); 175 } 176 177 @Override 178 public boolean equals( Object obj ) { 179 if (this == obj) { 180 return true; 181 } 182 if (obj instanceof Iterable) { 183 Iterable<?> that = (Iterable<?>)obj; 184 Iterator<?> thisIter = values.iterator(); 185 Iterator<?> thatIter = null; 186 if (obj instanceof List) { 187 List<?> thatList = (List<?>)that; 188 if (this.size() != thatList.size()) { 189 return false; 190 } 191 if (thatList instanceof BasicArray) { 192 thatIter = ((BasicArray)thatList).values.iterator(); 193 } else { 194 thatIter = that.iterator(); 195 } 196 } 197 assert thatIter != null; 198 while (thisIter.hasNext() && thatIter.hasNext()) { 199 Object thisValue = thisIter.next(); 200 Object thatValue = thatIter.next(); 201 if (!BsonUtils.valuesAreEqual(thisValue, thatValue)) { 202 return false; 203 } 204 } 205 return !thisIter.hasNext() && !thatIter.hasNext(); 206 } 207 if (obj.getClass().isArray()) { 208 if (this.size() != java.lang.reflect.Array.getLength(obj)) { 209 return false; 210 } 211 Iterator<?> thisIter = values.iterator(); 212 int index = 0; 213 while (thisIter.hasNext()) { 214 Object thisValue = thisIter.next(); 215 Object thatValue = java.lang.reflect.Array.get(obj, index++); 216 if (!BsonUtils.valuesAreEqual(thisValue, thatValue)) { 217 return false; 218 } 219 } 220 return true; 221 } 222 return false; 223 } 224 225 @Override 226 public String toString() { 227 return Json.write(this); 228 } 229 230 @Override 231 public int indexOf( Object o ) { 232 return values.indexOf(o); 233 } 234 235 @Override 236 public boolean isEmpty() { 237 return values.isEmpty(); 238 } 239 240 @Override 241 public int lastIndexOf( Object o ) { 242 return values.lastIndexOf(o); 243 } 244 245 @Override 246 public List<Object> subList( int fromIndex, 247 int toIndex ) { 248 return new BasicArray(values.subList(fromIndex, toIndex)); 249 } 250 251 @Override 252 public Object[] toArray() { 253 return values.toArray(); 254 } 255 256 @Override 257 public <T> T[] toArray( T[] a ) { 258 return values.toArray(a); 259 } 260 261 @Override 262 public Iterator<Object> iterator() { 263 final Iterator<Object> delegate = values.iterator(); 264 return new Iterator<Object>() { 265 @Override 266 public boolean hasNext() { 267 return delegate.hasNext(); 268 } 269 270 @Override 271 public Object next() { 272 return delegate.next(); 273 } 274 275 @Override 276 public void remove() { 277 throw new UnsupportedOperationException(); 278 } 279 }; 280 } 281 282 @Override 283 public Iterable<Entry> getEntries() { 284 return () -> new Iterator<Entry>() { 285 @SuppressWarnings( "synthetic-access" ) 286 private final Iterator<Object> valueIter = BasicArray.this.values.iterator(); 287 private int index = 0; 288 289 @Override 290 public boolean hasNext() { 291 return valueIter.hasNext(); 292 } 293 294 @Override 295 public Entry next() { 296 Object value = valueIter.next(); 297 return new BasicEntry(index++, value); 298 } 299 300 @Override 301 public void remove() { 302 throw new UnsupportedOperationException(); 303 } 304 }; 305 } 306 307 @Override 308 public Boolean getBoolean( String name ) { 309 Object value = get(name); 310 return (value instanceof Boolean) ? (Boolean)value : null; 311 } 312 313 @Override 314 public boolean getBoolean( String name, 315 boolean defaultValue ) { 316 Object value = get(name); 317 return (value instanceof Boolean) ? ((Boolean)value).booleanValue() : defaultValue; 318 } 319 320 @Override 321 public Integer getInteger( String name ) { 322 Object value = get(name); 323 return (value instanceof Integer) ? (Integer)value : null; 324 } 325 326 @Override 327 public int getInteger( String name, 328 int defaultValue ) { 329 Object value = get(name); 330 return (value instanceof Integer) ? ((Integer)value).intValue() : defaultValue; 331 } 332 333 @Override 334 public Long getLong( String name ) { 335 Object value = get(name); 336 if (value instanceof Long) return (Long)value; 337 if (value instanceof Integer) return new Long(((Integer)value).longValue()); 338 return null; 339 } 340 341 @Override 342 public long getLong( String name, 343 long defaultValue ) { 344 Object value = get(name); 345 if (value instanceof Long) return ((Long)value).longValue(); 346 if (value instanceof Integer) return ((Integer)value).longValue(); 347 return defaultValue; 348 } 349 350 @Override 351 public Double getDouble( String name ) { 352 Object value = get(name); 353 return (value instanceof Double) ? (Double)value : null; 354 } 355 356 @Override 357 public double getDouble( String name, 358 double defaultValue ) { 359 Object value = get(name); 360 return (value instanceof Double) ? ((Double)value).doubleValue() : defaultValue; 361 } 362 363 @Override 364 public Number getNumber( String name ) { 365 Object value = get(name); 366 return (value instanceof Number) ? (Number)value : null; 367 } 368 369 @Override 370 public Number getNumber( String name, 371 Number defaultValue ) { 372 Object value = get(name); 373 return (value instanceof Number) ? (Number)value : defaultValue; 374 } 375 376 @Override 377 public String getString( String name ) { 378 return getString(name, null); 379 } 380 381 @Override 382 public String getString( String name, 383 String defaultValue ) { 384 Object value = get(name); 385 if (value != null) { 386 if (value instanceof String) { 387 return (String)value; 388 } 389 if (value instanceof Symbol) { 390 return ((Symbol)value).getSymbol(); 391 } 392 } 393 return defaultValue; 394 } 395 396 @Override 397 public List<?> getArray( String name ) { 398 Object value = get(name); 399 return (value instanceof List) ? (List<?>)value : null; 400 } 401 402 @Override 403 public Document getDocument( String name ) { 404 Object value = get(name); 405 return (value instanceof Document) ? (Document)value : null; 406 } 407 408 @Override 409 public boolean isNull( String name ) { 410 return get(name) instanceof Null; 411 } 412 413 @Override 414 public boolean isNullOrMissing( String name ) { 415 return Null.matches(get(name)); 416 } 417 418 @Override 419 public MaxKey getMaxKey( String name ) { 420 Object value = get(name); 421 return (value instanceof MaxKey) ? (MaxKey)value : null; 422 } 423 424 @Override 425 public MinKey getMinKey( String name ) { 426 Object value = get(name); 427 return (value instanceof MinKey) ? (MinKey)value : null; 428 } 429 430 @Override 431 public Code getCode( String name ) { 432 Object value = get(name); 433 return (value instanceof Code) ? (Code)value : null; 434 } 435 436 @Override 437 public CodeWithScope getCodeWithScope( String name ) { 438 Object value = get(name); 439 return (value instanceof CodeWithScope) ? (CodeWithScope)value : null; 440 } 441 442 @Override 443 public ObjectId getObjectId( String name ) { 444 Object value = get(name); 445 return (value instanceof ObjectId) ? (ObjectId)value : null; 446 } 447 448 @Override 449 public Binary getBinary( String name ) { 450 Object value = get(name); 451 return (value instanceof Binary) ? (Binary)value : null; 452 } 453 454 @Override 455 public Date getDate(String name) { 456 Object value = get(name); 457 return (value instanceof Date) ? (Date) value : null; 458 } 459 460 @Override 461 public Symbol getSymbol( String name ) { 462 Object value = get(name); 463 if (value != null) { 464 if (value instanceof Symbol) { 465 return (Symbol)value; 466 } 467 if (value instanceof String) { 468 return new Symbol((String)value); 469 } 470 } 471 return null; 472 } 473 474 @Override 475 public Pattern getPattern( String name ) { 476 Object value = get(name); 477 return (value instanceof Pattern) ? (Pattern)value : null; 478 } 479 480 @Override 481 public UUID getUuid( String name ) { 482 return getUuid(name, null); 483 } 484 485 @Override 486 public UUID getUuid( String name, 487 UUID defaultValue ) { 488 Object value = get(name); 489 if (value != null) { 490 if (value instanceof UUID) { 491 return (UUID)value; 492 } 493 if (value instanceof String) { 494 try { 495 return UUID.fromString((String)value); 496 } catch (IllegalArgumentException e) { 497 // do nothing ... 498 } 499 } 500 } 501 return defaultValue; 502 } 503 504 @Override 505 public int getType( String name ) { 506 return Bson.getTypeForValue(get(name)); 507 } 508 509 @Override 510 public ListIterator<Object> listIterator() { 511 return new UnmodifiableListIterator(values.listIterator()); 512 } 513 514 @Override 515 public ListIterator<Object> listIterator( int index ) { 516 return new UnmodifiableListIterator(values.listIterator(index)); 517 } 518 519 protected static final class UnmodifiableListIterator implements ListIterator<Object> { 520 private final ListIterator<Object> delegate; 521 522 protected UnmodifiableListIterator( ListIterator<Object> delegate ) { 523 this.delegate = delegate; 524 } 525 526 @Override 527 public boolean hasNext() { 528 return delegate.hasNext(); 529 } 530 531 @Override 532 public Object next() { 533 return delegate.next(); 534 } 535 536 @Override 537 public void remove() { 538 throw new UnsupportedOperationException(); 539 } 540 541 @Override 542 public void add( Object e ) { 543 throw new UnsupportedOperationException(); 544 } 545 546 @Override 547 public boolean hasPrevious() { 548 return delegate.hasPrevious(); 549 } 550 551 @Override 552 public int nextIndex() { 553 return delegate.nextIndex(); 554 } 555 556 @Override 557 public Object previous() { 558 return delegate.previous(); 559 } 560 561 @Override 562 public int previousIndex() { 563 return delegate.previousIndex(); 564 } 565 566 @Override 567 public void set( Object e ) { 568 throw new UnsupportedOperationException(); 569 } 570 } 571 572 @Override 573 public void add( int index, 574 Object element ) { 575 throw new UnsupportedOperationException(); 576 } 577 578 @Override 579 public boolean add( Object e ) { 580 throw new UnsupportedOperationException(); 581 } 582 583 @Override 584 public boolean addAll( Collection<?> c ) { 585 throw new UnsupportedOperationException(); 586 } 587 588 @Override 589 public boolean addAll( int index, 590 Collection<?> c ) { 591 throw new UnsupportedOperationException(); 592 } 593 594 @Override 595 public void clear() { 596 throw new UnsupportedOperationException(); 597 } 598 599 @Override 600 public Object remove( int index ) { 601 throw new UnsupportedOperationException(); 602 } 603 604 @Override 605 public boolean remove( Object o ) { 606 throw new UnsupportedOperationException(); 607 } 608 609 @Override 610 public boolean removeAll( Collection<?> c ) { 611 throw new UnsupportedOperationException(); 612 } 613 614 @Override 615 public boolean retainAll( Collection<?> c ) { 616 throw new UnsupportedOperationException(); 617 } 618 619 @Override 620 public Object set( int index, 621 Object element ) { 622 throw new UnsupportedOperationException(); 623 } 624 625 protected final int indexFrom( String name ) { 626 return Integer.parseInt(name); 627 } 628 629 protected final boolean isValidIndex( int index ) { 630 return index >= 0 && index < size(); 631 } 632 633 // --------------------------------------------------------------------------------------------------------- 634 // Mutation methods, for use only by the editor framework 635 // --------------------------------------------------------------------------------------------------------- 636 637 protected Object unwrap( Object value ) { 638 if (value instanceof DocumentEditor) { 639 return unwrap(((DocumentEditor)value).unwrap()); 640 } 641 if (value instanceof ArrayEditor) { 642 return unwrap(((ArrayEditor)value).unwrap()); 643 } 644 return value; 645 } 646 647 @Override 648 public boolean addValueIfAbsent( Object value ) { 649 value = unwrap(value); 650 return !this.values.contains(value) ? this.values.add(value) : false; 651 } 652 653 @Override 654 public int addValue( Object value ) { 655 value = unwrap(value); 656 int index = this.values.size(); 657 this.values.add(index, value); 658 return index; 659 } 660 661 @Override 662 public void addValue( int index, 663 Object value ) { 664 value = unwrap(value); 665 this.values.add(index, value); 666 } 667 668 @Override 669 public Object setValue( int index, 670 Object value ) { 671 value = unwrap(value); 672 return this.values.set(index, value); 673 } 674 675 @Override 676 public boolean removeValue( Object value ) { 677 value = unwrap(value); 678 return this.values.remove(value); 679 } 680 681 @Override 682 public Object removeValue( int index ) { 683 return values.remove(index); 684 } 685 686 @Override 687 public boolean addAllValues( Collection<?> values ) { 688 if (values == null || values.isEmpty()) return false; 689 this.values.addAll(values.stream().map(this::unwrap).collect(Collectors.toList())); 690 return true; 691 } 692 693 @Override 694 public boolean addAllValues( int index, 695 Collection<?> values ) { 696 if (values == null || values.isEmpty()) return false; 697 for (Object value : values) { 698 this.values.add(index, unwrap(value)); 699 } 700 return true; 701 } 702 703 @Override 704 public List<Entry> removeAllValues( Collection<?> valuesToBeRemoved ) { 705 return removeValues(valuesToBeRemoved, true); 706 } 707 708 @Override 709 public List<Entry> retainAllValues( Collection<?> valuesToBeRetained ) { 710 return removeValues(valuesToBeRetained, false); 711 } 712 713 /** 714 * Remove some of the values in this array. 715 * 716 * @param values the values to be compared to this array's values 717 * @param ifMatch true if this method should retain all values that match the supplied values, or false if this method should 718 * remove all values that match the supplied values 719 * @return the entries that were removed; never null 720 */ 721 private List<Entry> removeValues( Collection<?> values, 722 boolean ifMatch ) { 723 LinkedList<Entry> results = null; 724 725 // Record the list of entries that are removed, but start at the end of the values (so the indexes are correct) 726 ListIterator<?> iter = this.values.listIterator(size()); 727 while (iter.hasPrevious()) { 728 int index = iter.previousIndex(); 729 Object value = iter.previous(); 730 if (ifMatch == values.contains(value)) { 731 iter.remove(); 732 if (results == null) { 733 results = new LinkedList<>(); 734 } 735 results.addFirst(new BasicEntry(index, value)); 736 } 737 } 738 739 return results != null ? results : Collections.<Entry>emptyList(); 740 } 741 742 @Immutable 743 public static final class BasicEntry implements Entry { 744 private final int index; 745 private final Object value; 746 747 public BasicEntry( int index, 748 Object value ) { 749 this.index = index; 750 this.value = value; 751 } 752 753 @Override 754 public int getIndex() { 755 return index; 756 } 757 758 @Override 759 public Object getValue() { 760 return value; 761 } 762 763 @Override 764 public int compareTo( Entry o ) { 765 return o == this ? 0 : o == null ? 1 : o.getIndex() - this.getIndex(); 766 } 767 } 768 769 @Override 770 public Object remove( String name ) { 771 try { 772 int index = indexFrom(name); 773 return isValidIndex(index) ? values.remove(index) : null; 774 } catch (NumberFormatException e) { 775 // Must be a value ... 776 return removeValue(name); 777 } 778 } 779 780 @Override 781 public void removeAll() { 782 values.clear(); 783 } 784 785 @Override 786 public Object put( String name, 787 Object value ) { 788 return put(indexFrom(name), value); 789 } 790 791 protected final Object put( int index, 792 Object value ) { 793 final int size = size(); 794 if (index == size) { 795 values.add(unwrap(value)); 796 return value; 797 } 798 return values.set(index, unwrap(value)); // may throw IndexOutOfBoundsException 799 } 800 801 @Override 802 public void putAll( Document object ) { 803 if (object instanceof BasicArray) { 804 BasicArray that = (BasicArray)object; 805 addAll(that.values); 806 } 807 } 808 809 @Override 810 public void putAll( Map<? extends String, ?> map ) { 811 // Attempt to convert all of the keys to integers ... 812 List<IndexEntry> sortableEntries = new ArrayList<>(map.size()); 813 for (Map.Entry<? extends String, ?> entry : map.entrySet()) { 814 int index = indexFrom(entry.getKey()); 815 sortableEntries.add(new IndexEntry(index, entry.getValue())); 816 } 817 Collections.sort(sortableEntries); 818 819 // Now add them in increasing order ... 820 for (IndexEntry entry : sortableEntries) { 821 put(entry.index, unwrap(entry.value)); 822 } 823 } 824 825 @Override 826 public MutableArray clone() { 827 BasicArray clone = new BasicArray(); 828 for (Object value : this) { 829 value = unwrap(value); 830 if (value instanceof Array) { 831 value = ((Array)value).clone(); 832 } else if (value instanceof Document) { 833 value = ((Document)value).clone(); 834 }// every other kind of value is immutable 835 clone.addValue(value); 836 } 837 return clone; 838 } 839 840 @Override 841 public Array with( Map<String, Object> changedFields ) { 842 BasicArray clone = new BasicArray(); 843 for (Field field : this.fields()) { 844 String name = field.getName(); 845 Object newValue = unwrap(changedFields.get(name)); 846 if (newValue != null) { 847 clone.put(name, newValue); 848 } else { 849 Object oldValue = field.getValue(); 850 clone.put(name, oldValue); 851 } 852 } 853 return clone; 854 } 855 856 @Override 857 public Document with( String fieldName, 858 Object newValue ) { 859 newValue = unwrap(newValue); 860 BasicArray clone = new BasicArray(); 861 for (Field field : this.fields()) { 862 String name = field.getName(); 863 if (name.equals(fieldName)) { 864 clone.put(name, newValue); 865 } else { 866 Object oldValue = field.getValue(); 867 clone.put(name, oldValue); 868 } 869 } 870 return clone; 871 } 872 873 @Override 874 public Array with( ValueTransformer transformer ) { 875 boolean transformed = false; 876 BasicArray clone = new BasicArray(); 877 for (Field field : this.fields()) { 878 String name = field.getName(); 879 Object oldValue = field.getValue(); 880 Object newValue = null; 881 if (oldValue instanceof Document) { 882 newValue = ((Document)oldValue).with(transformer); 883 } else { 884 newValue = transformer.transform(name, oldValue); 885 } 886 if (newValue != oldValue) transformed = true; 887 clone.put(name, unwrap(newValue)); 888 } 889 return transformed ? clone : this; 890 } 891 892 @Override 893 public Array withVariablesReplaced( Properties properties ) { 894 return with(new DocumentTransformer.PropertiesTransformer(properties)); 895 } 896 897 @Override 898 public Array withVariablesReplacedWithSystemProperties() { 899 return with(new DocumentTransformer.SystemPropertiesTransformer()); 900 } 901 902 @Immutable 903 protected static final class IndexEntry implements Comparable<IndexEntry> { 904 protected final int index; 905 protected final Object value; 906 907 protected IndexEntry( int index, 908 Object value ) { 909 this.index = index; 910 this.value = value; 911 } 912 913 @Override 914 public int compareTo( IndexEntry that ) { 915 return this.index - that.index; 916 } 917 918 @Override 919 public int hashCode() { 920 return index; 921 } 922 923 @Override 924 public boolean equals( Object obj ) { 925 if (obj == this) return true; 926 if (obj instanceof IndexEntry) { 927 IndexEntry that = (IndexEntry)obj; 928 if (this.index != that.index) return false; 929 if (this.value == null) return that.value == null; 930 return this.value.equals(that.value); 931 } 932 return false; 933 } 934 935 @Override 936 public String toString() { 937 return "[" + index + ',' + value + ']'; 938 } 939 } 940 941}