001/**
002 * Copyright 2010 The Kuali Foundation Licensed under the
003 * Educational Community License, Version 2.0 (the "License"); you may
004 * not use this file except in compliance with the License. You may
005 * obtain a copy of the License at
006 *
007 * http://www.osedu.org/licenses/ECL-2.0
008 *
009 * Unless required by applicable law or agreed to in writing,
010 * software distributed under the License is distributed on an "AS IS"
011 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
012 * or implied. See the License for the specific language governing
013 * permissions and limitations under the License.
014 */
015
016package org.kuali.student.r1.common.assembly.data;
017
018import java.io.Serializable;
019import java.sql.Time;
020import java.sql.Timestamp;
021import java.util.Date;
022import java.util.HashMap;
023import java.util.HashSet;
024import java.util.Iterator;
025import java.util.LinkedHashMap;
026import java.util.Map;
027import java.util.Set;
028import java.util.Map.Entry;
029
030import javax.xml.bind.annotation.XmlRootElement;
031import javax.xml.bind.annotation.XmlType;
032
033/**
034 * Kuali Student data representation.  Data is essentially a map of values.
035 * 
036 * @author Kuali Student Team
037 * @see Value
038 */
039@SuppressWarnings({"serial", "unchecked"})
040public class Data implements Serializable, Iterable<Data.Property>, HasChangeCallbacks {
041    @XmlType(name = "lookUpDataType")
042    public enum DataType {
043        STRING, INTEGER, LONG, FLOAT, DOUBLE, BOOLEAN, DATE, TRUNCATED_DATE, DATA, LIST
044    }
045
046    @XmlRootElement
047    public static class BooleanValue implements Value {
048        private Boolean value;
049
050        protected BooleanValue() {
051
052        }
053
054        public BooleanValue(final Boolean value) {
055            this.value = value;
056        }
057
058        @Override
059        public <T> T get() {
060            return (T) value;
061        }
062
063        @Override
064        public Class getType() {
065            return Boolean.class;
066        }
067
068        @Override
069        public String toString() {
070            if (value == null) {
071                return "";
072            } else {
073                return String.valueOf(value);
074            }
075        }
076    }
077
078    @XmlRootElement
079    public static class DataValue implements Value {
080        private Data value;
081
082        protected DataValue() {
083
084        }
085
086        public DataValue(final Data value) {
087            this.value = value;
088        }
089
090        @Override
091        public <T> T get() {
092            return (T) value;
093        }
094
095        @Override
096        public Class getType() {
097            return Data.class;
098        }
099
100        @Override
101        public String toString() {
102            if (value == null) {
103                return null;
104            } else {
105                return value.toString();
106            }
107        }
108    }
109
110    @XmlRootElement
111    public static class DateValue implements Value {
112        private Date value;
113
114        protected DateValue() {
115
116        }
117
118        public DateValue(final Date value) {
119            this.value = value;
120        }
121
122        @Override
123        public <T> T get() {
124            return (T) value;
125        }
126
127        @Override
128        public Class getType() {
129            return Date.class;
130        }
131
132        @Override
133        public String toString() {
134            if (value == null) {
135                return null;
136            } else {
137                return String.valueOf(value);
138            }
139        }
140    }
141
142    @XmlRootElement
143    public static class DoubleValue implements Value {
144        private Double value;
145
146        protected DoubleValue() {
147
148        }
149
150        public DoubleValue(final Double value) {
151            this.value = value;
152        }
153
154        @Override
155        public <T> T get() {
156            return (T) value;
157        }
158
159        @Override
160        public Class getType() {
161            return Double.class;
162        }
163
164        @Override
165        public String toString() {
166            if (value == null) {
167                return null;
168            } else {
169                return String.valueOf(value);
170            }
171        }
172    }
173
174    @XmlRootElement
175    public static class FloatValue implements Value {
176        private Float value;
177
178        protected FloatValue() {
179
180        }
181
182        public FloatValue(final Float value) {
183            this.value = value;
184        }
185
186        @Override
187        public <T> T get() {
188            return (T) value;
189        }
190
191        @Override
192        public Class getType() {
193            return Float.class;
194        }
195
196        @Override
197        public String toString() {
198            if (value == null) {
199                return null;
200            } else {
201                return String.valueOf(value);
202            }
203        }
204    }
205
206    public static class IntegerKey implements Key {
207        private Integer key;
208
209        protected IntegerKey() {
210
211        }
212
213        public IntegerKey(final Integer key) {
214            this.key = key;
215        }
216
217        /*
218         * (non-Javadoc)
219         * 
220         * @see java.lang.Object#equals(java.lang.Object)
221         */
222
223        @Override
224        public boolean equals(final Object obj) {
225            return obj instanceof IntegerKey && key.equals(((IntegerKey) obj).key);
226        }
227
228        @Override
229        public <T> T get() {
230            return (T) key;
231        }
232
233        @Override
234        public Class getType() {
235            return Integer.class;
236        }
237
238        /*
239         * (non-Javadoc)
240         * 
241         * @see java.lang.Object#hashCode()
242         */
243
244        @Override
245        public int hashCode() {
246            return key.hashCode();
247        }
248
249        /*
250         * (non-Javadoc)
251         * 
252         * @see java.lang.Object#toString()
253         */
254
255        @Override
256        public String toString() {
257            return String.valueOf(key);
258        }
259
260    }
261
262    @XmlRootElement
263    public static class IntegerValue implements Value {
264        private Integer value;
265
266        protected IntegerValue() {
267
268        }
269
270        public IntegerValue(final Integer value) {
271            this.value = value;
272        }
273
274        @Override
275        public <T> T get() {
276            return (T) value;
277        }
278
279        @Override
280        public Class getType() {
281            return Integer.class;
282        }
283
284        @Override
285        public String toString() {
286            if (value == null) {
287                return null;
288            } else {
289                return String.valueOf(value);
290            }
291        }
292    }
293
294    public interface Key extends Serializable {
295
296        <T> T get();
297
298        Class getType();
299    }
300
301    @XmlRootElement
302    public static class LongValue implements Value {
303        private Long value;
304
305        protected LongValue() {
306
307        }
308
309        public LongValue(final Long value) {
310            this.value = value;
311        }
312
313        @Override
314        public <T> T get() {
315            return (T) value;
316        }
317
318        @Override
319        public Class getType() {
320            return Long.class;
321        }
322
323        @Override
324        public String toString() {
325            if (value == null) {
326                return null;
327            } else {
328                return String.valueOf(value);
329            }
330        }
331    }
332
333    public interface Property {
334        <T> T getKey();
335
336        Class getKeyType();
337
338        Key getWrappedKey();
339
340        <T> T getValue();
341
342        Class getValueType();
343
344        Value getWrappedValue();
345    }
346
347    @XmlRootElement
348    public static class ShortValue implements Value {
349        private Short value;
350
351        protected ShortValue() {
352
353        }
354
355        public ShortValue(final Short value) {
356            this.value = value;
357        }
358
359        @Override
360        public <T> T get() {
361            return (T) value;
362        }
363
364        @Override
365        public Class getType() {
366            return Short.class;
367        }
368
369        @Override
370        public String toString() {
371            if (value == null) {
372                return null;
373            } else {
374                return String.valueOf(value);
375            }
376        }
377    }
378
379    @XmlRootElement
380    public static class StringKey implements Key {
381        private String key;
382
383        protected StringKey() {
384
385        }
386
387        public StringKey(final String key) {
388            this.key = key;
389        }
390
391        /*
392         * (non-Javadoc)
393         * 
394         * @see java.lang.Object#equals(java.lang.Object)
395         */
396
397        @Override
398        public boolean equals(final Object obj) {
399            return obj instanceof StringKey && key.equals(((StringKey) obj).key);
400        }
401
402        @Override
403        public <T> T get() {
404            return (T) key;
405        }
406
407        @Override
408        public Class<?> getType() {
409            return String.class;
410        }
411
412        /*
413         * (non-Javadoc)
414         * 
415         * @see java.lang.Object#hashCode()
416         */
417
418        @Override
419        public int hashCode() {
420            return key.hashCode();
421        }
422
423        /*
424         * (non-Javadoc)
425         * 
426         * @see java.lang.Object#toString()
427         */
428
429        @Override
430        public String toString() {
431            return key;
432        }
433
434    }
435
436    @XmlRootElement
437    public static class StringValue implements Value {
438        private String value;
439
440        protected StringValue() {
441
442        }
443
444        public StringValue(final String value) {
445            this.value = value;
446        }
447
448        @Override
449        public <T> T get() {
450            return (T) value;
451        }
452
453        @Override
454        public Class getType() {
455            return String.class;
456        }
457
458        @Override
459        public String toString() {
460            return value;
461        }
462
463        public void setValue(String value) {
464            this.value = value;
465        }
466    }
467
468    @XmlRootElement
469    public static class TimestampValue implements Value {
470        private Timestamp value;
471
472        protected TimestampValue() {
473
474        }
475
476        public TimestampValue(final Timestamp value) {
477            this.value = value;
478        }
479
480        @Override
481        public <T> T get() {
482            return (T) value;
483        }
484
485        @Override
486        public Class getType() {
487            return Timestamp.class;
488        }
489
490        @Override
491        public String toString() {
492            if (value == null) {
493                return null;
494            } else {
495                return String.valueOf(value);
496            }
497        }
498    }
499
500    @XmlRootElement
501    public static class TimeValue implements Value {
502        private Time value;
503
504        protected TimeValue() {
505
506        }
507
508        public TimeValue(final Time value) {
509            this.value = value;
510        }
511
512        @Override
513        public <T> T get() {
514            return (T) value;
515        }
516
517        @Override
518        public Class getType() {
519            return Time.class;
520        }
521
522        @Override
523        public String toString() {
524            if (value == null) {
525                return null;
526            } else {
527                return String.valueOf(value);
528            }
529        }
530    }
531
532    /**
533     * Kuali Student data value interface for use in Data
534     * 
535     * @author Kuali Student Team
536     *
537     */
538    public interface Value extends Serializable {
539        <T> T get();
540
541        Class getType();
542    }
543
544    public static final Key WILDCARD_KEY = new Data.StringKey(QueryPath.getWildCard());
545
546    private transient Set<ChangeCallback> changeCallbacks;
547
548    private String className;
549
550    private Map<Key, Value> map;
551
552    private Data parent = null;
553
554    private Key parentKey = null;
555
556    public Data() {
557        this(Data.class.getName());
558    }
559
560    public Data(final String className) {
561        this.className = className;
562        map = new LinkedHashMap<Key, Value>();
563    }
564
565    protected void _getQueryPath(final QueryPath path) {
566        if (parent != null) {
567            parent._getQueryPath(path);
568            path.add(parentKey);
569        }
570    }
571
572    protected void execChangeCallbacks(ChangeType type, QueryPath path) {
573        if (changeCallbacks != null) {
574            for (ChangeCallback c : changeCallbacks) {
575                c.onChange(type, path);
576            }
577        }
578        if (parent != null) {
579            parent.execChangeCallbacks(type, path);
580        }
581    }
582
583    public ChangeCallbackRegistration addChangeCallback(final ChangeCallback callback) {
584        if (changeCallbacks == null) {
585            changeCallbacks = new HashSet<ChangeCallback>();
586        }
587        changeCallbacks.add(callback);
588        return new ChangeCallbackRegistration() {
589            @Override
590            public void remove() {
591                if (changeCallbacks != null) {
592                    changeCallbacks.remove(callback);
593                }
594            }
595        };
596    }
597
598    private void put(Key key, Value value) {
599        Value existing = map.put(key, value);
600        ChangeType type = existing == null ? ChangeType.ADD : ChangeType.UPDATE;
601        QueryPath path = getQueryPath();
602        path.add(key);
603        execChangeCallbacks(type, path);
604    }
605
606    public void remove(Key key) {
607        // TODO probably need to add all of the other remove(type) methods
608        map.remove(key);
609        QueryPath path = getQueryPath();
610        path.add(key);
611        execChangeCallbacks(ChangeType.REMOVE, path);
612    }
613
614    public void add(final Boolean value) {
615        put(new IntegerKey(map.size()), new BooleanValue(value));
616    }
617
618    public void add(final Data value) {
619        final Key k = new IntegerKey(map.size());
620        put(k, new DataValue(value));
621        if (value != null) {
622            value.parent = this;
623            value.parentKey = k;
624        }
625    }
626
627    public void add(final Date value) {
628        put(new IntegerKey(map.size()), new DateValue(value));
629    }
630
631    public void add(final Double value) {
632        put(new IntegerKey(map.size()), new DoubleValue(value));
633    }
634
635    public void add(final Float value) {
636        put(new IntegerKey(map.size()), new FloatValue(value));
637    }
638
639    public void add(final Integer value) {
640        put(new IntegerKey(map.size()), new IntegerValue(value));
641    }
642
643    public void add(final Long value) {
644        put(new IntegerKey(map.size()), new LongValue(value));
645    }
646
647    public void add(final Short value) {
648        put(new IntegerKey(map.size()), new ShortValue(value));
649    }
650
651    public void add(final String value) {
652        put(new IntegerKey(map.size()), new StringValue(value));
653    }
654
655    public void add(final Time value) {
656        put(new IntegerKey(map.size()), new TimeValue(value));
657    }
658
659    public void add(final Timestamp value) {
660        put(new IntegerKey(map.size()), new TimestampValue(value));
661    }
662
663    public Data copy() {
664        return copy(new Data(this.className), true);
665    }
666
667    public Data copy(Data target, boolean recurse) {
668        // note, this was the clone() method, but my eclipse code cleanup insists on @Override, and the compiler gives an
669        // error
670        for (final Entry<Key, Value> e : map.entrySet()) {
671            if (recurse && e.getValue().getType().equals(Data.class)) {
672                Data value = e.getValue().get();
673                if (value != null) {
674                    value = value.copy();
675                }
676                target.map.put(e.getKey(), new DataValue(value));
677            } else {
678                target.map.put(e.getKey(), e.getValue());
679            }
680        }
681        return target;
682    }
683
684    public <T> T get(final Integer key) {
685        final Value v = map.get(new IntegerKey(key));
686        T result = null;
687        if (v != null) {
688            result = (T) v.get();
689        }
690        return result;
691    }
692
693    public <T> T get(final Key key) {
694        final Value v = map.get(key);
695        T result = null;
696        if (v != null) {
697            result = (T) v.get();
698        }
699        return result;
700    }
701
702    public <T> T get(final String key) {
703        final Value v = map.get(new StringKey(key));
704        T result = null;
705        if (v != null) {
706            result = (T) v.get();
707        }
708        return result;
709    }
710
711    public String getClassName() {
712        return this.className;
713    }
714
715    public Data getParent() {
716        return parent;
717    }
718
719    public QueryPath getQueryPath() {
720        final QueryPath result = new QueryPath();
721        _getQueryPath(result);
722        return result;
723    }
724
725
726    /**
727     * @return an Iterator that does not contain any _runtimeData
728     */
729    public Iterator<Property> realPropertyIterator() {
730        HashMap<Key, Value> propertyMap = new HashMap<Key, Value>(map);
731//      propertyMap.remove("_runtimeData");
732        propertyMap.remove(new StringKey("_runtimeData"));
733
734        final Iterator<Map.Entry<Key, Value>> impl = propertyMap.entrySet().iterator();
735
736        return new Iterator<Property>() {
737            Map.Entry<Key, Value> current;
738
739            @Override
740            public boolean hasNext() {
741                return impl.hasNext();
742            }
743
744            @Override
745            public Property next() {
746                final Map.Entry<Key, Value> entry = impl.next();
747                current = entry;
748                return new Property() {
749                    @Override
750                    public <T> T getKey() {
751                        return (T) entry.getKey().get();
752                    }
753
754                    @Override
755                    public Class<?> getKeyType() {
756                        return entry.getKey().getType();
757                    }
758
759                    @Override
760                    public <T> T getValue() {
761                        return (T) entry.getValue().get();
762                    }
763
764                    @Override
765                    public Class<?> getValueType() {
766                        return entry.getValue().getType();
767                    }
768
769                    @Override
770                    public Key getWrappedKey() {
771                        return entry.getKey();
772                    }
773
774                    @Override
775                    public Value getWrappedValue() {
776                        return entry.getValue();
777                    }
778                };
779            }
780
781            @Override
782            public void remove() {
783                impl.remove();
784                QueryPath path = getQueryPath();
785                path.add(current.getKey());
786                execChangeCallbacks(ChangeType.REMOVE, path);
787            }
788        };
789    }
790
791    /*
792    * (non-Javadoc)
793    *
794    * @see java.lang.Iterable#iterator()
795    */
796
797    @Override
798    public Iterator<Property> iterator() {
799        final Iterator<Map.Entry<Key, Value>> impl = map.entrySet().iterator();
800
801        return new Iterator<Property>() {
802            Map.Entry<Key, Value> current;
803
804            @Override
805            public boolean hasNext() {
806                return impl.hasNext();
807            }
808
809            @Override
810            public Property next() {
811                final Map.Entry<Key, Value> entry = impl.next();
812                current = entry;
813                return new Property() {
814                    @Override
815                    public <T> T getKey() {
816                        return (T) entry.getKey().get();
817                    }
818
819                    @Override
820                    public Class<?> getKeyType() {
821                        return entry.getKey().getType();
822                    }
823
824                    @Override
825                    public <T> T getValue() {
826                        return (T) entry.getValue().get();
827                    }
828
829                    @Override
830                    public Class<?> getValueType() {
831                        return entry.getValue().getType();
832                    }
833
834                    @Override
835                    public Key getWrappedKey() {
836                        return entry.getKey();
837                    }
838
839                    @Override
840                    public Value getWrappedValue() {
841                        return entry.getValue();
842                    }
843                };
844            }
845
846            @Override
847            public void remove() {
848                impl.remove();
849                QueryPath path = getQueryPath();
850                path.add(current.getKey());
851                execChangeCallbacks(ChangeType.REMOVE, path);
852            }
853        };
854    }
855
856    public <T> T query(final QueryPath path) {
857        T result = null;
858        Data d = this;
859        for (final Iterator itr = path.iterator(); itr.hasNext() && d != null;) {
860            final Key k = (Key) itr.next();
861            if (itr.hasNext()) {
862                Object obj = d.get(k);
863                if (obj != null && !(obj instanceof Data)) {
864                    // TODO what should be done if we try to query
865                    // cluset/clus/0/_runtimeData where cluset/0 returns a string instead of Data
866                    // throw an exception here?
867                    throw new java.lang.IllegalArgumentException();
868                } else {
869                    d = d.get(k);
870                }
871            } else {
872                result = (T) d.get(k);
873            }
874        }
875        return result;
876    }
877
878    public <T> T query(final String path) {
879        return (T) query(QueryPath.parse(path));
880    }
881
882    public Class<?> getType(final QueryPath path) {
883        Value result = null;
884        Data d = this;
885        for (final Iterator itr = path.iterator(); itr.hasNext();) {
886            final Key k = (Key) itr.next();
887            if (itr.hasNext()) {
888                d = d.get(k);
889            } else {
890                result = map.get(k);
891            }
892        }
893        return result.getType();
894
895    }
896
897    public void set(final Integer key, final Boolean value) {
898        put(new IntegerKey(key), new BooleanValue(value));
899    }
900
901    public void set(final Integer key, final Data value) {
902        final Key k = new IntegerKey(key);
903        put(k, new DataValue(value));
904        if (value != null) {
905            value.parent = this;
906            value.parentKey = k;
907        }
908    }
909
910    public void set(final Integer key, final Date value) {
911        put(new IntegerKey(key), new DateValue(value));
912    }
913
914    public void set(final Integer key, final Double value) {
915        put(new IntegerKey(key), new DoubleValue(value));
916    }
917
918    public void set(final Integer key, final Float value) {
919        put(new IntegerKey(key), new FloatValue(value));
920    }
921
922    public void set(final Integer key, final Integer value) {
923        put(new IntegerKey(key), new IntegerValue(value));
924    }
925
926    public void set(final Integer key, final Long value) {
927        put(new IntegerKey(key), new LongValue(value));
928    }
929
930    public void set(final Integer key, final Short value) {
931        put(new IntegerKey(key), new ShortValue(value));
932    }
933
934    public void set(final Integer key, final String value) {
935        put(new IntegerKey(key), new StringValue(value));
936    }
937
938    public void set(final Integer key, final Time value) {
939        put(new IntegerKey(key), new TimeValue(value));
940    }
941
942    public void set(final Integer key, final Timestamp value) {
943        put(new IntegerKey(key), new TimestampValue(value));
944    }
945
946    public void set(final Key key, final Boolean value) {
947        put(key, new BooleanValue(value));
948    }
949
950    public void set(final Key key, final Data value) {
951        put(key, new DataValue(value));
952        if (value != null) {
953            value.parent = this;
954            value.parentKey = key;
955        }
956    }
957
958    public void set(final Key key, final Date value) {
959        put(key, new DateValue(value));
960    }
961
962    public void set(final Key key, final Double value) {
963        put(key, new DoubleValue(value));
964    }
965
966    public void set(final Key key, final Float value) {
967        put(key, new FloatValue(value));
968    }
969
970    public void set(final Key key, final Integer value) {
971        put(key, new IntegerValue(value));
972    }
973
974    public void set(final Key key, final Long value) {
975        put(key, new LongValue(value));
976    }
977
978    public void set(final Key key, final Short value) {
979        put(key, new ShortValue(value));
980    }
981
982    public void set(final Key key, final String value) {
983        put(key, new StringValue(value));
984    }
985
986    public void set(final Key key, final Time value) {
987        put(key, new TimeValue(value));
988    }
989
990    public void set(final Key key, final Timestamp value) {
991        put(key, new TimestampValue(value));
992    }
993
994    public void set(final Key key, final Value value) {
995        put(key, value);
996        if (value instanceof DataValue) {
997            final Data d = value.get();
998            if (d != null) {
999                d.parent = this;
1000                d.parentKey = key;
1001            }
1002        }
1003    }
1004
1005    public void set(final String key, final Boolean value) {
1006        put(new StringKey(key), new BooleanValue(value));
1007    }
1008
1009    public void set(final String key, final Data value) {
1010        final Key k = new StringKey(key);
1011        put(k, new DataValue(value));
1012        if (value != null) {
1013            value.parent = this;
1014            value.parentKey = k;
1015        }
1016    }
1017
1018    public void set(final String key, final Date value) {
1019        put(new StringKey(key), new DateValue(value));
1020    }
1021
1022    public void set(final String key, final Double value) {
1023        put(new StringKey(key), new DoubleValue(value));
1024    }
1025
1026    public void set(final String key, final Float value) {
1027        put(new StringKey(key), new FloatValue(value));
1028    }
1029
1030    public void set(final String key, final Integer value) {
1031        put(new StringKey(key), new IntegerValue(value));
1032    }
1033
1034    public void set(final String key, final Long value) {
1035        put(new StringKey(key), new LongValue(value));
1036    }
1037
1038    public void set(final String key, final Short value) {
1039        put(new StringKey(key), new ShortValue(value));
1040    }
1041
1042    public void set(final String key, final String value) {
1043        put(new StringKey(key), new StringValue(value));
1044    }
1045
1046    public void set(final String key, final Time value) {
1047        put(new StringKey(key), new TimeValue(value));
1048    }
1049
1050    public void set(final String key, final Timestamp value) {
1051        put(new StringKey(key), new TimestampValue(value));
1052    }
1053
1054    public Integer size() {
1055        return map.size();
1056    }
1057
1058    public String toString() {
1059        return _toXmlString("");
1060    }
1061        
1062    /**
1063     * Converts the data map to a bracketed string representation
1064     * @return
1065     */
1066    private String _toString(){
1067        StringBuffer dataString = new StringBuffer();
1068
1069        dataString.append("{");
1070        for (Iterator itr = this.iterator(); itr.hasNext();) {
1071            Property p = (Property) itr.next();
1072            dataString.append(p.getKey() + "=" + p.getValue());
1073            if (itr.hasNext()) {
1074                dataString.append(", ");
1075            }
1076        }
1077        dataString.append("}");
1078
1079        return dataString.toString();
1080    }
1081    
1082    /**
1083     * Converts the data map to an xml representation
1084     * 
1085     */
1086    private String _toXmlString(String indent){
1087        StringBuffer dataString = new StringBuffer();
1088                
1089        for (Iterator itr = this.iterator(); itr.hasNext();) {
1090            Property p = (Property) itr.next();
1091            Object value = p.getValue();
1092            if (value instanceof Data){                 
1093                if (p.getKey() instanceof Integer){
1094                        dataString.append(indent + "<listitem index=\"" + p.getKey() + "\">\n");
1095                        dataString.append(((Data)value)._toXmlString(indent + "  "));
1096                        dataString.append(indent + "</listitem>\n");                      
1097                } else {
1098                        dataString.append(indent + "<" + p.getKey() + ">\n");
1099                                dataString.append(((Data)value)._toXmlString(indent + "  "));
1100                        dataString.append(indent + "</" + p.getKey() + ">\n");
1101                }
1102            } else if (p.getKey() instanceof Integer){
1103                dataString.append(indent + "<listitem index=\"" + p.getKey() + "\" value=\""+ value + "\"/>\n");
1104            } else {
1105                dataString.append(indent + "<" + p.getKey() + " value=\""+ value + "\"/>\n");             
1106            }
1107        }
1108
1109        return dataString.toString();
1110    }
1111
1112    public boolean containsKey(Key key) {
1113        return map.containsKey(key);
1114    }
1115
1116    public boolean containsValue(Value value) {
1117        return map.containsValue(value);
1118    }
1119
1120    /**
1121     * @return set of top level keys known to this data branch
1122     */
1123    public Set keySet() {
1124        return map.keySet();
1125    }
1126
1127    public boolean isEmpty() {
1128        return map.isEmpty();
1129    }
1130}