File D:\maven-qcr-plugin-testsunitaires\qcr4java\h2-1.3.170-sources\target\site\qc\lecode\mvstore\MVMap.java.html

1 /*
2 * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
3 * Version 1.0, and under the Eclipse Public License, Version 1.0
4 * (http://h2database.com/html/license.html).
5 * Initial Developer: H2 Group
6 */
7 package org.h2.mvstore;
8
9 import java.util.AbstractList;
10 import java.util.AbstractMap;
11 import java.util.AbstractSet;
12 import java.util.ArrayList;
13 import java.util.HashMap;
14 import java.util.Iterator;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.Set;
18 import java.util.concurrent.ConcurrentMap;
19 import org.h2.mvstore.type.DataType;
20 import org.h2.util.New;
21
22 /**
23 * A stored map.
24 *
25 * @param <K> the key class
26 * @param <V> the value class
27 */
28 public class MVMap<K, V> extends AbstractMap<K, V>
29 implements ConcurrentMap<K, V> {
30
31 /**
32 * The store.
33 */
34 protected MVStore store;
35
36 /**
37 * The current root page (may not be null).
38 */
39 protected volatile Page root;
40
41 private int id;
42 private String name;
43 private long createVersion;
44 private final DataType keyType;
45 private final DataType valueType;
46 private ArrayList<Page> oldRoots = new ArrayList<Page>();
47
48 private boolean closed;
49 private boolean readOnly;
50
51 public MVMap(DataType keyType, DataType valueType) {
52 this.keyType = keyType;
53 this.valueType = valueType;
54 this.root = Page.createEmpty(this, -1);
55 }
56
57 /**
58 * Open this map with the given store and configuration.
59 *
60 * @param store the store
61 * @param config the configuration
62 */
63 public void open(MVStore store, HashMap<String, String> config) {
64 this.store = store;
65 this.id = Integer.parseInt(config.get("id"));
66 this.name = config.get("name");
67 this.createVersion = Long.parseLong(config.get("createVersion"));
68 }
69
70 /**
71 * Add or replace a key-value pair.
72 *
73 * @param key the key (may not be null)
74 * @param value the value (may not be null)
75 * @return the old value if the key existed, or null otherwise
76 */
77 @SuppressWarnings("unchecked")
78 public V put(K key, V value) {
79 checkWrite();
80 long writeVersion = store.getCurrentVersion();
81 Page p = root.copyOnWrite(writeVersion);
82 if (p.getMemory() > store.getPageSize() && p.getKeyCount() > 1) {
83 int at = p.getKeyCount() / 2;
84 long totalCount = p.getTotalCount();
85 Object k = p.getKey(at);
86 Page split = p.split(at);
87 Object[] keys = { k };
88 long[] children = { p.getPos(), split.getPos() };
89 Page[] childrenPages = { p, split };
90 long[] counts = { p.getTotalCount(), split.getTotalCount() };
91 p = Page.create(this, writeVersion, 1,
92 keys, null, children, childrenPages, counts, totalCount, 0, 0);
93 store.registerUnsavedPage();
94 // now p is a node; insert continues
95 }
96 Object result = put(p, writeVersion, key, value);
97 newRoot(p);
98 return (V) result;
99 }
100
101 /**
102 * Add or update a key-value pair.
103 *
104 * @param p the page
105 * @param writeVersion the write version
106 * @param key the key (may not be null)
107 * @param value the value (may not be null)
108 * @return the old value, or null
109 */
110 protected Object put(Page p, long writeVersion, Object key, Object value) {
111 if (p.isLeaf()) {
112 int index = p.binarySearch(key);
113 if (index < 0) {
114 index = -index - 1;
115 p.insertLeaf(index, key, value);
116 return null;
117 }
118 return p.setValue(index, value);
119 }
120 // p is a node
121 int index = p.binarySearch(key);
122 if (index < 0) {
123 index = -index - 1;
124 } else {
125 index++;
126 }
127 Page c = p.getChildPage(index).copyOnWrite(writeVersion);
128 if (c.getMemory() > store.getPageSize() && c.getKeyCount() > 1) {
129 // split on the way down
130 int at = c.getKeyCount() / 2;
131 Object k = c.getKey(at);
132 Page split = c.split(at);
133 p.setChild(index, split);
134 p.insertNode(index, k, c);
135 // now we are not sure where to add
136 return put(p, writeVersion, key, value);
137 }
138 Object result = put(c, writeVersion, key, value);
139 p.setChild(index, c);
140 return result;
141 }
142
143 /**
144 * Get the first key, or null if the map is empty.
145 *
146 * @return the first key, or null
147 */
148 public K firstKey() {
149 return getFirstLast(true);
150 }
151
152 /**
153 * Get the last key, or null if the map is empty.
154 *
155 * @return the last key, or null
156 */
157 public K lastKey() {
158 return getFirstLast(false);
159 }
160
161 /**
162 * Get the key at the given index.
163 * <p>
164 * This is a O(log(size)) operation.
165 *
166 * @param index the index
167 * @return the key
168 */
169 @SuppressWarnings("unchecked")
170 public K getKey(long index) {
171 checkOpen();
172 if (index < 0 || index >= size()) {
173 return null;
174 }
175 Page p = root;
176 long offset = 0;
177 while (true) {
178 if (p.isLeaf()) {
179 if (index >= offset + p.getKeyCount()) {
180 return null;
181 }
182 return (K) p.getKey((int) (index - offset));
183 }
184 int i = 0, size = p.getChildPageCount();
185 for (; i < size; i++) {
186 long c = p.getCounts(i);
187 if (index < c + offset) {
188 break;
189 }
190 offset += c;
191 }
192 if (i == size) {
193 return null;
194 }
195 p = p.getChildPage(i);
196 }
197 }
198
199 /**
200 * Get the key list. The list is a read-only representation of all keys.
201 * <p>
202 * The get and indexOf methods are O(log(size)) operations. The result of
203 * indexOf is cast to an int.
204 *
205 * @return the key list
206 */
207 public List<K> keyList() {
208 return new AbstractList<K>() {
209
210 public K get(int index) {
211 return getKey(index);
212 }
213
214 public int size() {
215 return MVMap.this.size();
216 }
217
218 @SuppressWarnings("unchecked")
219 public int indexOf(Object key) {
220 return (int) getKeyIndex((K) key);
221 }
222
223 };
224 }
225
226 /**
227 * Get the index of the given key in the map.
228 * <p>
229 * This is a O(log(size)) operation.
230 * <p>
231 * If the key was found, the returned value is the index in the key array.
232 * If not found, the returned value is negative, where -1 means the provided
233 * key is smaller than any keys. See also Arrays.binarySearch.
234 *
235 * @param key the key
236 * @return the index
237 */
238 public long getKeyIndex(K key) {
239 checkOpen();
240 if (size() == 0) {
241 return -1;
242 }
243 Page p = root;
244 long offset = 0;
245 while (true) {
246 if (p.isLeaf()) {
247 int x = p.binarySearch(key);
248 if (x < 0) {
249 return -offset + x;
250 }
251 return offset + x;
252 }
253 int x = key == null ? -1 : p.binarySearch(key);
254 if (x < 0) {
255 x = -x - 1;
256 } else {
257 x++;
258 }
259 for (int i = 0; i < x; i++) {
260 offset += p.getCounts(i);
261 }
262 p = p.getChildPage(x);
263 }
264 }
265
266 @SuppressWarnings("unchecked")
267 private K getFirstLast(boolean first) {
268 checkOpen();
269 if (size() == 0) {
270 return null;
271 }
272 Page p = root;
273 while (true) {
274 if (p.isLeaf()) {
275 return (K) p.getKey(first ? 0 : p.getKeyCount() - 1);
276 }
277 p = p.getChildPage(first ? 0 : p.getChildPageCount() - 1);
278 }
279 }
280
281 /**
282 * Get the smallest key that is larger than the given key, or null if no
283 * such key exists.
284 *
285 * @param key the key (may not be null)
286 * @return the result
287 */
288 public K higherKey(K key) {
289 return getMinMax(key, false, true);
290 }
291
292 /**
293 * Get the smallest key that is larger or equal to this key.
294 *
295 * @param key the key (may not be null)
296 * @return the result
297 */
298 public K ceilingKey(K key) {
299 return getMinMax(key, false, false);
300 }
301
302 /**
303 * Get the largest key that is smaller or equal to this key.
304 *
305 * @param key the key (may not be null)
306 * @return the result
307 */
308 public K floorKey(K key) {
309 return getMinMax(key, true, false);
310 }
311
312 /**
313 * Get the largest key that is smaller than the given key, or null if no
314 * such key exists.
315 *
316 * @param key the key (may not be null)
317 * @return the result
318 */
319 public K lowerKey(K key) {
320 return getMinMax(key, true, true);
321 }
322
323 private K getMinMax(K key, boolean min, boolean excluding) {
324 checkOpen();
325 if (size() == 0) {
326 return null;
327 }
328 return getMinMax(root, key, min, excluding);
329 }
330
331 @SuppressWarnings("unchecked")
332 private K getMinMax(Page p, K key, boolean min, boolean excluding) {
333 if (p.isLeaf()) {
334 if (key == null) {
335 return (K) p.getKey(min ? 0 : p.getKeyCount() - 1);
336 }
337 int x = p.binarySearch(key);
338 if (x < 0) {
339 x = -x - (min ? 2 : 1);
340 } else if (excluding) {
341 x += min ? -1 : 1;
342 }
343 if (x < 0 || x >= p.getKeyCount()) {
344 return null;
345 }
346 return (K) p.getKey(x);
347 }
348 int x;
349 if (key == null) {
350 x = min ? 0 : p.getKeyCount() - 1;
351 } else {
352 x = p.binarySearch(key);
353 if (x < 0) {
354 x = -x - 1;
355 } else {
356 x++;
357 }
358 }
359 while (true) {
360 if (x < 0 || x >= p.getChildPageCount()) {
361 return null;
362 }
363 K k = getMinMax(p.getChildPage(x), key, min, excluding);
364 if (k != null) {
365 return k;
366 }
367 x += min ? -1 : 1;
368 }
369 }
370
371
372 /**
373 * Get a value.
374 *
375 * @param key the key
376 * @return the value, or null if not found
377 */
378 @SuppressWarnings("unchecked")
379 public V get(Object key) {
380 checkOpen();
381 return (V) binarySearch(root, key);
382 }
383
384 /**
385 * Get the value for the given key, or null if not found.
386 *
387 * @param p the page
388 * @param key the key
389 * @return the value or null
390 */
391 protected Object binarySearch(Page p, Object key) {
392 int x = p.binarySearch(key);
393 if (!p.isLeaf()) {
394 if (x < 0) {
395 x = -x - 1;
396 } else {
397 x++;
398 }
399 p = p.getChildPage(x);
400 return binarySearch(p, key);
401 }
402 if (x >= 0) {
403 return p.getValue(x);
404 }
405 return null;
406 }
407
408 public boolean containsKey(Object key) {
409 return get(key) != null;
410 }
411
412 /**
413 * Get the page for the given value.
414 *
415 * @param key the key
416 * @return the value, or null if not found
417 */
418 protected Page getPage(K key) {
419 return binarySearchPage(root, key);
420 }
421
422 /**
423 * Get the value for the given key, or null if not found.
424 *
425 * @param p the parent page
426 * @param key the key
427 * @return the page or null
428 */
429 protected Page binarySearchPage(Page p, Object key) {
430 int x = p.binarySearch(key);
431 if (!p.isLeaf()) {
432 if (x < 0) {
433 x = -x - 1;
434 } else {
435 x++;
436 }
437 p = p.getChildPage(x);
438 return binarySearchPage(p, key);
439 }
440 if (x >= 0) {
441 return p;
442 }
443 return null;
444 }
445
446 /**
447 * Remove all entries.
448 */
449 public void clear() {
450 checkWrite();
451 root.removeAllRecursive();
452 newRoot(Page.createEmpty(this, store.getCurrentVersion()));
453 }
454
455 /**
456 * Remove all entries, and close the map.
457 */
458 public void removeMap() {
459 if (this != store.getMetaMap()) {
460 checkWrite();
461 root.removeAllRecursive();
462 store.removeMap(name);
463 close();
464 }
465 }
466
467 /**
468 * Close the map, making it read only and release the memory.
469 */
470 public void close() {
471 closed = true;
472 readOnly = true;
473 removeAllOldVersions();
474 root = null;
475 }
476
477 public boolean isClosed() {
478 return closed;
479 }
480
481 /**
482 * Remove a key-value pair, if the key exists.
483 *
484 * @param key the key (may not be null)
485 * @return the old value if the key existed, or null otherwise
486 */
487 public V remove(Object key) {
488 checkWrite();
489 long writeVersion = store.getCurrentVersion();
490 Page p = root.copyOnWrite(writeVersion);
491 @SuppressWarnings("unchecked")
492 V result = (V) remove(p, writeVersion, key);
493 newRoot(p);
494 return result;
495 }
496
497 /**
498 * Add a key-value pair if it does not yet exist.
499 *
500 * @param key the key (may not be null)
501 * @param value the new value
502 * @return the old value if the key existed, or null otherwise
503 */
504 public synchronized V putIfAbsent(K key, V value) {
505 V old = get(key);
506 if (old == null) {
507 put(key, value);
508 }
509 return old;
510 }
511
512 /**
513 * Remove a key-value pair if the value matches the stored one.
514 *
515 * @param key the key (may not be null)
516 * @param value the expected value
517 * @return true if the item was removed
518 */
519 public synchronized boolean remove(Object key, Object value) {
520 V old = get(key);
521 if (old.equals(value)) {
522 remove(key);
523 return true;
524 }
525 return false;
526 }
527
528 /**
529 * Replace a value for an existing key, if the value matches.
530 *
531 * @param key the key (may not be null)
532 * @param oldValue the expected value
533 * @param newValue the new value
534 * @return true if the value was replaced
535 */
536 public synchronized boolean replace(K key, V oldValue, V newValue) {
537 V old = get(key);
538 if (old.equals(oldValue)) {
539 put(key, newValue);
540 return true;
541 }
542 return false;
543 }
544
545 /**
546 * Replace a value for an existing key.
547 *
548 * @param key the key (may not be null)
549 * @param value the new value
550 * @return true if the value was replaced
551 */
552 public synchronized V replace(K key, V value) {
553 V old = get(key);
554 if (old != null) {
555 put(key, value);
556 return old;
557 }
558 return null;
559 }
560
561 /**
562 * Remove a key-value pair.
563 *
564 * @param p the page (may not be null)
565 * @param writeVersion the write version
566 * @param key the key
567 * @return the old value, or null if the key did not exist
568 */
569 protected Object remove(Page p, long writeVersion, Object key) {
570 int index = p.binarySearch(key);
571 Object result = null;
572 if (p.isLeaf()) {
573 if (index >= 0) {
574 result = p.getValue(index);
575 p.remove(index);
576 if (p.getKeyCount() == 0) {
577 removePage(p);
578 }
579 }
580 return result;
581 }
582 // node
583 if (index < 0) {
584 index = -index - 1;
585 } else {
586 index++;
587 }
588 Page cOld = p.getChildPage(index);
589 Page c = cOld.copyOnWrite(writeVersion);
590 long oldCount = c.getTotalCount();
591 result = remove(c, writeVersion, key);
592 if (oldCount == c.getTotalCount()) {
593 return null;
594 }
595 if (c.getTotalCount() == 0) {
596 // this child was deleted
597 if (p.getKeyCount() == 0) {
598 p.setChild(index, c);
599 removePage(p);
600 } else {
601 p.remove(index);
602 }
603 } else {
604 p.setChild(index, c);
605 }
606 return result;
607 }
608
609 /**
610 * Use the new root page from now on.
611 *
612 * @param newRoot the new root page
613 */
614 protected void newRoot(Page newRoot) {
615 if (root != newRoot) {
616 removeUnusedOldVersions();
617 if (root.getVersion() != newRoot.getVersion()) {
618 ArrayList<Page> list = oldRoots;
619 if (list.size() > 0) {
620 Page last = list.get(list.size() - 1);
621 if (last.getVersion() != root.getVersion()) {
622 list.add(root);
623 }
624 } else {
625 list.add(root);
626 }
627 store.markChanged(this);
628 }
629 root = newRoot;
630 }
631 }
632
633 /**
634 * Check whether this map has any unsaved changes.
635 *
636 * @return true if there are unsaved changes.
637 */
638 public boolean hasUnsavedChanges() {
639 return !oldRoots.isEmpty();
640 }
641
642 /**
643 * Compare two keys.
644 *
645 * @param a the first key
646 * @param b the second key
647 * @return -1 if the first key is smaller, 1 if bigger, 0 if equal
648 */
649 int compare(Object a, Object b) {
650 return keyType.compare(a, b);
651 }
652
653 /**
654 * Get the key type.
655 *
656 * @return the key type
657 */
658 protected DataType getKeyType() {
659 return keyType;
660 }
661
662 /**
663 * Get the value type.
664 *
665 * @return the value type
666 */
667 protected DataType getValueType() {
668 return valueType;
669 }
670
671 /**
672 * Read a page.
673 *
674 * @param pos the position of the page
675 * @return the page
676 */
677 Page readPage(long pos) {
678 return store.readPage(this, pos);
679 }
680
681 /**
682 * Set the position of the root page.
683 *
684 * @param rootPos the position, 0 for empty
685 * @param version the version of the root
686 */
687 void setRootPos(long rootPos, long version) {
688 root = rootPos == 0 ? Page.createEmpty(this, -1) : readPage(rootPos);
689 root.setVersion(version);
690 }
691
692 /**
693 * Iterate over all keys.
694 *
695 * @param from the first key to return
696 * @return the iterator
697 */
698 public Cursor<K> keyIterator(K from) {
699 checkOpen();
700 return new Cursor<K>(this, root, from);
701 }
702
703 /**
704 * Iterate over all keys in changed pages.
705 *
706 * @param version the old version
707 * @return the iterator
708 */
709 public Iterator<K> changeIterator(long version) {
710 checkOpen();
711 MVMap<K, V> old = openVersion(version);
712 return new ChangeCursor<K, V>(this, root, old.root);
713 }
714
715 public Set<Map.Entry<K, V>> entrySet() {
716 HashMap<K, V> map = new HashMap<K, V>();
717 for (K k : keySet()) {
718 map.put(k, get(k));
719 }
720 return map.entrySet();
721 }
722
723 public Set<K> keySet() {
724 checkOpen();
725 final MVMap<K, V> map = this;
726 final Page root = this.root;
727 return new AbstractSet<K>() {
728
729 @Override
730 public Iterator<K> iterator() {
731 return new Cursor<K>(map, root, null);
732 }
733
734 @Override
735 public int size() {
736 return MVMap.this.size();
737 }
738
739 @Override
740 public boolean contains(Object o) {
741 return MVMap.this.containsKey(o);
742 }
743
744 };
745 }
746
747 /**
748 * Get the root page.
749 *
750 * @return the root page
751 */
752 public Page getRoot() {
753 return root;
754 }
755
756 /**
757 * Get the map name.
758 *
759 * @return the name
760 */
761 String getName() {
762 return name;
763 }
764
765 MVStore getStore() {
766 return store;
767 }
768
769 int getId() {
770 return id;
771 }
772
773 /**
774 * Rollback to the given version.
775 *
776 * @param version the version
777 */
778 void rollbackTo(long version) {
779 checkWrite();
780 removeUnusedOldVersions();
781 if (version <= createVersion) {
782 removeMap();
783 } else if (root.getVersion() >= version) {
784 // iterating in descending order -
785 // this is not terribly efficient if there are many versions
786 ArrayList<Page> list = oldRoots;
787 while (list.size() > 0) {
788 int i = list.size() - 1;
789 Page p = list.get(i);
790 root = p;
791 list.remove(i);
792 if (p.getVersion() < version) {
793 break;
794 }
795 }
796 }
797 }
798
799 /**
800 * Forget all old versions.
801 */
802 void removeAllOldVersions() {
803 // create a new instance
804 // because another thread might iterate over it
805 oldRoots = new ArrayList<Page>();
806 }
807
808 /**
809 * Forget those old versions that are no longer needed.
810 */
811 void removeUnusedOldVersions() {
812 long oldest = store.getRetainVersion();
813 if (oldest == -1) {
814 return;
815 }
816 int i = searchRoot(oldest);
817 if (i < 0) {
818 return;
819 }
820 // create a new instance
821 // because another thread might iterate over it
822 ArrayList<Page> list = new ArrayList<Page>();
823 list.addAll(oldRoots.subList(i, oldRoots.size()));
824 oldRoots = list;
825 }
826
827 public void setReadOnly(boolean readOnly) {
828 this.readOnly = readOnly;
829 }
830
831 public boolean isReadOnly() {
832 return readOnly;
833 }
834
835 /**
836 * Check whether the map is open.
837 *
838 * @throws IllegalStateException if the map is closed
839 */
840 protected void checkOpen() {
841 if (closed) {
842 throw new IllegalStateException("This map is closed");
843 }
844 }
845
846 /**
847 * Check whether writing is allowed.
848 *
849 * @throws IllegalStateException if the map is read-only
850 */
851 protected void checkWrite() {
852 if (readOnly) {
853 checkOpen();
854 throw new IllegalStateException("This map is read-only");
855 }
856 }
857
858 public int hashCode() {
859 return id;
860 }
861
862 public boolean equals(Object o) {
863 return this == o;
864 }
865
866 public int size() {
867 long size = getSize();
868 return size > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) size;
869 }
870
871 public long getSize() {
872 return root.getTotalCount();
873 }
874
875 long getCreateVersion() {
876 return createVersion;
877 }
878
879 /**
880 * Remove the given page (make the space available).
881 *
882 * @param p the page
883 */
884 protected void removePage(Page p) {
885 store.removePage(p.getPos());
886 }
887
888 /**
889 * Open an old version for the given map.
890 *
891 * @param version the version
892 * @return the map
893 */
894 public MVMap<K, V> openVersion(long version) {
895 if (readOnly) {
896 throw new IllegalArgumentException("This map is read-only - need to call the method on the writable map");
897 }
898 if (version < createVersion) {
899 throw new IllegalArgumentException("Unknown version");
900 }
901 Page newest = null;
902 // need to copy because it can change
903 Page r = root;
904 if (r.getVersion() <= version && r.getVersion() >= 0) {
905 newest = r;
906 } else {
907 // find the newest page that has a getVersion() <= version
908 int i = searchRoot(version);
909 if (i < 0) {
910 // not found
911 if (i == -1) {
912 // smaller than all in-memory versions
913 return store.openMapVersion(version, name, this);
914 }
915 i = -i - 2;
916 }
917 newest = oldRoots.get(i);
918 }
919 MVMap<K, V> m = openReadOnly();
920 m.root = newest;
921 return m;
922 }
923
924 /**
925 * Open a copy of the map in read-only mode.
926 *
927 * @return the opened map
928 */
929 protected MVMap<K, V> openReadOnly() {
930 MVMap<K, V> m = new MVMap<K, V>(keyType, valueType);
931 m.readOnly = true;
932 HashMap<String, String> config = New.hashMap();
933 config.put("id", String.valueOf(id));
934 config.put("name", name);
935 config.put("createVersion", String.valueOf(createVersion));
936 m.open(store, config);
937 m.root = root;
938 return m;
939 }
940
941 private int searchRoot(long version) {
942 int low = 0, high = oldRoots.size() - 1;
943 while (low <= high) {
944 int x = (low + high) >>> 1;
945 long v = oldRoots.get(x).getVersion();
946 if (v < version) {
947 low = x + 1;
948 } else if (version < v) {
949 high = x - 1;
950 } else {
951 return x;
952 }
953 }
954 return -(low + 1);
955 }
956
957 public long getVersion() {
958 return root.getVersion();
959 }
960
961 /**
962 * Get the child page count for this page. This is to allow another map
963 * implementation to override the default, in case the last child is not to
964 * be used.
965 *
966 * @param p the page
967 * @return the number of direct children
968 */
969 protected int getChildPageCount(Page p) {
970 return p.getChildPageCount();
971 }
972
973 /**
974 * Get the map type. When opening an existing map, the map type must match.
975 *
976 * @return the map type
977 */
978 public String getType() {
979 return "btree";
980 }
981
982 /**
983 * Get the map metadata as a string.
984 *
985 * @return the string
986 */
987 public String asString() {
988 StringBuilder buff = new StringBuilder();
989 DataUtils.appendMap(buff, "id", id);
990 DataUtils.appendMap(buff, "name", name);
991 DataUtils.appendMap(buff, "type", getType());
992 DataUtils.appendMap(buff, "createVersion", createVersion);
993 if (keyType != null) {
994 DataUtils.appendMap(buff, "key", keyType.asString());
995 }
996 if (valueType != null) {
997 DataUtils.appendMap(buff, "value", valueType.asString());
998 }
999 return buff.toString();
1000 }
1001
1002 }
1003