001package arcommon.xml;
002
003import java.io.IOException;
004import java.io.Serializable;
005import java.lang.reflect.Array;
006import java.lang.reflect.Field;
007import java.lang.reflect.Method;
008import java.text.ParseException;
009import java.util.ArrayList;
010import java.util.Collection;
011import java.util.Date;
012import java.util.HashMap;
013import java.util.List;
014import java.util.Map;
015import java.util.Stack;
016
017import org.xml.sax.Attributes;
018
019import arcommon.ReflectionUtil;
020import arcommon.StringUtil;
021import arcommon.xml.XmlSerializerHelperUtil.SpeciallyHandlingType;
022
023/**
024 * Deserializes XML in a pretty printed human-readable form to an object.
025 * <br>
026 * For XML format see {@link XmlSerializer}.*/
027public class XmlDeserializer extends AbstractSaxParser {
028    
029    private static class StateData {
030        public Object object;
031        public Field field;
032        public boolean fieldIsMissing;
033        public boolean fieldClassIsMissing;
034        public int arrayIndex;
035        @SuppressWarnings("rawtypes")
036        public Collection collection;
037        @SuppressWarnings("rawtypes")
038        public Map map;
039        public Object entryKeyObject;
040        public String entryKeyReference;
041        public boolean entryKeyIsSet;
042        public Object entryValueObject;
043        public String entryValueReference;
044        public boolean entryClassIsMissing;
045        public Type type;
046        public boolean classIsMissing;
047        
048        public boolean specialHandling;
049        public Class<?> specialHandlingClass;
050        public ArrayList<Update> specialHandlingUpdates;
051        public ArrayList<SpecialHandlingData> specialHandlingDatas;
052        
053        public ArrayList<Update> afterArrayCollectionMapUpdates;
054        
055        public void addAfterArrayCollectionMapUpdate(Update update) {
056            if (afterArrayCollectionMapUpdates == null) {
057                afterArrayCollectionMapUpdates = new ArrayList<Update>();
058            }
059            afterArrayCollectionMapUpdates.add(update);
060        }
061        
062        @Override
063        public String toString() {
064            return type + " " + object + " " + field;
065        }
066    }
067
068    private enum Type {OBJECT, ARRAY, COLLECTION, MAP};
069
070    /*package*/ static class Update {
071        public Type type;
072        public Object object;
073        public Field field;
074        public int arrayIndex;
075        @SuppressWarnings("rawtypes")
076        public Collection collection;
077        @SuppressWarnings("rawtypes")
078        public Map map;
079        public Object keyObject;
080        public String keyReferenceId;
081        public Object value;
082        public String referenceId;
083
084        public boolean specialHandling;
085        public Class<?> specialHandlingClass;
086        public ArrayList<Update> specialHandlingUpdates;
087        public ArrayList<SpecialHandlingData> specialHandlingDatas;
088        
089        @Override
090        public String toString() {
091            return type + " " + object + " " + field + " " + referenceId;
092        }
093    }
094    
095    /*package*/ static class SpecialHandlingData {
096        public Class<?> clazz;
097        public Object value;
098        public int index;
099        public String referenceId;
100        
101        @Override
102        public String toString() {
103            return ReflectionUtil.classToClassNameInJavaSyntax(clazz) + ": " + value;
104        }
105    }
106
107    private Serializable resultObject;
108    private StateData currentData;
109    private Stack<StateData> dataStack;
110    private Map<String, Object> idToObjectMap;
111    private XmlDeserializerMapper xmlDeserializerMapper;
112
113    private List<Update> updates;
114    
115    private boolean inSpecialHandlingObject;
116
117    private boolean done;
118    
119    private int xmlVersion;
120    
121    public XmlDeserializer() {
122        dataStack = new Stack<StateData>();
123        idToObjectMap = new HashMap<String, Object>();
124        xmlDeserializerMapper = new XmlDeserializerMapper(this);
125
126        updates = new ArrayList<Update>();
127        
128        inSpecialHandlingObject = false;
129    }
130
131    @Override
132    public void startElement(String qName, Attributes attributes, int childIndex) {
133        try {
134            if (qName.equals(XmlSerializerHelperUtil.TAG_OBJECT)) {
135                handleObjectTag(attributes);
136            } else if (qName.equals(XmlSerializerHelperUtil.TAG_FIELD)) {
137                handleFieldTag(attributes);
138            } else if (qName.equals(XmlSerializerHelperUtil.TAG_ENTRY)) {
139                handleEntryTag(attributes);
140            } else if (qName.equals(XmlSerializerHelperUtil.TAG_SPECIAL_HANDLING)) {
141                handleSpecialHandling(attributes);
142            } else if (qName.equals(XmlSerializerHelperUtil.TAG_DATA)) {
143                handleData(attributes);
144            } else if (qName.equals(XmlSerializerHelperUtil.TAG_NULL_ELEMENT)) {
145                handleNullElement();
146            } else if (qName.equals(XmlSerializerHelperUtil.TAG_XML_SERIALIZER)) {
147                handleXmlSerializerElement(attributes);
148            } else {
149                throw new RuntimeException("Unknown tag \"" + qName + "\"");
150            }
151        } catch (RuntimeException e) {
152            throw e;
153        } catch (Exception e) {
154            throw new RuntimeException(e);
155        }
156    }
157
158    private void handleObjectTag(Attributes attributes) throws ParseException {
159        if (currentData != null) {
160            dataStack.push(currentData);
161        }
162
163        currentData = new StateData();
164
165        Object newObject;
166        String className = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_CLASS);
167        if (className == null) {
168            String reference = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_REFERENCE);
169            if (reference == null) {
170                throw new RuntimeException(XmlSerializerHelperUtil.ATTRIBUTE_REFERENCE + " == null");
171            }
172            StateData parentData = dataStack.get(dataStack.size() - 1);
173            if (parentData == null) {
174                throw new RuntimeException("parentData == null");
175            }
176            if (parentData.type == null) {
177                throw new RuntimeException("parentData.type == null");
178            }
179            Update update;
180            switch (parentData.type) {
181                case ARRAY :
182                    update = new Update();
183                    update.type = Type.ARRAY;
184                    update.object = parentData.object;
185                    update.arrayIndex = parentData.arrayIndex++;
186                    update.referenceId = reference;
187                    parentData.addAfterArrayCollectionMapUpdate(update);
188                break;
189                case COLLECTION :
190                    update = new Update();
191                    update.type = Type.COLLECTION;
192                    update.collection = parentData.collection;
193                    update.referenceId = reference;
194                    parentData.addAfterArrayCollectionMapUpdate(update);
195                break;
196                case MAP :
197                    if (!parentData.entryKeyIsSet) {
198                        parentData.entryKeyIsSet = true;
199                        if (parentData.entryKeyReference != null) {
200                            throw new RuntimeException("parentData.entryKeyReference != null");
201                        }
202                        parentData.entryKeyReference = reference;
203                    } else {
204                        if (parentData.entryValueReference != null) {
205                            throw new RuntimeException("parentData.entryValueReference != null");
206                        }
207                        parentData.entryValueReference = reference;
208                    }
209                break;
210                default :
211                    throw new RuntimeException("Unknown type " + parentData.type);
212            }
213            return ;
214        }
215        
216        Class<?> clazz = xmlDeserializerMapper.loadClassWithLogLevel(className);
217        if (clazz == null) {
218            currentData.classIsMissing = true;
219            return ;
220        }
221
222        String xmlSerialVersionUIDString = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_SERIAL_VERSION_UID);
223        if (xmlSerialVersionUIDString != null) {
224            if (!StringUtil.isStringALong(xmlSerialVersionUIDString)) {
225                throw new RuntimeException("xmlSerialVersionUID is not a number for class " + clazz.getName() + ", xmlSerialVersionUID: " + xmlSerialVersionUIDString);
226            }
227            Long codeSerialVersionUID = XmlSerializerHelperUtil.getSerialVersionUID(clazz);
228            Long xmlSerialVersionUID = Long.valueOf(xmlSerialVersionUIDString);
229            if (!codeSerialVersionUID.equals(xmlSerialVersionUID)) {
230                throw new RuntimeException("!codeSerialVersionUID.equals(xmlSerialVersionUID) for class " + clazz.getName() + ", codeSerialVersionUID: " + codeSerialVersionUID + ", xmlSerialVersionUID: " + xmlSerialVersionUID);
231            }
232        }
233
234        boolean isDate = Date.class.isAssignableFrom(clazz);
235        if (clazz != null && (XmlSerializerHelperUtil.isSimpleClass(clazz) || isDate)) {
236            String value = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_VALUE);
237            String length = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_LENGTH);
238            newObject = stringValueToSimpleObject(clazz, value, length);
239        } else {
240            if (XmlSerializerHelperUtil.calculateSpeciallyHandlingTypeForHierarchy(clazz) != SpeciallyHandlingType.NONE) {
241                newObject = ReflectionUtil.createInstanceDefaultPrivateConstructor(clazz);
242            } else {
243                if (clazz.isArray()) {
244                    String lengthString = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_LENGTH);
245                    if (lengthString == null) {
246                        throw new RuntimeException("lengthString == null");
247                    }
248                    int length = StringUtil.stringToInt(lengthString);
249                    newObject = Array.newInstance(clazz.getComponentType(), length);
250                } else {
251                    Class<?> superSerializableClazz = XmlSerializerHelperUtil.findSuperSerializableClazz(clazz);
252                    if (superSerializableClazz == null) {
253                        throw new RuntimeException("Class " + ReflectionUtil.classToClassNameInJavaSyntax(clazz) + " doesn't have a super class that doesn't implementing Serializable");
254                    }
255                    Class<?> superNonSerializableClazz = superSerializableClazz.getSuperclass();
256                    newObject = ReflectionUtil.createInstanceUsingParentConstructorOnlyUnsafe(clazz, superNonSerializableClazz);
257                }
258            }
259        }
260
261        if (resultObject == null) {
262            resultObject = (Serializable)newObject;
263        }
264
265        String id = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_ID);
266        if (id != null) {
267            addObjectAndId(id, newObject);
268        }
269
270        currentData.object = newObject;
271        if (currentData.object.getClass().isArray()) {
272            currentData.type = Type.ARRAY;
273        } else {
274            SpeciallyHandlingType speciallyHandlingType = XmlSerializerHelperUtil.calculateSpeciallyHandlingTypeForHierarchy(currentData.object.getClass());
275            if (speciallyHandlingType == SpeciallyHandlingType.COLLECTION) {
276                currentData.type = Type.COLLECTION;
277                currentData.collection = (Collection<?>)newObject;
278            } else if (speciallyHandlingType == SpeciallyHandlingType.MAP) {
279                currentData.type = Type.MAP;
280                currentData.map = (Map<?, ?>)newObject;
281            } else {
282                currentData.type = Type.OBJECT;
283            }
284        }
285
286        if (dataStack.size() > 0) {
287            StateData parentData = dataStack.get(dataStack.size() - 1);
288            if (parentData.type != null && !parentData.fieldIsMissing && !parentData.fieldClassIsMissing) {
289                if (parentData.field != null) {
290                    Update update = new Update();
291                    update.type = Type.OBJECT;
292                    update.field = parentData.field;
293                    update.object = parentData.object;
294                    update.value = newObject;
295                    if (parentData.specialHandling) {
296                        parentData.specialHandlingUpdates.add(update);
297                    } else {
298                        updates.add(update);
299                    }
300
301                    parentData.field = null;
302                } else {
303                    Update update;
304                    switch (parentData.type) {
305                        case OBJECT :
306                            if (inSpecialHandlingObject) {
307                                int size = parentData.specialHandlingDatas.size();
308                                SpecialHandlingData specialHandlingData = parentData.specialHandlingDatas.get(size - 1);
309                                specialHandlingData.value = newObject;
310                            } else {
311                                throw new RuntimeException("currentType == Type.OBJECT");
312                            }
313                        break;
314                        case ARRAY :
315                            update = new Update();
316                            update.type = Type.ARRAY;
317                            update.object = parentData.object;
318                            update.arrayIndex = parentData.arrayIndex++;
319                            update.value = newObject;
320                            parentData.addAfterArrayCollectionMapUpdate(update);
321                        break;
322                        case COLLECTION :
323                            update = new Update();
324                            update.type = Type.COLLECTION;
325                            update.collection = parentData.collection;
326                            update.value = newObject;
327                            parentData.addAfterArrayCollectionMapUpdate(update);
328                        break;
329                        case MAP :
330                            if (!parentData.entryKeyIsSet) {
331                                parentData.entryKeyIsSet = true;
332                                parentData.entryKeyObject = newObject;
333                            } else {
334                                if (parentData.entryValueObject != null) {
335                                    throw new RuntimeException("parentData.entryValueObject != null");
336                                }
337                                parentData.entryValueObject = newObject;
338                            }
339                        break;
340                        default :
341                            throw new RuntimeException("Unknown type " + parentData.type);
342//                      break;
343                    }
344                }
345            }
346        }
347    }
348
349    private void handleFieldTag(Attributes attributes) throws ParseException {
350        if (currentData.classIsMissing) {
351            return ;
352        }
353        
354        Class<?> currentObjectClass = currentData.object.getClass();
355
356        Class<?> fieldClass = currentObjectClass;
357        String superClass = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_SUPER_CLASS);
358        if (superClass != null) {
359            fieldClass = xmlDeserializerMapper.loadClassWithLogLevel(superClass);
360            if (fieldClass == null) {
361                currentData.fieldClassIsMissing = true;
362                return ;
363            }
364            
365            currentData.fieldClassIsMissing = false;
366        }
367        
368        String name = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_NAME);
369        String reference = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_REFERENCE);
370        Field field = xmlDeserializerMapper.findField(fieldClass, name);
371        if (reference != null) {
372            if (field != null) {
373                Update update = new Update();
374                update.type = Type.OBJECT;
375                update.field = field;
376                update.object = currentData.object;
377                update.referenceId = reference;
378                if (currentData.specialHandling) {
379                    currentData.specialHandlingUpdates.add(update);
380                } else {
381                    updates.add(update);
382                }
383            }
384        } else {
385            String value = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_VALUE);
386            String className = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_CLASS);
387
388            Class<?> valueClazz = null;
389            if (className != null) {
390                valueClazz = xmlDeserializerMapper.loadClassWithLogLevel(className);
391            }
392            if (valueClazz == null && value != null && field != null) {
393                valueClazz = field.getType(); // Simple class where the class attribute is missing. It is missing by design on subsequently field occurrences.
394            }
395            if (valueClazz == Date.class && value == null) {
396                valueClazz = null; // HACK
397            }
398            
399            if (valueClazz != null && XmlSerializerHelperUtil.isSimpleClass(valueClazz)) {
400                String length = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_LENGTH);
401                Object newFieldValue = stringValueToSimpleObject(valueClazz, value, length);
402                if (field != null) {
403                    Update update = new Update();
404                    update.type = Type.OBJECT;
405                    update.field = field;
406                    update.object = currentData.object;
407                    update.value = newFieldValue;
408                    if (currentData.specialHandling) {
409                        currentData.specialHandlingUpdates.add(update);
410                    } else {
411                        updates.add(update);
412                    }
413                }
414                
415                String id = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_ID);
416                if (id != null) {
417                    addObjectAndId(id, newFieldValue);
418                }
419            } else {
420                currentData.field = field;
421                currentData.fieldIsMissing = field == null;
422            }
423        }
424    }
425
426    private void handleEntryTag(Attributes attributes) throws ParseException {
427        String key = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_KEY_PREFIX);
428        String className = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_KEY_CLASS);
429        if (key != null || className != null) {
430            Class<?> clazz = xmlDeserializerMapper.loadClassWithLogLevel(className);
431            String length = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_KEY_LENGTH);
432            if (clazz != null) {
433                currentData.entryKeyObject = stringValueToSimpleObject(clazz, key, length);
434            } else {
435                currentData.entryClassIsMissing = true;
436            }
437
438            if (currentData.entryKeyIsSet) {
439                throw new RuntimeException("parentData.entryKeyIsSet");
440            }
441            currentData.entryKeyIsSet = true;
442
443            String id = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_KEY_ID);
444            if (id != null) {
445                addObjectAndId(id, currentData.entryKeyObject);
446            }
447        }
448
449        String keyReference = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_KEY_REFERENCE);
450        if (keyReference != null) {
451            currentData.entryKeyReference = keyReference;
452
453            if (currentData.entryKeyIsSet) {
454                throw new RuntimeException("parentData.entryKeyIsSet");
455            }
456            currentData.entryKeyIsSet = true;
457        }
458        
459        String keyIsNull = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_KEY_IS_NULL);
460        if (keyIsNull != null) {
461            if (!keyIsNull.equals(XmlSerializerHelperUtil.ATTRIBUTE_VALUE_TRUE)) {
462                throw new RuntimeException(XmlSerializerHelperUtil.ATTRIBUTE_KEY_IS_NULL + " != null && !" + XmlSerializerHelperUtil.ATTRIBUTE_KEY_IS_NULL + ".equals(\"" + XmlSerializerHelperUtil.ATTRIBUTE_VALUE_TRUE + "\") " + keyIsNull);
463            }
464
465            if (currentData.entryKeyIsSet) {
466                throw new RuntimeException("parentData.entryKeyIsSet");
467            }
468            currentData.entryKeyIsSet = true;
469        }
470        
471        String value = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_VALUE_PREFIX);
472        className = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_VALUE_CLASS);
473        if (value != null || className != null) {
474            Class<?> clazz = xmlDeserializerMapper.loadClassWithLogLevel(className);
475            String length = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_VALUE_LENGTH);
476            if (clazz != null) {
477                currentData.entryValueObject = stringValueToSimpleObject(clazz, value, length);
478            } else {
479                currentData.entryClassIsMissing = true;
480            }
481
482            String id = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_VALUE_ID);
483            if (id != null) {
484                addObjectAndId(id, currentData.entryValueObject);
485            }
486        }
487        
488        String valueReference = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_VALUE_REFERENCE);
489        if (valueReference != null) {
490            currentData.entryValueReference = valueReference;
491        }
492    }
493    
494    private void handleSpecialHandling(Attributes attributes) {
495        if (currentData != null) {
496            dataStack.push(currentData);
497        }
498
499        StateData oldCurrentObject = currentData;
500        
501        currentData = new StateData();
502        currentData.object = oldCurrentObject.object;
503        currentData.type = oldCurrentObject.type;
504        currentData.specialHandling = true;
505        currentData.specialHandlingUpdates = new ArrayList<Update>();
506        currentData.specialHandlingDatas = new ArrayList<SpecialHandlingData>();
507        
508        String className = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_CLASS);
509        currentData.specialHandlingClass = xmlDeserializerMapper.loadClassWithLogLevel(className);
510    }
511
512    private void handleData(Attributes attributes) throws ParseException {
513        SpecialHandlingData specialHandlingData = new SpecialHandlingData();
514        String className = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_CLASS);
515
516        String dataObjectIsNull = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_DATA_OBJECT_IS_NULL);
517        
518        String referenceId = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_REFERENCE);
519        
520        if (className != null) {
521            specialHandlingData.clazz = xmlDeserializerMapper.loadClassWithLogLevel(className);
522            String value = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_VALUE);
523            String length = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_LENGTH);
524            specialHandlingData.value = stringValueToSimpleObject(specialHandlingData.clazz, value, length);
525            
526            String id = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_ID);
527            if (id != null) {
528                addObjectAndId(id, specialHandlingData.value);
529            }
530        } else if (referenceId != null) {
531            specialHandlingData.referenceId = referenceId;
532        } else if (dataObjectIsNull != XmlSerializerHelperUtil.ATTRIBUTE_VALUE_TRUE) {
533            inSpecialHandlingObject = true;
534        }
535        
536        currentData.specialHandlingDatas.add(specialHandlingData);
537    }
538
539    private void handleNullElement() {
540        Update update;
541        switch (currentData.type) {
542            case ARRAY :
543                update = new Update();
544                update.type = Type.ARRAY;
545                update.object = currentData.object;
546                update.arrayIndex = currentData.arrayIndex++;
547                update.value = null;
548                currentData.addAfterArrayCollectionMapUpdate(update);
549            break;
550            case COLLECTION :
551                update = new Update();
552                update.type = Type.COLLECTION;
553                update.collection = currentData.collection;
554                update.value = null;
555                currentData.addAfterArrayCollectionMapUpdate(update);
556            break;
557            default :
558                throw new RuntimeException("currentData.type != ARRAY && currentData.type != COLLECTION " + currentData.type);
559//          break;
560        }
561    }
562    
563    private void handleXmlSerializerElement(Attributes attributes) {
564        if (xmlVersion > 0) {
565            throw new RuntimeException("Xml version is already set " + xmlVersion + ". Two XmlSerializer tags?");
566        }
567        
568        String versionString = attributes.getValue(XmlSerializerHelperUtil.ATTRIBUTE_VERSION);
569        if (!StringUtil.isStringAnInt(versionString)) {
570            throw new RuntimeException("Xml version is not a number " + versionString);
571        }
572        
573        xmlVersion = StringUtil.stringToInt(versionString);
574        if (xmlVersion == 0 || xmlVersion > XmlSerializerHelperUtil.XML_VERSION_NUMBER) {
575            throw new RuntimeException("Wrong version number " + xmlVersion);
576        }
577    }
578    
579    private void addObjectAndId(String id, Object newObject) {
580        if (idToObjectMap.containsKey(id)) {
581            throw new RuntimeException("idToObjectMap.containsKey(id) " + id);
582        }
583        idToObjectMap.put(id, newObject);
584    }
585
586    private Object stringValueToSimpleObject(Class<?> clazz, String value, String lengthString) throws ParseException {
587        Object newFieldValue = null;
588        if (clazz == Long.class || clazz == long.class) {
589            newFieldValue = Long.valueOf(value);
590        } else if (clazz == Integer.class || clazz == int.class) {
591            newFieldValue = Integer.valueOf(value);
592        } else if (clazz == Short.class || clazz == short.class) {
593            newFieldValue = Short.valueOf(value);
594        } else if (clazz == Byte.class || clazz == byte.class) {
595            newFieldValue = Byte.valueOf(value);
596        } else if (clazz == String.class) {
597            newFieldValue = StringUtil.javaConstantDecodeString(value);
598//            newFieldValue = XmlUtil.decodeXmlString(newFieldValue); // No need to this here, as it has already been done by the SAX parser
599        } else if (clazz == Character.class || clazz == char.class) {
600            if (value.length() < 1 || value.length() > 6) {
601                throw new RuntimeException("value.length() < 1 || value.length() > 6 for " + clazz + ", value: \"" + value + "\"");
602            }
603            String c = StringUtil.javaConstantDecodeString(value);
604            newFieldValue = c.charAt(0);
605        } else if (clazz == Class.class) {
606            newFieldValue = xmlDeserializerMapper.loadClassWithLogLevel(value);
607        } else if (Date.class.isAssignableFrom(clazz)) {
608            newFieldValue = XmlSerializerHelperUtil.XML_DATE_TIME_FORMAT.parse(value);
609            if (clazz != Date.class) {
610                Date newObject = (Date)ReflectionUtil.createInstanceNoConstructorCall(clazz);
611                newObject.setTime(((Date)newFieldValue).getTime());
612                newFieldValue = newObject;
613            }
614        } else if (clazz == Boolean.class || clazz == boolean.class) {
615            newFieldValue = Boolean.valueOf(value);
616        } else if (clazz == Double.class || clazz == double.class) {
617            newFieldValue = Double.valueOf(value);
618        } else if (clazz == Float.class || clazz == float.class) {
619            newFieldValue = Float.valueOf(value);
620        } else if (ReflectionUtil.isClassEnum(clazz)) {
621            if (value.equals(XmlSerializerHelperUtil.ENUM_NULL)) {
622                newFieldValue = null;
623            } else {
624                newFieldValue = ReflectionUtil.stringToEnum(clazz, value);
625            }
626        } else if (clazz.isArray()) {
627            if (lengthString == null) {
628                throw new RuntimeException("length == null");
629            }
630            int length = StringUtil.stringToInt(lengthString);
631            newFieldValue = Array.newInstance(clazz.getComponentType(), length);
632
633            if (length < 0) {
634                throw new RuntimeException("length < 0");
635            }
636            if (length > 0) {
637                if (value.startsWith("[") && value.endsWith("]")) {
638                    value = value.substring(1, value.length() - 1);
639                }
640
641                String[] valuesArray = value.split(",");
642                for (int i = 0; i < valuesArray.length; i++) {
643                    String elementValueString = valuesArray[i];
644                    elementValueString = elementValueString.trim();
645                    Object elementValue = stringValueToSimpleObject(clazz.getComponentType(), elementValueString, null);
646                    Array.set(newFieldValue, i, elementValue);
647                }
648            }
649        } else {
650            throw new RuntimeException("Unknown simple class " + clazz);
651        }
652        return newFieldValue;
653    }
654
655    @Override
656    public void endElement(String qName, String content, int childTags) {
657        if (qName.equals(XmlSerializerHelperUtil.TAG_XML_SERIALIZER)) {
658            if (!done) {
659                throw new RuntimeException("Not done " + XmlSerializerHelperUtil.TAG_XML_SERIALIZER);
660            }
661            return ;
662        }
663            
664        if (done) {
665            throw new RuntimeException("Already done");
666        }
667        
668        if (!currentData.classIsMissing) {
669            if (qName.equals(XmlSerializerHelperUtil.TAG_ENTRY)) {
670                if (currentData.type != Type.MAP) {
671                    throw new RuntimeException("currentData.type != Type.MAP");
672                }
673                if (!currentData.entryClassIsMissing) {
674                    Update update = new Update();
675                    update.type = Type.MAP;
676                    update.map = currentData.map;
677                    update.keyObject = currentData.entryKeyObject;
678                    update.keyReferenceId = currentData.entryKeyReference;
679                    update.value = currentData.entryValueObject;
680                    update.referenceId = currentData.entryValueReference;
681                    currentData.addAfterArrayCollectionMapUpdate(update);
682                }
683    
684                currentData.entryKeyObject = null;
685                currentData.entryKeyReference = null;
686                currentData.entryKeyIsSet = false;
687                currentData.entryValueObject = null;
688                currentData.entryValueReference = null;
689                currentData.entryClassIsMissing = false;
690            }
691    
692            if (qName.equals(XmlSerializerHelperUtil.TAG_SPECIAL_HANDLING)) {
693                Update update = new Update();
694                update.object = currentData.object;
695                update.specialHandling = true;
696                update.specialHandlingClass = currentData.specialHandlingClass;
697                update.specialHandlingUpdates = currentData.specialHandlingUpdates;
698                update.specialHandlingDatas = currentData.specialHandlingDatas;
699                updates.add(update);
700            }
701            
702            if (qName.equals(XmlSerializerHelperUtil.TAG_OBJECT)) {
703                if (currentData.afterArrayCollectionMapUpdates != null) {
704                    updates.addAll(currentData.afterArrayCollectionMapUpdates);
705                    currentData.afterArrayCollectionMapUpdates = null;
706                }
707            }
708        }
709
710        if (qName.equals(XmlSerializerHelperUtil.TAG_OBJECT) || qName.equals(XmlSerializerHelperUtil.TAG_SPECIAL_HANDLING)) {
711            if (dataStack.size() == 0) {
712                done = true;
713                return ;
714            }
715
716            if (inSpecialHandlingObject) {
717                inSpecialHandlingObject = false;
718            }
719            
720            currentData = dataStack.pop();
721        }
722    }
723
724    @Override
725    public void endOfDocumentReached() {
726        if (!done) {
727            throw new RuntimeException("!done");
728        }
729        
730        executeUpdates(updates);
731    }
732    
733    @SuppressWarnings("unchecked")
734    /*package*/ void executeUpdates(List<Update> updatesToExecute) {
735        for (Update update : updatesToExecute) {
736            if (update.specialHandling) {
737                Method method = XmlSerializerHelperUtil.findReadMethod(update.specialHandlingClass);
738                if (method != null) {
739                    for (SpecialHandlingData specialHandlingData : update.specialHandlingDatas) {
740                        if (specialHandlingData.referenceId != null) {
741                            specialHandlingData.value = idToObjectMap.get(specialHandlingData.referenceId);
742                            if (specialHandlingData.value == null) {
743                                throw new RuntimeException("Data object not found " + specialHandlingData.referenceId);
744                            }
745                        }
746                    }
747                    
748                    XmlObjectInputStream xmlObjectInputStream;
749                    try {
750                        xmlObjectInputStream = new XmlObjectInputStream(this, update.specialHandlingUpdates, update.specialHandlingDatas);
751                    } catch (IOException e) {
752                        throw new RuntimeException(e);
753                    }
754                    ReflectionUtil.invokeMethod(update.object, method, new Object[] {xmlObjectInputStream});
755                    xmlObjectInputStream.checkAllDataRead();
756                } else {
757                    executeUpdates(update.specialHandlingUpdates);
758                }
759                continue;
760            }
761            
762            Object value;
763            if (update.referenceId != null) {
764                value = idToObjectMap.get(update.referenceId);
765                if (value == null) {
766                    throw new RuntimeException("Object not found " + update.referenceId);
767                }
768            } else {
769                value = update.value;
770            }
771
772            switch (update.type) {
773                case OBJECT :
774                    if (value == null || XmlSerializerHelperUtil.isAssignableFrom(update.field.getType(), value.getClass())) {
775                        ReflectionUtil.setFieldValue(update.object, update.field, value);
776                    } else {
777                        switch (XmlDeserializerMapper.fieldTypeMismatchLogLevel) {
778                            case ERROR :
779                                throw new RuntimeException("Field \"" + update.field.getName() + "\" in class \"" + update.field.getDeclaringClass().getCanonicalName() + "\" can't be assigned the value \"" + value + "\" of class \"" + value.getClass().getCanonicalName() + "\", because the field is of class \"" + update.field.getType().getCanonicalName() + "\".");
780//                          break;
781                            case WARNING :
782                                System.err.println("WARNING: Field \"" + update.field.getName() + "\" in class \"" + update.field.getDeclaringClass().getCanonicalName() + "\" can't be assigned the value \"" + value + "\" of class \"" + value.getClass().getCanonicalName() + "\", because the field is of class \"" + update.field.getType().getCanonicalName() + "\". The field value will not be loaded.");
783                            break;
784                            case INFO :
785                                System.out.println("Info: Field \"" + update.field.getName() + "\" in class \"" + update.field.getDeclaringClass().getCanonicalName() + "\" can't be assigned the value \"" + value + "\" of class \"" + value.getClass().getCanonicalName() + "\", because the field is of class \"" + update.field.getType().getCanonicalName() + "\". The field value will not be loaded.");
786                            break;
787                            case IGNORE :
788                                ;
789                            break;
790                            default :
791                                throw new RuntimeException("Internal error " + XmlDeserializerMapper.fieldTypeMismatchLogLevel);
792//                          break;
793                        }
794                    }
795                break;
796                case ARRAY :
797                    if (value == null || XmlSerializerHelperUtil.isAssignableFrom(update.object.getClass().getComponentType(), value.getClass())) {
798                        Array.set(update.object, update.arrayIndex, value);
799                    } else {
800                        switch (XmlDeserializerMapper.fieldTypeMismatchLogLevel) {
801                            case ERROR :
802                                throw new RuntimeException("Array index " + update.arrayIndex + " in class \"" + update.object.getClass().getCanonicalName() + "\" can't be assigned the value \"" + value + "\" of class \"" + value.getClass().getCanonicalName() + "\".");
803//                          break;
804                            case WARNING :
805                                System.err.println("WARNING: Array index " + update.arrayIndex + " in class \"" + update.object.getClass().getCanonicalName() + "\" can't be assigned the value \"" + value + "\" of class \"" + value.getClass().getCanonicalName() + "\". The value will not be loaded.");
806                            break;
807                            case INFO :
808                                System.out.println("Info: Array index " + update.arrayIndex + " in class \"" + update.object.getClass().getCanonicalName() + "\" can't be assigned the value \"" + value + "\" of class \"" + value.getClass().getCanonicalName() + "\". The value will not be loaded.");
809                            break;
810                            case IGNORE :
811                                ;
812                            break;
813                            default :
814                                throw new RuntimeException("Internal error " + XmlDeserializerMapper.fieldTypeMismatchLogLevel);
815//                          break;
816                        }
817                    }
818                break;
819                case COLLECTION :
820                    update.collection.add(value);
821                break;
822                case MAP :
823                    Object key;
824                    if (update.keyReferenceId != null) {
825                        key = idToObjectMap.get(update.keyReferenceId);
826                        if (value == null) {
827                            throw new RuntimeException("Object not found " + update.keyReferenceId);
828                        }
829                    } else {
830                        key = update.keyObject;
831                    }
832                    update.map.put(key, value);
833                break;
834                default :
835                    throw new RuntimeException("Unknown type " + update.type);
836            }
837        }
838    }
839
840    public Serializable getResultObject() {
841        return resultObject;
842    }
843}