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    
016    package org.kuali.student.common.assembly.data;
017    
018    import java.io.Serializable;
019    import java.sql.Time;
020    import java.sql.Timestamp;
021    import java.util.Date;
022    import java.util.HashMap;
023    import java.util.HashSet;
024    import java.util.Iterator;
025    import java.util.LinkedHashMap;
026    import java.util.Map;
027    import java.util.Set;
028    import java.util.Map.Entry;
029    
030    import javax.xml.bind.annotation.XmlRootElement;
031    import 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"})
040    public 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    }