ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/jsr166/jsr166/src/test/tck/ConcurrentHashMapTest.java
Revision: 1.39
Committed: Tue Sep 24 17:26:27 2013 UTC (10 years, 7 months ago) by jsr166
Branch: MAIN
Changes since 1.38: +11 -8 lines
Log Message:
improve runtimes of Comparable testing methods

File Contents

# Content
1 /*
2 * Written by Doug Lea with assistance from members of JCP JSR-166
3 * Expert Group and released to the public domain, as explained at
4 * http://creativecommons.org/publicdomain/zero/1.0/
5 * Other contributors include Andrew Wright, Jeffrey Hayes,
6 * Pat Fisher, Mike Judd.
7 */
8
9 import junit.framework.*;
10 import java.util.*;
11 import java.util.concurrent.ConcurrentHashMap;
12
13 public class ConcurrentHashMapTest extends JSR166TestCase {
14 public static void main(String[] args) {
15 junit.textui.TestRunner.run(suite());
16 }
17 public static Test suite() {
18 return new TestSuite(ConcurrentHashMapTest.class);
19 }
20
21 /**
22 * Returns a new map from Integers 1-5 to Strings "A"-"E".
23 */
24 private static ConcurrentHashMap<Integer, String> map5() {
25 ConcurrentHashMap map = new ConcurrentHashMap<Integer, String>(5);
26 assertTrue(map.isEmpty());
27 map.put(one, "A");
28 map.put(two, "B");
29 map.put(three, "C");
30 map.put(four, "D");
31 map.put(five, "E");
32 assertFalse(map.isEmpty());
33 assertEquals(5, map.size());
34 return map;
35 }
36
37 /** Re-implement Integer.compare for old java versions */
38 static int compare(int x, int y) { return x < y ? -1 : x > y ? 1 : 0; }
39
40 // classes for testing Comparable fallbacks
41 static class BI implements Comparable<BI> {
42 private final int value;
43 BI(int value) { this.value = value; }
44 public int compareTo(BI other) {
45 return compare(value, other.value);
46 }
47 public boolean equals(Object x) {
48 return (x instanceof BI) && ((BI)x).value == value;
49 }
50 public int hashCode() { return 42; }
51 }
52 static class CI extends BI { CI(int value) { super(value); } }
53 static class DI extends BI { DI(int value) { super(value); } }
54
55 static class BS implements Comparable<BS> {
56 private final String value;
57 BS(String value) { this.value = value; }
58 public int compareTo(BS other) {
59 return value.compareTo(other.value);
60 }
61 public boolean equals(Object x) {
62 return (x instanceof BS) && value.equals(((BS)x).value);
63 }
64 public int hashCode() { return 42; }
65 }
66
67 static class LexicographicList<E extends Comparable<E>> extends ArrayList<E>
68 implements Comparable<LexicographicList<E>> {
69 LexicographicList(Collection<E> c) { super(c); }
70 LexicographicList(E e) { super(Collections.singleton(e)); }
71 public int compareTo(LexicographicList<E> other) {
72 int common = Math.min(size(), other.size());
73 int r = 0;
74 for (int i = 0; i < common; i++) {
75 if ((r = get(i).compareTo(other.get(i))) != 0)
76 break;
77 }
78 if (r == 0)
79 r = compare(size(), other.size());
80 return r;
81 }
82 private static final long serialVersionUID = 0;
83 }
84
85 static class CollidingObject {
86 final String value;
87 CollidingObject(final String value) { this.value = value; }
88 public int hashCode() { return this.value.hashCode() & 1; }
89 public boolean equals(final Object obj) {
90 return (obj instanceof CollidingObject) && ((CollidingObject)obj).value.equals(value);
91 }
92 }
93
94 static class ComparableCollidingObject extends CollidingObject implements Comparable<ComparableCollidingObject> {
95 ComparableCollidingObject(final String value) { super(value); }
96 public int compareTo(final ComparableCollidingObject o) {
97 return value.compareTo(o.value);
98 }
99 }
100
101 /**
102 * Inserted elements that are subclasses of the same Comparable
103 * class are found.
104 */
105 public void testComparableFamily() {
106 int size = 500; // makes measured test run time -> 60ms
107 ConcurrentHashMap<BI, Boolean> m =
108 new ConcurrentHashMap<BI, Boolean>();
109 for (int i = 0; i < size; i++) {
110 assertTrue(m.put(new CI(i), true) == null);
111 }
112 for (int i = 0; i < size; i++) {
113 assertTrue(m.containsKey(new CI(i)));
114 assertTrue(m.containsKey(new DI(i)));
115 }
116 }
117
118 /**
119 * Elements of classes with erased generic type parameters based
120 * on Comparable can be inserted and found.
121 */
122 public void testGenericComparable() {
123 int size = 120; // makes measured test run time -> 60ms
124 ConcurrentHashMap<Object, Boolean> m =
125 new ConcurrentHashMap<Object, Boolean>();
126 for (int i = 0; i < size; i++) {
127 BI bi = new BI(i);
128 BS bs = new BS(String.valueOf(i));
129 LexicographicList<BI> bis = new LexicographicList<BI>(bi);
130 LexicographicList<BS> bss = new LexicographicList<BS>(bs);
131 assertTrue(m.putIfAbsent(bis, true) == null);
132 assertTrue(m.containsKey(bis));
133 if (m.putIfAbsent(bss, true) == null)
134 assertTrue(m.containsKey(bss));
135 assertTrue(m.containsKey(bis));
136 }
137 for (int i = 0; i < size; i++) {
138 assertTrue(m.containsKey(Collections.singletonList(new BI(i))));
139 }
140 }
141
142 /**
143 * Elements of non-comparable classes equal to those of classes
144 * with erased generic type parameters based on Comparable can be
145 * inserted and found.
146 */
147 public void testGenericComparable2() {
148 int size = 500; // makes measured test run time -> 60ms
149 ConcurrentHashMap<Object, Boolean> m =
150 new ConcurrentHashMap<Object, Boolean>();
151 for (int i = 0; i < size; i++) {
152 m.put(Collections.singletonList(new BI(i)), true);
153 }
154
155 for (int i = 0; i < size; i++) {
156 LexicographicList<BI> bis = new LexicographicList<BI>(new BI(i));
157 assertTrue(m.containsKey(bis));
158 }
159 }
160
161 /**
162 * Mixtures of instances of comparable and non-comparable classes
163 * can be inserted and found.
164 */
165 public void testMixedComparable() {
166 int size = 1200; // makes measured test run time -> 35ms
167 ConcurrentHashMap<Object, Object> map =
168 new ConcurrentHashMap<Object, Object>();
169 Random rng = new Random();
170 for (int i = 0; i < size; i++) {
171 Object x;
172 switch (rng.nextInt(4)) {
173 case 0:
174 x = new Object();
175 break;
176 case 1:
177 x = new CollidingObject(Integer.toString(i));
178 break;
179 default:
180 x = new ComparableCollidingObject(Integer.toString(i));
181 }
182 assertNull(map.put(x, x));
183 }
184 int count = 0;
185 for (Object k : map.keySet()) {
186 assertEquals(map.get(k), k);
187 ++count;
188 }
189 assertEquals(count, size);
190 assertEquals(map.size(), size);
191 for (Object k : map.keySet()) {
192 assertEquals(map.put(k, k), k);
193 }
194 }
195
196 /**
197 * clear removes all pairs
198 */
199 public void testClear() {
200 ConcurrentHashMap map = map5();
201 map.clear();
202 assertEquals(0, map.size());
203 }
204
205 /**
206 * Maps with same contents are equal
207 */
208 public void testEquals() {
209 ConcurrentHashMap map1 = map5();
210 ConcurrentHashMap map2 = map5();
211 assertEquals(map1, map2);
212 assertEquals(map2, map1);
213 map1.clear();
214 assertFalse(map1.equals(map2));
215 assertFalse(map2.equals(map1));
216 }
217
218 /**
219 * hashCode() equals sum of each key.hashCode ^ value.hashCode
220 */
221 public void testHashCode() {
222 ConcurrentHashMap<Integer,String> map = map5();
223 int sum = 0;
224 for (Map.Entry<Integer,String> e : map.entrySet())
225 sum += e.getKey().hashCode() ^ e.getValue().hashCode();
226 assertEquals(sum, map.hashCode());
227 }
228
229 /**
230 * contains returns true for contained value
231 */
232 public void testContains() {
233 ConcurrentHashMap map = map5();
234 assertTrue(map.contains("A"));
235 assertFalse(map.contains("Z"));
236 }
237
238 /**
239 * containsKey returns true for contained key
240 */
241 public void testContainsKey() {
242 ConcurrentHashMap map = map5();
243 assertTrue(map.containsKey(one));
244 assertFalse(map.containsKey(zero));
245 }
246
247 /**
248 * containsValue returns true for held values
249 */
250 public void testContainsValue() {
251 ConcurrentHashMap map = map5();
252 assertTrue(map.containsValue("A"));
253 assertFalse(map.containsValue("Z"));
254 }
255
256 /**
257 * enumeration returns an enumeration containing the correct
258 * elements
259 */
260 public void testEnumeration() {
261 ConcurrentHashMap map = map5();
262 Enumeration e = map.elements();
263 int count = 0;
264 while (e.hasMoreElements()) {
265 count++;
266 e.nextElement();
267 }
268 assertEquals(5, count);
269 }
270
271 /**
272 * get returns the correct element at the given key,
273 * or null if not present
274 */
275 public void testGet() {
276 ConcurrentHashMap map = map5();
277 assertEquals("A", (String)map.get(one));
278 ConcurrentHashMap empty = new ConcurrentHashMap();
279 assertNull(map.get("anything"));
280 }
281
282 /**
283 * isEmpty is true of empty map and false for non-empty
284 */
285 public void testIsEmpty() {
286 ConcurrentHashMap empty = new ConcurrentHashMap();
287 ConcurrentHashMap map = map5();
288 assertTrue(empty.isEmpty());
289 assertFalse(map.isEmpty());
290 }
291
292 /**
293 * keys returns an enumeration containing all the keys from the map
294 */
295 public void testKeys() {
296 ConcurrentHashMap map = map5();
297 Enumeration e = map.keys();
298 int count = 0;
299 while (e.hasMoreElements()) {
300 count++;
301 e.nextElement();
302 }
303 assertEquals(5, count);
304 }
305
306 /**
307 * keySet returns a Set containing all the keys
308 */
309 public void testKeySet() {
310 ConcurrentHashMap map = map5();
311 Set s = map.keySet();
312 assertEquals(5, s.size());
313 assertTrue(s.contains(one));
314 assertTrue(s.contains(two));
315 assertTrue(s.contains(three));
316 assertTrue(s.contains(four));
317 assertTrue(s.contains(five));
318 }
319
320 /**
321 * keySet.toArray returns contains all keys
322 */
323 public void testKeySetToArray() {
324 ConcurrentHashMap map = map5();
325 Set s = map.keySet();
326 Object[] ar = s.toArray();
327 assertTrue(s.containsAll(Arrays.asList(ar)));
328 assertEquals(5, ar.length);
329 ar[0] = m10;
330 assertFalse(s.containsAll(Arrays.asList(ar)));
331 }
332
333 /**
334 * Values.toArray contains all values
335 */
336 public void testValuesToArray() {
337 ConcurrentHashMap map = map5();
338 Collection v = map.values();
339 Object[] ar = v.toArray();
340 ArrayList s = new ArrayList(Arrays.asList(ar));
341 assertEquals(5, ar.length);
342 assertTrue(s.contains("A"));
343 assertTrue(s.contains("B"));
344 assertTrue(s.contains("C"));
345 assertTrue(s.contains("D"));
346 assertTrue(s.contains("E"));
347 }
348
349 /**
350 * entrySet.toArray contains all entries
351 */
352 public void testEntrySetToArray() {
353 ConcurrentHashMap map = map5();
354 Set s = map.entrySet();
355 Object[] ar = s.toArray();
356 assertEquals(5, ar.length);
357 for (int i = 0; i < 5; ++i) {
358 assertTrue(map.containsKey(((Map.Entry)(ar[i])).getKey()));
359 assertTrue(map.containsValue(((Map.Entry)(ar[i])).getValue()));
360 }
361 }
362
363 /**
364 * values collection contains all values
365 */
366 public void testValues() {
367 ConcurrentHashMap map = map5();
368 Collection s = map.values();
369 assertEquals(5, s.size());
370 assertTrue(s.contains("A"));
371 assertTrue(s.contains("B"));
372 assertTrue(s.contains("C"));
373 assertTrue(s.contains("D"));
374 assertTrue(s.contains("E"));
375 }
376
377 /**
378 * entrySet contains all pairs
379 */
380 public void testEntrySet() {
381 ConcurrentHashMap map = map5();
382 Set s = map.entrySet();
383 assertEquals(5, s.size());
384 Iterator it = s.iterator();
385 while (it.hasNext()) {
386 Map.Entry e = (Map.Entry) it.next();
387 assertTrue(
388 (e.getKey().equals(one) && e.getValue().equals("A")) ||
389 (e.getKey().equals(two) && e.getValue().equals("B")) ||
390 (e.getKey().equals(three) && e.getValue().equals("C")) ||
391 (e.getKey().equals(four) && e.getValue().equals("D")) ||
392 (e.getKey().equals(five) && e.getValue().equals("E")));
393 }
394 }
395
396 /**
397 * putAll adds all key-value pairs from the given map
398 */
399 public void testPutAll() {
400 ConcurrentHashMap empty = new ConcurrentHashMap();
401 ConcurrentHashMap map = map5();
402 empty.putAll(map);
403 assertEquals(5, empty.size());
404 assertTrue(empty.containsKey(one));
405 assertTrue(empty.containsKey(two));
406 assertTrue(empty.containsKey(three));
407 assertTrue(empty.containsKey(four));
408 assertTrue(empty.containsKey(five));
409 }
410
411 /**
412 * putIfAbsent works when the given key is not present
413 */
414 public void testPutIfAbsent() {
415 ConcurrentHashMap map = map5();
416 map.putIfAbsent(six, "Z");
417 assertTrue(map.containsKey(six));
418 }
419
420 /**
421 * putIfAbsent does not add the pair if the key is already present
422 */
423 public void testPutIfAbsent2() {
424 ConcurrentHashMap map = map5();
425 assertEquals("A", map.putIfAbsent(one, "Z"));
426 }
427
428 /**
429 * replace fails when the given key is not present
430 */
431 public void testReplace() {
432 ConcurrentHashMap map = map5();
433 assertNull(map.replace(six, "Z"));
434 assertFalse(map.containsKey(six));
435 }
436
437 /**
438 * replace succeeds if the key is already present
439 */
440 public void testReplace2() {
441 ConcurrentHashMap map = map5();
442 assertNotNull(map.replace(one, "Z"));
443 assertEquals("Z", map.get(one));
444 }
445
446 /**
447 * replace value fails when the given key not mapped to expected value
448 */
449 public void testReplaceValue() {
450 ConcurrentHashMap map = map5();
451 assertEquals("A", map.get(one));
452 assertFalse(map.replace(one, "Z", "Z"));
453 assertEquals("A", map.get(one));
454 }
455
456 /**
457 * replace value succeeds when the given key mapped to expected value
458 */
459 public void testReplaceValue2() {
460 ConcurrentHashMap map = map5();
461 assertEquals("A", map.get(one));
462 assertTrue(map.replace(one, "A", "Z"));
463 assertEquals("Z", map.get(one));
464 }
465
466 /**
467 * remove removes the correct key-value pair from the map
468 */
469 public void testRemove() {
470 ConcurrentHashMap map = map5();
471 map.remove(five);
472 assertEquals(4, map.size());
473 assertFalse(map.containsKey(five));
474 }
475
476 /**
477 * remove(key,value) removes only if pair present
478 */
479 public void testRemove2() {
480 ConcurrentHashMap map = map5();
481 map.remove(five, "E");
482 assertEquals(4, map.size());
483 assertFalse(map.containsKey(five));
484 map.remove(four, "A");
485 assertEquals(4, map.size());
486 assertTrue(map.containsKey(four));
487 }
488
489 /**
490 * size returns the correct values
491 */
492 public void testSize() {
493 ConcurrentHashMap map = map5();
494 ConcurrentHashMap empty = new ConcurrentHashMap();
495 assertEquals(0, empty.size());
496 assertEquals(5, map.size());
497 }
498
499 /**
500 * toString contains toString of elements
501 */
502 public void testToString() {
503 ConcurrentHashMap map = map5();
504 String s = map.toString();
505 for (int i = 1; i <= 5; ++i) {
506 assertTrue(s.contains(String.valueOf(i)));
507 }
508 }
509
510 // Exception tests
511
512 /**
513 * Cannot create with only negative capacity
514 */
515 public void testConstructor1() {
516 try {
517 new ConcurrentHashMap(-1);
518 shouldThrow();
519 } catch (IllegalArgumentException success) {}
520 }
521
522 /**
523 * Constructor (initialCapacity, loadFactor) throws
524 * IllegalArgumentException if either argument is negative
525 */
526 public void testConstructor2() {
527 try {
528 new ConcurrentHashMap(-1, .75f);
529 shouldThrow();
530 } catch (IllegalArgumentException e) {}
531
532 try {
533 new ConcurrentHashMap(16, -1);
534 shouldThrow();
535 } catch (IllegalArgumentException e) {}
536 }
537
538 /**
539 * Constructor (initialCapacity, loadFactor, concurrencyLevel)
540 * throws IllegalArgumentException if any argument is negative
541 */
542 public void testConstructor3() {
543 try {
544 new ConcurrentHashMap(-1, .75f, 1);
545 shouldThrow();
546 } catch (IllegalArgumentException e) {}
547
548 try {
549 new ConcurrentHashMap(16, -1, 1);
550 shouldThrow();
551 } catch (IllegalArgumentException e) {}
552
553 try {
554 new ConcurrentHashMap(16, .75f, -1);
555 shouldThrow();
556 } catch (IllegalArgumentException e) {}
557 }
558
559 /**
560 * ConcurrentHashMap(map) throws NullPointerException if the given
561 * map is null
562 */
563 public void testConstructor4() {
564 try {
565 new ConcurrentHashMap(null);
566 shouldThrow();
567 } catch (NullPointerException e) {}
568 }
569
570 /**
571 * ConcurrentHashMap(map) creates a new map with the same mappings
572 * as the given map
573 */
574 public void testConstructor5() {
575 ConcurrentHashMap map1 = map5();
576 ConcurrentHashMap map2 = new ConcurrentHashMap(map5());
577 assertTrue(map2.equals(map1));
578 map2.put(one, "F");
579 assertFalse(map2.equals(map1));
580 }
581
582
583 /**
584 * get(null) throws NPE
585 */
586 public void testGet_NullPointerException() {
587 try {
588 ConcurrentHashMap c = new ConcurrentHashMap(5);
589 c.get(null);
590 shouldThrow();
591 } catch (NullPointerException success) {}
592 }
593
594 /**
595 * containsKey(null) throws NPE
596 */
597 public void testContainsKey_NullPointerException() {
598 try {
599 ConcurrentHashMap c = new ConcurrentHashMap(5);
600 c.containsKey(null);
601 shouldThrow();
602 } catch (NullPointerException success) {}
603 }
604
605 /**
606 * containsValue(null) throws NPE
607 */
608 public void testContainsValue_NullPointerException() {
609 try {
610 ConcurrentHashMap c = new ConcurrentHashMap(5);
611 c.containsValue(null);
612 shouldThrow();
613 } catch (NullPointerException success) {}
614 }
615
616 /**
617 * contains(null) throws NPE
618 */
619 public void testContains_NullPointerException() {
620 try {
621 ConcurrentHashMap c = new ConcurrentHashMap(5);
622 c.contains(null);
623 shouldThrow();
624 } catch (NullPointerException success) {}
625 }
626
627 /**
628 * put(null,x) throws NPE
629 */
630 public void testPut1_NullPointerException() {
631 try {
632 ConcurrentHashMap c = new ConcurrentHashMap(5);
633 c.put(null, "whatever");
634 shouldThrow();
635 } catch (NullPointerException success) {}
636 }
637
638 /**
639 * put(x, null) throws NPE
640 */
641 public void testPut2_NullPointerException() {
642 try {
643 ConcurrentHashMap c = new ConcurrentHashMap(5);
644 c.put("whatever", null);
645 shouldThrow();
646 } catch (NullPointerException success) {}
647 }
648
649 /**
650 * putIfAbsent(null, x) throws NPE
651 */
652 public void testPutIfAbsent1_NullPointerException() {
653 try {
654 ConcurrentHashMap c = new ConcurrentHashMap(5);
655 c.putIfAbsent(null, "whatever");
656 shouldThrow();
657 } catch (NullPointerException success) {}
658 }
659
660 /**
661 * replace(null, x) throws NPE
662 */
663 public void testReplace_NullPointerException() {
664 try {
665 ConcurrentHashMap c = new ConcurrentHashMap(5);
666 c.replace(null, "whatever");
667 shouldThrow();
668 } catch (NullPointerException success) {}
669 }
670
671 /**
672 * replace(null, x, y) throws NPE
673 */
674 public void testReplaceValue_NullPointerException() {
675 try {
676 ConcurrentHashMap c = new ConcurrentHashMap(5);
677 c.replace(null, one, "whatever");
678 shouldThrow();
679 } catch (NullPointerException success) {}
680 }
681
682 /**
683 * putIfAbsent(x, null) throws NPE
684 */
685 public void testPutIfAbsent2_NullPointerException() {
686 try {
687 ConcurrentHashMap c = new ConcurrentHashMap(5);
688 c.putIfAbsent("whatever", null);
689 shouldThrow();
690 } catch (NullPointerException success) {}
691 }
692
693 /**
694 * replace(x, null) throws NPE
695 */
696 public void testReplace2_NullPointerException() {
697 try {
698 ConcurrentHashMap c = new ConcurrentHashMap(5);
699 c.replace("whatever", null);
700 shouldThrow();
701 } catch (NullPointerException success) {}
702 }
703
704 /**
705 * replace(x, null, y) throws NPE
706 */
707 public void testReplaceValue2_NullPointerException() {
708 try {
709 ConcurrentHashMap c = new ConcurrentHashMap(5);
710 c.replace("whatever", null, "A");
711 shouldThrow();
712 } catch (NullPointerException success) {}
713 }
714
715 /**
716 * replace(x, y, null) throws NPE
717 */
718 public void testReplaceValue3_NullPointerException() {
719 try {
720 ConcurrentHashMap c = new ConcurrentHashMap(5);
721 c.replace("whatever", one, null);
722 shouldThrow();
723 } catch (NullPointerException success) {}
724 }
725
726 /**
727 * remove(null) throws NPE
728 */
729 public void testRemove1_NullPointerException() {
730 try {
731 ConcurrentHashMap c = new ConcurrentHashMap(5);
732 c.put("sadsdf", "asdads");
733 c.remove(null);
734 shouldThrow();
735 } catch (NullPointerException success) {}
736 }
737
738 /**
739 * remove(null, x) throws NPE
740 */
741 public void testRemove2_NullPointerException() {
742 try {
743 ConcurrentHashMap c = new ConcurrentHashMap(5);
744 c.put("sadsdf", "asdads");
745 c.remove(null, "whatever");
746 shouldThrow();
747 } catch (NullPointerException success) {}
748 }
749
750 /**
751 * remove(x, null) returns false
752 */
753 public void testRemove3() {
754 ConcurrentHashMap c = new ConcurrentHashMap(5);
755 c.put("sadsdf", "asdads");
756 assertFalse(c.remove("sadsdf", null));
757 }
758
759 /**
760 * A deserialized map equals original
761 */
762 public void testSerialization() throws Exception {
763 Map x = map5();
764 Map y = serialClone(x);
765
766 assertNotSame(x, y);
767 assertEquals(x.size(), y.size());
768 assertEquals(x, y);
769 assertEquals(y, x);
770 }
771
772 /**
773 * SetValue of an EntrySet entry sets value in the map.
774 */
775 public void testSetValueWriteThrough() {
776 // Adapted from a bug report by Eric Zoerner
777 ConcurrentHashMap map = new ConcurrentHashMap(2, 5.0f, 1);
778 assertTrue(map.isEmpty());
779 for (int i = 0; i < 20; i++)
780 map.put(new Integer(i), new Integer(i));
781 assertFalse(map.isEmpty());
782 Map.Entry entry1 = (Map.Entry)map.entrySet().iterator().next();
783 // Unless it happens to be first (in which case remainder of
784 // test is skipped), remove a possibly-colliding key from map
785 // which, under some implementations, may cause entry1 to be
786 // cloned in map
787 if (!entry1.getKey().equals(new Integer(16))) {
788 map.remove(new Integer(16));
789 entry1.setValue("XYZ");
790 assertTrue(map.containsValue("XYZ")); // fails if write-through broken
791 }
792 }
793
794 }